diff options
Diffstat (limited to 'lib/docu-list-rule/combo-box-settings')
6 files changed, 201 insertions, 33 deletions
diff --git a/lib/docu-list-rule/combo-box-settings/service.ts b/lib/docu-list-rule/combo-box-settings/service.ts index c733f978..d2f7d0f7 100644 --- a/lib/docu-list-rule/combo-box-settings/service.ts +++ b/lib/docu-list-rule/combo-box-settings/service.ts @@ -19,6 +19,7 @@ export async function getComboBoxCodeGroups(input: { groupId?: string description?: string isActive?: string + projectId?: string }) { unstable_noStore() @@ -29,10 +30,15 @@ export async function getComboBoxCodeGroups(input: { // Control Type이 combobox이고 plant 타입 프로젝트인 조건 let whereConditions = sql`${codeGroups.controlType} = 'combobox' AND ${projects.type} = 'plant'` + // 프로젝트 ID 필터링 + if (input.projectId) { + whereConditions = sql`${whereConditions} AND ${codeGroups.projectId} = ${parseInt(input.projectId)}` + } + // 검색 조건 if (search) { const searchTerm = `%${search}%` - whereConditions = sql`${codeGroups.controlType} = 'combobox' AND ${projects.type} = 'plant' AND ( + whereConditions = sql`${whereConditions} AND ( ${codeGroups.groupId} ILIKE ${searchTerm} OR ${codeGroups.description} ILIKE ${searchTerm} OR ${codeGroups.codeFormat} ILIKE ${searchTerm} OR @@ -168,7 +174,7 @@ export async function getComboBoxOptions(codeGroupId: number, input?: { } // 정렬 (안전한 필드 체크 적용) - let orderBy = sql`${comboBoxSettings.code} ASC` + let orderBy = sql`${comboBoxSettings.sdq} ASC` if (sort && sort.length > 0) { const sortField = sort[0] // 안전성 체크: 필드가 실제 테이블에 존재하는지 확인 @@ -193,6 +199,7 @@ export async function getComboBoxOptions(codeGroupId: number, input?: { code: comboBoxSettings.code, description: comboBoxSettings.description, remark: comboBoxSettings.remark, + sdq: comboBoxSettings.sdq, createdAt: comboBoxSettings.createdAt, updatedAt: comboBoxSettings.updatedAt, projectCode: projects.code, @@ -274,6 +281,14 @@ export async function createComboBoxOption(input: { } } + // 다음 순서 번호 계산 + const maxSdqResult = await db + .select({ maxSdq: sql<number>`COALESCE(MAX(sdq), 0)` }) + .from(comboBoxSettings) + .where(eq(comboBoxSettings.codeGroupId, input.codeGroupId)) + + const nextSdq = (maxSdqResult[0]?.maxSdq || 0) + 1 + const [newOption] = await db .insert(comboBoxSettings) .values({ @@ -281,6 +296,7 @@ export async function createComboBoxOption(input: { code: input.code, description: input.description || "-", remark: input.remark, + sdq: nextSdq, }) .returning({ id: comboBoxSettings.id }) @@ -308,6 +324,7 @@ export async function updateComboBoxOption(input: { code: string description: string remark?: string + sdq?: number }) { try { // 현재 수정 중인 항목의 codeGroupId 가져오기 @@ -346,6 +363,7 @@ export async function updateComboBoxOption(input: { code: input.code, description: input.description, remark: input.remark, + ...(input.sdq !== undefined && { sdq: input.sdq }), updatedAt: new Date(), }) .where(eq(comboBoxSettings.id, input.id)) @@ -421,4 +439,99 @@ export async function clearComboBoxOptions(codeGroupId: number) { } } -
\ No newline at end of file +// Combo Box 옵션 순서 업데이트 (드래그 앤 드롭) +export async function updateComboBoxOptionOrder(codeGroupId: number, reorderedOptions: { id: number; sdq: number }[]) { + try { + console.log("Updating combo box option order:", { codeGroupId, reorderedOptions }) + + // 유니크 제약조건 때문에 임시로 큰 값으로 업데이트 후 실제 값으로 업데이트 + await db.transaction(async (tx) => { + // 1단계: 모든 옵션을 임시 큰 값으로 업데이트 + for (const option of reorderedOptions) { + console.log("Step 1 - Setting temporary value for option:", option.id) + await tx + .update(comboBoxSettings) + .set({ + sdq: option.sdq + 1000, // 임시로 큰 값 설정 + updatedAt: new Date(), + }) + .where(eq(comboBoxSettings.id, option.id)) + } + + // 2단계: 실제 값으로 업데이트 + for (const option of reorderedOptions) { + console.log("Step 2 - Setting final value for option:", option.id, "sdq:", option.sdq) + const result = await tx + .update(comboBoxSettings) + .set({ + sdq: option.sdq, + updatedAt: new Date(), + }) + .where(eq(comboBoxSettings.id, option.id)) + .returning({ id: comboBoxSettings.id, sdq: comboBoxSettings.sdq }) + + console.log("Update result:", result) + } + }) + + revalidatePath("/evcp/docu-list-rule/combo-box-settings") + + return { + success: true, + message: "Combo Box options reordered successfully" + } + } catch (error) { + console.error("Error updating combo box option order:", error) + return { + success: false, + error: "Failed to update combo box option order" + } + } +} + +// 기존 데이터에 기본 순서값 설정 (마이그레이션 후 한 번만 실행) +export async function initializeComboBoxOptionOrder() { + try { + // sdq가 null인 모든 옵션들을 찾아서 순서대로 업데이트 + const codeGroupsWithOptions = await db + .select({ + codeGroupId: comboBoxSettings.codeGroupId, + id: comboBoxSettings.id, + }) + .from(comboBoxSettings) + .orderBy(comboBoxSettings.codeGroupId, comboBoxSettings.createdAt) + + // codeGroupId별로 그룹화하여 순서 설정 + const groupedOptions = codeGroupsWithOptions.reduce((acc, option) => { + if (!acc[option.codeGroupId]) { + acc[option.codeGroupId] = [] + } + acc[option.codeGroupId].push(option.id) + return acc + }, {} as Record<number, number[]>) + + // 각 그룹별로 순서 업데이트 + for (const [codeGroupId, optionIds] of Object.entries(groupedOptions)) { + for (let i = 0; i < optionIds.length; i++) { + await db + .update(comboBoxSettings) + .set({ + sdq: i + 1, + updatedAt: new Date(), + }) + .where(eq(comboBoxSettings.id, optionIds[i])) + } + } + + return { + success: true, + message: "Combo Box option order initialized successfully" + } + } catch (error) { + console.error("Error initializing combo box option order:", error) + return { + success: false, + error: "Failed to initialize combo box option order" + } + } +}
\ No newline at end of file diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-detail-sheet.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-detail-sheet.tsx index 286acfbf..7e81fdab 100644 --- a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-detail-sheet.tsx +++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-detail-sheet.tsx @@ -4,12 +4,16 @@ 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 { DragEndEvent } from '@dnd-kit/core' +import { arrayMove } from '@dnd-kit/sortable' +import { toast } from "sonner" import { Sheet, SheetContent, } from "@/components/ui/sheet" -import { getComboBoxOptions } from "@/lib/docu-list-rule/combo-box-settings/service" +import { getComboBoxOptions, updateComboBoxOption } from "@/lib/docu-list-rule/combo-box-settings/service" import { getColumns } from "@/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns" import { ComboBoxOptionsEditSheet } from "@/lib/docu-list-rule/combo-box-settings/table/combo-box-options-edit-sheet" import { DeleteComboBoxOptionsDialog } from "@/lib/docu-list-rule/combo-box-settings/table/delete-combo-box-options-dialog" @@ -21,6 +25,7 @@ type ComboBoxOption = { code: string description: string remark: string | null + sdq: number isActive: boolean createdAt: Date updatedAt: Date @@ -57,7 +62,7 @@ export function ComboBoxOptionsDetailSheet({ page: 1, perPage: 10, search: "", - sort: [{ id: "createdAt", desc: true }], + sort: [{ id: "sdq", desc: false }], filters: [], joinOperator: "and", }) @@ -90,6 +95,7 @@ export function ComboBoxOptionsDetailSheet({ const result = await getComboBoxOptions(codeGroup.id, { page: 1, perPage: 10, + sort: [{ id: "sdq", desc: false }], }) if (result.success && result.data) { const optionsWithIsActive = result.data.map(option => ({ @@ -123,7 +129,7 @@ export function ComboBoxOptionsDetailSheet({ getFilteredRowModel: getFilteredRowModel(), getPaginationRowModel: getPaginationRowModel(), initialState: { - sorting: [{ id: "code", desc: false }], + sorting: [{ id: "sdq", desc: false }], pagination: { pageSize: 10, }, @@ -131,6 +137,66 @@ export function ComboBoxOptionsDetailSheet({ getRowId: (originalRow) => String((originalRow as any).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 })) + + // 서버에 순서 업데이트 (Number Type Config와 같은 방식) + try { + // 모든 항목을 임시 값으로 먼저 업데이트 + for (let i = 0; i < updatedOptions.length; i++) { + const option = updatedOptions[i] + await updateComboBoxOption({ + id: option.id, + codeGroupId: option.codeGroupId, + code: option.code, + description: option.description, + remark: option.remark, + sdq: -(i + 1), // 임시 음수 값 + }) + } + + // 최종 순서로 업데이트 + for (const option of updatedOptions) { + await updateComboBoxOption({ + id: option.id, + codeGroupId: option.codeGroupId, + code: option.code, + description: option.description, + remark: option.remark, + sdq: option.sdq, + }) + } + + toast.success("순서가 성공적으로 변경되었습니다.") + } catch (error) { + console.error("Error updating order:", error) + toast.error("순서 변경 중 오류가 발생했습니다.") + // 에러 시 원래 데이터로 복원 + await refreshData() + } + } + } + }, [rawData.data, codeGroup, refreshData]) + if (!codeGroup) return null return ( @@ -151,12 +217,16 @@ export function ComboBoxOptionsDetailSheet({ onSuccess={refreshData} /> - <DataTableDetail table={table as any}> + <DragDropTable + table={table as any} + data={rawData.data} + onDragEnd={handleDragEnd} + > <DataTableAdvancedToolbarDetail table={table as any} filterFields={advancedFilterFields} /> - </DataTableDetail> + </DragDropTable> <DeleteComboBoxOptionsDialog open={rowAction?.type === "delete"} diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns.tsx index 17754331..cf770c33 100644 --- a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns.tsx +++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns.tsx @@ -25,6 +25,7 @@ interface ComboBoxOption { code: string description: string remark: string | null + sdq: number isActive?: boolean createdAt: Date updatedAt: Date @@ -114,17 +115,17 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<ComboBo // ---------------------------------------------------------------- const dataColumns: ColumnDef<ComboBoxOption>[] = [ { - accessorKey: "projectCode", + accessorKey: "sdq", enableResizing: true, header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" /> + <DataTableColumnHeaderSimple column={column} title="순서" /> ), meta: { - excelHeader: "프로젝트 코드", - type: "text", + excelHeader: "순서", + type: "number", }, - cell: ({ row }) => row.getValue("projectCode") ?? "", - minSize: 120 + cell: ({ row }) => row.getValue("sdq") ?? "", + minSize: 50 }, { accessorKey: "code", diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-columns.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-columns.tsx index d41fe5ec..0775c1d2 100644 --- a/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-columns.tsx +++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-columns.tsx @@ -91,19 +91,7 @@ export function getColumns({ onDetail }: GetColumnsProps): ColumnDef<typeof code // 3) 데이터 컬럼들 // ---------------------------------------------------------------- const dataColumns: ColumnDef<typeof codeGroups.$inferSelect>[] = [ - { - accessorKey: "projectCode", - enableResizing: true, - header: ({ column }) => ( - <DataTableColumnHeaderSimple column={column} title="프로젝트 코드" /> - ), - meta: { - excelHeader: "프로젝트 코드", - type: "text", - }, - cell: ({ row }) => row.getValue("projectCode") ?? "", - minSize: 120 - }, + { accessorKey: "groupId", enableResizing: true, diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table.tsx index 8e469149..fef4cf8e 100644 --- a/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table.tsx +++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table.tsx @@ -66,12 +66,7 @@ export function ComboBoxSettingsTable({ promises }: ComboBoxSettingsTableProps) clearOnDefault: true, }) - // 컴포넌트 마운트 후 그룹핑 설정 - 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/combo-box-settings/validation.ts b/lib/docu-list-rule/combo-box-settings/validation.ts index ca8e9192..51671fb2 100644 --- a/lib/docu-list-rule/combo-box-settings/validation.ts +++ b/lib/docu-list-rule/combo-box-settings/validation.ts @@ -24,6 +24,7 @@ export const searchParamsComboBoxSettingsCache = createSearchParamsCache({ description: parseAsString.withDefault(""), controlType: parseAsString.withDefault(""), isActive: parseAsString.withDefault(""), + projectId: parseAsString.withDefault(""), // advanced filter filters: getFiltersStateParser().withDefault([]), |
