diff options
| author | joonhoekim <26rote@gmail.com> | 2025-09-15 01:23:00 +0000 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-09-15 01:23:00 +0000 |
| commit | e7818a457371849e29519497ebf046f385f05ab6 (patch) | |
| tree | 9bf08ba1b31a512c481dc521c9dd7c90091a75b8 /lib/avl/table/columns-detail.tsx | |
| parent | 3f293c90beb58ce206a66ff444d7acfc41b56429 (diff) | |
(김준회) AVL 기능 구현 1차 및 벤더풀 E/B 구분 개선
Diffstat (limited to 'lib/avl/table/columns-detail.tsx')
| -rw-r--r-- | lib/avl/table/columns-detail.tsx | 680 |
1 files changed, 680 insertions, 0 deletions
diff --git a/lib/avl/table/columns-detail.tsx b/lib/avl/table/columns-detail.tsx new file mode 100644 index 00000000..204d34f5 --- /dev/null +++ b/lib/avl/table/columns-detail.tsx @@ -0,0 +1,680 @@ +import { Checkbox } from "@/components/ui/checkbox" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { Edit, Trash2 } from "lucide-react" +import { type ColumnDef, TableMeta } from "@tanstack/react-table" +import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" +import { EditableCell } from "@/components/data-table/editable-cell" + +// 수정 여부 확인 헬퍼 함수 +const getIsModified = (table: any, rowId: string, fieldName: string) => { + const pendingChanges = table.options.meta?.getPendingChanges?.() || {} + return String(rowId) in pendingChanges && fieldName in pendingChanges[String(rowId)] +} + +// 테이블 메타 타입 확장 +declare module "@tanstack/react-table" { + interface TableMeta<TData> { + onCellUpdate?: (id: string, field: keyof TData, newValue: any) => Promise<void> + onCellCancel?: (id: string, field: keyof TData) => void + onAction?: (action: string, data?: any) => void + onSaveEmptyRow?: (tempId: string) => Promise<void> + onCancelEmptyRow?: (tempId: string) => void + isEmptyRow?: (id: string) => boolean + } +} + +// AVL 상세 아이템 타입 +export type AvlDetailItem = { + id: string + no: number + selected: boolean + // AVL 리스트 ID (외래키) + avlListId: number + // 설계 정보 + equipBulkDivision: 'EQUIP' | 'BULK' + disciplineCode: string + disciplineName: string + // 자재 정보 + materialNameCustomerSide: string + packageCode: string + packageName: string + materialGroupCode: string + materialGroupName: string + // 협력업체 정보 + vendorId?: number + vendorName: string + vendorCode: string + avlVendorName: string + tier: string + // FA 정보 + faTarget: boolean + faStatus: string + // Agent 정보 + isAgent: boolean + agentStatus: string // UI 표시용 + // 계약 서명주체 + contractSignerId?: number + contractSignerName: string + contractSignerCode: string + // 위치 정보 + headquarterLocation: string + manufacturingLocation: string + // SHI Qualification + shiAvl: boolean + shiBlacklist: boolean + shiBcc: boolean + // 기술영업 견적결과 + salesQuoteNumber: string + quoteCode: string + salesVendorInfo: string + salesCountry: string + totalAmount: string + quoteReceivedDate: string + // 업체 실적 현황(구매) + recentQuoteDate: string + recentQuoteNumber: string + recentOrderDate: string + recentOrderNumber: string + // 기타 + remarks: string + // 타임스탬프 + createdAt: string + updatedAt: string +} + +// 테이블 컬럼 정의 +export const columns: ColumnDef<AvlDetailItem>[] = [ + // 기본 정보 그룹 + { + header: "기본 정보", + columns: [ + { + id: "select", + header: ({ table }) => ( + <Checkbox + checked={ + table.getIsAllPageRowsSelected() || + (table.getIsSomePageRowsSelected() && "indeterminate") + } + onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)} + aria-label="Select all" + /> + ), + cell: ({ row }) => ( + <Checkbox + checked={row.getIsSelected()} + onCheckedChange={(value) => row.toggleSelected(!!value)} + aria-label="Select row" + /> + ), + enableSorting: false, + enableHiding: false, + size: 50, + }, + { + accessorKey: "no", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="No." /> + ), + size: 60, + }, + { + accessorKey: "equipBulkDivision", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Equip/Bulk 구분" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("equipBulkDivision") as string + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "equipBulkDivision", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "equipBulkDivision") + + return ( + <EditableCell + value={value} + type="select" + onSave={onSave} + options={[ + { label: "EQUIP", value: "EQUIP" }, + { label: "BULK", value: "BULK" } + ]} + placeholder="구분 선택" + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "disciplineName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="설계공종" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("disciplineName") + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "disciplineName", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "disciplineName") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="설계공종 입력" + maxLength={50} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "materialNameCustomerSide", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="고객사 AVL 자재명" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("materialNameCustomerSide") + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "materialNameCustomerSide", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "materialNameCustomerSide") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="자재명 입력" + maxLength={100} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 150, + }, + { + accessorKey: "packageName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="패키지 정보" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("packageName") + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "packageName", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "packageName") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="패키지명 입력" + maxLength={100} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 130, + }, + { + accessorKey: "materialGroupCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="자재그룹코드" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("materialGroupCode") + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "materialGroupCode", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "materialGroupCode") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="자재그룹코드 입력" + maxLength={50} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "materialGroupName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="자재그룹명" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("materialGroupName") + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "materialGroupName", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "materialGroupName") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="자재그룹명 입력" + maxLength={100} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 130, + }, + { + accessorKey: "vendorCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="협력업체코드" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("vendorCode") + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "vendorCode", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "vendorCode") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="협력업체코드 입력" + maxLength={50} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "vendorName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="협력업체명" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("vendorName") + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "vendorName", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "vendorName") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="협력업체명 입력" + maxLength={100} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 140, + }, + { + accessorKey: "avlVendorName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="AVL 등재업체명" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("avlVendorName") + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "avlVendorName", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "avlVendorName") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="AVL 등재업체명 입력" + maxLength={100} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 140, + }, + { + accessorKey: "tier", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="등급 (Tier)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("tier") as string + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "tier", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "tier") + + return ( + <EditableCell + value={value} + type="select" + onSave={onSave} + options={[ + { label: "Tier 1", value: "Tier 1" }, + { label: "Tier 2", value: "Tier 2" }, + { label: "Tier 3", value: "Tier 3" } + ]} + placeholder="등급 선택" + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 100, + }, + ], + }, + // FA 정보 그룹 + { + header: "FA 정보", + columns: [ + { + accessorKey: "faTarget", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="FA 대상" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("faTarget") as boolean + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "faTarget", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "faTarget") + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 80, + }, + { + accessorKey: "faStatus", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="FA 현황" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("faStatus") + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "faStatus", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "faStatus") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="FA 현황 입력" + maxLength={50} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 100, + }, + ], + }, + // SHI Qualification 그룹 + { + header: "SHI Qualification", + columns: [ + { + accessorKey: "shiAvl", + header: "AVL", + cell: ({ row, table }) => { + const value = row.getValue("shiAvl") as boolean + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shiAvl", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "shiAvl") + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 80, + }, + { + accessorKey: "shiBlacklist", + header: "Blacklist", + cell: ({ row, table }) => { + const value = row.getValue("shiBlacklist") as boolean + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shiBlacklist", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "shiBlacklist") + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 100, + }, + { + accessorKey: "shiBcc", + header: "BCC", + cell: ({ row, table }) => { + const value = row.getValue("shiBcc") as boolean + const isEmptyRow = String(row.original.id).startsWith('temp-') + + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shiBcc", newValue) + } + } + + const isModified = getIsModified(table, row.original.id, "shiBcc") + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 80, + }, + ], + }, + // 액션 컬럼 + { + id: "actions", + header: "액션", + cell: ({ row, table }) => { + const isEmptyRow = String(row.original.id).startsWith('temp-') + + return ( + <div className="flex items-center gap-2"> + {!isEmptyRow && ( + <> + <Button + variant="ghost" + size="sm" + onClick={() => table.options.meta?.onAction?.('edit', row.original)} + className="h-8 w-8 p-0" + > + <Edit className="h-4 w-4" /> + </Button> + <Button + variant="ghost" + size="sm" + onClick={() => table.options.meta?.onAction?.('delete', row.original)} + className="h-8 w-8 p-0 text-destructive hover:text-destructive" + > + <Trash2 className="h-4 w-4" /> + </Button> + </> + )} + {isEmptyRow && ( + <> + <Button + variant="ghost" + size="sm" + onClick={() => table.options.meta?.onSaveEmptyRow?.(row.original.id)} + className="h-8 w-8 p-0" + > + 저장 + </Button> + <Button + variant="ghost" + size="sm" + onClick={() => table.options.meta?.onCancelEmptyRow?.(row.original.id)} + className="h-8 w-8 p-0" + > + 취소 + </Button> + </> + )} + </div> + ) + }, + size: 100, + enableSorting: false, + enableHiding: false, + }, +] |
