summaryrefslogtreecommitdiff
path: root/lib/bidding/detail/table/bidding-detail-vendor-table.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bidding/detail/table/bidding-detail-vendor-table.tsx')
-rw-r--r--lib/bidding/detail/table/bidding-detail-vendor-table.tsx225
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}
+ />
+ </>
+ )
+}