diff options
| author | joonhoekim <26rote@gmail.com> | 2025-12-01 19:52:06 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-12-01 19:52:06 +0900 |
| commit | 44b74ff4170090673b6eeacd8c528e0abf47b7aa (patch) | |
| tree | 3f3824b4e2cb24536c1677188b4cae5b8909d3da /lib/b-rfq/final | |
| parent | 4953e770929b82ef77da074f77071ebd0f428529 (diff) | |
(김준회) deprecated code 정리
Diffstat (limited to 'lib/b-rfq/final')
| -rw-r--r-- | lib/b-rfq/final/final-rfq-detail-columns.tsx | 589 | ||||
| -rw-r--r-- | lib/b-rfq/final/final-rfq-detail-table.tsx | 297 | ||||
| -rw-r--r-- | lib/b-rfq/final/final-rfq-detail-toolbar-actions.tsx | 201 | ||||
| -rw-r--r-- | lib/b-rfq/final/update-final-rfq-sheet.tsx | 70 |
4 files changed, 0 insertions, 1157 deletions
diff --git a/lib/b-rfq/final/final-rfq-detail-columns.tsx b/lib/b-rfq/final/final-rfq-detail-columns.tsx deleted file mode 100644 index 88d62765..00000000 --- a/lib/b-rfq/final/final-rfq-detail-columns.tsx +++ /dev/null @@ -1,589 +0,0 @@ -// final-rfq-detail-columns.tsx -"use client" - -import * as React from "react" -import { type ColumnDef } from "@tanstack/react-table" -import { type Row } from "@tanstack/react-table" -import { - Ellipsis, Building, Eye, Edit, - MessageSquare, Settings, CheckCircle2, XCircle, DollarSign, Calendar -} from "lucide-react" - -import { formatDate } from "@/lib/utils" -import { Badge } from "@/components/ui/badge" -import { Button } from "@/components/ui/button" -import { Checkbox } from "@/components/ui/checkbox" -import { - DropdownMenu, DropdownMenuContent, DropdownMenuItem, - DropdownMenuSeparator, DropdownMenuTrigger, DropdownMenuShortcut -} from "@/components/ui/dropdown-menu" -import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" -import { FinalRfqDetailView } from "@/db/schema" - -// RowAction 타입 정의 -export interface DataTableRowAction<TData> { - row: Row<TData> - type: "update" -} - -interface GetFinalRfqDetailColumnsProps { - onSelectDetail?: (detail: any) => void - setRowAction?: React.Dispatch<React.SetStateAction<DataTableRowAction<FinalRfqDetailView> | null>> -} - -export function getFinalRfqDetailColumns({ - onSelectDetail, - setRowAction -}: GetFinalRfqDetailColumnsProps = {}): ColumnDef<FinalRfqDetailView>[] { - - return [ - /** ───────────── 체크박스 ───────────── */ - { - id: "select", - header: ({ table }) => ( - <Checkbox - checked={ - table.getIsAllPageRowsSelected() || - (table.getIsSomePageRowsSelected() && "indeterminate") - } - onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - className="translate-y-0.5" - /> - ), - cell: ({ row }) => ( - <Checkbox - checked={row.getIsSelected()} - onCheckedChange={(value) => row.toggleSelected(!!value)} - aria-label="Select row" - className="translate-y-0.5" - /> - ), - size: 40, - enableSorting: false, - enableHiding: false, - }, - - /** 1. RFQ Status */ - { - accessorKey: "finalRfqStatus", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="최종 RFQ Status" /> - ), - cell: ({ row }) => { - const status = row.getValue("finalRfqStatus") as string - const getFinalStatusColor = (status: string) => { - switch (status) { - case "DRAFT": return "outline" - case "Final RFQ Sent": return "default" - case "Quotation Received": return "success" - case "Vendor Selected": return "default" - default: return "secondary" - } - } - return ( - <Badge variant={getFinalStatusColor(status) as any}> - {status} - </Badge> - ) - }, - size: 120 - }, - - /** 2. RFQ No. */ - { - accessorKey: "rfqCode", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="RFQ No." /> - ), - cell: ({ row }) => ( - <div className="text-sm font-medium"> - {row.getValue("rfqCode") as string} - </div> - ), - size: 120, - }, - - /** 3. Rev. */ - { - accessorKey: "returnRevision", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Rev." /> - ), - cell: ({ row }) => { - const revision = row.getValue("returnRevision") as number - return revision > 0 ? ( - <Badge variant="outline"> - Rev. {revision} - </Badge> - ) : ( - <Badge variant="outline"> - Rev. 0 - </Badge> - ) - }, - size: 80, - }, - - /** 4. Vendor Code */ - { - accessorKey: "vendorCode", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Vendor Code" /> - ), - cell: ({ row }) => ( - <div className="text-sm font-medium"> - {row.original.vendorCode} - </div> - ), - size: 100, - }, - - /** 5. Vendor Name */ - { - accessorKey: "vendorName", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Vendor Name" /> - ), - cell: ({ row }) => ( - <div className="text-sm font-medium"> - {row.original.vendorName} - </div> - ), - size: 150, - }, - - /** 6. 업체분류 */ - { - id: "vendorClassification", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="업체분류" /> - ), - cell: ({ row }) => { - const vendorCode = row.original.vendorCode as string - return vendorCode ? ( - <Badge variant="success" className="text-xs"> - 정규업체 - </Badge> - ) : ( - <Badge variant="secondary" className="text-xs"> - 잠재업체 - </Badge> - ) - }, - size: 100, - }, - - /** 7. CP 현황 */ - { - accessorKey: "cpRequestYn", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="CP 현황" /> - ), - cell: ({ row }) => { - const cpRequest = row.getValue("cpRequestYn") as boolean - return cpRequest ? ( - <Badge variant="success" className="text-xs"> - 신청 - </Badge> - ) : ( - <Badge variant="outline" className="text-xs"> - 미신청 - </Badge> - ) - }, - size: 80, - }, - - /** 8. GTC현황 */ - { - id: "gtcStatus", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="GTC현황" /> - ), - cell: ({ row }) => { - const gtc = row.original.gtc as string - const gtcValidDate = row.original.gtcValidDate as string - const prjectGtcYn = row.original.prjectGtcYn as boolean - - if (prjectGtcYn || gtc) { - return ( - <div className="space-y-1"> - <Badge variant="success" className="text-xs"> - 보유 - </Badge> - {gtcValidDate && ( - <div className="text-xs text-muted-foreground"> - {gtcValidDate} - </div> - )} - </div> - ) - } - return ( - <Badge variant="outline" className="text-xs"> - 미보유 - </Badge> - ) - }, - size: 100, - }, - - /** 9. TBE 결과 (스키마에 없어서 placeholder) */ - { - id: "tbeResult", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="TBE 결과" /> - ), - cell: ({ row }) => { - // TODO: TBE 결과 로직 구현 필요 - return ( - <span className="text-muted-foreground text-xs">-</span> - ) - }, - size: 80, - }, - - /** 10. 최종 선정 */ - { - id: "finalSelection", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="최종 선정" /> - ), - cell: ({ row }) => { - const status = row.original.finalRfqStatus as string - return status === "Vendor Selected" ? ( - <Badge variant="success" className="text-xs"> - <CheckCircle2 className="h-3 w-3 mr-1" /> - 선정 - </Badge> - ) : ( - <span className="text-muted-foreground text-xs">-</span> - ) - }, - size: 80, - }, - - /** 11. Currency */ - { - accessorKey: "currency", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Currency" /> - ), - cell: ({ row }) => { - const currency = row.getValue("currency") as string - return currency ? ( - <Badge variant="outline" className="text-xs"> - {/* <DollarSign className="h-3 w-3 mr-1" /> */} - {currency} - </Badge> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 80, - }, - - /** 12. Terms of Payment */ - { - accessorKey: "paymentTermsCode", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Terms of Payment" /> - ), - cell: ({ row }) => { - const paymentTermsCode = row.getValue("paymentTermsCode") as string - return paymentTermsCode ? ( - <Badge variant="secondary" className="text-xs"> - {paymentTermsCode} - </Badge> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 120, - }, - - /** 13. Payment Desc. */ - { - accessorKey: "paymentTermsDescription", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Payment Desc." /> - ), - cell: ({ row }) => { - const description = row.getValue("paymentTermsDescription") as string - return description ? ( - <div className="text-xs max-w-[150px] truncate" title={description}> - {description} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 150, - }, - - /** 14. TAX */ - { - accessorKey: "taxCode", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="TAX" /> - ), - cell: ({ row }) => { - const taxCode = row.getValue("taxCode") as string - return taxCode ? ( - <Badge variant="outline" className="text-xs"> - {taxCode} - </Badge> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 80, - }, - - /** 15. Delivery Date* */ - { - accessorKey: "deliveryDate", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Delivery Date*" /> - ), - cell: ({ row }) => { - const deliveryDate = row.getValue("deliveryDate") as Date - return deliveryDate ? ( - <div className="text-sm"> - {formatDate(deliveryDate, "KR")} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 120, - }, - - /** 16. Country */ - { - accessorKey: "vendorCountry", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Country" /> - ), - cell: ({ row }) => { - const country = row.getValue("vendorCountry") as string - const countryDisplay = country === "KR" ? "D" : "F" - return ( - <Badge variant="outline" className="text-xs"> - {countryDisplay} - </Badge> - ) - }, - size: 80, - }, - - /** 17. Place of Shipping */ - { - accessorKey: "placeOfShipping", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Place of Shipping" /> - ), - cell: ({ row }) => { - const placeOfShipping = row.getValue("placeOfShipping") as string - return placeOfShipping ? ( - <div className="text-xs max-w-[120px] truncate" title={placeOfShipping}> - {placeOfShipping} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 120, - }, - - /** 18. Place of Destination */ - { - accessorKey: "placeOfDestination", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Place of Destination" /> - ), - cell: ({ row }) => { - const placeOfDestination = row.getValue("placeOfDestination") as string - return placeOfDestination ? ( - <div className="text-xs max-w-[120px] truncate" title={placeOfDestination}> - {placeOfDestination} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 120, - }, - - /** 19. 초도 여부* */ - { - accessorKey: "firsttimeYn", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="초도 여부*" /> - ), - cell: ({ row }) => { - const firsttime = row.getValue("firsttimeYn") as boolean - return firsttime ? ( - <Badge variant="success" className="text-xs"> - 초도 - </Badge> - ) : ( - <Badge variant="outline" className="text-xs"> - 재구매 - </Badge> - ) - }, - size: 80, - }, - - /** 20. 연동제 적용* */ - { - accessorKey: "materialPriceRelatedYn", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="연동제 적용*" /> - ), - cell: ({ row }) => { - const materialPrice = row.getValue("materialPriceRelatedYn") as boolean - return materialPrice ? ( - <Badge variant="success" className="text-xs"> - 적용 - </Badge> - ) : ( - <Badge variant="outline" className="text-xs"> - 미적용 - </Badge> - ) - }, - size: 100, - }, - - /** 21. Business Size */ - { - id: "businessSizeDisplay", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Business Size" /> - ), - cell: ({ row }) => { - const businessSize = row.original.vendorBusinessSize as string - return businessSize ? ( - <Badge variant="outline" className="text-xs"> - {businessSize} - </Badge> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 100, - }, - - /** 22. 최종 Update일 */ - { - accessorKey: "updatedAt", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="최종 Update일" /> - ), - cell: ({ row }) => { - const updated = row.getValue("updatedAt") as Date - return updated ? ( - <div className="text-sm"> - {formatDate(updated, "KR")} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 120, - }, - - /** 23. 최종 Update담당자 (스키마에 없어서 placeholder) */ - { - id: "updatedByUser", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="최종 Update담당자" /> - ), - cell: ({ row }) => { - // TODO: updatedBy 사용자 정보 조인 필요 - return ( - <span className="text-muted-foreground text-xs">-</span> - ) - }, - size: 120, - }, - - /** 24. Vendor 설명 */ - { - accessorKey: "vendorRemark", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="Vendor 설명" /> - ), - cell: ({ row }) => { - const vendorRemark = row.getValue("vendorRemark") as string - return vendorRemark ? ( - <div className="text-xs max-w-[150px] truncate" title={vendorRemark}> - {vendorRemark} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 150, - }, - - /** 25. 비고 */ - { - accessorKey: "remark", - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="비고" /> - ), - cell: ({ row }) => { - const remark = row.getValue("remark") as string - return remark ? ( - <div className="text-xs max-w-[150px] truncate" title={remark}> - {remark} - </div> - ) : ( - <span className="text-muted-foreground">-</span> - ) - }, - size: 150, - }, - - /** ───────────── 액션 ───────────── */ - { - id: "actions", - enableHiding: false, - cell: function Cell({ row }) { - return ( - <DropdownMenu> - <DropdownMenuTrigger asChild> - <Button - aria-label="Open menu" - variant="ghost" - className="flex size-8 p-0 data-[state=open]:bg-muted" - > - <Ellipsis className="size-4" aria-hidden="true" /> - </Button> - </DropdownMenuTrigger> - <DropdownMenuContent align="end" className="w-48"> - <DropdownMenuItem> - <MessageSquare className="mr-2 h-4 w-4" /> - 벤더 견적 보기 - </DropdownMenuItem> - <DropdownMenuSeparator /> - {setRowAction && ( - <DropdownMenuItem - onSelect={() => setRowAction({ row, type: "update" })} - > - <Edit className="mr-2 h-4 w-4" /> - 수정 - </DropdownMenuItem> - )} - </DropdownMenuContent> - </DropdownMenu> - ) - }, - size: 40, - }, - ] -}
\ No newline at end of file diff --git a/lib/b-rfq/final/final-rfq-detail-table.tsx b/lib/b-rfq/final/final-rfq-detail-table.tsx deleted file mode 100644 index 8ae42e7e..00000000 --- a/lib/b-rfq/final/final-rfq-detail-table.tsx +++ /dev/null @@ -1,297 +0,0 @@ -"use client" - -import * as React from "react" -import { type DataTableAdvancedFilterField, type DataTableFilterField } from "@/types/table" -import { useDataTable } from "@/hooks/use-data-table" -import { DataTable } from "@/components/data-table/data-table" -import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar" -import { getFinalRfqDetail } from "../service" // 앞서 만든 서버 액션 -import { - getFinalRfqDetailColumns, - type DataTableRowAction -} from "./final-rfq-detail-columns" -import { FinalRfqDetailTableToolbarActions } from "./final-rfq-detail-toolbar-actions" -import { UpdateFinalRfqSheet } from "./update-final-rfq-sheet" -import { FinalRfqDetailView } from "@/db/schema" - -interface FinalRfqDetailTableProps { - promises: Promise<Awaited<ReturnType<typeof getFinalRfqDetail>>> - rfqId?: number -} - -export function FinalRfqDetailTable({ promises, rfqId }: FinalRfqDetailTableProps) { - const { data, pageCount } = React.use(promises) - - // 선택된 상세 정보 - const [selectedDetail, setSelectedDetail] = React.useState<any>(null) - - // Row action 상태 (update만) - const [rowAction, setRowAction] = React.useState<DataTableRowAction<FinalRfqDetailView> | null>(null) - - const columns = React.useMemo( - () => getFinalRfqDetailColumns({ - onSelectDetail: setSelectedDetail, - setRowAction: setRowAction - }), - [] - ) - - /** - * 필터 필드 정의 - */ - const filterFields: DataTableFilterField<any>[] = [ - { - id: "rfqCode", - label: "RFQ 코드", - placeholder: "RFQ 코드로 검색...", - }, - { - id: "vendorName", - label: "벤더명", - placeholder: "벤더명으로 검색...", - }, - { - id: "rfqStatus", - label: "RFQ 상태", - options: [ - { label: "Draft", value: "DRAFT", count: 0 }, - { label: "문서 접수", value: "Doc. Received", count: 0 }, - { label: "담당자 배정", value: "PIC Assigned", count: 0 }, - { label: "문서 확정", value: "Doc. Confirmed", count: 0 }, - { label: "초기 RFQ 발송", value: "Init. RFQ Sent", count: 0 }, - { label: "초기 RFQ 응답", value: "Init. RFQ Answered", count: 0 }, - { label: "TBE 시작", value: "TBE started", count: 0 }, - { label: "TBE 완료", value: "TBE finished", count: 0 }, - { label: "최종 RFQ 발송", value: "Final RFQ Sent", count: 0 }, - { label: "견적 접수", value: "Quotation Received", count: 0 }, - { label: "벤더 선정", value: "Vendor Selected", count: 0 }, - ], - }, - { - id: "finalRfqStatus", - label: "최종 RFQ 상태", - options: [ - { label: "초안", value: "DRAFT", count: 0 }, - { label: "발송", value: "Final RFQ Sent", count: 0 }, - { label: "견적 접수", value: "Quotation Received", count: 0 }, - { label: "벤더 선정", value: "Vendor Selected", count: 0 }, - ], - }, - { - id: "vendorCountry", - label: "벤더 국가", - options: [ - { label: "한국", value: "KR", count: 0 }, - { label: "중국", value: "CN", count: 0 }, - { label: "일본", value: "JP", count: 0 }, - { label: "미국", value: "US", count: 0 }, - { label: "독일", value: "DE", count: 0 }, - ], - }, - { - id: "currency", - label: "통화", - options: [ - { label: "USD", value: "USD", count: 0 }, - { label: "EUR", value: "EUR", count: 0 }, - { label: "KRW", value: "KRW", count: 0 }, - { label: "JPY", value: "JPY", count: 0 }, - { label: "CNY", value: "CNY", count: 0 }, - ], - }, - ] - - /** - * 고급 필터 필드 - */ - const advancedFilterFields: DataTableAdvancedFilterField<any>[] = [ - { - id: "rfqCode", - label: "RFQ 코드", - type: "text", - }, - { - id: "vendorName", - label: "벤더명", - type: "text", - }, - { - id: "vendorCode", - label: "벤더 코드", - type: "text", - }, - { - id: "vendorCountry", - label: "벤더 국가", - type: "multi-select", - options: [ - { label: "한국", value: "KR" }, - { label: "중국", value: "CN" }, - { label: "일본", value: "JP" }, - { label: "미국", value: "US" }, - { label: "독일", value: "DE" }, - ], - }, - { - id: "rfqStatus", - label: "RFQ 상태", - type: "multi-select", - options: [ - { label: "Draft", value: "DRAFT" }, - { label: "문서 접수", value: "Doc. Received" }, - { label: "담당자 배정", value: "PIC Assigned" }, - { label: "문서 확정", value: "Doc. Confirmed" }, - { label: "초기 RFQ 발송", value: "Init. RFQ Sent" }, - { label: "초기 RFQ 응답", value: "Init. RFQ Answered" }, - { label: "TBE 시작", value: "TBE started" }, - { label: "TBE 완료", value: "TBE finished" }, - { label: "최종 RFQ 발송", value: "Final RFQ Sent" }, - { label: "견적 접수", value: "Quotation Received" }, - { label: "벤더 선정", value: "Vendor Selected" }, - ], - }, - { - id: "finalRfqStatus", - label: "최종 RFQ 상태", - type: "multi-select", - options: [ - { label: "초안", value: "DRAFT" }, - { label: "발송", value: "Final RFQ Sent" }, - { label: "견적 접수", value: "Quotation Received" }, - { label: "벤더 선정", value: "Vendor Selected" }, - ], - }, - { - id: "vendorBusinessSize", - label: "벤더 규모", - type: "multi-select", - options: [ - { label: "대기업", value: "LARGE" }, - { label: "중기업", value: "MEDIUM" }, - { label: "소기업", value: "SMALL" }, - { label: "스타트업", value: "STARTUP" }, - ], - }, - { - id: "incotermsCode", - label: "Incoterms", - type: "text", - }, - { - id: "paymentTermsCode", - label: "Payment Terms", - type: "text", - }, - { - id: "currency", - label: "통화", - type: "multi-select", - options: [ - { label: "USD", value: "USD" }, - { label: "EUR", value: "EUR" }, - { label: "KRW", value: "KRW" }, - { label: "JPY", value: "JPY" }, - { label: "CNY", value: "CNY" }, - ], - }, - { - id: "dueDate", - label: "마감일", - type: "date", - }, - { - id: "validDate", - label: "유효일", - type: "date", - }, - { - id: "deliveryDate", - label: "납기일", - type: "date", - }, - { - id: "shortList", - label: "Short List", - type: "boolean", - }, - { - id: "returnYn", - label: "Return 여부", - type: "boolean", - }, - { - id: "cpRequestYn", - label: "CP Request 여부", - type: "boolean", - }, - { - id: "prjectGtcYn", - label: "Project GTC 여부", - type: "boolean", - }, - { - id: "firsttimeYn", - label: "First Time 여부", - type: "boolean", - }, - { - id: "materialPriceRelatedYn", - label: "Material Price Related 여부", - type: "boolean", - }, - { - id: "classification", - label: "분류", - type: "text", - }, - { - id: "sparepart", - label: "예비부품", - type: "text", - }, - { - id: "createdAt", - label: "등록일", - type: "date", - }, - ] - - const { table } = useDataTable({ - data, - columns, - pageCount, - filterFields, - enableAdvancedFilter: true, - initialState: { - sorting: [{ id: "createdAt", desc: true }], - columnPinning: { right: ["actions"] }, - }, - getRowId: (originalRow) => originalRow.finalRfqId ? originalRow.finalRfqId.toString() : "1", - shallow: false, - clearOnDefault: true, - }) - - return ( - <div className="space-y-6"> - {/* 메인 테이블 */} - <div className="h-full w-full"> - <DataTable table={table} className="h-full"> - <DataTableAdvancedToolbar - table={table} - filterFields={advancedFilterFields} - shallow={false} - > - <FinalRfqDetailTableToolbarActions table={table} rfqId={rfqId} /> - </DataTableAdvancedToolbar> - </DataTable> - </div> - - {/* Update Sheet */} - <UpdateFinalRfqSheet - open={rowAction?.type === "update"} - onOpenChange={() => setRowAction(null)} - finalRfq={rowAction?.type === "update" ? rowAction.row.original : null} - /> - </div> - ) -}
\ No newline at end of file diff --git a/lib/b-rfq/final/final-rfq-detail-toolbar-actions.tsx b/lib/b-rfq/final/final-rfq-detail-toolbar-actions.tsx deleted file mode 100644 index d8be4f7b..00000000 --- a/lib/b-rfq/final/final-rfq-detail-toolbar-actions.tsx +++ /dev/null @@ -1,201 +0,0 @@ -"use client" - -import * as React from "react" -import { type Table } from "@tanstack/react-table" -import { useRouter } from "next/navigation" -import { toast } from "sonner" -import { Button } from "@/components/ui/button" -import { - Mail, - CheckCircle2, - Loader, - Award, - RefreshCw -} from "lucide-react" -import { FinalRfqDetailView } from "@/db/schema" - -interface FinalRfqDetailTableToolbarActionsProps { - table: Table<FinalRfqDetailView> - rfqId?: number - onRefresh?: () => void // 데이터 새로고침 콜백 -} - -export function FinalRfqDetailTableToolbarActions({ - table, - rfqId, - onRefresh -}: FinalRfqDetailTableToolbarActionsProps) { - const router = useRouter() - - // 선택된 행들 가져오기 - const selectedRows = table.getFilteredSelectedRowModel().rows - const selectedDetails = selectedRows.map((row) => row.original) - const selectedCount = selectedRows.length - - // 상태 관리 - const [isEmailSending, setIsEmailSending] = React.useState(false) - const [isSelecting, setIsSelecting] = React.useState(false) - - // RFQ 발송 핸들러 (로직 없음) - const handleBulkRfqSend = async () => { - if (selectedCount === 0) { - toast.error("발송할 RFQ를 선택해주세요.") - return - } - - setIsEmailSending(true) - - try { - // TODO: 실제 RFQ 발송 로직 구현 - await new Promise(resolve => setTimeout(resolve, 2000)) // 임시 딜레이 - - toast.success(`${selectedCount}개의 최종 RFQ가 발송되었습니다.`) - - // 선택 해제 - table.toggleAllRowsSelected(false) - - // 데이터 새로고침 - if (onRefresh) { - onRefresh() - } - - } catch (error) { - console.error("RFQ sending error:", error) - toast.error("최종 RFQ 발송 중 오류가 발생했습니다.") - } finally { - setIsEmailSending(false) - } - } - - // 최종 선정 핸들러 (로직 없음) - const handleFinalSelection = async () => { - if (selectedCount === 0) { - toast.error("최종 선정할 벤더를 선택해주세요.") - return - } - - if (selectedCount > 1) { - toast.error("최종 선정은 1개의 벤더만 가능합니다.") - return - } - - setIsSelecting(true) - - try { - // TODO: 실제 최종 선정 로직 구현 - await new Promise(resolve => setTimeout(resolve, 1500)) // 임시 딜레이 - - const selectedVendor = selectedDetails[0] - toast.success(`${selectedVendor.vendorName}이(가) 최종 선정되었습니다.`) - - // 선택 해제 - table.toggleAllRowsSelected(false) - - // 데이터 새로고침 - if (onRefresh) { - onRefresh() - } - - // 계약서 페이지로 이동 (필요시) - if (rfqId) { - setTimeout(() => { - toast.info("계약서 작성 페이지로 이동합니다.") - // router.push(`/evcp/contracts/${rfqId}`) - }, 1500) - } - - } catch (error) { - console.error("Final selection error:", error) - toast.error("최종 선정 중 오류가 발생했습니다.") - } finally { - setIsSelecting(false) - } - } - - // 발송 가능한 RFQ 필터링 (DRAFT 상태) - const sendableRfqs = selectedDetails.filter( - detail => detail.finalRfqStatus === "DRAFT" - ) - const sendableCount = sendableRfqs.length - - // 선정 가능한 벤더 필터링 (견적 접수 상태) - const selectableVendors = selectedDetails.filter( - detail => detail.finalRfqStatus === "Quotation Received" - ) - const selectableCount = selectableVendors.length - - // 전체 벤더 중 견적 접수 완료된 벤더 수 - const allVendors = table.getRowModel().rows.map(row => row.original) - const quotationReceivedCount = allVendors.filter( - vendor => vendor.finalRfqStatus === "Quotation Received" - ).length - - return ( - <div className="flex items-center gap-2"> - {/** 선택된 항목이 있을 때만 표시되는 액션들 */} - {selectedCount > 0 && ( - <> - {/* RFQ 발송 버튼 */} - <Button - variant="outline" - size="sm" - onClick={handleBulkRfqSend} - className="h-8" - disabled={isEmailSending || sendableCount === 0} - title={sendableCount === 0 ? "발송 가능한 RFQ가 없습니다 (DRAFT 상태만 가능)" : `${sendableCount}개의 최종 RFQ 발송`} - > - {isEmailSending ? ( - <Loader className="mr-2 h-4 w-4 animate-spin" /> - ) : ( - <Mail className="mr-2 h-4 w-4" /> - )} - 최종 RFQ 발송 ({sendableCount}/{selectedCount}) - </Button> - - {/* 최종 선정 버튼 */} - <Button - variant="default" - size="sm" - onClick={handleFinalSelection} - className="h-8" - disabled={isSelecting || selectedCount !== 1 || selectableCount === 0} - title={ - selectedCount !== 1 - ? "최종 선정은 1개의 벤더만 선택해주세요" - : selectableCount === 0 - ? "견적 접수가 완료된 벤더만 선정 가능합니다" - : "선택된 벤더를 최종 선정" - } - > - {isSelecting ? ( - <Loader className="mr-2 h-4 w-4 animate-spin" /> - ) : ( - <Award className="mr-2 h-4 w-4" /> - )} - 최종 선정 - </Button> - </> - )} - - {/* 정보 표시 (선택이 없을 때) */} - {selectedCount === 0 && quotationReceivedCount > 0 && ( - <div className="text-sm text-muted-foreground"> - 견적 접수 완료: {quotationReceivedCount}개 벤더 - </div> - )} - - {/* 새로고침 버튼 */} - {onRefresh && ( - <Button - variant="ghost" - size="sm" - onClick={onRefresh} - className="h-8" - title="데이터 새로고침" - > - <RefreshCw className="h-4 w-4" /> - </Button> - )} - </div> - ) -}
\ No newline at end of file diff --git a/lib/b-rfq/final/update-final-rfq-sheet.tsx b/lib/b-rfq/final/update-final-rfq-sheet.tsx deleted file mode 100644 index 65e23a92..00000000 --- a/lib/b-rfq/final/update-final-rfq-sheet.tsx +++ /dev/null @@ -1,70 +0,0 @@ -"use client" - -import * as React from "react" -import { - Sheet, - SheetContent, - SheetDescription, - SheetHeader, - SheetTitle, -} from "@/components/ui/sheet" -import { Button } from "@/components/ui/button" -import { FinalRfqDetailView } from "@/db/schema" - -interface UpdateFinalRfqSheetProps { - open: boolean - onOpenChange: (open: boolean) => void - finalRfq: FinalRfqDetailView | null -} - -export function UpdateFinalRfqSheet({ - open, - onOpenChange, - finalRfq -}: UpdateFinalRfqSheetProps) { - return ( - <Sheet open={open} onOpenChange={onOpenChange}> - <SheetContent className="sm:max-w-md"> - <SheetHeader> - <SheetTitle>최종 RFQ 수정</SheetTitle> - <SheetDescription> - 최종 RFQ 정보를 수정합니다. - </SheetDescription> - </SheetHeader> - - <div className="py-6"> - {finalRfq && ( - <div className="space-y-4"> - <div> - <h4 className="font-medium">RFQ 정보</h4> - <p className="text-sm text-muted-foreground"> - RFQ Code: {finalRfq.rfqCode} - </p> - <p className="text-sm text-muted-foreground"> - 벤더: {finalRfq.vendorName} - </p> - <p className="text-sm text-muted-foreground"> - 상태: {finalRfq.finalRfqStatus} - </p> - </div> - - {/* TODO: 실제 업데이트 폼 구현 */} - <div className="text-center text-muted-foreground"> - 업데이트 폼이 여기에 구현됩니다. - </div> - </div> - )} - </div> - - <div className="flex justify-end gap-2"> - <Button variant="outline" onClick={() => onOpenChange(false)}> - 취소 - </Button> - <Button onClick={() => onOpenChange(false)}> - 저장 - </Button> - </div> - </SheetContent> - </Sheet> - ) -}
\ No newline at end of file |
