summaryrefslogtreecommitdiff
path: root/lib/procurement-rfqs/table/rfq-table-toolbar-actions.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/procurement-rfqs/table/rfq-table-toolbar-actions.tsx')
-rw-r--r--lib/procurement-rfqs/table/rfq-table-toolbar-actions.tsx279
1 files changed, 279 insertions, 0 deletions
diff --git a/lib/procurement-rfqs/table/rfq-table-toolbar-actions.tsx b/lib/procurement-rfqs/table/rfq-table-toolbar-actions.tsx
new file mode 100644
index 00000000..26725797
--- /dev/null
+++ b/lib/procurement-rfqs/table/rfq-table-toolbar-actions.tsx
@@ -0,0 +1,279 @@
+"use client"
+
+import * as React from "react"
+import { type Table } from "@tanstack/react-table"
+import { ClipboardList, Download, Send, Lock, Upload } from "lucide-react"
+import { toast } from "sonner"
+
+import { exportTableToExcel } from "@/lib/export"
+import { Button } from "@/components/ui/button"
+import { ProcurementRfqsView } from "@/db/schema"
+import { PrDetailsDialog } from "./pr-item-dialog"
+import { sealRfq, sendRfq, getPORfqs, fetchExternalRfqs } from "../services"
+
+// total 필드 추가하여 타입 정의 수정
+type PORfqsReturn = Awaited<ReturnType<typeof getPORfqs>>
+
+interface RFQTableToolbarActionsProps {
+ table: Table<ProcurementRfqsView>;
+ // 타입 수정
+ localData?: PORfqsReturn;
+ setLocalData?: React.Dispatch<React.SetStateAction<PORfqsReturn>>;
+ onSuccess?: () => void;
+}
+
+export function RFQTableToolbarActions({
+ table,
+ localData,
+ setLocalData,
+ onSuccess
+}: RFQTableToolbarActionsProps) {
+ // 다이얼로그 열림/닫힘 상태 관리
+ const [dialogOpen, setDialogOpen] = React.useState(false)
+ const [isProcessing, setIsProcessing] = React.useState(false)
+
+ // 선택된 RFQ 가져오기
+ const getSelectedRfq = (): ProcurementRfqsView | null => {
+ const selectedRows = table.getFilteredSelectedRowModel().rows
+ if (selectedRows.length === 1) {
+ return selectedRows[0].original
+ }
+ return null
+ }
+
+ // 선택된 RFQ
+ const selectedRfq = getSelectedRfq()
+
+ // PR 상세보기 버튼 클릭 핸들러
+ const handleViewPrDetails = () => {
+ const rfq = getSelectedRfq()
+ if (!rfq) {
+ toast.warning("RFQ를 선택해주세요")
+ return
+ }
+
+ if (!rfq.prItemsCount || rfq.prItemsCount <= 0) {
+ toast.warning("선택한 RFQ에 PR 항목이 없습니다")
+ return
+ }
+
+ setDialogOpen(true)
+ }
+
+ // RFQ 밀봉 버튼 클릭 핸들러
+ const handleSealRfq = async () => {
+ const rfq = getSelectedRfq()
+ if (!rfq) {
+ toast.warning("RFQ를 선택해주세요")
+ return
+ }
+
+ // 이미 밀봉된 RFQ인 경우
+ if (rfq.rfqSealedYn) {
+ toast.warning("이미 밀봉된 RFQ입니다")
+ return
+ }
+
+ try {
+ setIsProcessing(true)
+
+ // 낙관적 UI 업데이트 (로컬 데이터 먼저 갱신)
+ if (localData?.data && setLocalData) {
+ // 로컬 데이터에서 해당 행 찾기
+ const rowIndex = localData.data.findIndex(row => row.id === rfq.id);
+ if (rowIndex >= 0) {
+ // 불변성을 유지하면서 로컬 데이터 업데이트 - 타입 안전하게 복사
+ const newData = [...localData.data] as ProcurementRfqsView[];
+ newData[rowIndex] = { ...newData[rowIndex], rfqSealedYn: "Y" };
+
+ // 전체 데이터 구조 복사하여 업데이트, total 필드가 있다면 유지
+ setLocalData({
+ ...localData,
+ data: newData ?? [],
+ pageCount: localData.pageCount,
+ total: localData.total ?? 0
+ });
+ }
+ }
+
+ const result = await sealRfq(rfq.id)
+
+ if (result.success) {
+ toast.success("RFQ가 성공적으로 밀봉되었습니다")
+ // 데이터 리프레시
+ onSuccess?.()
+ } else {
+ toast.error(result.message || "RFQ 밀봉 중 오류가 발생했습니다")
+
+ // 서버 요청 실패 시 낙관적 업데이트 되돌리기
+ if (localData?.data && setLocalData) {
+ const rowIndex = localData.data.findIndex(row => row.id === rfq.id);
+ if (rowIndex >= 0) {
+ const newData = [...localData.data] as ProcurementRfqsView[];
+ newData[rowIndex] = { ...newData[rowIndex], rfqSealedYn: rfq.rfqSealedYn }; // 원래 값으로 복원
+ setLocalData({
+ ...localData,
+ data: newData ?? [],
+ pageCount: localData.pageCount,
+ total: localData.total ?? 0
+ });
+ }
+ }
+ }
+ } catch (error) {
+ console.error("RFQ 밀봉 오류:", error)
+ toast.error("RFQ 밀봉 중 오류가 발생했습니다")
+
+ // 에러 발생 시 낙관적 업데이트 되돌리기
+ if (localData?.data && setLocalData) {
+ const rowIndex = localData.data.findIndex(row => row.id === rfq.id);
+ if (rowIndex >= 0) {
+ const newData = [...localData.data] as ProcurementRfqsView[];
+ newData[rowIndex] = { ...newData[rowIndex], rfqSealedYn: rfq.rfqSealedYn }; // 원래 값으로 복원
+ setLocalData({
+ ...localData,
+ data: newData ?? [],
+ pageCount: localData.pageCount,
+ total: localData.total ?? 0
+ });
+ }
+ }
+ } finally {
+ setIsProcessing(false)
+ }
+ }
+
+ // RFQ 전송 버튼 클릭 핸들러
+ const handleSendRfq = async () => {
+ const rfq = getSelectedRfq()
+ if (!rfq) {
+ toast.warning("RFQ를 선택해주세요")
+ return
+ }
+
+ // 전송 가능한 상태인지 확인
+ if (rfq.status !== "RFQ Vendor Assignned" && rfq.status !== "RFQ Sent") {
+ toast.warning("벤더가 할당된 RFQ이거나 전송한 적이 있는 RFQ만 전송할 수 있습니다")
+ return
+ }
+
+ try {
+ setIsProcessing(true)
+
+ const result = await sendRfq(rfq.id)
+
+ if (result.success) {
+ toast.success("RFQ가 성공적으로 전송되었습니다")
+ // 데이터 리프레시
+ onSuccess?.()
+ } else {
+ toast.error(result.message || "RFQ 전송 중 오류가 발생했습니다")
+ }
+ } catch (error) {
+ console.error("RFQ 전송 오류:", error)
+ toast.error("RFQ 전송 중 오류가 발생했습니다")
+ } finally {
+ setIsProcessing(false)
+ }
+ }
+
+ const handleFetchExternalRfqs = async () => {
+ try {
+ setIsProcessing(true);
+
+ const result = await fetchExternalRfqs();
+
+ if (result.success) {
+ toast.success(result.message || "외부 RFQ를 성공적으로 가져왔습니다");
+ // 데이터 리프레시
+ onSuccess?.()
+ } else {
+ toast.error(result.message || "외부 RFQ를 가져오는 중 오류가 발생했습니다");
+ }
+ } catch (error) {
+ console.error("외부 RFQ 가져오기 오류:", error);
+ toast.error("외부 RFQ를 가져오는 중 오류가 발생했습니다");
+ } finally {
+ setIsProcessing(false);
+ }
+ };
+
+ return (
+ <>
+ <div className="flex items-center gap-2">
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={() =>
+ exportTableToExcel(table, {
+ filename: "rfq",
+ excludeColumns: ["select", "actions"],
+ })
+ }
+ className="gap-2"
+ >
+ <Download className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">Export</span>
+ </Button>
+ {/* RFQ 가져오기 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleFetchExternalRfqs}
+ className="gap-2"
+ disabled={isProcessing}
+ >
+ <Upload className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">RFQ 가져오기</span>
+ </Button>
+
+ {/* PR 상세보기 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleViewPrDetails}
+ className="gap-2"
+ disabled={!selectedRfq || !(selectedRfq.prItemsCount && selectedRfq.prItemsCount > 0)}
+ >
+ <ClipboardList className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">PR 상세보기</span>
+ </Button>
+
+ {/* RFQ 밀봉 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleSealRfq}
+ className="gap-2"
+ disabled={!selectedRfq || selectedRfq.rfqSealedYn === "Y" || selectedRfq.status !== "RFQ Sent" || isProcessing}
+ >
+ <Lock className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">RFQ 밀봉</span>
+ </Button>
+
+ {/* RFQ 전송 버튼 */}
+ <Button
+ variant="outline"
+ size="sm"
+ onClick={handleSendRfq}
+ className="gap-2"
+ disabled={
+ !selectedRfq ||
+ (selectedRfq.status !== "RFQ Vendor Assignned" && selectedRfq.status !== "RFQ Sent") ||
+ isProcessing
+ }
+ >
+ <Send className="size-4" aria-hidden="true" />
+ <span className="hidden sm:inline">RFQ 전송</span>
+ </Button>
+ </div>
+
+ {/* PR 상세정보 다이얼로그 */}
+ <PrDetailsDialog
+ open={dialogOpen}
+ onOpenChange={setDialogOpen}
+ selectedRfq={selectedRfq}
+ />
+ </>
+ )
+} \ No newline at end of file