summaryrefslogtreecommitdiff
path: root/lib/docu-list-rule/document-class
diff options
context:
space:
mode:
Diffstat (limited to 'lib/docu-list-rule/document-class')
-rw-r--r--lib/docu-list-rule/document-class/service.ts61
-rw-r--r--lib/docu-list-rule/document-class/table/delete-document-class-dialog.tsx2
-rw-r--r--lib/docu-list-rule/document-class/table/delete-document-class-option-dialog.tsx2
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx76
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-edit-sheet.tsx2
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-option-edit-sheet.tsx2
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-table-columns.tsx14
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-table-toolbar.tsx6
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-table.tsx24
9 files changed, 153 insertions, 36 deletions
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<number>`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<typeof Dialog> {
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<typeof Dialog> {
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<Array<{ id: number; code: string; name: string }>>([])
const form = useForm<CreateDocumentClassSchema>({
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,16 +131,41 @@ export function DocumentClassAddDialog({
</DialogTrigger>
<DialogContent className="sm:max-w-[425px]">
<DialogHeader>
- <DialogTitle>Document Class 추가</DialogTitle>
- <DialogDescription>
- 새로운 Document Class를 추가합니다.
- <span className="text-red-500 mt-1 block text-sm">* 표시된 항목은 필수 입력사항입니다.</span>
- </DialogDescription>
+ <DialogTitle>Document Class 추가</DialogTitle>
+ <DialogDescription>
+ 새로운 Document Class를 추가합니다.
+ <span className="text-red-500 mt-1 block text-sm">* 표시된 항목은 필수 입력사항입니다.</span>
+ </DialogDescription>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
+ name="projectId"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>프로젝트 *</FormLabel>
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
+ <FormControl>
+ <SelectTrigger>
+ <SelectValue placeholder="프로젝트를 선택하세요" />
+ </SelectTrigger>
+ </FormControl>
+ <SelectContent>
+ {projects.map((project) => (
+ <SelectItem key={project.id} value={project.id.toString()}>
+ {project.code} - {project.name}
+ </SelectItem>
+ ))}
+ </SelectContent>
+ </Select>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+
+ <FormField
+ control={form.control}
name="value"
render={({ field }) => (
<FormItem>
diff --git a/lib/docu-list-rule/document-class/table/document-class-edit-sheet.tsx b/lib/docu-list-rule/document-class/table/document-class-edit-sheet.tsx
index 5ad23b22..32c1976d 100644
--- a/lib/docu-list-rule/document-class/table/document-class-edit-sheet.tsx
+++ b/lib/docu-list-rule/document-class/table/document-class-edit-sheet.tsx
@@ -28,7 +28,7 @@ import {
import { Input } from "@/components/ui/input"
import { updateDocumentClassCodeGroup } from "@/lib/docu-list-rule/document-class/service"
-import { documentClasses } from "@/db/schema"
+import { documentClasses } from "@/db/schema/docu-list-rule"
const updateDocumentClassSchema = z.object({
value: z.string().min(1, "Value는 필수입니다."),
diff --git a/lib/docu-list-rule/document-class/table/document-class-option-edit-sheet.tsx b/lib/docu-list-rule/document-class/table/document-class-option-edit-sheet.tsx
index bc2318c6..8444285e 100644
--- a/lib/docu-list-rule/document-class/table/document-class-option-edit-sheet.tsx
+++ b/lib/docu-list-rule/document-class/table/document-class-option-edit-sheet.tsx
@@ -26,7 +26,7 @@ import {
import { Input } from "@/components/ui/input"
import { updateDocumentClassOption } from "@/lib/docu-list-rule/document-class/service"
-import { documentClassOptions } from "@/db/schema"
+import { documentClassOptions } from "@/db/schema/docu-list-rule"
const updateOptionSchema = z.object({
optionCode: z.string().min(1, "코드는 필수입니다."),
diff --git a/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx b/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx
index 8c391def..ad8494c7 100644
--- a/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx
+++ b/lib/docu-list-rule/document-class/table/document-class-table-columns.tsx
@@ -107,6 +107,20 @@ export function getColumns({ setRowAction, onDetail }: GetColumnsProps): ColumnD
// ----------------------------------------------------------------
const dataColumns: ColumnDef<typeof documentClasses.$inferSelect>[] = [
{
+ accessorKey: "projectCode",
+ enableResizing: true,
+ enableColumnFilter: true,
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" />
+ ),
+ meta: {
+ excelHeader: "프로젝트 코드",
+ type: "text",
+ },
+ cell: ({ row }) => row.getValue("projectCode") ?? "",
+ minSize: 120
+ },
+ {
accessorKey: "code",
enableResizing: true,
header: ({ column }) => (
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<typeof documentClasses.$inferSelect>
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 (
<>
<DataTable table={table}>