diff options
| author | joonhoekim <26rote@gmail.com> | 2025-11-06 17:44:59 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-11-06 17:44:59 +0900 |
| commit | 08b73d56c2d887931cecdf2b0af6b277381763e6 (patch) | |
| tree | e2a1e466445c718dad79c100241048684b8a1923 /lib/vendors/table | |
| parent | ba43cd261d10c6b0c5218a9da3f946993b21de6e (diff) | |
(김준회) 결재 프리뷰 공통컴포넌트 작성 및 index.ts --> client.ts 분리 (서버사이드 코드가 번들링되어 클라측에서 실행되는 문제 해결 목적)
Diffstat (limited to 'lib/vendors/table')
| -rw-r--r-- | lib/vendors/table/approve-vendor-dialog.tsx | 185 |
1 files changed, 150 insertions, 35 deletions
diff --git a/lib/vendors/table/approve-vendor-dialog.tsx b/lib/vendors/table/approve-vendor-dialog.tsx index 786399a4..fea5a006 100644 --- a/lib/vendors/table/approve-vendor-dialog.tsx +++ b/lib/vendors/table/approve-vendor-dialog.tsx @@ -29,8 +29,10 @@ import { } from "@/components/ui/drawer" import { Vendor } from "@/db/schema/vendors" import { rejectVendors } from "../service" -import { approveVendorsWithApproval } from "../approval-actions" +import { approveVendorsWithApproval, prepareVendorApprovalVariables } from "../approval-actions" import { useSession } from "next-auth/react" +import { checkVendorsBlacklist } from "../blacklist-check" +import { ApprovalPreviewDialog } from "@/lib/approval/client" interface VendorDecisionDialogProps extends React.ComponentPropsWithoutRef<typeof Dialog> { @@ -50,6 +52,17 @@ export function VendorDecisionDialog({ const isDesktop = useMediaQuery("(min-width: 640px)") const { data: session } = useSession() + // 결재 미리보기 다이얼로그 상태 + const [showPreview, setShowPreview] = React.useState(false) + const [previewData, setPreviewData] = React.useState<{ + variables: Record<string, string> + title: string + description: string + } | null>(null) + + /** + * 승인 버튼 클릭 - 미리보기 다이얼로그 열기 + */ function onApprove() { if (!session?.user?.id) { toast.error("사용자 인증 정보를 찾을 수 없습니다.") @@ -63,37 +76,97 @@ export function VendorDecisionDialog({ startApproveTransition(async () => { try { - console.log("🔍 [DEBUG] 결재 상신 시작 - vendors:", vendors.map(v => ({ id: v.id, vendorName: v.vendorName, email: v.email }))); - console.log("🔍 [DEBUG] 세션 정보:", { userId: session.user.id, epId: session.user.epId }); + // 1. 블랙리스트 검사 (최우선 처리) + const vendorsToCheck = vendors.map(v => ({ + id: String(v.id), + name: v.vendorName || '', + representativeName: v.representativeName, + representativeBirth: v.representativeBirth, + })); + + const blacklistCheckResult = await checkVendorsBlacklist(vendorsToCheck); - const result = await approveVendorsWithApproval({ - vendorIds: vendors.map((vendor) => vendor.id), - currentUser: { - id: Number(session.user.id), - epId: session.user.epId as string, // 위에서 검증했으므로 타입 단언 - email: session.user.email || undefined, - }, - // TODO: 필요시 approvers 배열 추가 - // approvers: ['EP001', 'EP002'], - }) - - if (!result.success) { - console.error("🚨 [DEBUG] 결재 상신 에러:", result.message); - toast.error(result.message || "결재 상신에 실패했습니다.") - return + if (!blacklistCheckResult.success) { + // 블랙리스트에 있는 벤더 목록 표시 + const blacklistedNames = blacklistCheckResult.blacklistedVendors + .map(v => `• ${v.name}: ${v.message}`) + .join('\n'); + + toast.error( + `문제가 있는 데이터가 있습니다:\n${blacklistedNames}`, + { duration: 10000 } + ); + return; } - console.log("✅ [DEBUG] 결재 상신 성공:", result); - props.onOpenChange?.(false) - toast.success(`결재가 상신되었습니다. (결재ID: ${result.approvalId})`) - onSuccess?.() + // 2. 템플릿 변수 준비 + const { variables, vendorRecords, vendorNames } = await prepareVendorApprovalVariables( + vendors.map(v => v.id), + session.user.email || undefined + ); + + // 3. 미리보기 데이터 설정 + setPreviewData({ + variables, + title: `벤더 가입 승인 요청 - ${vendorRecords.length}개 업체`, + description: `${vendorNames} 의 가입을 승인합니다.`, + }); + + // 4. 미리보기 다이얼로그 열기 + setShowPreview(true); + } catch (error) { - console.error("🚨 [DEBUG] 예상치 못한 에러:", error); - toast.error("예상치 못한 오류가 발생했습니다.") + console.error("🚨 [Vendor Decision] 미리보기 준비 실패:", error); + toast.error(error instanceof Error ? error.message : "미리보기를 준비하는 중 오류가 발생했습니다.") } }) } + /** + * 결재 미리보기에서 확인 클릭 - 실제 결재 상신 + */ + async function handleApprovalConfirm(approvalData: { + approvers: string[] + title: string + description?: string + }) { + if (!session?.user?.id || !session?.user?.epId) { + toast.error("사용자 인증 정보가 없습니다.") + return + } + + try { + const result = await approveVendorsWithApproval({ + vendorIds: vendors.map((vendor) => vendor.id), + currentUser: { + id: Number(session.user.id), + epId: session.user.epId, + email: session.user.email || undefined, + }, + approvers: approvalData.approvers, // 미리보기에서 설정한 결재선 + }) + + if (!result.success) { + console.error("🚨 [Vendor Decision] 결재 상신 에러:", result.message); + toast.error(result.message || "결재 상신에 실패했습니다.") + return + } + + console.log("✅ [Vendor Decision] 결재 상신 성공:", result); + + // 다이얼로그 모두 닫기 + setShowPreview(false) + props.onOpenChange?.(false) + + toast.success(`결재가 상신되었습니다. (결재ID: ${result.approvalId})`) + onSuccess?.() + + } catch (error) { + console.error("🚨 [Vendor Decision] 예상치 못한 에러:", error); + toast.error("예상치 못한 오류가 발생했습니다.") + } + } + function onReject() { if (!session?.user?.id) { toast.error("사용자 인증 정보를 찾을 수 없습니다.") @@ -129,16 +202,17 @@ export function VendorDecisionDialog({ if (isDesktop) { return ( - <Dialog {...props}> - {showTrigger ? ( - <DialogTrigger asChild> - <Button variant="outline" size="sm" className="gap-2"> - <Check className="size-4" aria-hidden="true" /> - 가입 결정 ({vendors.length}) - </Button> - </DialogTrigger> - ) : null} - <DialogContent className="max-w-2xl"> + <> + <Dialog {...props}> + {showTrigger ? ( + <DialogTrigger asChild> + <Button variant="outline" size="sm" className="gap-2"> + <Check className="size-4" aria-hidden="true" /> + 가입 결정 ({vendors.length}) + </Button> + </DialogTrigger> + ) : null} + <DialogContent className="max-w-2xl"> <DialogHeader> <DialogTitle>협력업체 가입 결정</DialogTitle> <DialogDescription> @@ -199,11 +273,32 @@ export function VendorDecisionDialog({ </DialogFooter> </DialogContent> </Dialog> + + {/* 결재 미리보기 다이얼로그 */} + {previewData && session?.user?.epId && ( + <ApprovalPreviewDialog + open={showPreview} + onOpenChange={setShowPreview} + templateName="벤더 가입 승인 요청" + variables={previewData.variables} + title={previewData.title} + description={previewData.description} + currentUser={{ + id: Number(session.user.id), + epId: session.user.epId, + name: session.user.name || undefined, + email: session.user.email || undefined, + }} + onConfirm={handleApprovalConfirm} + /> + )} + </> ) } return ( - <Drawer {...props}> + <> + <Drawer {...props}> {showTrigger ? ( <DrawerTrigger asChild> <Button variant="outline" size="sm" className="gap-2"> @@ -272,5 +367,25 @@ export function VendorDecisionDialog({ </DrawerFooter> </DrawerContent> </Drawer> + + {/* 결재 미리보기 다이얼로그 */} + {previewData && session?.user?.epId && ( + <ApprovalPreviewDialog + open={showPreview} + onOpenChange={setShowPreview} + templateName="벤더 가입 승인 요청" + variables={previewData.variables} + title={previewData.title} + description={previewData.description} + currentUser={{ + id: Number(session.user.id), + epId: session.user.epId, + name: session.user.name || undefined, + email: session.user.email || undefined, + }} + onConfirm={handleApprovalConfirm} + /> + )} + </> ) }
\ No newline at end of file |
