From d0d2eaa2de58a0c33e9a21604b126961403cd69e Mon Sep 17 00:00:00 2001 From: dujinkim Date: Wed, 14 May 2025 06:12:13 +0000 Subject: (최겸) 기술영업 조선, 해양Top, 해양 Hull 아이템 리스트 개발(CRUD, excel import/export/template) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/items-ship/repository.ts | 125 --- lib/items-ship/service.ts | 416 ------- lib/items-ship/table/Items-ship-table.tsx | 141 --- lib/items-ship/table/add-items-dialog.tsx | 211 ---- lib/items-ship/table/delete-items-dialog.tsx | 154 --- lib/items-ship/table/feature-flags.tsx | 96 -- lib/items-ship/table/import-excel-button.tsx | 263 ----- lib/items-ship/table/import-item-handler.tsx | 145 --- lib/items-ship/table/item-excel-template.tsx | 122 --- lib/items-ship/table/items-ship-table-columns.tsx | 244 ----- .../table/items-table-toolbar-actions.tsx | 177 --- lib/items-ship/table/update-items-sheet.tsx | 202 ---- lib/items-ship/validations.ts | 88 -- lib/items-tech/repository.ts | 125 +++ lib/items-tech/service.ts | 1139 ++++++++++++++++++++ lib/items-tech/table/add-items-dialog.tsx | 400 +++++++ lib/items-tech/table/delete-items-dialog.tsx | 194 ++++ lib/items-tech/table/feature-flags.tsx | 96 ++ lib/items-tech/table/hull/import-item-handler.tsx | 143 +++ lib/items-tech/table/hull/item-excel-template.tsx | 125 +++ .../table/hull/offshore-hull-table-columns.tsx | 282 +++++ .../hull/offshore-hull-table-toolbar-actions.tsx | 184 ++++ lib/items-tech/table/hull/offshore-hull-table.tsx | 152 +++ lib/items-tech/table/import-excel-button.tsx | 298 +++++ lib/items-tech/table/ship/Items-ship-table.tsx | 146 +++ lib/items-tech/table/ship/import-item-handler.tsx | 145 +++ lib/items-tech/table/ship/item-excel-template.tsx | 122 +++ .../table/ship/items-ship-table-columns.tsx | 244 +++++ .../table/ship/items-table-toolbar-actions.tsx | 178 +++ lib/items-tech/table/top/import-item-handler.tsx | 143 +++ lib/items-tech/table/top/item-excel-template.tsx | 125 +++ .../table/top/offshore-top-table-columns.tsx | 282 +++++ .../top/offshore-top-table-toolbar-actions.tsx | 184 ++++ lib/items-tech/table/top/offshore-top-table.tsx | 153 +++ lib/items-tech/table/update-items-sheet.tsx | 390 +++++++ lib/items-tech/validations.ts | 156 +++ 36 files changed, 5406 insertions(+), 2384 deletions(-) delete mode 100644 lib/items-ship/repository.ts delete mode 100644 lib/items-ship/service.ts delete mode 100644 lib/items-ship/table/Items-ship-table.tsx delete mode 100644 lib/items-ship/table/add-items-dialog.tsx delete mode 100644 lib/items-ship/table/delete-items-dialog.tsx delete mode 100644 lib/items-ship/table/feature-flags.tsx delete mode 100644 lib/items-ship/table/import-excel-button.tsx delete mode 100644 lib/items-ship/table/import-item-handler.tsx delete mode 100644 lib/items-ship/table/item-excel-template.tsx delete mode 100644 lib/items-ship/table/items-ship-table-columns.tsx delete mode 100644 lib/items-ship/table/items-table-toolbar-actions.tsx delete mode 100644 lib/items-ship/table/update-items-sheet.tsx delete mode 100644 lib/items-ship/validations.ts create mode 100644 lib/items-tech/repository.ts create mode 100644 lib/items-tech/service.ts create mode 100644 lib/items-tech/table/add-items-dialog.tsx create mode 100644 lib/items-tech/table/delete-items-dialog.tsx create mode 100644 lib/items-tech/table/feature-flags.tsx create mode 100644 lib/items-tech/table/hull/import-item-handler.tsx create mode 100644 lib/items-tech/table/hull/item-excel-template.tsx create mode 100644 lib/items-tech/table/hull/offshore-hull-table-columns.tsx create mode 100644 lib/items-tech/table/hull/offshore-hull-table-toolbar-actions.tsx create mode 100644 lib/items-tech/table/hull/offshore-hull-table.tsx create mode 100644 lib/items-tech/table/import-excel-button.tsx create mode 100644 lib/items-tech/table/ship/Items-ship-table.tsx create mode 100644 lib/items-tech/table/ship/import-item-handler.tsx create mode 100644 lib/items-tech/table/ship/item-excel-template.tsx create mode 100644 lib/items-tech/table/ship/items-ship-table-columns.tsx create mode 100644 lib/items-tech/table/ship/items-table-toolbar-actions.tsx create mode 100644 lib/items-tech/table/top/import-item-handler.tsx create mode 100644 lib/items-tech/table/top/item-excel-template.tsx create mode 100644 lib/items-tech/table/top/offshore-top-table-columns.tsx create mode 100644 lib/items-tech/table/top/offshore-top-table-toolbar-actions.tsx create mode 100644 lib/items-tech/table/top/offshore-top-table.tsx create mode 100644 lib/items-tech/table/update-items-sheet.tsx create mode 100644 lib/items-tech/validations.ts (limited to 'lib') diff --git a/lib/items-ship/repository.ts b/lib/items-ship/repository.ts deleted file mode 100644 index 550e6b1d..00000000 --- a/lib/items-ship/repository.ts +++ /dev/null @@ -1,125 +0,0 @@ -// src/lib/items/repository.ts -import db from "@/db/db"; -import { Item, items } from "@/db/schema/items"; -import { - eq, - inArray, - not, - asc, - desc, - and, - ilike, - gte, - lte, - count, - gt, -} from "drizzle-orm"; -import { PgTransaction } from "drizzle-orm/pg-core"; -export type NewItem = typeof items.$inferInsert - -/** - * 단건/복수 조회 시 공통으로 사용 가능한 SELECT 함수 예시 - * - 트랜잭션(tx)을 받아서 사용하도록 구현 - */ -export async function selectItems( - tx: PgTransaction, - params: { - where?: any; // drizzle-orm의 조건식 (and, eq...) 등 - orderBy?: (ReturnType | ReturnType)[]; - offset?: number; - limit?: number; - } -) { - const { where, orderBy, offset = 0, limit = 10 } = params; - - return tx - .select() - .from(items) - .where(where) - .orderBy(...(orderBy ?? [])) - .offset(offset) - .limit(limit); -} -/** 총 개수 count */ -export async function countItems( - tx: PgTransaction, - where?: any -) { - const res = await tx.select({ count: count() }).from(items).where(where); - return res[0]?.count ?? 0; -} - -/** 단건 Insert 예시 */ -export async function insertItem( - tx: PgTransaction, - data: NewItem // DB와 동일한 insert 가능한 타입 -) { - // returning() 사용 시 배열로 돌아오므로 [0]만 리턴 - return tx - .insert(items) - .values(data) - .returning({ id: items.id, createdAt: items.createdAt }); -} - -/** 복수 Insert 예시 */ -export async function insertItems( - tx: PgTransaction, - data: Item[] -) { - return tx.insert(items).values(data).onConflictDoNothing(); -} - - - -/** 단건 삭제 */ -export async function deleteItemById( - tx: PgTransaction, - itemId: number -) { - return tx.delete(items).where(eq(items.id, itemId)); -} - -/** 복수 삭제 */ -export async function deleteItemsByIds( - tx: PgTransaction, - ids: number[] -) { - return tx.delete(items).where(inArray(items.id, ids)); -} - -/** 전체 삭제 */ -export async function deleteAllItems( - tx: PgTransaction, -) { - return tx.delete(items); -} - -/** 단건 업데이트 */ -export async function updateItem( - tx: PgTransaction, - itemId: number, - data: Partial -) { - return tx - .update(items) - .set(data) - .where(eq(items.id, itemId)) - .returning({ id: items.id, createdAt: items.createdAt }); -} - -/** 복수 업데이트 */ -export async function updateItems( - tx: PgTransaction, - ids: number[], - data: Partial -) { - return tx - .update(items) - .set(data) - .where(inArray(items.id, ids)) - .returning({ id: items.id, createdAt: items.createdAt }); -} - -export async function findAllItems(): Promise { - return db.select().from(items).orderBy(asc(items.itemCode)); -} diff --git a/lib/items-ship/service.ts b/lib/items-ship/service.ts deleted file mode 100644 index 37b623c1..00000000 --- a/lib/items-ship/service.ts +++ /dev/null @@ -1,416 +0,0 @@ -// src/lib/items-ship/service.ts -"use server"; // Next.js 서버 액션에서 직접 import하려면 (선택) - -import { revalidateTag, unstable_noStore } from "next/cache"; -import db from "@/db/db"; - -import { filterColumns } from "@/lib/filter-columns"; -import { unstable_cache } from "@/lib/unstable-cache"; -import { getErrorMessage } from "@/lib/handle-error"; - -import { asc, desc, ilike, and, or, eq, count, inArray, sql } from "drizzle-orm"; -import { GetItemsSchema, UpdateItemSchema, ShipbuildingItemCreateData, TypedItemCreateData } from "./validations"; -import { Item, items, itemShipbuilding } from "@/db/schema/items"; -import { deleteItemById, deleteItemsByIds, findAllItems, insertItem, updateItem } from "./repository"; - -/* ----------------------------------------------------- - 1) 조회 관련 ------------------------------------------------------ */ - -/** - * 복잡한 조건으로 Item 목록을 조회 (+ pagination) 하고, - * 총 개수에 따라 pageCount를 계산해서 리턴. - * Next.js의 unstable_cache를 사용해 일정 시간 캐시. - */ -export async function getShipbuildingItems(input: GetItemsSchema) { - return unstable_cache( - async () => { - try { - const offset = (input.page - 1) * input.perPage; - - // advancedTable 모드면 filterColumns()로 where 절 구성 - const advancedWhere = filterColumns({ - table: items, - filters: input.filters, - joinOperator: input.joinOperator, - }); - - let globalWhere; - if (input.search) { - const s = `%${input.search}%`; - globalWhere = or( - ilike(items.itemCode, s), - ilike(items.itemName, s), - ilike(items.description, s) - ); - } - - const finalWhere = and( - advancedWhere, - globalWhere - ); - - const where = finalWhere; - - const orderBy = - input.sort.length > 0 - ? input.sort.map((item) => - item.desc ? desc(items[item.id]) : asc(items[item.id]) - ) - : [asc(items.createdAt)]; - - // 조선 아이템 테이블과 기본 아이템 테이블 조인하여 조회 - const result = await db.select({ - id: itemShipbuilding.id, - itemId: itemShipbuilding.itemId, - workType: itemShipbuilding.workType, - shipTypes: itemShipbuilding.shipTypes, - itemCode: items.itemCode, - itemName: items.itemName, - description: items.description, - createdAt: items.createdAt, - updatedAt: items.updatedAt, - }) - .from(itemShipbuilding) - .innerJoin(items, eq(itemShipbuilding.itemId, items.id)) - .where(where) - .orderBy(...orderBy) - .offset(offset) - .limit(input.perPage); - - // 전체 데이터 개수 조회 - const [{ count: total }] = await db.select({ - count: count() - }) - .from(itemShipbuilding) - .innerJoin(items, eq(itemShipbuilding.itemId, items.id)) - .where(where); - - const pageCount = Math.ceil(Number(total) / input.perPage); - - return { data: result, pageCount }; - } catch (err) { - console.error("Error fetching shipbuilding items:", err); - return { data: [], pageCount: 0 }; - } - }, - [JSON.stringify(input)], - { - revalidate: 3600, - tags: ["items"], - } - )(); -} - -/* ----------------------------------------------------- - 2) 생성(Create) ------------------------------------------------------ */ - -/** - * Item 생성 - 아이템 타입에 따라 해당 테이블에 데이터 삽입 - */ -export async function createShipbuildingItem(input: TypedItemCreateData) { - unstable_noStore() - - try { - if (!input.itemCode || !input.itemName) { - return { - success: false, - message: "아이템 코드와 아이템 명은 필수입니다", - data: null, - error: "필수 필드 누락" - } - } - - let result: unknown[] = [] - - result = await db.transaction(async (tx) => { - const existingItem = await tx.query.items.findFirst({ - where: eq(items.itemCode, input.itemCode), - }) - - let itemId: number - let itemResult - - if (existingItem) { - itemResult = await updateItem(tx, existingItem.id, { - itemName: input.itemName, - description: input.description, - }) - itemId = existingItem.id - } else { - itemResult = await insertItem(tx, { - itemCode: input.itemCode, - itemName: input.itemName, - description: input.description, - }) - itemId = itemResult[0].id - } - - const shipData = input as ShipbuildingItemCreateData; - const typeResult = await tx.insert(itemShipbuilding).values({ - itemId: itemId, - workType: shipData.workType ? (shipData.workType as '기장' | '전장' | '선실' | '배관' | '철의') : '기장', - shipTypes: shipData.shipTypes || '' - }).returning(); - - return [...itemResult, ...typeResult] - }) - - revalidateTag("items") - - return { - success: true, - data: result[0] || null, - error: null - } - } catch (err) { - console.error("아이템 생성/업데이트 오류:", err) - - if (err instanceof Error && err.message.includes("unique constraint")) { - return { - success: false, - message: "이미 존재하는 아이템 코드입니다", - data: null, - error: "중복 키 오류" - } - } - - return { - success: false, - message: getErrorMessage(err), - data: null, - error: getErrorMessage(err) - } - } -} - -/** - * Excel import를 위한 조선 아이템 생성 함수 - * 하나의 아이템 코드에 대해 여러 선종을 처리 (1:N 관계) - */ -export async function createShipbuildingImportItem(input: { - itemCode: string; - itemName: string; - workType: '기장' | '전장' | '선실' | '배관' | '철의'; - description?: string | null; - shipTypes: Record; -}) { - unstable_noStore(); - - try { - - if (!input.itemCode || !input.itemName) { - return { - success: false, - message: "아이템 코드와 아이템 명은 필수입니다", - data: null, - error: "필수 필드 누락" - } - } - let results: any[] = [] - results = await db.transaction(async (tx) => { - // 1. itemCode 정규화해서 직접 쿼리 - const existRows = await tx.select().from(items) - .where(eq(items.itemCode, input.itemCode)); - const existingItem = existRows[0]; - - console.log('DB에서 직접 조회한 기존 아이템:', existingItem); - - let itemId: number; - - if (existingItem) { - // 이미 있으면 업데이트 - await updateItem(tx, existingItem.id, { - itemName: input.itemName, - description: input.description, - }); - itemId = existingItem.id; - console.log('기존 아이템 업데이트, id:', itemId); - } else { - // 없으면 새로 생성 - // 현재 가장 큰 ID 값 가져오기 - const maxIdResult = await tx.select({ maxId: sql`MAX(id)` }).from(items); - const maxId = maxIdResult[0]?.maxId || 0; - const newId = Number(maxId) + 1; - console.log('새 아이템 생성을 위한 ID 계산:', { maxId, newId }); - - // 새 ID로 아이템 생성 - const insertResult = await tx.insert(items).values({ - id: newId, - itemCode: input.itemCode, - itemName: input.itemName, - description: input.description, - }).returning(); - - itemId = insertResult[0].id; - console.log('새 아이템 생성 완료, id:', itemId); - } - - const createdItems = []; - for (const shipType of Object.keys(input.shipTypes)) { - // 그대로 선종명 string으로 저장 - const existShip = await tx.select().from(itemShipbuilding) - .where( - and( - eq(itemShipbuilding.itemId, itemId), - eq(itemShipbuilding.shipTypes, shipType) - ) - ); - if (!existShip[0]) { - const shipbuildingResult = await tx.insert(itemShipbuilding).values({ - itemId: itemId, - workType: input.workType, - shipTypes: shipType - }).returning(); - createdItems.push({ - ...shipbuildingResult[0] - }); - console.log('조선아이템 생성:', shipType, shipbuildingResult[0]); - } else { - console.log('이미 존재하는 조선아이템:', shipType); - } - } - return createdItems; - }); - - revalidateTag("items"); - - return { - success: true, - data: results, - error: null - } - } catch (err) { - // DB에 실제로 존재하는 itemCode 목록도 함께 출력 - const allCodes = await db.select({ code: items.itemCode }).from(items); - console.error("아이템 import 오류:", err); - console.error("DB에 존재하는 모든 itemCode:", allCodes.map(x => x.code)); - return { - success: false, - message: getErrorMessage(err), - data: null, - error: getErrorMessage(err) - } - } -} - -/* ----------------------------------------------------- - 3) 업데이트 ------------------------------------------------------ */ - -// 업데이트 타입 정의 인터페이스 -interface UpdateShipbuildingItemInput extends UpdateItemSchema { - id: number; - workType?: string; - shipTypes?: string; - itemCode?: string; - itemName?: string; - description?: string; -} - -/** 단건 업데이트 */ -export async function modifyShipbuildingItem(input: UpdateShipbuildingItemInput) { - unstable_noStore(); - try { - await db.transaction(async (tx) => { - // 기본 아이템 테이블 업데이트 - const [item] = await updateItem(tx, input.id, { - itemCode: input.itemCode, - itemName: input.itemName, - description: input.description, - }); - - // 조선 아이템 테이블 업데이트 - if (input.workType || input.shipTypes) { - await tx.update(itemShipbuilding) - .set({ - workType: input.workType as '기장' | '전장' | '선실' | '배관' | '철의', - shipTypes: input.shipTypes - }) - .where(eq(itemShipbuilding.itemId, item.id)); - } - - return item; - }); - - revalidateTag("items"); - return { data: null, error: null }; - } catch (err) { - return { data: null, error: getErrorMessage(err) }; - } -} - -/* ----------------------------------------------------- - 4) 삭제 ------------------------------------------------------ */ - -// 삭제 타입 정의 인터페이스 -interface DeleteItemInput { - id: number; -} - -interface DeleteItemsInput { - ids: number[]; -} - -/** 단건 삭제 */ -export async function removeShipbuildingItem(input: DeleteItemInput) { - unstable_noStore(); - try { - await db.transaction(async (tx) => { - const item = await tx.query.items.findFirst({ - where: eq(items.id, input.id), - }); - - if (!item) { - throw new Error("아이템을 찾을 수 없습니다."); - } - - // 조선 아이템 테이블에서 먼저 삭제 - await tx.delete(itemShipbuilding) - .where(eq(itemShipbuilding.itemId, input.id)); - - // 기본 아이템 테이블에서 삭제 - await deleteItemById(tx, input.id); - }); - - revalidateTag("items"); - - return { data: null, error: null }; - } catch (err) { - return { data: null, error: getErrorMessage(err) }; - } -} - -/** 복수 삭제 */ -export async function removeShipbuildingItems(input: DeleteItemsInput) { - unstable_noStore(); - try { - await db.transaction(async (tx) => { - if (input.ids.length > 0) { - // 조선 아이템 테이블에서 먼저 삭제 - await tx.delete(itemShipbuilding) - .where(inArray(itemShipbuilding.itemId, input.ids)); - - // 기본 아이템 테이블에서 삭제 - await deleteItemsByIds(tx, input.ids); - } - }); - - revalidateTag("items"); - - return { data: null, error: null, success: true, message: "아이템이 성공적으로 삭제되었습니다." }; - } catch (err) { - return { data: null, error: getErrorMessage(err), success: false, message: "아이템 삭제 중 오류가 발생했습니다." }; - } -} - -export async function getAllShipbuildingItems(): Promise { - try { - return await findAllItems(); - } catch (error) { - console.error("Failed to get items:", error); - throw new Error("Failed to get items"); - } -} diff --git a/lib/items-ship/table/Items-ship-table.tsx b/lib/items-ship/table/Items-ship-table.tsx deleted file mode 100644 index 6fec9ac0..00000000 --- a/lib/items-ship/table/Items-ship-table.tsx +++ /dev/null @@ -1,141 +0,0 @@ -"use client" - -import * as React from "react" -import type { - DataTableFilterField, - DataTableAdvancedFilterField, - 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 { getShipbuildingItems } from "../service" -import { getShipbuildingColumns } from "./items-ship-table-columns" -import { ItemsTableToolbarActions } from "./items-table-toolbar-actions" -import { DeleteItemsDialog } from "./delete-items-dialog" -import { UpdateItemSheet } from "./update-items-sheet" - -// 서비스에서 반환하는 데이터 타입 정의 -type ShipbuildingItem = { - id: number; - itemId: number; - workType: "기장" | "전장" | "선실" | "배관" | "철의"; - shipTypes: string; - itemCode: string; - itemName: string; - description: string | null; - createdAt: Date; - updatedAt: Date; -} - -interface ItemsTableProps { - promises: Promise>> -} - -export function ItemsShipTable({ promises }: ItemsTableProps) { - const { data, pageCount } = React.use(promises) - - // 아이템 타입에 따른 행 액션 상태 관리 - const [rowAction, setRowAction] = React.useState | null>(null) - const columns = getShipbuildingColumns({ setRowAction }) - const filterFields: DataTableFilterField[] = [ - { - id: "itemCode", - label: "Item Code", - }, - { - id: "itemName", - label: "Item Name", - }, - { - id: "workType", - label: "기능(공종)", - }, - { - id: "shipTypes", - label: "선종", - }, - ] - - /** - * Advanced filter fields for the data table. - * These fields provide more complex filtering options compared to the regular filterFields. - * - * Key differences from regular filterFields: - * 1. More field types: Includes 'text', 'multi-select', 'date', and 'boolean'. - * 2. Enhanced flexibility: Allows for more precise and varied filtering options. - * 3. Used with DataTableAdvancedToolbar: Enables a more sophisticated filtering UI. - * 4. Date and boolean types: Adds support for filtering by date ranges and boolean values. - */ - - const advancedFilterFields: DataTableAdvancedFilterField[] = [ - { - id: "itemCode", - label: "Item Code", - type: "text", - }, - { - id: "itemName", - label: "Item Name", - type: "text", - }, - { - id: "description", - label: "Description", - type: "text", - }, - { - id: "workType", - label: "기능(공종)", - type: "text", - }, - { - id: "shipTypes", - label: "선종", - type: "text", - }, - ] - - 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 ( - <> - - - - - - {/* setRowAction(null)} - item={rowAction?.row.original as ShipbuildingItem} - /> */} - setRowAction(null)} - items={rowAction?.row.original ? [rowAction?.row.original] : []} - showTrigger={false} - onSuccess={() => rowAction?.row.toggleSelected(false)} - /> - - ) -} diff --git a/lib/items-ship/table/add-items-dialog.tsx b/lib/items-ship/table/add-items-dialog.tsx deleted file mode 100644 index fdfe54cc..00000000 --- a/lib/items-ship/table/add-items-dialog.tsx +++ /dev/null @@ -1,211 +0,0 @@ -"use client" - -import * as React from "react" -import { useForm } from "react-hook-form" -import { zodResolver } from "@hookform/resolvers/zod" -import { z } from "zod" - -import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from "@/components/ui/dialog" -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Textarea } from "@/components/ui/textarea" -// react-hook-form + shadcn/ui Form -import { - Form, - FormControl, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form" - -import { - createShipbuildingItemSchema, - -} from "../validations" -import { createShipbuildingItem } from "../service" -import { Plus } from "lucide-react" -import { toast } from "sonner" -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select" - -const workTypes = [ - { value: "기장", label: "기장" }, - { value: "전장", label: "전장" }, - { value: "선실", label: "선실" }, - { value: "배관", label: "배관" }, - { value: "철의", label: "철의" }, -] as const - -export function AddItemDialog() { - const [open, setOpen] = React.useState(false) - const [isSubmitting, setIsSubmitting] = React.useState(false) - - // 아이템 타입에 따라 다른 폼과 스키마 사용 - const getFormAndSchema = () => { - return { - schema: createShipbuildingItemSchema, - defaultValues: { - itemCode: "", - itemName: "", - description: "", - workType: "", - shipTypes: "", - } - }; - }; - - const { schema, defaultValues } = getFormAndSchema(); - - // 타입 안전성을 위해 구체적인 타입 사용 - type ItemFormSchema = - | z.infer - - - const form = useForm({ - resolver: zodResolver(schema), - defaultValues, - }); - - async function onSubmit(values: ItemFormSchema) { - try { - setIsSubmitting(true) - - // 타입에 따라 다른 로직 추가 가능 - const result = await createShipbuildingItem(values) - - if (result.success) { - toast.success("아이템이 추가되었습니다.") - form.reset() - setOpen(false) - } else { - toast.error(result.message || "아이템 추가 실패") - } - } catch (error) { - toast.error("오류가 발생했습니다.") - console.error(error) - } finally { - setIsSubmitting(false) - } - } - - - - return ( - - - - - - - 아이템 추가 - - {'조선 아이템을 추가합니다. 아이템 코드, 이름, 설명을 입력하세요.'} - - -
-
- - ( - - 아이템 코드 - - - - - - )} - /> - ( - - 아이템 이름 - - - - - - )} - /> - ( - - 설명 - -