diff options
| author | joonhoekim <26rote@gmail.com> | 2025-09-23 20:05:46 +0900 |
|---|---|---|
| committer | joonhoekim <26rote@gmail.com> | 2025-09-23 20:05:46 +0900 |
| commit | 3cde5c2c7d157bb0fe353de5e67e4b35506bb4e2 (patch) | |
| tree | ea2dd4927ce79543317ad5fc37a45d4bf193f670 /lib/vendor-pool/table/vendor-pool-table-columns.tsx | |
| parent | a72c894c5b65b45f99fe03770f2d54098c5507c3 (diff) | |
(김준회) Vendorpool 수정요청사항
- 컬럼리사이징 관련 문제 해결
- 공통컴포넌트에 columnResizeMode: "onChange" 속성 추가
Diffstat (limited to 'lib/vendor-pool/table/vendor-pool-table-columns.tsx')
| -rw-r--r-- | lib/vendor-pool/table/vendor-pool-table-columns.tsx | 1640 |
1 files changed, 1640 insertions, 0 deletions
diff --git a/lib/vendor-pool/table/vendor-pool-table-columns.tsx b/lib/vendor-pool/table/vendor-pool-table-columns.tsx new file mode 100644 index 00000000..8f09e684 --- /dev/null +++ b/lib/vendor-pool/table/vendor-pool-table-columns.tsx @@ -0,0 +1,1640 @@ +import { Checkbox } from "@/components/ui/checkbox" +import { Button } from "@/components/ui/button" +import { MoreHorizontal, Eye, 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 + onTaxIdChange?: (id: string, taxId: string) => Promise<void> + onMaterialGroupCodeChange?: (id: string, materialGroupCode: string) => Promise<void> + } +} + +// Vendor Pool 데이터 타입 +export type VendorPoolItem = { + id: string + no: number + selected: boolean + constructionSector: string // 공사부문: 조선 또는 해양 + htDivision: string // H/T구분: H 또는 T + designCategoryCode: string // 설계기능(공종) 코드: 2자리 영문대문자 + designCategory: string // 설계기능(공종): 전장 등 + equipBulkDivision: string // Equip/Bulk 구분: E 또는 B + // 패키지 정보 (스키마: packageCode, packageName) + packageCode: string + packageName: string + // 자재그룹 (스키마: materialGroupCode, materialGroupName) + materialGroupCode: string + materialGroupName: string + smCode: string // SM Code + similarMaterialNamePurchase: string // 유사자재명 (구매) + similarMaterialNameOther: string // 유사자재명 (구매 외) + // 협력업체 정보 (스키마: vendorCode, vendorName) + vendorCode: string + vendorName: string + taxId: string // 사업자번호(Tax ID) + faTarget: boolean // FA대상 + faStatus: string // FA현황 + faRemark: string // FA상세(Remark) + tier: string // 등급(Tier) + isAgent: boolean // Agent 여부 + // 계약서명주체 (스키마: contractSignerCode, contractSignerName) + contractSignerCode: string + contractSignerName: string + headquarterLocation: string // 본사 위치(국가) + manufacturingLocation: string // 제작/선적지(국가) + avlVendorName: string // AVL 등재업체명 + similarVendorName: string // 유사업체명(기술영업) + hasAvl: boolean // AVL: 존재여부 + isBlacklist: boolean // Blacklist + isBcc: boolean // BCC + purchaseOpinion: string // 구매의견 + // AVL 적용 선종(조선) + shipTypeCommon: boolean // 공통 + shipTypeAmax: boolean // A-max + shipTypeSmax: boolean // S-max + shipTypeVlcc: boolean // VLCC + shipTypeLngc: boolean // LNGC + shipTypeCont: boolean // CONT + // AVL 적용 선종(해양) + offshoreTypeCommon: boolean // 공통 + offshoreTypeFpso: boolean // FPSO + offshoreTypeFlng: boolean // FLNG + offshoreTypeFpu: boolean // FPU + offshoreTypePlatform: boolean // Platform + offshoreTypeWtiv: boolean // WTIV + offshoreTypeGom: boolean // GOM + // eVCP 미등록 정보 + picName: string // PIC(담당자) + picEmail: string // PIC(E-mail) + picPhone: string // PIC(Phone) + agentName: string // Agent(담당자) + agentEmail: string // Agent(E-mail) + agentPhone: string // Agent(Phone) + // 업체 실적 현황 + recentQuoteDate: string // 최근견적일 + recentQuoteNumber: string // 최근견적번호 + recentOrderDate: string // 최근발주일 + recentOrderNumber: string // 최근발주번호 + // 업데이트 히스토리 + registrationDate: string // 등재일 + registrant: string // 등재자 + lastModifiedDate: string // 최종변경일 + lastModifier: string // 최종변경자 +} + +// 테이블 컬럼 정의 +export const columns: ColumnDef<VendorPoolItem>[] = [ + + { + 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: 40, + }, + { + accessorKey: "id", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="No." /> + ), + cell: ({ row }) => { + const id = String(row.original.id) + + // 빈 행의 경우 No. 표시하지 않음 + if (id.startsWith('temp-')) { + return <div className="text-sm text-muted-foreground italic">신규</div> + } + + // vendor_pool 테이블의 실제 id 표시 + return <div className="text-sm font-mono">{id}</div> + }, + size: 60, + }, + { + accessorKey: "constructionSector", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">공사부문 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("constructionSector") + 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, "constructionSector", newValue) + } + } + + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "constructionSector") + + return ( + <EditableCell + value={value} + type="select" + onSave={onSave} + options={[ + { label: "조선", value: "조선" }, + { label: "해양", value: "해양" } + ]} + placeholder="공사부문 선택" + autoSave={true} + disabled={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 100, + }, + { + accessorKey: "htDivision", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">H/T구분 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("htDivision") + 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, "htDivision", newValue) + } + } + + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "htDivision") + + return ( + <EditableCell + value={value} + type="select" + onSave={onSave} + options={[ + { label: "H", value: "H" }, + { label: "T", value: "T" }, + { label: "공통", value: "공통" }, + ]} + placeholder="H/T 선택" + autoSave={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 80, + }, + { + accessorKey: "designCategoryCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="설계기능코드" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("designCategoryCode") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "designCategoryCode", newValue) + } + } + + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "designCategoryCode") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="설계기능코드 입력" + maxLength={10} + autoSave={false} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "designCategory", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">설계기능(공종) *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("designCategory") + 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, "designCategory", newValue) + } + } + + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "designCategory") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="설계기능(공종) 입력" + maxLength={50} + autoSave={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "equipBulkDivision", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Equip/Bulk 구분" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("equipBulkDivision") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "equipBulkDivision", newValue) + } + } + + return ( + <EditableCell + value={value} + type="select" + onSave={onSave} + options={[ + { label: "E (Equip)", value: "E" }, + { label: "B (Bulk)", value: "B" }, + { label: "S (강재)", value: "S" } + ]} + placeholder="구분 선택" + autoSave={false} + /> + ) + }, + size: 120, + }, + { + accessorKey: "packageCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="패키지 코드" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("packageCode") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "packageCode", newValue) + } + } + + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "packageCode") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="패키지 코드 입력" + maxLength={50} + autoSave={false} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + 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={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "materialGroupCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">자재그룹 코드 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("materialGroupCode") + 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") + + const onChange = async (newValue: any) => { + if (table.options.meta?.onMaterialGroupCodeChange) { + await table.options.meta.onMaterialGroupCodeChange(row.original.id, newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + onChange={onChange} + placeholder="자재그룹 코드 입력" + maxLength={50} + autoSave={false} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "materialGroupName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">자재그룹 명 *</span>} /> + ), + 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={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "smCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="SM Code" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("smCode") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "smCode", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="SM Code 입력" + maxLength={50} + /> + ) + }, + size: 100, + }, + { + accessorKey: "similarMaterialNamePurchase", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="유사자재명(구매)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("similarMaterialNamePurchase") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "similarMaterialNamePurchase", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="유사자재명(구매) 입력" + maxLength={100} + autoSave={false} + /> + ) + }, + size: 140, + }, + { + accessorKey: "similarMaterialNameOther", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="유사자재명(구매외)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("similarMaterialNameOther") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "similarMaterialNameOther", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="유사자재명(구매외) 입력" + maxLength={100} + autoSave={false} + /> + ) + }, + size: 140, + }, + { + accessorKey: "vendorCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="협력업체 코드" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("vendorCode") + 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={false} + isModified={isModified} + /> + ) + }, + size: 130, + }, + { + accessorKey: "vendorName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">협력업체 명 *</span>} /> + ), + 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={false} + initialEditMode={isEmptyRow} + isModified={isModified} + /> + ) + }, + size: 130, + }, + { + accessorKey: "taxId", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">사업자번호 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("taxId") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "taxId", newValue) + } + } + + const onChange = async (newValue: any) => { + if (table.options.meta?.onTaxIdChange) { + await table.options.meta.onTaxIdChange(row.original.id, newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + onChange={onChange} + placeholder="사업자번호 입력" + maxLength={50} + /> + ) + }, + size: 120, + }, + { + accessorKey: "faTarget", + header: "FA대상", + cell: ({ row, table }) => { + const value = row.getValue("faTarget") as boolean + 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={false} + isModified={isModified} + /> + ) + }, + enableSorting: false, + size: 80, + }, + { + accessorKey: "faStatus", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="FA현황" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("faStatus") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "faStatus", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="FA현황 입력" + maxLength={50} + /> + ) + }, + size: 100, + }, + { + accessorKey: "faRemark", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="FA상세" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("faRemark") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "faRemark", newValue) + } + } + + return ( + <EditableCell + value={value} + type="textarea" + onSave={onSave} + placeholder="FA상세 입력" + maxLength={500} + autoSave={false} + /> + ) + }, + size: 120, + }, + { + accessorKey: "tier", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">등급 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("tier") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "tier", newValue) + } + } + + 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" }, + { label: "Tier 4", value: "Tier 4" }, + ]} + placeholder="등급 선택" + /> + ) + }, + size: 80, + }, + { + accessorKey: "isAgent", + header: "Agent 여부", + cell: ({ row, table }) => { + const value = row.getValue("isAgent") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "isAgent", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 100, + }, + { + accessorKey: "contractSignerCode", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="계약서명주체 코드" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("contractSignerCode") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "contractSignerCode", newValue) + } + } + + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "contractSignerCode") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="계약서명주체 코드 입력" + maxLength={50} + autoSave={false} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "contractSignerName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">계약서명주체 명 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("contractSignerName") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "contractSignerName", newValue) + } + } + + // 수정 여부 확인 + const isModified = getIsModified(table, row.original.id, "contractSignerName") + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="계약서명주체 명 입력" + maxLength={100} + autoSave={false} + isModified={isModified} + /> + ) + }, + size: 120, + }, + { + accessorKey: "headquarterLocation", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">본사 위치 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("headquarterLocation") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "headquarterLocation", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="본사 위치 입력" + maxLength={50} + /> + ) + }, + size: 100, + }, + { + accessorKey: "manufacturingLocation", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">제작/선적지 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("manufacturingLocation") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "manufacturingLocation", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="제작/선적지 입력" + maxLength={50} + /> + ) + }, + size: 110, + }, + { + accessorKey: "avlVendorName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title={<span className="text-red-600 font-medium">AVL 등재업체명 *</span>} /> + ), + cell: ({ row, table }) => { + const value = row.getValue("avlVendorName") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "avlVendorName", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="AVL 등재업체명 입력" + maxLength={100} + /> + ) + }, + size: 140, + }, + { + accessorKey: "similarVendorName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="유사업체명" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("similarVendorName") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "similarVendorName", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="유사업체명 입력" + maxLength={100} + /> + ) + }, + size: 130, + }, + { + accessorKey: "hasAvl", + header: "AVL", + cell: ({ row, table }) => { + const value = row.getValue("hasAvl") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "hasAvl", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 60, + }, + { + accessorKey: "isBlacklist", + header: "Blacklist", + cell: ({ row, table }) => { + const value = row.getValue("isBlacklist") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "isBlacklist", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 100, + }, + { + accessorKey: "isBcc", + header: "BCC", + cell: ({ row, table }) => { + const value = row.getValue("isBcc") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "isBcc", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 80, + }, + { + accessorKey: "purchaseOpinion", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="구매의견" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("purchaseOpinion") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "purchaseOpinion", newValue) + } + } + + return ( + <EditableCell + value={value} + type="textarea" + onSave={onSave} + placeholder="구매의견 입력" + maxLength={500} + /> + ) + }, + size: 120, + }, + { + accessorKey: "shipTypeCommon", + header: "공통", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeCommon") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeCommon", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 80, + }, + { + accessorKey: "shipTypeAmax", + header: "A-max", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeAmax") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeAmax", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 80, + }, + { + accessorKey: "shipTypeSmax", + header: "S-max", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeSmax") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeSmax", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 80, + }, + { + accessorKey: "shipTypeVlcc", + header: "VLCC", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeVlcc") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeVlcc", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + enableSorting: false, + size: 80, + }, + { + accessorKey: "shipTypeLngc", + header: "LNGC", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeLngc") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeLngc", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 80, + }, + { + accessorKey: "shipTypeCont", + header: "CONT", + cell: ({ row, table }) => { + const value = row.getValue("shipTypeCont") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "shipTypeCont", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 80, + }, + { + accessorKey: "offshoreTypeCommon", + header: "공통", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeCommon") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeCommon", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 80, + }, + { + accessorKey: "offshoreTypeFpso", + header: "FPSO", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeFpso") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeFpso", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 80, + }, + { + accessorKey: "offshoreTypeFlng", + header: "FLNG", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeFlng") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeFlng", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 80, + }, + { + accessorKey: "offshoreTypeFpu", + header: "FPU", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeFpu") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeFpu", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 80, + }, + { + accessorKey: "offshoreTypePlatform", + header: "Platform", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypePlatform") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypePlatform", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 100, + }, + { + accessorKey: "offshoreTypeWtiv", + header: "WTIV", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeWtiv") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeWtiv", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 80, + }, + { + accessorKey: "offshoreTypeGom", + header: "GOM", + cell: ({ row, table }) => { + const value = row.getValue("offshoreTypeGom") as boolean + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "offshoreTypeGom", newValue) + } + } + + return ( + <EditableCell + value={value} + type="checkbox" + onSave={onSave} + autoSave={false} + /> + ) + }, + size: 80, + }, + { + accessorKey: "picName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="PIC(담당자)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("picName") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "picName", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="PIC 담당자명 입력" + maxLength={50} + /> + ) + }, + size: 120, + }, + { + accessorKey: "picEmail", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="PIC(E-mail)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("picEmail") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "picEmail", newValue) + } + } + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="PIC 이메일 입력" + maxLength={100} + /> + ) + }, + size: 140, + }, + { + accessorKey: "picPhone", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="PIC(Phone)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("picPhone") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "picPhone", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="PIC 전화번호 입력" + maxLength={20} + /> + ) + }, + size: 120, + }, + { + accessorKey: "agentName", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Agent(담당자)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("agentName") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "agentName", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="Agent 담당자명 입력" + maxLength={50} + /> + ) + }, + size: 120, + }, + { + accessorKey: "agentEmail", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Agent(E-mail)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("agentEmail") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "agentEmail", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="Agent 이메일 입력" + maxLength={100} + /> + ) + }, + size: 140, + }, + { + accessorKey: "agentPhone", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="Agent(Phone)" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("agentPhone") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "agentPhone", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="Agent 전화번호 입력" + maxLength={20} + /> + ) + }, + size: 120, + }, + { + accessorKey: "recentQuoteDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최근견적일" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("recentQuoteDate") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "recentQuoteDate", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="최근견적일 입력 (YYYY-MM-DD)" + maxLength={20} + autoSave={false} + /> + ) + }, + size: 120, + }, + { + accessorKey: "recentQuoteNumber", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최근견적번호" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("recentQuoteNumber") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "recentQuoteNumber", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="최근견적번호 입력" + maxLength={50} + /> + ) + }, + size: 130, + }, + { + accessorKey: "recentOrderDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최근발주일" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("recentOrderDate") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "recentOrderDate", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="최근발주일 입력 (YYYY-MM-DD)" + maxLength={20} + autoSave={false} + /> + ) + }, + size: 120, + }, + { + accessorKey: "recentOrderNumber", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최근발주번호" /> + ), + cell: ({ row, table }) => { + const value = row.getValue("recentOrderNumber") + const onSave = async (newValue: any) => { + if (table.options.meta?.onCellUpdate) { + await table.options.meta.onCellUpdate(row.original.id, "recentOrderNumber", newValue) + } + } + + return ( + <EditableCell + value={value} + type="text" + onSave={onSave} + placeholder="최근발주번호 입력" + maxLength={50} + /> + ) + }, + size: 130, + }, + { + accessorKey: "registrationDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="등재일" /> + ), + size: 120, + }, + { + accessorKey: "registrant", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="등재자" /> + ), + cell: ({ row }) => { + const value = row.getValue("registrant") as string + return <div className="text-sm">{value || ""}</div> + }, + size: 100, + }, + { + accessorKey: "lastModifiedDate", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최종변경일" /> + ), + size: 120, + }, + { + accessorKey: "lastModifier", + header: ({ column }) => ( + <DataTableColumnHeaderSimple column={column} title="최종변경자" /> + ), + cell: ({ row }) => { + const value = row.getValue("lastModifier") as string + return <div className="text-sm">{value || ""}</div> + }, + size: 120, + }, + // 액션 그룹 + { + id: "actions", + header: "액션", + cell: ({ row, table }) => { + const data = row.original + const isEmptyRow = (table.options.meta as any)?.isEmptyRow?.(String(data.id)) + + if (isEmptyRow) { + // 빈 행의 경우 저장/취소 버튼 표시 + return ( + <div className="flex items-center gap-2 overflow-visible relative"> + <Button + variant="default" + size="sm" + onClick={() => { + const onSaveEmptyRow = (table.options.meta as any)?.onSaveEmptyRow + onSaveEmptyRow?.(data.id) + }} + title="저장" + className="bg-green-600 hover:bg-green-700" + > + <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" /> + </svg> + </Button> + <Button + variant="outline" + size="sm" + onClick={() => { + const onCancelEmptyRow = (table.options.meta as any)?.onCancelEmptyRow + onCancelEmptyRow?.(data.id) + }} + title="취소" + className="border-red-300 text-red-600 hover:bg-red-50" + > + <svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"> + <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> + </svg> + </Button> + </div> + ) + } + + // 일반 행의 경우 기존 액션 버튼들 표시 + return ( + <div className="flex items-center gap-2 overflow-visible relative"> + <Button + variant="ghost" + size="sm" + onClick={() => { + const onAction = (table.options.meta as any)?.onAction + onAction?.('delete', data) + }} + title="삭제" + > + <Trash2 className="h-4 w-4" /> + </Button> + </div> + ) + }, + size: 120, + enableSorting: false, + enableHiding: false, + }, +] + |
