summaryrefslogtreecommitdiff
path: root/lib/vendor-pool/table/columns.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'lib/vendor-pool/table/columns.tsx')
-rw-r--r--lib/vendor-pool/table/columns.tsx1687
1 files changed, 1687 insertions, 0 deletions
diff --git a/lib/vendor-pool/table/columns.tsx b/lib/vendor-pool/table/columns.tsx
new file mode 100644
index 00000000..0a6b0c8f
--- /dev/null
+++ b/lib/vendor-pool/table/columns.tsx
@@ -0,0 +1,1687 @@
+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>[] = [
+ // 기본 정보 그룹
+ {
+ 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: 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,
+ },
+ ]
+ },
+ // 자재 정보 그룹
+ {
+ header: "자재 정보",
+ columns: [
+ {
+ 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,
+ },
+ ]
+ },
+ // 협력업체 정보 그룹
+ {
+ header: "협력업체 정보",
+ columns: [
+ {
+ 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,
+ },
+ ]
+ },
+ // AVL 적용 선종(조선) 그룹
+ {
+ header: "AVL 적용 선종(조선)",
+ columns: [
+ {
+ 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,
+ },
+ ]
+ },
+ // AVL 적용 선종(해양) 그룹
+ {
+ header: "AVL 적용 선종(해양)",
+ columns: [
+ {
+ 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,
+ },
+ ]
+ },
+ // eVCP 미등록 정보 그룹
+ {
+ header: "eVCP 미등록 정보",
+ columns: [
+ {
+ 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,
+ },
+ ]
+ },
+ // 업체 실적 현황 그룹
+ {
+ header: "업체 실적 현황",
+ columns: [
+ {
+ 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,
+ },
+ ]
+ },
+ // 업데이트 히스토리 그룹
+ {
+ header: "업데이트 히스토리",
+ columns: [
+ {
+ 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,
+ },
+]