From 67bb1ad7d7e001e19c8d1dd9153a5f663e2afa03 Mon Sep 17 00:00:00 2001 From: 0-Zz-ang Date: Thu, 7 Aug 2025 18:02:54 +0900 Subject: (박서영)docu-list-rule Project_code적용 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/docu-list-rule/document-class/service.ts | 61 +++++++++++------ .../table/delete-document-class-dialog.tsx | 2 +- .../table/delete-document-class-option-dialog.tsx | 2 +- .../table/document-class-add-dialog.tsx | 76 ++++++++++++++++++++-- .../table/document-class-edit-sheet.tsx | 2 +- .../table/document-class-option-edit-sheet.tsx | 2 +- .../table/document-class-table-columns.tsx | 14 ++++ .../table/document-class-table-toolbar.tsx | 6 +- .../document-class/table/document-class-table.tsx | 24 ++++++- 9 files changed, 153 insertions(+), 36 deletions(-) (limited to 'lib/docu-list-rule/document-class') diff --git a/lib/docu-list-rule/document-class/service.ts b/lib/docu-list-rule/document-class/service.ts index a1bb14a7..91a4e053 100644 --- a/lib/docu-list-rule/document-class/service.ts +++ b/lib/docu-list-rule/document-class/service.ts @@ -3,7 +3,8 @@ import { revalidatePath } from "next/cache" import db from "@/db/db" import { documentClasses, documentClassOptions, codeGroups } from "@/db/schema/docu-list-rule" -import { eq, desc, asc, sql, and } from "drizzle-orm" +import { projects } from "@/db/schema/projects" +import { eq, desc, sql, and } from "drizzle-orm" // Document Class 목록 조회 (A Class, B Class 등) export async function getDocumentClassCodeGroups(input: { @@ -31,7 +32,8 @@ export async function getDocumentClassCodeGroups(input: { whereConditions = sql`${whereConditions} AND ( ${documentClasses.code} ILIKE ${searchTerm} OR ${documentClasses.value} ILIKE ${searchTerm} OR - ${documentClasses.description} ILIKE ${searchTerm} + ${documentClasses.description} ILIKE ${searchTerm} OR + ${projects.code} ILIKE ${searchTerm} )` } @@ -48,6 +50,8 @@ export async function getDocumentClassCodeGroups(input: { return sql`${documentClasses.value} ILIKE ${`%${value}%`}` case "description": return sql`${documentClasses.description} ILIKE ${`%${value}%`}` + case "projectCode": + return sql`${projects.code} ILIKE ${`%${value}%`}` case "isActive": return sql`${documentClasses.isActive} = ${value === "true"}` case "createdAt": @@ -73,14 +77,20 @@ export async function getDocumentClassCodeGroups(input: { if (sort && sort.length > 0) { const sortField = sort[0] // 안전성 체크: 필드가 실제 테이블에 존재하는지 확인 - if (sortField && sortField.id && typeof sortField.id === "string" && sortField.id in documentClasses) { + if (sortField && sortField.id && typeof sortField.id === "string") { const direction = sortField.desc ? sql`DESC` : sql`ASC` - const col = documentClasses[sortField.id as keyof typeof documentClasses] - orderBy = sql`${col} ${direction}` + + // 프로젝트 코드 정렬 처리 + if (sortField.id === "projectCode") { + orderBy = sql`${projects.code} ${direction}` + } else if (sortField.id in documentClasses) { + const col = documentClasses[sortField.id as keyof typeof documentClasses] + orderBy = sql`${col} ${direction}` + } } } - // 데이터 조회 + // 데이터 조회 (프로젝트 정보 포함) const data = await db .select({ id: documentClasses.id, @@ -90,32 +100,36 @@ export async function getDocumentClassCodeGroups(input: { isActive: documentClasses.isActive, createdAt: documentClasses.createdAt, updatedAt: documentClasses.updatedAt, + projectId: documentClasses.projectId, + projectCode: projects.code, + projectName: projects.name, }) .from(documentClasses) + .leftJoin(projects, eq(documentClasses.projectId, projects.id)) .where(whereConditions) .orderBy(orderBy) .limit(perPage) .offset(offset) - // 총 개수 조회 - const [{ count: total }] = await db - .select({ count: sql`count(*)` }) + // 총 개수 조회 (프로젝트 정보 포함) + const totalCountResult = await db + .select({ count: sql`count(*)` }) .from(documentClasses) + .leftJoin(projects, eq(documentClasses.projectId, projects.id)) .where(whereConditions) - const pageCount = Math.ceil(Number(total) / perPage) + const totalCount = totalCountResult[0]?.count || 0 return { - success: true, data, - pageCount, + totalCount, + pageCount: Math.ceil(totalCount / perPage), } } catch (error) { console.error("Error fetching document classes:", error) return { - success: false, - error: "Failed to fetch document classes", data: [], + totalCount: 0, pageCount: 0, } } @@ -123,14 +137,15 @@ export async function getDocumentClassCodeGroups(input: { // Document Class 생성 export async function createDocumentClassCodeGroup(input: { + projectId: number // projectCode를 projectId로 변경 value: string description?: string }) { try { // Value 자동 변환: "A", "AB", "A Class", "A CLASS" 등을 "A Class", "AB Class" 형태로 변환 - const formatValue = (value: string): string => { + const formatValue = (input: string): string => { // 공백 제거 및 대소문자 정규화 - const cleaned = value.trim().toLowerCase() + const cleaned = input.trim().toLowerCase() // "class"가 포함되어 있으면 제거 const withoutClass = cleaned.replace(/\s*class\s*/g, '') @@ -139,7 +154,7 @@ export async function createDocumentClassCodeGroup(input: { const letters = withoutClass.replace(/[^a-z0-9]/g, '') if (letters.length === 0) { - return value.trim() // 변환할 수 없으면 원본 반환 + return input.trim() // 변환할 수 없으면 원본 반환 } // 첫 글자를 대문자로 변환하고 "Class" 추가 @@ -148,10 +163,11 @@ export async function createDocumentClassCodeGroup(input: { const formattedValue = formatValue(input.value) - // 자동으로 code 생성 (예: "DOC_CLASS_001", "DOC_CLASS_002" 등) + // 해당 프로젝트의 자동으로 code 생성 (예: "DOC_CLASS_001", "DOC_CLASS_002" 등) const existingClasses = await db .select({ code: documentClasses.code }) .from(documentClasses) + .where(eq(documentClasses.projectId, input.projectId)) // projectId로 변경 .orderBy(desc(documentClasses.code)) let newCode = "DOC_CLASS_001" @@ -163,11 +179,14 @@ export async function createDocumentClassCodeGroup(input: { } } - // Code Group이 존재하는지 확인 + // 해당 프로젝트의 Code Group이 존재하는지 확인 const existingCodeGroup = await db .select({ id: codeGroups.id }) .from(codeGroups) - .where(eq(codeGroups.groupId, 'DOC_CLASS')) + .where(and( + eq(codeGroups.projectId, input.projectId), // projectId로 변경 + eq(codeGroups.groupId, 'DOC_CLASS') + )) .limit(1) let codeGroupId: number | null = null @@ -177,6 +196,7 @@ export async function createDocumentClassCodeGroup(input: { const [newCodeGroup] = await db .insert(codeGroups) .values({ + projectId: input.projectId, // projectId로 변경 groupId: 'DOC_CLASS', description: 'Document Class', codeFormat: 'DOC_CLASS_###', @@ -194,6 +214,7 @@ export async function createDocumentClassCodeGroup(input: { const [newDocumentClass] = await db .insert(documentClasses) .values({ + projectId: input.projectId, // projectId로 변경 code: newCode, value: formattedValue, description: input.description || "", diff --git a/lib/docu-list-rule/document-class/table/delete-document-class-dialog.tsx b/lib/docu-list-rule/document-class/table/delete-document-class-dialog.tsx index e81e4df6..08e73a36 100644 --- a/lib/docu-list-rule/document-class/table/delete-document-class-dialog.tsx +++ b/lib/docu-list-rule/document-class/table/delete-document-class-dialog.tsx @@ -29,7 +29,7 @@ import { } from "@/components/ui/drawer" import { deleteDocumentClassCodeGroup, getDocumentClassOptionsCount } from "@/lib/docu-list-rule/document-class/service" -import { documentClasses } from "@/db/schema" +import { documentClasses } from "@/db/schema/docu-list-rule" interface DeleteDocumentClassDialogProps extends React.ComponentPropsWithoutRef { diff --git a/lib/docu-list-rule/document-class/table/delete-document-class-option-dialog.tsx b/lib/docu-list-rule/document-class/table/delete-document-class-option-dialog.tsx index 34ce239f..4ac4eae0 100644 --- a/lib/docu-list-rule/document-class/table/delete-document-class-option-dialog.tsx +++ b/lib/docu-list-rule/document-class/table/delete-document-class-option-dialog.tsx @@ -28,7 +28,7 @@ import { } from "@/components/ui/drawer" import { deleteDocumentClassOption } from "@/lib/docu-list-rule/document-class/service" -import { documentClassOptions } from "@/db/schema" +import { documentClassOptions } from "@/db/schema/docu-list-rule" interface DeleteDocumentClassOptionDialogProps extends React.ComponentPropsWithoutRef { diff --git a/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx b/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx index ef9c50a8..dfd1d7f2 100644 --- a/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx +++ b/lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx @@ -26,11 +26,19 @@ import { FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" - +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select" import { createDocumentClassCodeGroup } from "@/lib/docu-list-rule/document-class/service" +import { getProjectLists } from "@/lib/projects/service" const createDocumentClassSchema = z.object({ + projectId: z.string().min(1, "프로젝트는 필수입니다."), value: z.string().min(1, "Value는 필수입니다."), description: z.string().optional(), }) @@ -46,20 +54,49 @@ export function DocumentClassAddDialog({ }: DocumentClassAddDialogProps) { const [open, setOpen] = React.useState(false) const [isPending, startTransition] = React.useTransition() + const [projects, setProjects] = React.useState>([]) const form = useForm({ resolver: zodResolver(createDocumentClassSchema), defaultValues: { + projectId: "", value: "", description: "", }, mode: "onChange" }) + // 프로젝트 목록 로드 + React.useEffect(() => { + if (open) { + const loadProjects = async () => { + try { + const result = await getProjectLists({ + page: 1, + perPage: 1000, + search: "", + sort: [], + filters: [], + joinOperator: "and", + flags: [] + }) + if (result.data) { + setProjects(result.data) + } + } catch (error) { + console.error("Failed to load projects:", error) + toast.error("프로젝트 목록을 불러오는데 실패했습니다.") + } + } + loadProjects() + } + }, [open]) + async function onSubmit(input: CreateDocumentClassSchema) { startTransition(async () => { try { - const result = await createDocumentClassCodeGroup({ + const result = await createDocumentClassCodeGroup({ + projectId: parseInt(input.projectId), value: input.value, description: input.description, }) @@ -94,14 +131,39 @@ export function DocumentClassAddDialog({ - Document Class 추가 - - 새로운 Document Class를 추가합니다. - * 표시된 항목은 필수 입력사항입니다. - + Document Class 추가 + + 새로운 Document Class를 추가합니다. + * 표시된 항목은 필수 입력사항입니다. +
+ ( + + 프로젝트 * + + + + )} + /> + [] = [ + { + accessorKey: "projectCode", + enableResizing: true, + enableColumnFilter: true, + header: ({ column }) => ( + + ), + meta: { + excelHeader: "프로젝트 코드", + type: "text", + }, + cell: ({ row }) => row.getValue("projectCode") ?? "", + minSize: 120 + }, { accessorKey: "code", enableResizing: true, diff --git a/lib/docu-list-rule/document-class/table/document-class-table-toolbar.tsx b/lib/docu-list-rule/document-class/table/document-class-table-toolbar.tsx index 9b43f43d..a9ab660a 100644 --- a/lib/docu-list-rule/document-class/table/document-class-table-toolbar.tsx +++ b/lib/docu-list-rule/document-class/table/document-class-table-toolbar.tsx @@ -3,9 +3,9 @@ import * as React from "react" import { type Table } from "@tanstack/react-table" -import { DeleteDocumentClassDialog } from "./delete-document-class-dialog" -import { DocumentClassAddDialog } from "./document-class-add-dialog" -import { documentClasses } from "@/db/schema" +import { DeleteDocumentClassDialog } from "@/lib/docu-list-rule/document-class/table/delete-document-class-dialog" +import { DocumentClassAddDialog } from "@/lib/docu-list-rule/document-class/table/document-class-add-dialog" +import { documentClasses } from "@/db/schema/docu-list-rule" interface DocumentClassTableToolbarActionsProps { table: Table diff --git a/lib/docu-list-rule/document-class/table/document-class-table.tsx b/lib/docu-list-rule/document-class/table/document-class-table.tsx index c66a1395..c9156ff7 100644 --- a/lib/docu-list-rule/document-class/table/document-class-table.tsx +++ b/lib/docu-list-rule/document-class/table/document-class-table.tsx @@ -46,9 +46,9 @@ export function DocumentClassTable({ promises }: DocumentClassTableProps) { ] const { table } = useDataTable({ - data: rawData[0].data as typeof documentClasses.$inferSelect[], + data: rawData[0]?.data as typeof documentClasses.$inferSelect[] || [], columns, - pageCount: rawData[0].pageCount, + pageCount: rawData[0]?.pageCount || 0, enablePinning: true, enableAdvancedFilter: true, initialState: { @@ -59,6 +59,26 @@ export function DocumentClassTable({ promises }: DocumentClassTableProps) { clearOnDefault: true, }) + + // 컴포넌트 마운트 후 그룹핑 설정 + React.useEffect(() => { + if (rawData[0]?.data && table.getState().grouping.length === 0) { + table.setGrouping(["projectCode"]) + } + }, [table, rawData]) + + // 정렬 시 펼쳐진 상태 유지 + React.useEffect(() => { + const currentExpanded = table.getState().expanded + if (Object.keys(currentExpanded).length > 0) { + // 약간의 지연 후 현재 펼쳐진 상태를 다시 설정 + const timer = setTimeout(() => { + table.setExpanded(currentExpanded) + }, 100) + return () => clearTimeout(timer) + } + }, [table.getState().sorting, table]) + return ( <> -- cgit v1.2.3