"use client" import * as React from "react" import { format } from "date-fns" import { ko } from "date-fns/locale" import { Building2, Calendar, Users, MessageSquare, Ellipsis, Eye, Edit, Download, Paperclip } from "lucide-react" import { toast } from "sonner" import { downloadFile } from "@/lib/file-download" import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "@/components/ui/table" import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" import { VendorInfoSheet } from "./vendor-info-sheet" import type { VendorInfoFormValues } from "./vendor-info-sheet" import { submitVendorInfoAction } from "./service" import { SiteVisitDetailDialog } from "./site-visit-detail-dialog" import { ShiAttendeesDialog } from "./shi-attendees-dialog" import { formatDate } from "../utils" // SHI 참석자 총 인원수 계산 함수 function getTotalShiAttendees(shiAttendees: Record | null): number { if (!shiAttendees) return 0 let total = 0 Object.entries(shiAttendees).forEach(([, value]) => { if (value && typeof value === 'object' && 'checked' in value && 'count' in value) { const attendee = value as { checked: boolean; count: number } if (attendee.checked) { total += attendee.count } } }) return total } interface SiteVisitRequest { id: number investigationId: number requesterId: number | null inspectionDuration: string | null requestedStartDate: Date | null requestedEndDate: Date | null shiAttendees: Record | null shiAttendeeDetails?: string | null vendorRequests: Record | null additionalRequests: string | null status: string sentAt: Date | null createdAt: Date updatedAt: Date // 실사 정보 investigationMethod: string | null // QM담당자가 작성한 실사방법 investigationAddress: string | null investigationNotes: string | null forecastedAt: Date | null actualAt: Date | null result: string | null resultNotes: string | null // PQ 정보 pqItems: string | null | Array<{itemCode: string, itemName: string}> // 요청자 정보 requesterName: string | null requesterEmail: string | null requesterTitle: string | null // QM 매니저 정보 qmManagerName: string | null qmManagerEmail: string | null qmManagerTitle: string | null // 협력업체 정보 vendorInfo?: { id: number siteVisitRequestId: number factoryName: string factoryLocation: string factoryAddress: string factoryPicName: string factoryPicPhone: string factoryPicEmail: string factoryDirections: string | null accessProcedure: string | null hasAttachments: boolean otherInfo: string | null submittedAt: Date submittedBy: number createdAt: Date updatedAt: Date } | null // SHI 첨부파일 shiAttachments?: Array<{ id: number siteVisitRequestId: number vendorSiteVisitInfoId: number | null fileName: string originalFileName: string filePath: string fileSize: number mimeType: string createdAt: Date updatedAt: Date }> | null } interface ClientSiteVisitWrapperProps { siteVisitRequests: SiteVisitRequest[] vendorId: number } export function ClientSiteVisitWrapper({ siteVisitRequests, vendorId, }: ClientSiteVisitWrapperProps) { const [selectedRequest, setSelectedRequest] = React.useState(null) const [isDetailDialogOpen, setIsDetailDialogOpen] = React.useState(false) const [isVendorInfoSheetOpen, setIsVendorInfoSheetOpen] = React.useState(false) const [selectedSiteVisitRequestId, setSelectedSiteVisitRequestId] = React.useState(null) const [isShiAttendeesDialogOpen, setIsShiAttendeesDialogOpen] = React.useState(false) const getInvestigationMethodLabel = (method: string | null) => { switch (method) { case "PURCHASE_SELF_EVAL": return "구매자체평가" case "DOCUMENT_EVAL": return "서류평가" case "PRODUCT_INSPECTION": return "제품검사평가" case "SITE_VISIT_EVAL": return "방문실사평가" default: return method || "-" } } const getStatusLabel = (status: string) => { switch (status) { case "REQUESTED": return "요청됨" case "SENT": return "발송됨" case "COMPLETED": return "완료" case "VENDOR_SUBMITTED": return "협력업체 제출" default: return status } } const getStatusVariant = (status: string) => { switch (status) { case "REQUESTED": return "secondary" case "SENT": return "default" case "COMPLETED": return "outline" case "VENDOR_SUBMITTED": return "default" default: return "secondary" } } const formatDateRange = (startDate: Date | null, endDate: Date | null) => { if (!startDate) return "-" if (!endDate || startDate.getTime() === endDate.getTime()) { return formatDate(startDate) } return `${formatDate(startDate)} ~ ${formatDate(endDate)}` } return (
{/* 헤더 */}

