From ef4c533ebacc2cdc97e518f30e9a9350004fcdfb Mon Sep 17 00:00:00 2001 From: dujinkim Date: Mon, 28 Apr 2025 02:13:30 +0000 Subject: ~20250428 작업사항 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../table/request-vendor-investigate-dialog.tsx | 243 ++++++++++++++++++--- 1 file changed, 218 insertions(+), 25 deletions(-) (limited to 'lib/vendors/table/request-vendor-investigate-dialog.tsx') diff --git a/lib/vendors/table/request-vendor-investigate-dialog.tsx b/lib/vendors/table/request-vendor-investigate-dialog.tsx index 0309ee4a..b3deafce 100644 --- a/lib/vendors/table/request-vendor-investigate-dialog.tsx +++ b/lib/vendors/table/request-vendor-investigate-dialog.tsx @@ -2,7 +2,7 @@ import * as React from "react" import { type Row } from "@tanstack/react-table" -import { Loader, Check, SendHorizonal } from "lucide-react" +import { Loader, Check, SendHorizonal, AlertCircle, AlertTriangle } from "lucide-react" import { toast } from "sonner" import { useMediaQuery } from "@/hooks/use-media-query" @@ -27,8 +27,29 @@ import { DrawerTitle, DrawerTrigger, } from "@/components/ui/drawer" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { + Accordion, + AccordionContent, + AccordionItem, + AccordionTrigger, +} from "@/components/ui/accordion" +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Badge } from "@/components/ui/badge" +import { Separator } from "@/components/ui/separator" + import { Vendor } from "@/db/schema/vendors" -import { requestInvestigateVendors } from "@/lib/vendor-investigation/service" +import { requestInvestigateVendors, getExistingInvestigationsForVendors } from "@/lib/vendor-investigation/service" +import { useSession } from "next-auth/react" +import { formatDate } from "@/lib/utils" interface ApprovalVendorDialogProps extends React.ComponentPropsWithoutRef { @@ -37,21 +58,98 @@ interface ApprovalVendorDialogProps onSuccess?: () => void } +// Helper function to get status badge variant and text +function getStatusBadge(status: string) { + switch (status) { + case "REQUESTED": + return { variant: "secondary", text: "Requested" } + case "SCHEDULED": + return { variant: "warning", text: "Scheduled" } + case "IN_PROGRESS": + return { variant: "default", text: "In Progress" } + case "COMPLETED": + return { variant: "success", text: "Completed" } + case "CANCELLED": + return { variant: "destructive", text: "Cancelled" } + default: + return { variant: "outline", text: status } + } +} + export function RequestVendorsInvestigateDialog({ vendors, showTrigger = true, onSuccess, ...props }: ApprovalVendorDialogProps) { - - console.log(vendors) const [isApprovePending, startApproveTransition] = React.useTransition() + const [isLoading, setIsLoading] = React.useState(true) + const [existingInvestigations, setExistingInvestigations] = React.useState([]) const isDesktop = useMediaQuery("(min-width: 640px)") + const { data: session } = useSession() + + // Fetch existing investigations when dialog opens + React.useEffect(() => { + if (vendors.length > 0) { + setIsLoading(true) + const fetchExistingInvestigations = async () => { + try { + const vendorIds = vendors.map(vendor => vendor.id) + const result = await getExistingInvestigationsForVendors(vendorIds) + setExistingInvestigations(result) + } catch (error) { + console.error("Failed to fetch existing investigations:", error) + toast.error("Failed to fetch existing investigations") + } finally { + setIsLoading(false) + } + } + + fetchExistingInvestigations() + } + }, [vendors]) + + // Group vendors by investigation status + const vendorsWithInvestigations = React.useMemo(() => { + if (!existingInvestigations.length) return { withInvestigations: [], withoutInvestigations: vendors } + + const vendorMap = new Map(vendors.map(v => [v.id, v])) + const withInvestigations: Array<{ vendor: typeof vendors[0], investigation: any }> = [] + + // Find vendors with existing investigations + existingInvestigations.forEach(inv => { + const vendor = vendorMap.get(inv.vendorId) + if (vendor) { + withInvestigations.push({ vendor, investigation: inv }) + vendorMap.delete(inv.vendorId) + } + }) + + // Remaining vendors don't have investigations + const withoutInvestigations = Array.from(vendorMap.values()) + + return { withInvestigations, withoutInvestigations } + }, [vendors, existingInvestigations]) function onApprove() { + if (!session?.user?.id) { + toast.error("사용자 인증 정보를 찾을 수 없습니다.") + return + } + + // Only request investigations for vendors without existing ones + const vendorsToRequest = vendorsWithInvestigations.withoutInvestigations + + if (vendorsToRequest.length === 0) { + toast.info("모든 선택된 업체에 이미 실사 요청이 있습니다.") + props.onOpenChange?.(false) + return + } + startApproveTransition(async () => { const { error } = await requestInvestigateVendors({ - ids: vendors.map((vendor) => vendor.id), + ids: vendorsToRequest.map((vendor) => vendor.id), + userId: Number(session.user.id) }) if (error) { @@ -60,11 +158,102 @@ export function RequestVendorsInvestigateDialog({ } props.onOpenChange?.(false) - toast.success("Vendor Investigation successfully sent to 벤더실사담당자") + toast.success(`${vendorsToRequest.length}개 업체에 대한 실사 요청을 보냈습니다.`) onSuccess?.() }) } + const renderContent = () => { + return ( + <> +
+ {isLoading ? ( +
+ +
+ ) : ( + <> + {vendorsWithInvestigations.withInvestigations.length > 0 && ( + + + 기존 실사 요청 정보가 있습니다 + + 선택한 {vendors.length}개 업체 중 {vendorsWithInvestigations.withInvestigations.length}개 업체에 대한 + 기존 실사 요청이 있습니다. 새로운 요청은 기존 데이터가 없는 업체에만 적용됩니다. + + + )} + + {vendorsWithInvestigations.withInvestigations.length > 0 && ( + + + + 기존 실사 요청 ({vendorsWithInvestigations.withInvestigations.length}) + + + + + + + 업체명 + 상태 + 요청일 + 예정 일정 + + + + {vendorsWithInvestigations.withInvestigations.map(({ vendor, investigation }) => { + const status = getStatusBadge(investigation.investigationStatus) + return ( + + {vendor.vendorName} + + {status.text} + + {formatDate(investigation.createdAt)} + + {investigation.scheduledStartAt + ? formatDate(investigation.scheduledStartAt) + : "미정"} + + + ) + })} + +
+
+
+
+
+ )} + +
+

+ 새로운 실사가 요청될 업체 ({vendorsWithInvestigations.withoutInvestigations.length}) +

+ {vendorsWithInvestigations.withoutInvestigations.length > 0 ? ( + +
    + {vendorsWithInvestigations.withoutInvestigations.map((vendor) => ( +
  • + {vendor.vendorName} ({vendor.vendorCode || "코드 없음"}) +
  • + ))} +
+
+ ) : ( +

+ 모든 선택된 업체에 이미 실사 요청이 있습니다. +

+ )} +
+ + )} +
+ + ) + } + if (isDesktop) { return ( @@ -72,29 +261,30 @@ export function RequestVendorsInvestigateDialog({ ) : null} - + - Confirm Vendor Investigation Requst + Confirm Vendor Investigation Request - Are you sure you want to request{" "} - {vendors.length} - {vendors.length === 1 ? " vendor" : " vendors"}? - After sent, 벤더실사담당자 will be notified and can manage it. + 선택한 {vendors.length}개 업체에 대한 실사 요청을 확인합니다. + 요청 후 협력업체실사담당자에게 알림이 전송됩니다. - + + {renderContent()} + + - + @@ -124,26 +314,29 @@ export function RequestVendorsInvestigateDialog({ Confirm Vendor Investigation - Are you sure you want to request{" "} - {vendors.length} - {vendors.length === 1 ? " vendor" : " vendors"}? - After sent, 벤더실사담당자 will be notified and can manage it. + 선택한 {vendors.length}개 업체에 대한 실사 요청을 확인합니다. + 요청 후 협력업체실사담당자에게 알림이 전송됩니다. - + +
+ {renderContent()} +
+ + - + -- cgit v1.2.3