diff options
Diffstat (limited to 'lib/bidding/detail/table/bidding-detail-vendor-table.tsx')
| -rw-r--r-- | lib/bidding/detail/table/bidding-detail-vendor-table.tsx | 225 |
1 files changed, 225 insertions, 0 deletions
diff --git a/lib/bidding/detail/table/bidding-detail-vendor-table.tsx b/lib/bidding/detail/table/bidding-detail-vendor-table.tsx new file mode 100644 index 00000000..7ad7056c --- /dev/null +++ b/lib/bidding/detail/table/bidding-detail-vendor-table.tsx @@ -0,0 +1,225 @@ +'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 { BiddingDetailVendorToolbarActions } from './bidding-detail-vendor-toolbar-actions' +import { BiddingDetailVendorCreateDialog } from './bidding-detail-vendor-create-dialog' +import { BiddingDetailVendorEditDialog } from './bidding-detail-vendor-edit-dialog' +import { getBiddingDetailVendorColumns } from './bidding-detail-vendor-columns' +import { QuotationVendor } from '@/lib/bidding/detail/service' +import { + deleteQuotationVendor, + selectWinner +} from '@/lib/bidding/detail/service' +import { selectWinnerSchema } from '@/lib/bidding/validation' +import { useToast } from '@/hooks/use-toast' +import { useTransition } from 'react' + +interface BiddingDetailVendorTableContentProps { + biddingId: number + vendors: QuotationVendor[] + onRefresh: () => void + onOpenItemsDialog: () => void + onOpenTargetPriceDialog: () => void + onOpenSelectionReasonDialog: () => void + onEdit?: (vendor: QuotationVendor) => void + onDelete?: (vendor: QuotationVendor) => void + onSelectWinner?: (vendor: QuotationVendor) => void +} + +const filterFields: DataTableFilterField<QuotationVendor>[] = [ + { + id: 'vendorName', + label: '업체명', + placeholder: '업체명으로 검색...', + }, + { + id: 'vendorCode', + label: '업체코드', + placeholder: '업체코드로 검색...', + }, + { + id: 'contactPerson', + label: '담당자', + placeholder: '담당자로 검색...', + }, +] + +const advancedFilterFields: DataTableAdvancedFilterField<QuotationVendor>[] = [ + { + id: 'vendorName', + label: '업체명', + type: 'text', + }, + { + id: 'vendorCode', + label: '업체코드', + type: 'text', + }, + { + id: 'contactPerson', + label: '담당자', + type: 'text', + }, + { + id: 'quotationAmount', + label: '견적금액', + type: 'number', + }, + { + id: 'status', + label: '상태', + type: 'multi-select', + options: [ + { label: '제출완료', value: 'submitted' }, + { label: '선정완료', value: 'selected' }, + { label: '미제출', value: 'pending' }, + ], + }, +] + +export function BiddingDetailVendorTableContent({ + biddingId, + vendors, + onRefresh, + onOpenItemsDialog, + onOpenTargetPriceDialog, + onOpenSelectionReasonDialog, + onEdit, + onDelete, + onSelectWinner +}: BiddingDetailVendorTableContentProps) { + const { toast } = useToast() + const [isPending, startTransition] = useTransition() + const [selectedVendor, setSelectedVendor] = React.useState<QuotationVendor | null>(null) + const [isEditDialogOpen, setIsEditDialogOpen] = React.useState(false) + + const handleDelete = (vendor: QuotationVendor) => { + if (!confirm(`${vendor.vendorName} 업체를 삭제하시겠습니까?`)) return + + startTransition(async () => { + const response = await deleteQuotationVendor(vendor.id) + + if (response.success) { + toast({ + title: '성공', + description: response.message, + }) + onRefresh() + } else { + toast({ + title: '오류', + description: response.error, + variant: 'destructive', + }) + } + }) + } + + const handleSelectWinner = (vendor: QuotationVendor) => { + if (!vendor.awardRatio || vendor.awardRatio <= 0) { + toast({ + title: '오류', + description: '발주비율을 먼저 설정해주세요.', + variant: 'destructive', + }) + return + } + + if (!confirm(`${vendor.vendorName} 업체를 낙찰자로 선정하시겠습니까?`)) return + + startTransition(async () => { + const result = selectWinnerSchema.safeParse({ + biddingId, + vendorId: vendor.id, + awardRatio: vendor.awardRatio, + }) + + if (!result.success) { + toast({ + title: '유효성 오류', + description: result.error.issues[0]?.message || '입력값을 확인해주세요.', + variant: 'destructive', + }) + return + } + + const response = await selectWinner(biddingId, vendor.id, vendor.awardRatio, 'current-user') + + if (response.success) { + toast({ + title: '성공', + description: response.message, + }) + onRefresh() + } else { + toast({ + title: '오류', + description: response.error, + variant: 'destructive', + }) + } + }) + } + + const handleEdit = (vendor: QuotationVendor) => { + setSelectedVendor(vendor) + setIsEditDialogOpen(true) + } + + const columns = React.useMemo( + () => getBiddingDetailVendorColumns({ + onEdit: onEdit || handleEdit, + onDelete: onDelete || handleDelete, + onSelectWinner: onSelectWinner || handleSelectWinner + }), + [onEdit, onDelete, onSelectWinner, handleEdit, handleDelete, handleSelectWinner] + ) + + const { table } = useDataTable({ + data: vendors, + columns, + pageCount: 1, + filterFields, + enableAdvancedFilter: true, + initialState: { + sorting: [{ id: 'vendorName', desc: false }], + columnPinning: { right: ['actions'] }, + }, + getRowId: (originalRow) => originalRow.id.toString(), + shallow: false, + clearOnDefault: true, + }) + + return ( + <> + <DataTable table={table}> + <DataTableAdvancedToolbar + table={table} + filterFields={advancedFilterFields} + shallow={false} + > + <BiddingDetailVendorToolbarActions + table={table} + biddingId={biddingId} + onOpenItemsDialog={onOpenItemsDialog} + onOpenTargetPriceDialog={onOpenTargetPriceDialog} + onOpenSelectionReasonDialog={onOpenSelectionReasonDialog} + + onSuccess={onRefresh} + /> + </DataTableAdvancedToolbar> + </DataTable> + + <BiddingDetailVendorEditDialog + vendor={selectedVendor} + open={isEditDialogOpen} + onOpenChange={setIsEditDialogOpen} + onSuccess={onRefresh} + /> + </> + ) +} |
