summaryrefslogtreecommitdiff
path: root/lib/docu-list-rule/document-class
diff options
context:
space:
mode:
author0-Zz-ang <s1998319@gmail.com>2025-08-25 09:23:30 +0900
committer0-Zz-ang <s1998319@gmail.com>2025-08-25 09:23:30 +0900
commitb12a06766e32e3c76544b1d12bec91653e1fe9db (patch)
tree57ca1ddff3342677d132e07b78fc03873a960255 /lib/docu-list-rule/document-class
parentd38877eef87917087a4a217bea32ae84d6738a7d (diff)
docu-list-rule페이지 수정
Diffstat (limited to 'lib/docu-list-rule/document-class')
-rw-r--r--lib/docu-list-rule/document-class/service.ts78
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-add-dialog.tsx73
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-options-detail-sheet.tsx69
-rw-r--r--lib/docu-list-rule/document-class/table/document-class-options-table-columns.tsx13
-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.tsx7
-rw-r--r--lib/docu-list-rule/document-class/validation.ts1
7 files changed, 141 insertions, 114 deletions
diff --git a/lib/docu-list-rule/document-class/service.ts b/lib/docu-list-rule/document-class/service.ts
index 2ec31ae6..378c3215 100644
--- a/lib/docu-list-rule/document-class/service.ts
+++ b/lib/docu-list-rule/document-class/service.ts
@@ -18,6 +18,7 @@ export async function getDocumentClassCodeGroups(input: {
classId?: string
description?: string
isActive?: string
+ projectId?: string
}) {
try {
const { page, perPage, sort, search, filters, joinOperator } = input
@@ -26,10 +27,15 @@ export async function getDocumentClassCodeGroups(input: {
// 기본 조건 (plant 타입 프로젝트만)
let whereConditions = sql`${documentClasses.isActive} = true AND ${projects.type} = 'plant'`
+ // 프로젝트 ID 필터링
+ if (input.projectId) {
+ whereConditions = sql`${whereConditions} AND ${documentClasses.projectId} = ${parseInt(input.projectId)}`
+ }
+
// 검색 조건
if (search) {
const searchTerm = `%${search}%`
- whereConditions = sql`${documentClasses.isActive} = true AND ${projects.type} = 'plant' AND (
+ whereConditions = sql`${whereConditions} AND (
${documentClasses.code} ILIKE ${searchTerm} OR
${documentClasses.value} ILIKE ${searchTerm} OR
${documentClasses.description} ILIKE ${searchTerm} OR
@@ -363,7 +369,7 @@ export async function getDocumentClassSubOptions(documentClassId: number, input?
}
// 정렬 (안전한 필드 체크 적용)
- let orderBy = sql`${documentClassOptions.optionCode} ASC`
+ let orderBy = sql`${documentClassOptions.sdq} ASC`
if (sort && sort.length > 0) {
const sortField = sort[0]
// 안전성 체크: 필드가 실제 테이블에 존재하는지 확인
@@ -380,6 +386,7 @@ export async function getDocumentClassSubOptions(documentClassId: number, input?
documentClassId: documentClassOptions.documentClassId,
description: documentClassOptions.description,
optionCode: documentClassOptions.optionCode,
+ sdq: documentClassOptions.sdq,
isActive: documentClassOptions.isActive,
createdAt: documentClassOptions.createdAt,
updatedAt: documentClassOptions.updatedAt,
@@ -448,12 +455,21 @@ export async function createDocumentClassOptionItem(input: {
}
}
+ // 해당 Document Class의 최대 sdq 값 찾기
+ const maxSdqResult = await db
+ .select({ maxSdq: sql<number>`COALESCE(MAX(${documentClassOptions.sdq}), 0)` })
+ .from(documentClassOptions)
+ .where(eq(documentClassOptions.documentClassId, input.documentClassId))
+
+ const nextSdq = (maxSdqResult[0]?.maxSdq || 0) + 1
+
const [newOption] = await db
.insert(documentClassOptions)
.values({
documentClassId: input.documentClassId,
description: userOptionCode, // 코드값을 description에도 자동 설정
optionCode: userOptionCode,
+ sdq: nextSdq,
isActive: true,
})
.returning({ id: documentClassOptions.id })
@@ -477,11 +493,10 @@ export async function createDocumentClassOptionItem(input: {
// Document Class 옵션 수정
export async function updateDocumentClassOption(input: {
id: number
- optionCode: string
+ optionCode?: string
+ sdq?: number
}) {
try {
- const userOptionCode = input.optionCode.toUpperCase().trim()
-
// 기존 옵션 조회하여 documentClassId 가져오기
const currentOption = await db
.select({ documentClassId: documentClassOptions.documentClassId })
@@ -496,32 +511,47 @@ export async function updateDocumentClassOption(input: {
}
}
- // 같은 Document Class 내에서 코드 중복 체크 (자신 제외)
- const existingOption = await db
- .select({ id: documentClassOptions.id })
- .from(documentClassOptions)
- .where(
- and(
- eq(documentClassOptions.documentClassId, currentOption[0].documentClassId),
- eq(documentClassOptions.optionCode, userOptionCode)
+ // optionCode가 제공된 경우에만 중복 체크 및 업데이트
+ if (input.optionCode) {
+ const userOptionCode = input.optionCode.toUpperCase().trim()
+
+ // 같은 Document Class 내에서 코드 중복 체크 (자신 제외)
+ const existingOption = await db
+ .select({ id: documentClassOptions.id })
+ .from(documentClassOptions)
+ .where(
+ and(
+ eq(documentClassOptions.documentClassId, currentOption[0].documentClassId),
+ eq(documentClassOptions.optionCode, userOptionCode)
+ )
)
- )
- .limit(1)
+ .limit(1)
- if (existingOption.length > 0 && existingOption[0].id !== input.id) {
- return {
- success: false,
- error: "이미 존재하는 코드입니다."
+ if (existingOption.length > 0 && existingOption[0].id !== input.id) {
+ return {
+ success: false,
+ error: "이미 존재하는 코드입니다."
+ }
}
}
+ // 업데이트할 데이터 준비
+ const updateData: any = {
+ updatedAt: new Date(),
+ }
+
+ if (input.optionCode) {
+ updateData.description = input.optionCode.toUpperCase().trim()
+ updateData.optionCode = input.optionCode.toUpperCase().trim()
+ }
+
+ if (input.sdq !== undefined) {
+ updateData.sdq = input.sdq
+ }
+
const [updatedOption] = await db
.update(documentClassOptions)
- .set({
- description: userOptionCode, // 코드값을 description에도 자동 설정
- optionCode: userOptionCode,
- updatedAt: new Date(),
- })
+ .set(updateData)
.where(eq(documentClassOptions.id, input.id))
.returning({ id: documentClassOptions.id })
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 a51b0598..e2cfc39e 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,19 +26,12 @@ 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"
+import { useParams } from "next/navigation"
const createDocumentClassSchema = z.object({
- projectId: z.string().min(1, "프로젝트는 필수입니다."),
value: z.string().min(1, "Value는 필수입니다."),
description: z.string().optional(),
})
@@ -52,56 +45,27 @@ interface DocumentClassAddDialogProps {
export function DocumentClassAddDialog({
onSuccess,
}: DocumentClassAddDialogProps) {
+ const params = useParams()
+ const projectId = Number(params?.projectId)
const [open, setOpen] = React.useState(false)
const [isPending, startTransition] = React.useTransition()
- const [projects, setProjects] = React.useState<Array<{ id: number; code: string; name: string; type: 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: [],
- code: "",
- name: "",
- type: ""
- })
- if (result.data) {
- // plant 타입의 프로젝트만 필터링
- const plantProjects = result.data.filter(project => project.type === 'plant')
- setProjects(plantProjects)
- }
- } 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({
- projectId: parseInt(input.projectId),
+ projectId: projectId,
value: input.value,
description: input.description,
})
@@ -144,30 +108,7 @@ export function DocumentClassAddDialog({
</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}
diff --git a/lib/docu-list-rule/document-class/table/document-class-options-detail-sheet.tsx b/lib/docu-list-rule/document-class/table/document-class-options-detail-sheet.tsx
index 50e79d89..07384dd6 100644
--- a/lib/docu-list-rule/document-class/table/document-class-options-detail-sheet.tsx
+++ b/lib/docu-list-rule/document-class/table/document-class-options-detail-sheet.tsx
@@ -4,17 +4,21 @@ import * as React from "react"
import { useReactTable, getCoreRowModel, getSortedRowModel, getFilteredRowModel, getPaginationRowModel } from "@tanstack/react-table"
import { DataTableDetail } from "@/components/data-table/data-table-detail"
import { DataTableAdvancedToolbarDetail } from "@/components/data-table/data-table-advanced-toolbar-detail"
+import { DragDropTable } from "@/lib/docu-list-rule/number-type-configs/table/drag-drop-table"
import type { DataTableAdvancedFilterField, DataTableRowAction } from "@/types/table"
import {
Sheet,
SheetContent,
} from "@/components/ui/sheet"
-import { getDocumentClassSubOptions } from "@/lib/docu-list-rule/document-class/service"
+import { getDocumentClassSubOptions, updateDocumentClassOption } from "@/lib/docu-list-rule/document-class/service"
import { getColumns } from "@/lib/docu-list-rule/document-class/table/document-class-options-table-columns"
import { DocumentClassOptionEditSheet } from "@/lib/docu-list-rule/document-class/table/document-class-option-edit-sheet"
import { DeleteDocumentClassOptionDialog } from "@/lib/docu-list-rule/document-class/table/delete-document-class-option-dialog"
import { DocumentClassOptionsTableToolbarActions } from "@/lib/docu-list-rule/document-class/table/document-class-options-table-toolbar"
import { documentClasses, documentClassOptions } from "@/db/schema/docu-list-rule"
+import { DragEndEvent } from '@dnd-kit/core'
+import { arrayMove } from '@dnd-kit/sortable'
+import { toast } from "sonner"
type DocumentClassOption = typeof documentClassOptions.$inferSelect
@@ -65,6 +69,7 @@ export function DocumentClassOptionsDetailSheet({
const result = await getDocumentClassSubOptions(documentClass.id, {
page: 1,
perPage: 10,
+ sort: [{ id: "sdq", desc: false }],
})
if (result.success && result.data) {
setRawData({
@@ -94,7 +99,7 @@ export function DocumentClassOptionsDetailSheet({
getFilteredRowModel: getFilteredRowModel(),
getPaginationRowModel: getPaginationRowModel(),
initialState: {
- sorting: [{ id: "optionCode", desc: false }],
+ sorting: [{ id: "sdq", desc: false }],
pagination: {
pageSize: 10,
},
@@ -102,6 +107,58 @@ export function DocumentClassOptionsDetailSheet({
getRowId: (originalRow) => String(originalRow.id),
})
+ // 드래그 종료 핸들러
+ const handleDragEnd = React.useCallback(async (event: DragEndEvent) => {
+ const { active, over } = event
+ console.log("Drag end event:", { active, over })
+
+ if (active.id !== over?.id) {
+ const oldIndex = rawData.data.findIndex((item) => String(item.id) === active.id)
+ const newIndex = rawData.data.findIndex((item) => String(item.id) === over?.id)
+ console.log("Indices:", { oldIndex, newIndex })
+
+ if (oldIndex !== -1 && newIndex !== -1) {
+ const reorderedData = arrayMove(rawData.data, oldIndex, newIndex)
+
+ // 새로운 순서로 sdq 값 업데이트
+ const updatedOptions = reorderedData.map((item, index) => ({
+ ...item,
+ sdq: index + 1
+ }))
+
+ // 로컬 상태 먼저 업데이트
+ setRawData(prev => ({ ...prev, data: updatedOptions }))
+
+ // 서버에 순서 업데이트 (Combo Box Settings와 같은 방식)
+ try {
+ // 모든 항목을 임시 값으로 먼저 업데이트
+ for (let i = 0; i < updatedOptions.length; i++) {
+ const option = updatedOptions[i]
+ await updateDocumentClassOption({
+ id: option.id,
+ sdq: -(i + 1), // 임시 음수 값
+ })
+ }
+
+ // 최종 순서로 업데이트
+ for (const option of updatedOptions) {
+ await updateDocumentClassOption({
+ id: option.id,
+ sdq: option.sdq,
+ })
+ }
+
+ toast.success("순서가 성공적으로 변경되었습니다.")
+ } catch (error) {
+ console.error("Error updating order:", error)
+ toast.error("순서 변경 중 오류가 발생했습니다.")
+ // 에러 시 원래 데이터로 복원
+ await refreshData()
+ }
+ }
+ }
+ }, [rawData.data, refreshData])
+
if (!documentClass) return null
return (
@@ -122,12 +179,16 @@ export function DocumentClassOptionsDetailSheet({
onSuccess={refreshData}
/>
- <DataTableDetail table={table}>
+ <DragDropTable
+ table={table}
+ data={rawData.data}
+ onDragEnd={handleDragEnd}
+ >
<DataTableAdvancedToolbarDetail
table={table}
filterFields={advancedFilterFields}
/>
- </DataTableDetail>
+ </DragDropTable>
<DeleteDocumentClassOptionDialog
open={rowAction?.type === "delete"}
diff --git a/lib/docu-list-rule/document-class/table/document-class-options-table-columns.tsx b/lib/docu-list-rule/document-class/table/document-class-options-table-columns.tsx
index c3bf440d..c8ee4676 100644
--- a/lib/docu-list-rule/document-class/table/document-class-options-table-columns.tsx
+++ b/lib/docu-list-rule/document-class/table/document-class-options-table-columns.tsx
@@ -101,6 +101,19 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<typeof
// ----------------------------------------------------------------
const dataColumns: ColumnDef<typeof documentClassOptions.$inferSelect>[] = [
{
+ accessorKey: "sdq",
+ enableResizing: true,
+ header: ({ column }) => (
+ <DataTableColumnHeaderSimple column={column} title="순서" />
+ ),
+ meta: {
+ excelHeader: "순서",
+ type: "number",
+ },
+ cell: ({ row }) => row.getValue("sdq") ?? "",
+ minSize: 50
+ },
+ {
accessorKey: "optionCode",
enableResizing: true,
header: ({ column }) => (
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 ad8494c7..8c391def 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,20 +107,6 @@ 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.tsx b/lib/docu-list-rule/document-class/table/document-class-table.tsx
index 03855fe1..11ec3a3c 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
@@ -59,12 +59,7 @@ export function DocumentClassTable({ promises }: DocumentClassTableProps) {
})
- // 컴포넌트 마운트 후 그룹핑 설정
- React.useEffect(() => {
- if (rawData[0]?.data && table.getState().grouping.length === 0) {
- table.setGrouping(["projectCode"])
- }
- }, [table, rawData])
+
// 정렬 시 펼쳐진 상태 유지
React.useEffect(() => {
diff --git a/lib/docu-list-rule/document-class/validation.ts b/lib/docu-list-rule/document-class/validation.ts
index 78f87484..b69b49ea 100644
--- a/lib/docu-list-rule/document-class/validation.ts
+++ b/lib/docu-list-rule/document-class/validation.ts
@@ -9,4 +9,5 @@ export const searchParamsDocumentClassCache = createSearchParamsCache({
filters: getFiltersStateParser(),
search: parseAsString.withDefault(""),
joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
+ projectId: parseAsString.withDefault(""),
}); \ No newline at end of file