summaryrefslogtreecommitdiff
path: root/lib/b-rfq/final
diff options
context:
space:
mode:
Diffstat (limited to 'lib/b-rfq/final')
-rw-r--r--lib/b-rfq/final/final-rfq-detail-columns.tsx589
-rw-r--r--lib/b-rfq/final/final-rfq-detail-table.tsx297
-rw-r--r--lib/b-rfq/final/final-rfq-detail-toolbar-actions.tsx201
-rw-r--r--lib/b-rfq/final/update-final-rfq-sheet.tsx70
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