실사정보 관리

방문실사 요청 정보를 조회하고 회신할 수 있습니다.

{/*
Vendor ID: {vendorId}
*/}
{/* 통계 카드 */}
전체 요청
{siteVisitRequests.length}
발송됨
{siteVisitRequests.filter(r => r.status === "SENT").length}
완료
{siteVisitRequests.filter(r => r.status === "COMPLETED").length}
대기중
{siteVisitRequests.filter(r => r.status === "REQUESTED").length}
{/* 테이블 */} 방문실사 요청 목록 SHI에서 요청한 방문실사 정보를 확인하고 회신할 수 있습니다. No. 상태 실사품목 실사방법 실사기간 SHI 자료 실사요청일 실제 실사일 실사결과 SHI참석자 작업 {siteVisitRequests.map((request, index) => ( {index + 1} {getStatusLabel(request.status)} {/* 실사품목 - PQ에서 가져온 정보 표시 */} {request.pqItems ? ( typeof request.pqItems === 'string' ? ( request.pqItems ) : Array.isArray(request.pqItems) && request.pqItems.length > 0 ? (
{request.pqItems.map((item, index) => (
{item.itemCode} {item.itemName}
))}
) : "-" ) : "-"}
{getInvestigationMethodLabel(request.investigationMethod)} {request.inspectionDuration ? `${request.inspectionDuration}일` : "-"} {request.shiAttachments && request.shiAttachments.length > 0 ? ( {request.shiAttachments.map((attachment) => ( { downloadFile( attachment.filePath, attachment.originalFileName, { showToast: true, onSuccess: (fileName) => { toast.success(`파일 다운로드 완료: ${fileName}`); }, onError: (error) => { toast.error(`다운로드 실패: ${error}`); } } ); }} > {attachment.originalFileName} ))} ) : "-"} {formatDateRange(request.requestedStartDate, request.requestedEndDate)} {formatDate(request.actualAt, "kr")} {request.result ? ( {request.result === "APPROVED" ? "통과" : request.result === "SUPPLEMENT" ? "보완" : "불가"} ) : "-"} {getTotalShiAttendees(request.shiAttendees) > 0 ? ( ) : "-"} { setSelectedRequest(request) setIsDetailDialogOpen(true) }} > 상세보기 { setSelectedSiteVisitRequestId(request.id) setIsVendorInfoSheetOpen(true) }} > 정보입력
))}
{/* 상세 정보 다이얼로그 */} {/* 협력업체 정보 입력 Sheet */} {selectedSiteVisitRequestId && ( { setIsVendorInfoSheetOpen(false) setSelectedSiteVisitRequestId(null) }} onSubmit={async (data: VendorInfoFormValues & { attachments?: File[] }) => { try { const result = await submitVendorInfoAction({ siteVisitRequestId: selectedSiteVisitRequestId, ...data }) if (result.success) { toast.success(result.message || "협력업체 정보가 성공적으로 제출되었습니다.") // 페이지 새로고침으로 데이터 업데이트 window.location.reload() } else { toast.error(result.error || "협력업체 정보 제출 중 오류가 발생했습니다.") } } catch (error) { console.error("협력업체 정보 제출 오류:", error) toast.error("협력업체 정보 제출 중 오류가 발생했습니다.") } }} siteVisitRequestId={selectedSiteVisitRequestId} initialData={siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo ? { factoryName: siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo?.factoryName || "", factoryLocation: siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo?.factoryLocation || "", factoryAddress: siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo?.factoryAddress || "", factoryPicName: siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo?.factoryPicName || "", factoryPicPhone: siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo?.factoryPicPhone || "", factoryPicEmail: siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo?.factoryPicEmail || "", factoryDirections: siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo?.factoryDirections || "", accessProcedure: siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo?.accessProcedure || "", hasAttachments: siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo?.hasAttachments || false, otherInfo: siteVisitRequests.find(r => r.id === selectedSiteVisitRequestId)?.vendorInfo?.otherInfo || "", } : null} /> )} {/* SHI 참석자 정보 다이얼로그 */}
) }