diff options
Diffstat (limited to 'lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx')
| -rw-r--r-- | lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx | 351 |
1 files changed, 351 insertions, 0 deletions
diff --git a/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx b/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx new file mode 100644 index 00000000..abba72d1 --- /dev/null +++ b/lib/pq/pq-review-table-new/vendors-table-toolbar-actions.tsx @@ -0,0 +1,351 @@ +"use client" + +import * as React from "react" +import { type Table } from "@tanstack/react-table" +import { Download, ClipboardCheck, X, Send } from "lucide-react" +import { toast } from "sonner" + +import { exportTableToExcel } from "@/lib/export" +import { Button } from "@/components/ui/button" +import { PQSubmission } from "./vendors-table-columns" +import { + requestInvestigationAction, + cancelInvestigationAction, + sendInvestigationResultsAction, + getFactoryLocationAnswer +} from "@/lib/pq/service" +import { RequestInvestigationDialog } from "./request-investigation-dialog" +import { CancelInvestigationDialog } from "./cancel-investigation-dialog" +import { SendResultsDialog } from "./send-results-dialog" + +interface VendorsTableToolbarActionsProps { + table: Table<PQSubmission> +} + +interface InvestigationInitialData { + evaluationType?: "SITE_AUDIT" | "QM_SELF_AUDIT"; + qmManagerId?: number; + forecastedAt?: Date; + createdAt?: Date; + investigationAddress?: string; + investigationNotes?: string; +} + +export function VendorsTableToolbarActions({ table }: VendorsTableToolbarActionsProps) { + const selectedRows = table.getFilteredSelectedRowModel().rows + const [isLoading, setIsLoading] = React.useState(false) + + // Dialog 상태 관리 + const [isRequestDialogOpen, setIsRequestDialogOpen] = React.useState(false) + const [isCancelDialogOpen, setIsCancelDialogOpen] = React.useState(false) + const [isSendResultsDialogOpen, setIsSendResultsDialogOpen] = React.useState(false) + + // 초기 데이터 상태 + const [dialogInitialData, setDialogInitialData] = React.useState<InvestigationInitialData | undefined>(undefined) + + // 실사 의뢰 대화상자 열기 핸들러 +// 실사 의뢰 대화상자 열기 핸들러 +const handleOpenRequestDialog = async () => { + setIsLoading(true); + const initialData: InvestigationInitialData = {}; + + try { + // 선택된 행이 정확히 1개인 경우에만 초기값 설정 + if (selectedRows.length === 1) { + const row = selectedRows[0].original; + + // 승인된 PQ이고 아직 실사가 없는 경우 + if (row.status === "APPROVED" && !row.investigation) { + // Factory Location 정보 가져오기 + const locationResponse = await getFactoryLocationAnswer( + row.vendorId, + row.projectId + ); + + // 기본 주소 설정 - Factory Location 응답 또는 fallback + let defaultAddress = ""; + if (locationResponse.success && locationResponse.factoryLocation) { + defaultAddress = locationResponse.factoryLocation; + } else { + // Factory Location을 찾지 못한 경우 fallback + defaultAddress = row.taxId ? + `${row.vendorName} 사업장 (${row.taxId})` : + `${row.vendorName} 사업장`; + } + + // 이미 같은 회사에 대한 다른 실사가 있는지 확인 + const existingInvestigations = table.getFilteredRowModel().rows + .map(r => r.original) + .filter(r => + r.vendorId === row.vendorId && + r.investigation !== null + ); + + // 같은 업체의 이전 실사 기록이 있다면 참고하되, 주소는 Factory Location 사용 + if (existingInvestigations.length > 0) { + // 날짜 기준으로 정렬하여 가장 최근 것을 가져옴 + const latestInvestigation = existingInvestigations.sort((a, b) => { + const dateA = a.investigation?.createdAt || new Date(0); + const dateB = b.investigation?.createdAt || new Date(0); + return (dateB as Date).getTime() - (dateA as Date).getTime(); + })[0].investigation; + + if (latestInvestigation) { + initialData.evaluationType = latestInvestigation.evaluationType || "SITE_AUDIT"; + initialData.qmManagerId = latestInvestigation.qmManagerId || undefined; + initialData.investigationAddress = defaultAddress; // Factory Location 사용 + + // 날짜는 미래로 설정 + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 14); // 기본값으로 2주 후 + initialData.forecastedAt = futureDate; + } + } else { + // 기본값 설정 + initialData.evaluationType = "SITE_AUDIT"; + const futureDate = new Date(); + futureDate.setDate(futureDate.getDate() + 14); // 기본값으로 2주 후 + initialData.forecastedAt = futureDate; + initialData.investigationAddress = defaultAddress; // Factory Location 사용 + } + } + // 실사가 이미 있고 수정하는 경우 + else if (row.investigation) { + initialData.evaluationType = row.investigation.evaluationType || "SITE_AUDIT"; + initialData.qmManagerId = row.investigation.qmManagerId !== null ? + row.investigation.qmManagerId : undefined; + initialData.forecastedAt = row.investigation.forecastedAt || new Date(); + initialData.investigationAddress = row.investigation.investigationAddress || ""; + initialData.investigationNotes = row.investigation.investigationNotes || ""; + } + } + } catch (error) { + console.error("초기 데이터 로드 중 오류:", error); + toast.error("초기 데이터 로드 중 오류가 발생했습니다."); + } finally { + setIsLoading(false); + + // 초기 데이터 설정 및 대화상자 열기 + setDialogInitialData(Object.keys(initialData).length > 0 ? initialData : undefined); + setIsRequestDialogOpen(true); + } +}; + // 실사 의뢰 요청 처리 + const handleRequestInvestigation = async (formData: { + evaluationType: "SITE_AUDIT" | "QM_SELF_AUDIT", + qmManagerId: number, + forecastedAt: Date, + investigationAddress: string, + investigationNotes?: string + }) => { + setIsLoading(true) + try { + // 승인된 PQ 제출만 필터링 + const approvedPQs = selectedRows.filter(row => + row.original.status === "APPROVED" && !row.original.investigation + ) + + if (approvedPQs.length === 0) { + toast.error("실사를 의뢰할 수 있는 업체가 없습니다. 승인된 PQ 제출만 실사 의뢰가 가능합니다.") + return + } + + // 서버 액션 호출 + const result = await requestInvestigationAction( + approvedPQs.map(row => row.original.id), + formData + ) + + if (result.success) { + toast.success(`${result.count}개 업체에 대한 ${formData.evaluationType === "SITE_AUDIT" ? "실사의뢰평가" : "QM자체평가"}가 의뢰되었습니다.`) + window.location.reload() + } else { + toast.error(result.error || "실사 의뢰 처리 중 오류가 발생했습니다.") + } + } catch (error) { + console.error("실사 의뢰 중 오류 발생:", error) + toast.error("실사 의뢰 중 오류가 발생했습니다.") + } finally { + setIsLoading(false) + setIsRequestDialogOpen(false) + setDialogInitialData(undefined); // 초기 데이터 초기화 + } + } + + const handleCloseRequestDialog = () => { + setIsRequestDialogOpen(false); + setDialogInitialData(undefined); + }; + + + // 실사 의뢰 취소 처리 + const handleCancelInvestigation = async () => { + setIsLoading(true) + try { + // 실사가 계획됨 상태인 PQ만 필터링 + const plannedInvestigations = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.investigationStatus === "PLANNED" + ) + + if (plannedInvestigations.length === 0) { + toast.error("취소할 수 있는 실사 의뢰가 없습니다. 계획 상태의 실사만 취소할 수 있습니다.") + return + } + + // 서버 액션 호출 + const result = await cancelInvestigationAction( + plannedInvestigations.map(row => row.original.investigation!.id) + ) + + if (result.success) { + toast.success(`${result.count}개 업체에 대한 실사 의뢰가 취소되었습니다.`) + window.location.reload() + } else { + toast.error(result.error || "실사 취소 처리 중 오류가 발생했습니다.") + } + } catch (error) { + console.error("실사 의뢰 취소 중 오류 발생:", error) + toast.error("실사 의뢰 취소 중 오류가 발생했습니다.") + } finally { + setIsLoading(false) + setIsCancelDialogOpen(false) + } + } + + // 실사 결과 발송 처리 + const handleSendInvestigationResults = async () => { + setIsLoading(true) + try { + // 완료된 실사만 필터링 + const completedInvestigations = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.investigationStatus === "COMPLETED" + ) + + if (completedInvestigations.length === 0) { + toast.error("발송할 실사 결과가 없습니다. 완료된 실사만 결과를 발송할 수 있습니다.") + return + } + + // 서버 액션 호출 + const result = await sendInvestigationResultsAction( + completedInvestigations.map(row => row.original.investigation!.id) + ) + + if (result.success) { + toast.success(`${result.count}개 업체에 대한 실사 결과가 발송되었습니다.`) + window.location.reload() + } else { + toast.error(result.error || "실사 결과 발송 처리 중 오류가 발생했습니다.") + } + } catch (error) { + console.error("실사 결과 발송 중 오류 발생:", error) + toast.error("실사 결과 발송 중 오류가 발생했습니다.") + } finally { + setIsLoading(false) + setIsSendResultsDialogOpen(false) + } + } + + // 승인된 업체 수 확인 + const approvedPQsCount = selectedRows.filter(row => + row.original.status === "APPROVED" && !row.original.investigation + ).length + + // 계획 상태 실사 수 확인 + const plannedInvestigationsCount = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.investigationStatus === "PLANNED" + ).length + + // 완료된 실사 수 확인 + const completedInvestigationsCount = selectedRows.filter(row => + row.original.investigation && + row.original.investigation.investigationStatus === "COMPLETED" + ).length + + return ( + <> + <div className="flex items-center gap-2"> + {/* 실사 의뢰 버튼 */} + <Button + variant="outline" + size="sm" + onClick={handleOpenRequestDialog} // 여기를 수정: 새로운 핸들러 함수 사용 + disabled={isLoading || selectedRows.length === 0} + className="gap-2" + > + <ClipboardCheck className="size-4" aria-hidden="true" /> + <span className="hidden sm:inline">실사 의뢰</span> + </Button> + + {/* 실사 의뢰 취소 버튼 */} + <Button + variant="outline" + size="sm" + onClick={() => setIsCancelDialogOpen(true)} + disabled={isLoading || selectedRows.length === 0} + className="gap-2" + > + <X className="size-4" aria-hidden="true" /> + <span className="hidden sm:inline">실사 취소</span> + </Button> + + {/* 실사 결과 발송 버튼 */} + <Button + variant="outline" + size="sm" + onClick={() => setIsSendResultsDialogOpen(true)} + disabled={isLoading || selectedRows.length === 0} + className="gap-2" + > + <Send className="size-4" aria-hidden="true" /> + <span className="hidden sm:inline">결과 발송</span> + </Button> + + {/** Export 버튼 */} + <Button + variant="outline" + size="sm" + onClick={() => + exportTableToExcel(table, { + filename: "vendors-pq-submissions", + excludeColumns: ["select", "actions"], + }) + } + className="gap-2" + > + <Download className="size-4" aria-hidden="true" /> + <span className="hidden sm:inline">Export</span> + </Button> + </div> + + {/* 실사 의뢰 Dialog */} + <RequestInvestigationDialog + isOpen={isRequestDialogOpen} + onClose={handleCloseRequestDialog} // 새로운 핸들러로 변경 + onSubmit={handleRequestInvestigation} + selectedCount={approvedPQsCount} + initialData={dialogInitialData} // 초기 데이터 전달 + /> + + + {/* 실사 취소 Dialog */} + <CancelInvestigationDialog + isOpen={isCancelDialogOpen} + onClose={() => setIsCancelDialogOpen(false)} + onConfirm={handleCancelInvestigation} + selectedCount={plannedInvestigationsCount} + /> + + {/* 결과 발송 Dialog */} + <SendResultsDialog + isOpen={isSendResultsDialogOpen} + onClose={() => setIsSendResultsDialogOpen(false)} + onConfirm={handleSendInvestigationResults} + selectedCount={completedInvestigationsCount} + /> + </> + ) +}
\ No newline at end of file |
