summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/[lng]/evcp/poa/page.tsx61
-rw-r--r--config/poaColumnsConfig.ts131
-rw-r--r--db/migrations/0097_poa_initial_setup.sql95
-rw-r--r--db/schema/contract.ts104
-rw-r--r--db/seeds_2/poaSeed.ts109
-rw-r--r--lib/poa/service.ts132
-rw-r--r--lib/poa/table/poa-table-columns.tsx165
-rw-r--r--lib/poa/table/poa-table-toolbar-actions.tsx45
-rw-r--r--lib/poa/table/poa-table.tsx189
-rw-r--r--lib/poa/validations.ts66
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