summaryrefslogtreecommitdiff
path: root/lib/vendors/table/request-vendor-investigate-dialog.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendors/table/request-vendor-investigate-dialog.tsx')
-rw-r--r--lib/vendors/table/request-vendor-investigate-dialog.tsx243
1 files changed, 218 insertions, 25 deletions
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<typeof Dialog> {
@@ -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<any[]>([])
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 (
+ <>
+ <div className="space-y-4">
+ {isLoading ? (
+ <div className="flex items-center justify-center py-4">
+ <Loader className="size-6 animate-spin text-muted-foreground" />
+ </div>
+ ) : (
+ <>
+ {vendorsWithInvestigations.withInvestigations.length > 0 && (
+ <Alert>
+ <AlertTriangle className="h-4 w-4" />
+ <AlertTitle>기존 실사 요청 정보가 있습니다</AlertTitle>
+ <AlertDescription>
+ 선택한 {vendors.length}개 업체 중 {vendorsWithInvestigations.withInvestigations.length}개 업체에 대한
+ 기존 실사 요청이 있습니다. 새로운 요청은 기존 데이터가 없는 업체에만 적용됩니다.
+ </AlertDescription>
+ </Alert>
+ )}
+
+ {vendorsWithInvestigations.withInvestigations.length > 0 && (
+ <Accordion type="single" collapsible className="w-full">
+ <AccordionItem value="existing-investigations">
+ <AccordionTrigger className="font-medium">
+ 기존 실사 요청 ({vendorsWithInvestigations.withInvestigations.length})
+ </AccordionTrigger>
+ <AccordionContent>
+ <ScrollArea className="max-h-[200px]">
+ <Table>
+ <TableHeader>
+ <TableRow>
+ <TableHead>업체명</TableHead>
+ <TableHead>상태</TableHead>
+ <TableHead>요청일</TableHead>
+ <TableHead>예정 일정</TableHead>
+ </TableRow>
+ </TableHeader>
+ <TableBody>
+ {vendorsWithInvestigations.withInvestigations.map(({ vendor, investigation }) => {
+ const status = getStatusBadge(investigation.investigationStatus)
+ return (
+ <TableRow key={investigation.investigationId}>
+ <TableCell className="font-medium">{vendor.vendorName}</TableCell>
+ <TableCell>
+ <Badge variant={status.variant as any}>{status.text}</Badge>
+ </TableCell>
+ <TableCell>{formatDate(investigation.createdAt)}</TableCell>
+ <TableCell>
+ {investigation.scheduledStartAt
+ ? formatDate(investigation.scheduledStartAt)
+ : "미정"}
+ </TableCell>
+ </TableRow>
+ )
+ })}
+ </TableBody>
+ </Table>
+ </ScrollArea>
+ </AccordionContent>
+ </AccordionItem>
+ </Accordion>
+ )}
+
+ <div>
+ <h3 className="text-sm font-medium mb-2">
+ 새로운 실사가 요청될 업체 ({vendorsWithInvestigations.withoutInvestigations.length})
+ </h3>
+ {vendorsWithInvestigations.withoutInvestigations.length > 0 ? (
+ <ScrollArea className="max-h-[200px]">
+ <ul className="space-y-1">
+ {vendorsWithInvestigations.withoutInvestigations.map((vendor) => (
+ <li key={vendor.id} className="text-sm py-1 px-2 border-b">
+ {vendor.vendorName} ({vendor.vendorCode || "코드 없음"})
+ </li>
+ ))}
+ </ul>
+ </ScrollArea>
+ ) : (
+ <p className="text-sm text-muted-foreground py-2">
+ 모든 선택된 업체에 이미 실사 요청이 있습니다.
+ </p>
+ )}
+ </div>
+ </>
+ )}
+ </div>
+ </>
+ )
+ }
+
if (isDesktop) {
return (
<Dialog {...props}>
@@ -72,29 +261,30 @@ export function RequestVendorsInvestigateDialog({
<DialogTrigger asChild>
<Button variant="outline" size="sm" className="gap-2">
<SendHorizonal className="size-4" aria-hidden="true" />
- Vendor Investigation Request ({vendors.length})
+ 실사 요청 ({vendors.length})
</Button>
</DialogTrigger>
) : null}
- <DialogContent>
+ <DialogContent className="max-w-md">
<DialogHeader>
- <DialogTitle>Confirm Vendor Investigation Requst</DialogTitle>
+ <DialogTitle>Confirm Vendor Investigation Request</DialogTitle>
<DialogDescription>
- Are you sure you want to request{" "}
- <span className="font-medium">{vendors.length}</span>
- {vendors.length === 1 ? " vendor" : " vendors"}?
- After sent, 벤더실사담당자 will be notified and can manage it.
+ 선택한 {vendors.length}개 업체에 대한 실사 요청을 확인합니다.
+ 요청 후 협력업체실사담당자에게 알림이 전송됩니다.
</DialogDescription>
</DialogHeader>
- <DialogFooter className="gap-2 sm:space-x-0">
+
+ {renderContent()}
+
+ <DialogFooter className="gap-2 sm:space-x-0 mt-4">
<DialogClose asChild>
- <Button variant="outline">Cancel</Button>
+ <Button variant="outline">취소</Button>
</DialogClose>
<Button
aria-label="Request selected vendors"
variant="default"
onClick={onApprove}
- disabled={isApprovePending}
+ disabled={isApprovePending || isLoading || vendorsWithInvestigations.withoutInvestigations.length === 0}
>
{isApprovePending && (
<Loader
@@ -102,7 +292,7 @@ export function RequestVendorsInvestigateDialog({
aria-hidden="true"
/>
)}
- Request
+ 요청하기
</Button>
</DialogFooter>
</DialogContent>
@@ -124,26 +314,29 @@ export function RequestVendorsInvestigateDialog({
<DrawerHeader>
<DrawerTitle>Confirm Vendor Investigation</DrawerTitle>
<DrawerDescription>
- Are you sure you want to request{" "}
- <span className="font-medium">{vendors.length}</span>
- {vendors.length === 1 ? " vendor" : " vendors"}?
- After sent, 벤더실사담당자 will be notified and can manage it.
+ 선택한 {vendors.length}개 업체에 대한 실사 요청을 확인합니다.
+ 요청 후 협력업체실사담당자에게 알림이 전송됩니다.
</DrawerDescription>
</DrawerHeader>
- <DrawerFooter className="gap-2 sm:space-x-0">
+
+ <div className="px-4">
+ {renderContent()}
+ </div>
+
+ <DrawerFooter className="gap-2 sm:space-x-0 mt-4">
<DrawerClose asChild>
- <Button variant="outline">Cancel</Button>
+ <Button variant="outline">취소</Button>
</DrawerClose>
<Button
aria-label="Request selected vendors"
variant="default"
onClick={onApprove}
- disabled={isApprovePending}
+ disabled={isApprovePending || isLoading || vendorsWithInvestigations.withoutInvestigations.length === 0}
>
{isApprovePending && (
<Loader className="mr-2 size-4 animate-spin" aria-hidden="true" />
)}
- Request
+ 요청하기
</Button>
</DrawerFooter>
</DrawerContent>