diff options
| -rw-r--r-- | app/[lng]/evcp/poa/page.tsx | 61 | ||||
| -rw-r--r-- | config/poaColumnsConfig.ts | 131 | ||||
| -rw-r--r-- | db/migrations/0097_poa_initial_setup.sql | 95 | ||||
| -rw-r--r-- | db/schema/contract.ts | 104 | ||||
| -rw-r--r-- | db/seeds_2/poaSeed.ts | 109 | ||||
| -rw-r--r-- | lib/poa/service.ts | 132 | ||||
| -rw-r--r-- | lib/poa/table/poa-table-columns.tsx | 165 | ||||
| -rw-r--r-- | lib/poa/table/poa-table-toolbar-actions.tsx | 45 | ||||
| -rw-r--r-- | lib/poa/table/poa-table.tsx | 189 | ||||
| -rw-r--r-- | lib/poa/validations.ts | 66 |
10 files changed, 1 insertions, 1096 deletions
diff --git a/app/[lng]/evcp/poa/page.tsx b/app/[lng]/evcp/poa/page.tsx deleted file mode 100644 index dec5e05b..00000000 --- a/app/[lng]/evcp/poa/page.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import * as React from "react" -import { type SearchParams } from "@/types/table" - -import { getValidFilters } from "@/lib/data-table" -import { Skeleton } from "@/components/ui/skeleton" -import { DataTableSkeleton } from "@/components/data-table/data-table-skeleton" -import { Shell } from "@/components/shell" -import { getChangeOrders } from "@/lib/poa/service" -import { searchParamsCache } from "@/lib/poa/validations" -import { ChangeOrderListsTable } from "@/lib/poa/table/poa-table" - -interface IndexPageProps { - searchParams: Promise<SearchParams> -} - -export default async function IndexPage(props: IndexPageProps) { - const searchParams = await props.searchParams - const search = searchParamsCache.parse(searchParams) - - const validFilters = getValidFilters(search.filters) - - const promises = Promise.all([ - getChangeOrders({ - ...search, - filters: validFilters, - }), - ]) - - return ( - <Shell className="gap-2"> - <div className="flex items-center justify-between space-y-2"> - <div className="flex items-center justify-between space-y-2"> - <div> - <h2 className="text-2xl font-bold tracking-tight"> - 변경 PO 확인 및 전자서명 - </h2> - <p className="text-muted-foreground"> - 발행된 PO의 변경 내역을 확인하고 관리할 수 있습니다. - </p> - </div> - </div> - </div> - - <React.Suspense fallback={<Skeleton className="h-7 w-52" />}> - </React.Suspense> - <React.Suspense - fallback={ - <DataTableSkeleton - columnCount={6} - searchableColumnCount={1} - filterableColumnCount={2} - cellWidths={["10rem", "40rem", "12rem", "12rem", "8rem", "8rem"]} - shrinkZero - /> - } - > - <ChangeOrderListsTable promises={promises} /> - </React.Suspense> - </Shell> - ) -}
\ No newline at end of file diff --git a/config/poaColumnsConfig.ts b/config/poaColumnsConfig.ts deleted file mode 100644 index 268a2259..00000000 --- a/config/poaColumnsConfig.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { POADetail } from "@/db/schema/contract" - -export interface PoaColumnConfig { - id: keyof POADetail - label: string - group?: string - excelHeader?: string - type?: string -} - -export const poaColumnsConfig: PoaColumnConfig[] = [ - { - id: "id", - label: "ID", - excelHeader: "ID", - group: "Key Info", - type: "number", - }, - { - id: "projectId", - label: "Project ID", - excelHeader: "Project ID", - group: "Key Info", - type: "number", - }, - { - id: "vendorId", - label: "Vendor ID", - excelHeader: "Vendor ID", - group: "Key Info", - type: "number", - }, - { - id: "contractNo", - label: "Form Code", - excelHeader: "Form Code", - group: "Original Info", - type: "text", - }, - { - id: "originalContractName", - label: "Contract Name", - excelHeader: "Contract Name", - group: "Original Info", - type: "text", - }, - { - id: "originalStatus", - label: "Status", - excelHeader: "Status", - group: "Original Info", - type: "text", - }, - { - id: "deliveryTerms", - label: "Delivery Terms", - excelHeader: "Delivery Terms", - group: "Change Info", - type: "text", - }, - { - id: "deliveryDate", - label: "Delivery Date", - excelHeader: "Delivery Date", - group: "Change Info", - type: "date", - }, - { - id: "deliveryLocation", - label: "Delivery Location", - excelHeader: "Delivery Location", - group: "Change Info", - type: "text", - }, - { - id: "currency", - label: "Currency", - excelHeader: "Currency", - group: "Change Info", - type: "text", - }, - { - id: "totalAmount", - label: "Total Amount", - excelHeader: "Total Amount", - group: "Change Info", - type: "number", - }, - { - id: "discount", - label: "Discount", - excelHeader: "Discount", - group: "Change Info", - type: "number", - }, - { - id: "tax", - label: "Tax", - excelHeader: "Tax", - group: "Change Info", - type: "number", - }, - { - id: "shippingFee", - label: "Shipping Fee", - excelHeader: "Shipping Fee", - group: "Change Info", - type: "number", - }, - { - id: "netTotal", - label: "Net Total", - excelHeader: "Net Total", - group: "Change Info", - type: "number", - }, - { - id: "createdAt", - label: "Created At", - excelHeader: "Created At", - group: "System Info", - type: "date", - }, - { - id: "updatedAt", - label: "Updated At", - excelHeader: "Updated At", - group: "System Info", - type: "date", - }, -]
\ No newline at end of file diff --git a/db/migrations/0097_poa_initial_setup.sql b/db/migrations/0097_poa_initial_setup.sql deleted file mode 100644 index fae3f4d1..00000000 --- a/db/migrations/0097_poa_initial_setup.sql +++ /dev/null @@ -1,95 +0,0 @@ --- Drop existing tables and views -DROP VIEW IF EXISTS change_orders_detail_view; -DROP TABLE IF EXISTS change_order_items CASCADE; -DROP TABLE IF EXISTS change_orders CASCADE; -DROP VIEW IF EXISTS poa_detail_view; -DROP TABLE IF EXISTS poa CASCADE; - --- Create POA table -CREATE TABLE poa ( - id SERIAL PRIMARY KEY, - contract_no VARCHAR(100) NOT NULL, - original_contract_no VARCHAR(100) NOT NULL, - project_id INTEGER NOT NULL, - vendor_id INTEGER NOT NULL, - original_contract_name VARCHAR(255) NOT NULL, - original_status VARCHAR(50) NOT NULL, - delivery_terms TEXT, - delivery_date DATE, - delivery_location VARCHAR(255), - currency VARCHAR(10), - total_amount NUMERIC(12,2), - discount NUMERIC(12,2), - tax NUMERIC(12,2), - shipping_fee NUMERIC(12,2), - net_total NUMERIC(12,2), - change_reason TEXT, - approval_status VARCHAR(50) DEFAULT 'PENDING', - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP NOT NULL DEFAULT NOW(), - CONSTRAINT poa_original_contract_no_contracts_contract_no_fk - FOREIGN KEY (original_contract_no) - REFERENCES contracts(contract_no) - ON DELETE CASCADE, - CONSTRAINT poa_project_id_projects_id_fk - FOREIGN KEY (project_id) - REFERENCES projects(id) - ON DELETE CASCADE, - CONSTRAINT poa_vendor_id_vendors_id_fk - FOREIGN KEY (vendor_id) - REFERENCES vendors(id) - ON DELETE CASCADE -); - --- Create POA detail view -CREATE VIEW poa_detail_view AS -SELECT - -- POA primary information - poa.id, - poa.contract_no, - poa.change_reason, - poa.approval_status, - - -- Original PO information - poa.original_contract_no, - poa.original_contract_name, - poa.original_status, - c.start_date as original_start_date, - c.end_date as original_end_date, - - -- Project information - poa.project_id, - p.code as project_code, - p.name as project_name, - - -- Vendor information - poa.vendor_id, - v.vendor_name, - - -- Changed delivery details - poa.delivery_terms, - poa.delivery_date, - poa.delivery_location, - - -- Changed financial information - poa.currency, - poa.total_amount, - poa.discount, - poa.tax, - poa.shipping_fee, - poa.net_total, - - -- Timestamps - poa.created_at, - poa.updated_at, - - -- Electronic signature status - EXISTS ( - SELECT 1 - FROM contract_envelopes - WHERE contract_envelopes.contract_id = poa.id - ) as has_signature -FROM poa -LEFT JOIN contracts c ON poa.original_contract_no = c.contract_no -LEFT JOIN projects p ON poa.project_id = p.id -LEFT JOIN vendors v ON poa.vendor_id = v.id;
\ No newline at end of file diff --git a/db/schema/contract.ts b/db/schema/contract.ts index c14921bb..10721b4d 100644 --- a/db/schema/contract.ts +++ b/db/schema/contract.ts @@ -257,106 +257,4 @@ export const contractsDetailView = pgView("contracts_detail_view").as((qb) => { }); // Type inference for the view -export type ContractDetail = typeof contractsDetailView.$inferSelect; - - - - -// ============ poa (Purchase Order Amendment) ============ -export const poa = pgTable("poa", { - // 주 키 - id: integer("id").primaryKey().generatedAlwaysAsIdentity(), - - // Form code는 원본과 동일하게 유지 - contractNo: varchar("contract_no", { length: 100 }).notNull(), - - // 원본 PO 참조 - originalContractNo: varchar("original_contract_no", { length: 100 }) - .notNull() - .references(() => contracts.contractNo, { onDelete: "cascade" }), - - // 원본 계약 정보 - projectId: integer("project_id") - .notNull() - .references(() => projects.id, { onDelete: "cascade" }), - vendorId: integer("vendor_id") - .notNull() - .references(() => vendors.id, { onDelete: "cascade" }), - originalContractName: varchar("original_contract_name", { length: 255 }).notNull(), - originalStatus: varchar("original_status", { length: 50 }).notNull(), - - // 변경된 납품 조건 - deliveryTerms: text("delivery_terms"), // 변경된 납품 조건 - deliveryDate: date("delivery_date"), // 변경된 납품 기한 - deliveryLocation: varchar("delivery_location", { length: 255 }), // 변경된 납품 장소 - - // 변경된 가격/금액 관련 - currency: varchar("currency", { length: 10 }), // 변경된 통화 - totalAmount: numeric("total_amount", { precision: 12, scale: 2 }), // 변경된 총 금액 - discount: numeric("discount", { precision: 12, scale: 2 }), // 변경된 할인 - tax: numeric("tax", { precision: 12, scale: 2 }), // 변경된 세금 - shippingFee: numeric("shipping_fee", { precision: 12, scale: 2 }), // 변경된 배송비 - netTotal: numeric("net_total", { precision: 12, scale: 2 }), // 변경된 순 총액 - - // 변경 사유 - changeReason: text("change_reason"), - - // 승인 상태 - approvalStatus: varchar("approval_status", { length: 50 }).default("PENDING"), - - // 생성/수정 시각 - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at").defaultNow().notNull(), -}) - -// 타입 추론 -export type POA = typeof poa.$inferSelect - -// ============ poa_detail_view ============ -export const poaDetailView = pgView("poa_detail_view").as((qb) => { - return qb - .select({ - // POA primary information - id: poa.id, - contractNo: poa.contractNo, - projectId: contracts.projectId, - vendorId: contracts.vendorId, - changeReason: poa.changeReason, - approvalStatus: poa.approvalStatus, - - // Original PO information - originalContractName: sql<string>`${contracts.contractName}`.as('original_contract_name'), - originalStatus: sql<string>`${contracts.status}`.as('original_status'), - originalStartDate: sql<Date>`${contracts.startDate}`.as('original_start_date'), - originalEndDate: sql<Date>`${contracts.endDate}`.as('original_end_date'), - - // Changed delivery details - deliveryTerms: poa.deliveryTerms, - deliveryDate: poa.deliveryDate, - deliveryLocation: poa.deliveryLocation, - - // Changed financial information - currency: poa.currency, - totalAmount: poa.totalAmount, - discount: poa.discount, - tax: poa.tax, - shippingFee: poa.shippingFee, - netTotal: poa.netTotal, - - // Timestamps - createdAt: poa.createdAt, - updatedAt: poa.updatedAt, - - // Electronic signature status - hasSignature: sql<boolean>`EXISTS ( - SELECT 1 - FROM ${contractEnvelopes} - WHERE ${contractEnvelopes.contractId} = ${poa.id} - )`.as('has_signature'), - }) - .from(poa) - .leftJoin(contracts, eq(poa.contractNo, contracts.contractNo)) -}); - -// Type inference for the view -export type POADetail = typeof poaDetailView.$inferSelect;
\ No newline at end of file +export type ContractDetail = typeof contractsDetailView.$inferSelect;
\ No newline at end of file diff --git a/db/seeds_2/poaSeed.ts b/db/seeds_2/poaSeed.ts deleted file mode 100644 index d93cde14..00000000 --- a/db/seeds_2/poaSeed.ts +++ /dev/null @@ -1,109 +0,0 @@ -import { faker } from "@faker-js/faker" -import db from "../db" -import { contracts, poa } from "../schema/contract" -import { sql } from "drizzle-orm" - -export async function seedPOA({ count = 10 } = {}) { - try { - console.log(`📝 Inserting POA ${count}`) - - // 기존 POA 데이터 삭제 및 ID 시퀀스 초기화 - await db.delete(poa) - await db.execute(sql`ALTER SEQUENCE poa_id_seq RESTART WITH 1;`) - console.log("✅ 기존 POA 데이터 삭제 및 ID 초기화 완료") - - // 조선업 맥락에 맞는 예시 문구들 - const deliveryTermsExamples = [ - "FOB 부산항", - "CIF 상하이항", - "DAP 울산조선소", - "DDP 거제 옥포조선소", - ] - const deliveryLocations = [ - "부산 영도조선소", - "울산 본사 도크 #3", - "거제 옥포조선소 해양공장", - "목포신항 부두", - ] - const changeReasonExamples = [ - "납품 일정 조정 필요", - "자재 사양 변경", - "선박 설계 변경에 따른 수정", - "추가 부품 요청", - "납품 장소 변경", - "계약 조건 재협상" - ] - - // 1. 기존 계약(PO) 목록 가져오기 - const existingContracts = await db.select().from(contracts) - console.log(`Found ${existingContracts.length} existing contracts`) - - if (existingContracts.length === 0) { - throw new Error("계약(PO) 데이터가 없습니다. 먼저 계약 데이터를 생성해주세요.") - } - - // 2. POA 생성 - for (let i = 0; i < count; i++) { - try { - // 랜덤으로 원본 계약 선택 - const originalContract = faker.helpers.arrayElement(existingContracts) - console.log(`Selected original contract: ${originalContract.contractNo}`) - - // POA 생성 - const totalAmount = faker.number.float({ min: 5000000, max: 500000000 }) - const discount = faker.helpers.maybe(() => faker.number.float({ min: 0, max: 500000 }), { probability: 0.3 }) - const tax = faker.helpers.maybe(() => faker.number.float({ min: 0, max: 1000000 }), { probability: 0.8 }) - const shippingFee = faker.helpers.maybe(() => faker.number.float({ min: 0, max: 300000 }), { probability: 0.5 }) - const netTotal = totalAmount - (discount || 0) + (tax || 0) + (shippingFee || 0) - - const poaData = { - // Form code는 원본과 동일하게 유지 - contractNo: originalContract.contractNo, - originalContractNo: originalContract.contractNo, - projectId: originalContract.projectId, - vendorId: originalContract.vendorId, - originalContractName: originalContract.contractName, - originalStatus: originalContract.status, - - // 변경 가능한 정보들 - deliveryTerms: faker.helpers.arrayElement(deliveryTermsExamples), - deliveryDate: faker.helpers.maybe(() => faker.date.future().toISOString(), { probability: 0.7 }), - deliveryLocation: faker.helpers.arrayElement(deliveryLocations), - currency: "KRW", - totalAmount: totalAmount.toString(), - discount: discount?.toString(), - tax: tax?.toString(), - shippingFee: shippingFee?.toString(), - netTotal: netTotal.toString(), - changeReason: faker.helpers.arrayElement(changeReasonExamples), - approvalStatus: faker.helpers.arrayElement(["PENDING", "APPROVED", "REJECTED"]), - createdAt: new Date(), - updatedAt: new Date(), - } - - console.log("POA data:", poaData) - - await db.insert(poa).values(poaData) - console.log(`Created POA for contract: ${originalContract.contractNo}`) - } catch (error) { - console.error(`Error creating POA ${i + 1}:`, error) - throw error - } - } - - console.log(`✅ Successfully added ${count} new POAs`) - } catch (error) { - console.error("Error in seedPOA:", error) - throw error - } -} - -// 실행 -if (require.main === module) { - seedPOA({ count: 5 }) - .then(() => process.exit(0)) - .catch((error) => { - console.error(error) - process.exit(1) - }) -}
\ No newline at end of file diff --git a/lib/poa/service.ts b/lib/poa/service.ts deleted file mode 100644 index a11cbdd8..00000000 --- a/lib/poa/service.ts +++ /dev/null @@ -1,132 +0,0 @@ -"use server"; - -import db from "@/db/db"; -import { GetChangeOrderSchema } from "./validations"; -import { unstable_cache } from "@/lib/unstable-cache"; -import { filterColumns } from "@/lib/filter-columns"; -import { - asc, - desc, - ilike, - and, - or, - count, -} from "drizzle-orm"; - -import { - poaDetailView, -} from "@/db/schema/contract"; - -/** - * POA 목록 조회 - */ -export async function getChangeOrders(input: GetChangeOrderSchema) { - return unstable_cache( - async () => { - try { - const offset = (input.page - 1) * input.perPage; - - // 1. Build where clause - let advancedWhere; - try { - advancedWhere = filterColumns({ - table: poaDetailView, - filters: input.filters, - joinOperator: input.joinOperator, - }); - } catch (whereErr) { - console.error("Error building advanced where:", whereErr); - advancedWhere = undefined; - } - - let globalWhere; - if (input.search) { - try { - const s = `%${input.search}%`; - globalWhere = or( - ilike(poaDetailView.contractNo, s), - ilike(poaDetailView.originalContractName, s), - ilike(poaDetailView.projectCode, s), - ilike(poaDetailView.projectName, s), - ilike(poaDetailView.vendorName, s) - ); - } catch (searchErr) { - console.error("Error building search where:", searchErr); - globalWhere = undefined; - } - } - - // 2. Combine where clauses - let finalWhere; - if (advancedWhere && globalWhere) { - finalWhere = and(advancedWhere, globalWhere); - } else { - finalWhere = advancedWhere || globalWhere; - } - - // 3. Build order by - let orderBy; - try { - orderBy = - input.sort.length > 0 - ? input.sort.map((item) => - item.desc - ? desc(poaDetailView[item.id]) - : asc(poaDetailView[item.id]) - ) - : [desc(poaDetailView.createdAt)]; - } catch (orderErr) { - console.error("Error building order by:", orderErr); - orderBy = [desc(poaDetailView.createdAt)]; - } - - // 4. Execute queries - let data = []; - let total = 0; - - try { - const queryBuilder = db.select().from(poaDetailView); - - if (finalWhere) { - queryBuilder.where(finalWhere); - } - - queryBuilder.orderBy(...orderBy); - queryBuilder.offset(offset).limit(input.perPage); - - data = await queryBuilder; - - const countBuilder = db - .select({ count: count() }) - .from(poaDetailView); - - if (finalWhere) { - countBuilder.where(finalWhere); - } - - const countResult = await countBuilder; - total = countResult[0]?.count || 0; - } catch (queryErr) { - console.error("Query execution failed:", queryErr); - throw queryErr; - } - - const pageCount = Math.ceil(total / input.perPage); - - return { data, pageCount }; - } catch (err) { - console.error("Error in getChangeOrders:", err); - if (err instanceof Error) { - console.error("Error message:", err.message); - console.error("Error stack:", err.stack); - } - return { data: [], pageCount: 0 }; - } - }, - [JSON.stringify(input)], - { - revalidate: 3600, - tags: [`poa`], - } - )(); -}
\ No newline at end of file diff --git a/lib/poa/table/poa-table-columns.tsx b/lib/poa/table/poa-table-columns.tsx deleted file mode 100644 index b362e54c..00000000 --- a/lib/poa/table/poa-table-columns.tsx +++ /dev/null @@ -1,165 +0,0 @@ -"use client" - -import * as React from "react" -import { type DataTableRowAction } from "@/types/table" -import { type ColumnDef } from "@tanstack/react-table" -import { InfoIcon, PenIcon } from "lucide-react" - -import { formatDate } from "@/lib/utils" -import { Button } from "@/components/ui/button" -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from "@/components/ui/tooltip" -import { DataTableColumnHeaderSimple } from "@/components/data-table/data-table-column-simple-header" -import { POADetail } from "@/db/schema/contract" -import { poaColumnsConfig } from "@/config/poaColumnsConfig" - -interface GetColumnsProps { - setRowAction: React.Dispatch<React.SetStateAction<DataTableRowAction<POADetail> | null>> -} - -/** - * tanstack table column definitions with nested headers - */ -export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<POADetail>[] { - // ---------------------------------------------------------------- - // 1) actions column (buttons for item info) - // ---------------------------------------------------------------- - const actionsColumn: ColumnDef<POADetail> = { - id: "actions", - enableHiding: false, - cell: function Cell({ row }) { - const hasSignature = row.original.hasSignature; - - return ( - <div className="flex items-center space-x-1"> - {/* Item Info Button */} - <TooltipProvider> - <Tooltip> - <TooltipTrigger asChild> - <Button - variant="ghost" - size="icon" - onClick={() => setRowAction({ row, type: "items" })} - > - <InfoIcon className="h-4 w-4" aria-hidden="true" /> - </Button> - </TooltipTrigger> - <TooltipContent> - View Item Info - </TooltipContent> - </Tooltip> - </TooltipProvider> - - {/* Signature Request Button - only show if no signature exists */} - {!hasSignature && ( - <TooltipProvider> - <Tooltip> - <TooltipTrigger asChild> - <Button - variant="ghost" - size="icon" - onClick={() => setRowAction({ row, type: "signature" })} - > - <PenIcon className="h-4 w-4" aria-hidden="true" /> - </Button> - </TooltipTrigger> - <TooltipContent> - Request Electronic Signature - </TooltipContent> - </Tooltip> - </TooltipProvider> - )} - </div> - ); - }, - size: 80, - }; - - // ---------------------------------------------------------------- - // 2) Regular columns grouped by group name - // ---------------------------------------------------------------- - // 2-1) groupMap: { [groupName]: ColumnDef<POADetail>[] } - const groupMap: Record<string, ColumnDef<POADetail>[]> = {}; - - poaColumnsConfig.forEach((cfg) => { - // Use "_noGroup" if no group is specified - const groupName = cfg.group || "_noGroup"; - - if (!groupMap[groupName]) { - groupMap[groupName] = []; - } - - // Child column definition - const childCol: ColumnDef<POADetail> = { - accessorKey: cfg.id, - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title={cfg.label} /> - ), - meta: { - excelHeader: cfg.excelHeader, - group: cfg.group, - type: cfg.type, - }, - cell: ({ cell }) => { - const value = cell.getValue(); - - if (cfg.type === "date") { - const dateVal = value as Date; - return ( - <div className="text-sm"> - {formatDate(dateVal)} - </div> - ); - } - if (cfg.type === "number") { - const numVal = value as number; - return ( - <div className="text-sm"> - {numVal ? numVal.toLocaleString() : "-"} - </div> - ); - } - return ( - <div className="text-sm"> - {value ?? "-"} - </div> - ); - }, - }; - - groupMap[groupName].push(childCol); - }); - - // ---------------------------------------------------------------- - // 2-2) Create actual parent columns (groups) from the groupMap - // ---------------------------------------------------------------- - const nestedColumns: ColumnDef<POADetail>[] = []; - - // Order can be fixed by pre-defining group order or sorting - Object.entries(groupMap).forEach(([groupName, colDefs]) => { - if (groupName === "_noGroup") { - // No group → Add as top-level columns - nestedColumns.push(...colDefs); - } else { - // Parent column - nestedColumns.push({ - id: groupName, - header: groupName, - columns: colDefs, - }); - } - }); - - // ---------------------------------------------------------------- - // 3) Final column array: nestedColumns + actionsColumn - // ---------------------------------------------------------------- - return [ - ...nestedColumns, - actionsColumn, - ]; -}
\ No newline at end of file diff --git a/lib/poa/table/poa-table-toolbar-actions.tsx b/lib/poa/table/poa-table-toolbar-actions.tsx deleted file mode 100644 index 97a9cc55..00000000 --- a/lib/poa/table/poa-table-toolbar-actions.tsx +++ /dev/null @@ -1,45 +0,0 @@ -"use client" - -import * as React from "react" -import { type Table } from "@tanstack/react-table" -import { Download, RefreshCcw } from "lucide-react" - -import { exportTableToExcel } from "@/lib/export" -import { Button } from "@/components/ui/button" -import { POADetail } from "@/db/schema/contract" - -interface ItemsTableToolbarActionsProps { - table: Table<POADetail> -} - -export function PoaTableToolbarActions({ table }: ItemsTableToolbarActionsProps) { - return ( - <div className="flex items-center gap-2"> - {/** Refresh 버튼 */} - <Button - variant="samsung" - size="sm" - className="gap-2" - > - <RefreshCcw className="size-4" aria-hidden="true" /> - <span className="hidden sm:inline">Get POAs</span> - </Button> - - {/** Export 버튼 */} - <Button - variant="outline" - size="sm" - onClick={() => - exportTableToExcel(table, { - filename: "poa-list", - excludeColumns: ["select", "actions"], - }) - } - className="gap-2" - > - <Download className="size-4" aria-hidden="true" /> - <span className="hidden sm:inline">Export</span> - </Button> - </div> - ) -}
\ No newline at end of file diff --git a/lib/poa/table/poa-table.tsx b/lib/poa/table/poa-table.tsx deleted file mode 100644 index a5cad02a..00000000 --- a/lib/poa/table/poa-table.tsx +++ /dev/null @@ -1,189 +0,0 @@ -"use client" - -import * as React from "react" -import type { - DataTableAdvancedFilterField, - DataTableFilterField, - DataTableRowAction, -} from "@/types/table" - -import { useDataTable } from "@/hooks/use-data-table" -import { DataTable } from "@/components/data-table/data-table" -import { DataTableAdvancedToolbar } from "@/components/data-table/data-table-advanced-toolbar" - -import { getChangeOrders } from "../service" -import { POADetail } from "@/db/schema/contract" -import { getColumns } from "./poa-table-columns" -import { PoaTableToolbarActions } from "./poa-table-toolbar-actions" - -interface ItemsTableProps { - promises: Promise< - [ - Awaited<ReturnType<typeof getChangeOrders>>, - ] - > -} - -export function ChangeOrderListsTable({ promises }: ItemsTableProps) { - const [result] = React.use(promises) - const { data, pageCount } = result - - const [rowAction, setRowAction] = - React.useState<DataTableRowAction<POADetail> | null>(null) - - // Handle row actions - React.useEffect(() => { - if (!rowAction) return - - if (rowAction.type === "items") { - // Handle items view action - setRowAction(null) - } - }, [rowAction]) - - const columns = React.useMemo( - () => getColumns({ setRowAction }), - [setRowAction] - ) - - const filterFields: DataTableFilterField<POADetail>[] = [ - { - id: "contractNo", - label: "계약번호", - }, - { - id: "originalContractName", - label: "계약명", - }, - { - id: "approvalStatus", - label: "승인 상태", - }, - ] - - const advancedFilterFields: DataTableAdvancedFilterField<POADetail>[] = [ - { - id: "contractNo", - label: "계약번호", - type: "text", - }, - { - id: "originalContractName", - label: "계약명", - type: "text", - }, - { - id: "projectId", - label: "프로젝트 ID", - type: "number", - }, - { - id: "vendorId", - label: "벤더 ID", - type: "number", - }, - { - id: "originalStatus", - label: "상태", - type: "text", - }, - { - id: "deliveryTerms", - label: "납품조건", - type: "text", - }, - { - id: "deliveryDate", - label: "납품기한", - type: "date", - }, - { - id: "deliveryLocation", - label: "납품장소", - type: "text", - }, - { - id: "currency", - label: "통화", - type: "text", - }, - { - id: "totalAmount", - label: "총 금액", - type: "number", - }, - { - id: "discount", - label: "할인", - type: "number", - }, - { - id: "tax", - label: "세금", - type: "number", - }, - { - id: "shippingFee", - label: "배송비", - type: "number", - }, - { - id: "netTotal", - label: "최종 금액", - type: "number", - }, - { - id: "changeReason", - label: "변경 사유", - type: "text", - }, - { - id: "approvalStatus", - label: "승인 상태", - type: "text", - }, - { - id: "createdAt", - label: "생성일", - type: "date", - }, - { - id: "updatedAt", - label: "수정일", - type: "date", - }, - ] - - const { table } = useDataTable({ - data, - columns, - pageCount, - filterFields, - enablePinning: true, - enableAdvancedFilter: true, - initialState: { - sorting: [{ id: "createdAt", desc: true }], - columnPinning: { right: ["actions"] }, - }, - getRowId: (originalRow) => String(originalRow.id), - shallow: false, - clearOnDefault: true, - }) - - return ( - <> - <DataTable - table={table} - className="h-[calc(100vh-12rem)]" - > - <DataTableAdvancedToolbar - table={table} - filterFields={advancedFilterFields} - shallow={false} - > - <PoaTableToolbarActions table={table} /> - </DataTableAdvancedToolbar> - </DataTable> - </> - ) -}
\ No newline at end of file diff --git a/lib/poa/validations.ts b/lib/poa/validations.ts deleted file mode 100644 index eae1b5ab..00000000 --- a/lib/poa/validations.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { - createSearchParamsCache, - parseAsArrayOf, - parseAsInteger, - parseAsString, - parseAsStringEnum, -} from "nuqs/server" -import * as z from "zod" - -import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers" -import { POADetail } from "@/db/schema/contract" - -export const searchParamsCache = createSearchParamsCache({ - // UI 모드나 플래그 관련 - flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault([]), - - // 페이징 - page: parseAsInteger.withDefault(1), - perPage: parseAsInteger.withDefault(10), - - // 정렬 (createdAt 기준 내림차순) - sort: getSortingStateParser<POADetail>().withDefault([ - { id: "createdAt", desc: true }, - ]), - - // 원본 PO 관련 - contractNo: parseAsString.withDefault(""), - originalContractName: parseAsString.withDefault(""), - originalStatus: parseAsString.withDefault(""), - originalStartDate: parseAsString.withDefault(""), - originalEndDate: parseAsString.withDefault(""), - - // 프로젝트 정보 - projectId: parseAsString.withDefault(""), - projectCode: parseAsString.withDefault(""), - projectName: parseAsString.withDefault(""), - - // 벤더 정보 - vendorId: parseAsString.withDefault(""), - vendorName: parseAsString.withDefault(""), - - // 납품 관련 - deliveryTerms: parseAsString.withDefault(""), - deliveryDate: parseAsString.withDefault(""), - deliveryLocation: parseAsString.withDefault(""), - - // 금액 관련 - currency: parseAsString.withDefault(""), - totalAmount: parseAsString.withDefault(""), - discount: parseAsString.withDefault(""), - tax: parseAsString.withDefault(""), - shippingFee: parseAsString.withDefault(""), - netTotal: parseAsString.withDefault(""), - - // 변경 사유 및 승인 상태 - changeReason: parseAsString.withDefault(""), - approvalStatus: parseAsString.withDefault(""), - - // 고급 필터(Advanced) & 검색 - filters: getFiltersStateParser().withDefault([]), - joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"), - search: parseAsString.withDefault(""), -}) - -// 최종 타입 -export type GetChangeOrderSchema = Awaited<ReturnType<typeof searchParamsCache.parse>>
\ No newline at end of file |
