diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-04 08:31:31 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-09-04 08:31:31 +0000 |
| commit | b67e36df49f067cbd5ba899f9fbcc755f38d4b4f (patch) | |
| tree | 5a71c5960f90d988cd509e3ef26bff497a277661 /lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx | |
| parent | b7f54b06c1ef9e619f5358fb0a5caad9703c8905 (diff) | |
(대표님, 최겸, 임수민) 작업사항 커밋
Diffstat (limited to 'lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx')
| -rw-r--r-- | lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx | 303 |
1 files changed, 303 insertions, 0 deletions
diff --git a/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx b/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx new file mode 100644 index 00000000..30cddbce --- /dev/null +++ b/lib/bidding/pre-quote/table/bidding-pre-quote-vendor-columns.tsx @@ -0,0 +1,303 @@ +"use client" + +import * as React from "react" +import { type ColumnDef } from "@tanstack/react-table" +import { Checkbox } from "@/components/ui/checkbox" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { + MoreHorizontal, Edit, Trash2, UserPlus +} from "lucide-react" +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu" + +// bidding_companies 테이블 타입 정의 (company_condition_responses와 join) +export interface BiddingCompany { + id: number + biddingId: number + companyId: number + invitationStatus: 'pending' | 'sent' | 'accepted' | 'declined' | 'submitted' + invitedAt: Date | null + respondedAt: Date | null + preQuoteAmount: string | null + preQuoteSubmittedAt: Date | null + isPreQuoteSelected: boolean + isAttendingMeeting: boolean | null + notes: string | null + contactPerson: string | null + contactEmail: string | null + contactPhone: string | null + createdAt: Date + updatedAt: Date + + // company_condition_responses 필드들 + paymentTermsResponse: string | null + taxConditionsResponse: string | null + proposedContractDeliveryDate: string | null + priceAdjustmentResponse: boolean | null + isInitialResponse: boolean | null + incotermsResponse: string | null + proposedShippingPort: string | null + proposedDestinationPort: string | null + sparePartResponse: string | null + additionalProposals: string | null + + // 조인된 업체 정보 + companyName?: string + companyCode?: string +} + +interface GetBiddingCompanyColumnsProps { + onEdit: (company: BiddingCompany) => void + onDelete: (company: BiddingCompany) => void + onInvite: (company: BiddingCompany) => void +} + +export function getBiddingPreQuoteVendorColumns({ + onEdit, + onDelete, + onInvite +}: GetBiddingCompanyColumnsProps): ColumnDef<BiddingCompany>[] { + return [ + { + id: 'select', + header: ({ table }) => ( + <Checkbox + checked={table.getIsAllPageRowsSelected()} + onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} + aria-label="모두 선택" + /> + ), + cell: ({ row }) => ( + <Checkbox + checked={row.getIsSelected()} + onCheckedChange={(value) => row.toggleSelected(!!value)} + aria-label="행 선택" + /> + ), + enableSorting: false, + enableHiding: false, + }, + { + accessorKey: 'companyName', + header: '업체명', + cell: ({ row }) => ( + <div className="font-medium">{row.original.companyName || '-'}</div> + ), + }, + { + accessorKey: 'companyCode', + header: '업체코드', + cell: ({ row }) => ( + <div className="font-mono text-sm">{row.original.companyCode || '-'}</div> + ), + }, + { + accessorKey: 'invitationStatus', + header: '초대 상태', + cell: ({ row }) => { + const status = row.original.invitationStatus + const variant = status === 'accepted' ? 'default' : + status === 'declined' ? 'destructive' : 'outline' + + const label = status === 'accepted' ? '수락' : + status === 'declined' ? '거절' : '대기중' + + return <Badge variant={variant}>{label}</Badge> + }, + }, + { + accessorKey: 'preQuoteAmount', + header: '사전견적금액', + cell: ({ row }) => ( + <div className="text-right font-mono"> + {row.original.preQuoteAmount ? Number(row.original.preQuoteAmount).toLocaleString() : '-'} KRW + </div> + ), + }, + { + accessorKey: 'preQuoteSubmittedAt', + header: '사전견적 제출일', + cell: ({ row }) => ( + <div className="text-sm"> + {row.original.preQuoteSubmittedAt ? new Date(row.original.preQuoteSubmittedAt).toLocaleDateString('ko-KR') : '-'} + </div> + ), + }, + { + accessorKey: 'isPreQuoteSelected', + header: '본입찰 선정', + cell: ({ row }) => ( + <Badge variant={row.original.isPreQuoteSelected ? 'default' : 'secondary'}> + {row.original.isPreQuoteSelected ? '선정' : '미선정'} + </Badge> + ), + }, + { + accessorKey: 'isAttendingMeeting', + header: '사양설명회 참석', + cell: ({ row }) => { + const isAttending = row.original.isAttendingMeeting + if (isAttending === null) return <div className="text-sm">-</div> + return ( + <Badge variant={isAttending ? 'default' : 'secondary'}> + {isAttending ? '참석' : '불참석'} + </Badge> + ) + }, + }, + { + accessorKey: 'paymentTermsResponse', + header: '지급조건', + cell: ({ row }) => ( + <div className="text-sm max-w-32 truncate" title={row.original.paymentTermsResponse || ''}> + {row.original.paymentTermsResponse || '-'} + </div> + ), + }, + { + accessorKey: 'taxConditionsResponse', + header: '세금조건', + cell: ({ row }) => ( + <div className="text-sm max-w-32 truncate" title={row.original.taxConditionsResponse || ''}> + {row.original.taxConditionsResponse || '-'} + </div> + ), + }, + { + accessorKey: 'incotermsResponse', + header: '운송조건', + cell: ({ row }) => ( + <div className="text-sm max-w-24 truncate" title={row.original.incotermsResponse || ''}> + {row.original.incotermsResponse || '-'} + </div> + ), + }, + { + accessorKey: 'isInitialResponse', + header: '초도여부', + cell: ({ row }) => { + const isInitial = row.original.isInitialResponse + if (isInitial === null) return <div className="text-sm">-</div> + return ( + <Badge variant={isInitial ? 'default' : 'secondary'}> + {isInitial ? 'Y' : 'N'} + </Badge> + ) + }, + }, + { + accessorKey: 'priceAdjustmentResponse', + header: '연동제', + cell: ({ row }) => { + const hasPriceAdjustment = row.original.priceAdjustmentResponse + if (hasPriceAdjustment === null) return <div className="text-sm">-</div> + return ( + <Badge variant={hasPriceAdjustment ? 'default' : 'secondary'}> + {hasPriceAdjustment ? '적용' : '미적용'} + </Badge> + ) + }, + }, + { + accessorKey: 'proposedContractDeliveryDate', + header: '제안납기일', + cell: ({ row }) => ( + <div className="text-sm"> + {row.original.proposedContractDeliveryDate ? + new Date(row.original.proposedContractDeliveryDate).toLocaleDateString('ko-KR') : '-'} + </div> + ), + }, + { + accessorKey: 'proposedShippingPort', + header: '제안선적지', + cell: ({ row }) => ( + <div className="text-sm max-w-24 truncate" title={row.original.proposedShippingPort || ''}> + {row.original.proposedShippingPort || '-'} + </div> + ), + }, + { + accessorKey: 'proposedDestinationPort', + header: '제안도착지', + cell: ({ row }) => ( + <div className="text-sm max-w-24 truncate" title={row.original.proposedDestinationPort || ''}> + {row.original.proposedDestinationPort || '-'} + </div> + ), + }, + { + accessorKey: 'sparePartResponse', + header: '스페어파트', + cell: ({ row }) => ( + <div className="text-sm max-w-24 truncate" title={row.original.sparePartResponse || ''}> + {row.original.sparePartResponse || '-'} + </div> + ), + }, + { + accessorKey: 'additionalProposals', + header: '추가제안', + cell: ({ row }) => ( + <div className="text-sm max-w-32 truncate" title={row.original.additionalProposals || ''}> + {row.original.additionalProposals || '-'} + </div> + ), + }, + { + accessorKey: 'notes', + header: '특이사항', + cell: ({ row }) => ( + <div className="text-sm max-w-32 truncate" title={row.original.notes || ''}> + {row.original.notes || '-'} + </div> + ), + }, + { + id: 'actions', + header: '작업', + cell: ({ row }) => { + const company = row.original + + return ( + <DropdownMenu> + <DropdownMenuTrigger asChild> + <Button variant="ghost" className="h-8 w-8 p-0"> + <span className="sr-only">메뉴 열기</span> + <MoreHorizontal className="h-4 w-4" /> + </Button> + </DropdownMenuTrigger> + <DropdownMenuContent align="end"> + <DropdownMenuLabel>작업</DropdownMenuLabel> + <DropdownMenuItem onClick={() => onEdit(company)}> + <Edit className="mr-2 h-4 w-4" /> + 수정 + </DropdownMenuItem> + {company.invitationStatus === 'pending' && ( + <DropdownMenuItem onClick={() => onInvite(company)}> + <UserPlus className="mr-2 h-4 w-4" /> + 초대 발송 + </DropdownMenuItem> + )} + <DropdownMenuSeparator /> + <DropdownMenuItem + onClick={() => onDelete(company)} + className="text-destructive" + > + <Trash2 className="mr-2 h-4 w-4" /> + 삭제 + </DropdownMenuItem> + </DropdownMenuContent> + </DropdownMenu> + ) + }, + }, + ] +} |
