summaryrefslogtreecommitdiff
path: root/lib/docu-list-rule/combo-box-settings
diff options
context:
space:
mode:
Diffstat (limited to 'lib/docu-list-rule/combo-box-settings')
-rw-r--r--lib/docu-list-rule/combo-box-settings/service.ts34
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-options-add-dialog.tsx22
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-options-detail-sheet.tsx58
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-options-dialog.tsx234
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-options-edit-sheet.tsx52
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-options-expandable-row.tsx263
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-columns.tsx27
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-toolbar.tsx33
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table.tsx23
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/delete-combo-box-settings-dialog.tsx85
-rw-r--r--lib/docu-list-rule/combo-box-settings/table/document-class-options-sheet.tsx436
-rw-r--r--lib/docu-list-rule/combo-box-settings/validation.ts38
12 files changed, 156 insertions, 1149 deletions
diff --git a/lib/docu-list-rule/combo-box-settings/service.ts b/lib/docu-list-rule/combo-box-settings/service.ts
index 2c5ee42b..80c1942d 100644
--- a/lib/docu-list-rule/combo-box-settings/service.ts
+++ b/lib/docu-list-rule/combo-box-settings/service.ts
@@ -74,7 +74,7 @@ export async function getComboBoxCodeGroups(input: {
}
// 정렬 (안전한 필드 체크 적용)
- let orderBy = sql`${codeGroups.createdAt} DESC`
+ let orderBy = sql`${codeGroups.groupId} ASC`
if (sort && sort.length > 0) {
const sortField = sort[0]
// 안전성 체크: 필드가 실제 테이블에 존재하는지 확인
@@ -155,7 +155,7 @@ export async function getComboBoxOptions(codeGroupId: number, input?: {
}
// 정렬 (안전한 필드 체크 적용)
- let orderBy = sql`${comboBoxSettings.createdAt} DESC`
+ let orderBy = sql`${comboBoxSettings.code} ASC`
if (sort && sort.length > 0) {
const sortField = sort[0]
// 안전성 체크: 필드가 실제 테이블에 존재하는지 확인
@@ -281,6 +281,36 @@ export async function updateComboBoxOption(input: {
remark?: string
}) {
try {
+ // 현재 수정 중인 항목의 codeGroupId 가져오기
+ const currentOption = await db
+ .select({ codeGroupId: comboBoxSettings.codeGroupId })
+ .from(comboBoxSettings)
+ .where(eq(comboBoxSettings.id, input.id))
+ .limit(1)
+
+ if (currentOption.length === 0) {
+ return {
+ success: false,
+ error: "Option not found"
+ }
+ }
+
+ // 코드 중복 체크 (현재 수정 중인 항목 제외)
+ const existingOption = await db
+ .select({ id: comboBoxSettings.id })
+ .from(comboBoxSettings)
+ .where(
+ sql`${comboBoxSettings.codeGroupId} = ${currentOption[0].codeGroupId} AND ${comboBoxSettings.code} = ${input.code} AND ${comboBoxSettings.id} != ${input.id}`
+ )
+ .limit(1)
+
+ if (existingOption.length > 0) {
+ return {
+ success: false,
+ error: "이미 존재하는 코드입니다."
+ }
+ }
+
const [updatedOption] = await db
.update(comboBoxSettings)
.set({
diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-add-dialog.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-add-dialog.tsx
index a5a8af2f..049e2c1a 100644
--- a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-add-dialog.tsx
+++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-add-dialog.tsx
@@ -31,7 +31,7 @@ import { createComboBoxOption } from "../service"
const createOptionSchema = z.object({
code: z.string().min(1, "코드는 필수입니다."),
- description: z.string().default("-"),
+ description: z.string().default(""),
remark: z.string().optional(),
})
@@ -50,18 +50,24 @@ export function ComboBoxOptionsAddDialog({ codeGroupId, onSuccess }: ComboBoxOpt
resolver: zodResolver(createOptionSchema),
defaultValues: {
code: "",
- description: "-",
+ description: "",
remark: "",
},
})
+ // 코드 입력 시 자동으로 description에 반영
+ const handleCodeChange = (value: string) => {
+ form.setValue("code", value)
+ form.setValue("description", value) // 코드값을 description에도 자동 설정
+ }
+
const handleSubmit = (data: CreateOptionSchema) => {
startTransition(async () => {
try {
const result = await createComboBoxOption({
codeGroupId,
code: data.code,
- description: data.description || "-",
+ description: data.description || data.code, // description이 비어있으면 code 사용
remark: data.remark,
})
@@ -109,7 +115,11 @@ export function ComboBoxOptionsAddDialog({ codeGroupId, onSuccess }: ComboBoxOpt
<FormItem>
<FormLabel>코드</FormLabel>
<FormControl>
- <Input {...field} placeholder="옵션 코드" />
+ <Input
+ {...field}
+ placeholder="옵션 코드"
+ onChange={(e) => handleCodeChange(e.target.value)}
+ />
</FormControl>
<FormMessage />
</FormItem>
@@ -120,9 +130,9 @@ export function ComboBoxOptionsAddDialog({ codeGroupId, onSuccess }: ComboBoxOpt
name="description"
render={({ field }) => (
<FormItem>
- <FormLabel>값</FormLabel>
+ <FormLabel>Description</FormLabel>
<FormControl>
- <Input {...field} placeholder="옵션 값" />
+ <Input {...field} placeholder="옵션 설명" />
</FormControl>
<FormMessage />
</FormItem>
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 0b2a76a4..b62b258e 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
@@ -1,22 +1,20 @@
"use client"
import * as React from "react"
-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 { 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 type { DataTableAdvancedFilterField, DataTableRowAction } from "@/types/table"
import {
Sheet,
SheetContent,
} from "@/components/ui/sheet"
-
-import { getComboBoxOptions } from "../service"
-import { getColumns } from "./combo-box-options-table-columns"
-import { ComboBoxOptionsEditSheet } from "./combo-box-options-edit-sheet"
-import { DeleteComboBoxOptionsDialog } from "./delete-combo-box-options-dialog"
-import { ComboBoxOptionsTableToolbarActions } from "./combo-box-options-table-toolbar"
-import { codeGroups } from "@/db/schema/codeGroups"
-
+import { getComboBoxOptions } 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"
+import { ComboBoxOptionsTableToolbarActions } from "@/lib/docu-list-rule/combo-box-settings/table/combo-box-options-table-toolbar"
+import { codeGroups } from "@/db/schema"
type ComboBoxOption = {
id: number
codeGroupId: number
@@ -88,10 +86,6 @@ export function ComboBoxOptionsDetailSheet({
const result = await getComboBoxOptions(codeGroup.id, {
page: 1,
perPage: 10,
- search: table.getState().globalFilter || "",
- sort: table.getState().sorting,
- filters: table.getState().columnFilters,
- joinOperator: "and",
})
if (result.success && result.data) {
const optionsWithIsActive = result.data.map(option => ({
@@ -112,27 +106,25 @@ export function ComboBoxOptionsDetailSheet({
// 고급 필터 필드 설정
const advancedFilterFields: DataTableAdvancedFilterField<any>[] = [
- { id: "code", label: "코드", type: "text" },
- { id: "description", label: "값", type: "text" },
- { id: "remark", label: "비고", type: "text" },
+ { id: "code", label: "code", type: "text" },
+ { id: "remark", label: "remark", type: "text" },
+ { id: "updatedAt", label: "updated_at", type: "date" },
]
- const { table } = useDataTable({
+ const table = useReactTable({
data: rawData.data as any,
columns: columns as any,
- pageCount: rawData.pageCount,
- enablePinning: true,
- enableAdvancedFilter: true,
- manualSorting: true,
- manualFiltering: true,
- manualPagination: true, // 수동 페이징 활성화
+ getCoreRowModel: getCoreRowModel(),
+ getSortedRowModel: getSortedRowModel(),
+ getFilteredRowModel: getFilteredRowModel(),
+ getPaginationRowModel: getPaginationRowModel(),
initialState: {
- sorting: [{ id: "createdAt", desc: true }],
- columnPinning: { right: ["actions"] },
+ sorting: [{ id: "code", desc: false }],
+ pagination: {
+ pageSize: 10,
+ },
},
getRowId: (originalRow) => String((originalRow as any).id),
- shallow: false,
- clearOnDefault: true,
})
if (!codeGroup) return null
@@ -144,7 +136,7 @@ export function ComboBoxOptionsDetailSheet({
<div>
<h3 className="text-lg font-medium">{codeGroup.description} 옵션 관리</h3>
<p className="text-sm text-muted-foreground">
- {codeGroup.groupId}의 Combo Box 옵션들을 관리합니다.
+ {codeGroup.description}의 Combo Box 옵션들을 관리합니다.
</p>
</div>
</div>
@@ -155,12 +147,12 @@ export function ComboBoxOptionsDetailSheet({
onSuccess={refreshData}
/>
- <DataTable table={table as any}>
- <DataTableAdvancedToolbar
+ <DataTableDetail table={table as any}>
+ <DataTableAdvancedToolbarDetail
table={table as any}
filterFields={advancedFilterFields}
/>
- </DataTable>
+ </DataTableDetail>
<DeleteComboBoxOptionsDialog
open={rowAction?.type === "delete"}
diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-dialog.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-dialog.tsx
deleted file mode 100644
index 6459ae14..00000000
--- a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-dialog.tsx
+++ /dev/null
@@ -1,234 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { useState } from "react"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Plus, Trash2 } from "lucide-react"
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog"
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table"
-import { toast } from "sonner"
-import { codeGroups } from "@/db/schema/codeGroups"
-import { createComboBoxOption } from "../service"
-
-interface ComboBoxOptionsDialogProps {
- open: boolean
- onOpenChange: (open: boolean) => void
- codeGroup: typeof codeGroups.$inferSelect | null
- onSuccess: () => void
-}
-
-interface OptionRow {
- id: string
- description: string
- remark: string
-}
-
-export function ComboBoxOptionsDialog({
- open,
- onOpenChange,
- codeGroup,
- onSuccess
-}: ComboBoxOptionsDialogProps) {
- const [optionRows, setOptionRows] = useState<OptionRow[]>([])
- const [loading, setLoading] = useState(false)
-
- // 다이얼로그가 열릴 때 초기 행 생성
- React.useEffect(() => {
- if (open && optionRows.length === 0) {
- addRow()
- }
- }, [open])
-
- // 새 행 추가
- const addRow = () => {
- const newRow: OptionRow = {
- id: `row-${Date.now()}-${Math.random()}`,
- description: "",
- remark: "",
- }
- setOptionRows(prev => [...prev, newRow])
- }
-
- // 행 삭제
- const removeRow = (id: string) => {
- setOptionRows(prev => prev.filter(row => row.id !== id))
- }
-
- // 행 업데이트
- const updateRow = (id: string, field: keyof OptionRow, value: string) => {
- setOptionRows(prev =>
- prev.map(row =>
- row.id === id ? { ...row, [field]: value } : row
- )
- )
- }
-
- // 일괄 저장
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault()
-
- if (!codeGroup) return
-
- // 유효한 행들만 필터링 (Description이 있는 것만)
- const validRows = optionRows.filter(row => row.description.trim())
-
- if (validRows.length === 0) {
- toast.error("최소 하나의 Description을 입력해주세요.")
- return
- }
-
- setLoading(true)
- try {
- let successCount = 0
- let errorCount = 0
-
- // 각 행을 순차적으로 저장
- for (const row of validRows) {
- try {
- const result = await createComboBoxOption({
- codeGroupId: codeGroup.id,
- code: "", // 서비스에서 자동 생성
- description: row.description.trim(),
- remark: row.remark.trim() || undefined,
- })
-
- if (result.success) {
- successCount++
- } else {
- errorCount++
- }
- } catch (error) {
- console.error("옵션 추가 실패:", error)
- errorCount++
- }
- }
-
- if (successCount > 0) {
- toast.success(`${successCount}개의 옵션이 추가되었습니다.${errorCount > 0 ? ` (${errorCount}개 실패)` : ''}`)
- // 폼 초기화
- setOptionRows([])
- onSuccess()
- } else {
- toast.error("모든 옵션 추가에 실패했습니다.")
- }
- } catch (error) {
- console.error("옵션 추가 실패:", error)
- toast.error("옵션 추가에 실패했습니다.")
- } finally {
- setLoading(false)
- }
- }
-
- const handleCancel = () => {
- // 폼 초기화
- setOptionRows([])
- onOpenChange(false)
- }
-
- return (
- <Dialog open={open} onOpenChange={onOpenChange}>
- <DialogContent className="sm:max-w-[600px] max-h-[80vh] overflow-y-auto">
- <DialogHeader>
- <DialogTitle>Combo Box 옵션 추가</DialogTitle>
- <DialogDescription>
- {codeGroup?.description}에 새로운 옵션들을 추가합니다. Code는 자동으로 생성됩니다.
- </DialogDescription>
- </DialogHeader>
-
- <div className="space-y-4">
- {/* 테이블 헤더와 추가 버튼 */}
- <div className="flex items-center justify-between">
- <h4 className="text-sm font-medium">옵션 목록</h4>
- <Button
- type="button"
- variant="outline"
- size="sm"
- onClick={addRow}
- className="h-8"
- >
- <Plus className="h-4 w-4 mr-1" />
- 행 추가
- </Button>
- </div>
-
- {/* 옵션 테이블 - 항상 표시 */}
- <div className="border rounded-lg overflow-hidden">
- <Table>
- <TableHeader>
- <TableRow className="bg-muted/30">
- <TableHead className="w-[50%]">Description *</TableHead>
- <TableHead className="w-[40%]">Remark</TableHead>
- <TableHead className="w-[10%]"></TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- {optionRows.map((row) => (
- <TableRow key={row.id} className="hover:bg-muted/30">
- <TableCell>
- <Input
- value={row.description}
- onChange={(e) => updateRow(row.id, "description", e.target.value)}
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- </TableCell>
- <TableCell>
- <Input
- value={row.remark}
- onChange={(e) => updateRow(row.id, "remark", e.target.value)}
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- </TableCell>
- <TableCell>
- <Button
- type="button"
- variant="ghost"
- size="sm"
- onClick={() => removeRow(row.id)}
- className="h-6 w-6 p-0"
- >
- <Trash2 className="h-3 w-3" />
- </Button>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </div>
- </div>
-
- <DialogFooter>
- <Button
- type="button"
- variant="outline"
- onClick={handleCancel}
- disabled={loading}
- >
- 취소
- </Button>
- <Button
- type="submit"
- onClick={handleSubmit}
- disabled={loading}
- >
- {loading ? "저장 중..." : "저장"}
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- )
-} \ No newline at end of file
diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-edit-sheet.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-edit-sheet.tsx
index 5732674e..4ac539d0 100644
--- a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-edit-sheet.tsx
+++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-edit-sheet.tsx
@@ -5,10 +5,17 @@ import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { toast } from "sonner"
import * as z from "zod"
-import { Loader } from "lucide-react"
import { Button } from "@/components/ui/button"
import {
+ Sheet,
+ SheetContent,
+ SheetDescription,
+ SheetFooter,
+ SheetHeader,
+ SheetTitle,
+} from "@/components/ui/sheet"
+import {
Form,
FormControl,
FormField,
@@ -17,19 +24,12 @@ import {
FormMessage,
} from "@/components/ui/form"
import { Input } from "@/components/ui/input"
-import {
- Sheet,
- SheetContent,
- SheetDescription,
- SheetFooter,
- SheetHeader,
- SheetTitle,
-} from "@/components/ui/sheet"
import { updateComboBoxOption } from "../service"
const updateOptionSchema = z.object({
- value: z.string().min(1, "값은 필수입니다."),
+ code: z.string().min(1, "코드는 필수입니다."),
+ remark: z.string().optional(),
})
type UpdateOptionSchema = z.infer<typeof updateOptionSchema>
@@ -63,14 +63,16 @@ export function ComboBoxOptionsEditSheet({
const form = useForm<UpdateOptionSchema>({
resolver: zodResolver(updateOptionSchema),
defaultValues: {
- value: "",
+ code: "",
+ remark: "",
},
})
React.useEffect(() => {
if (data) {
form.reset({
- value: data.description,
+ code: data.code,
+ remark: data.remark || "",
})
}
}, [data, form])
@@ -82,7 +84,9 @@ export function ComboBoxOptionsEditSheet({
try {
const result = await updateComboBoxOption({
id: data.id,
- value: formData.value,
+ code: formData.code,
+ description: data.description, // 기존 description 유지
+ remark: formData.remark,
})
if (result.success) {
@@ -117,12 +121,25 @@ export function ComboBoxOptionsEditSheet({
<form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
<FormField
control={form.control}
- name="value"
+ name="code"
+ render={({ field }) => (
+ <FormItem>
+ <FormLabel>code</FormLabel>
+ <FormControl>
+ <Input {...field} placeholder="옵션 코드" />
+ </FormControl>
+ <FormMessage />
+ </FormItem>
+ )}
+ />
+ <FormField
+ control={form.control}
+ name="remark"
render={({ field }) => (
<FormItem>
- <FormLabel>값</FormLabel>
+ <FormLabel>remark</FormLabel>
<FormControl>
- <Input {...field} placeholder="옵션 값" />
+ <Input {...field} placeholder="비고 (선택사항)" />
</FormControl>
<FormMessage />
</FormItem>
@@ -133,9 +150,6 @@ export function ComboBoxOptionsEditSheet({
취소
</Button>
<Button type="submit" disabled={isPending || !form.formState.isValid}>
- {isPending && (
- <Loader className="mr-2 size-4 animate-spin" aria-hidden="true" />
- )}
수정
</Button>
</SheetFooter>
diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-expandable-row.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-expandable-row.tsx
deleted file mode 100644
index 07b63de5..00000000
--- a/lib/docu-list-rule/combo-box-settings/table/combo-box-options-expandable-row.tsx
+++ /dev/null
@@ -1,263 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { useState, useEffect } from "react"
-import { MoreHorizontal, Settings } from "lucide-react"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table"
-import { toast } from "sonner"
-import { codeGroups } from "@/db/schema/codeGroups"
-import { getComboBoxOptions, updateComboBoxOption, deleteComboBoxOption } from "../service"
-import { DocumentClassOptionsSheet } from "./document-class-options-sheet"
-
-
-interface ComboBoxOptionsExpandableRowProps {
- codeGroup: typeof codeGroups.$inferSelect
-}
-
-interface ComboBoxOption {
- id: number
- codeGroupId: number
- code: string
- description: string
- remark: string | null
- createdAt: Date
- updatedAt: Date
-}
-
-export function ComboBoxOptionsExpandableRow({ codeGroup }: ComboBoxOptionsExpandableRowProps) {
- const [options, setOptions] = useState<ComboBoxOption[]>([])
- const [loading, setLoading] = useState(true)
- const [editingOption, setEditingOption] = useState<ComboBoxOption | null>(null)
- const [selectedOptionForSubOptions, setSelectedOptionForSubOptions] = useState<ComboBoxOption | null>(null)
-
- // 옵션 목록 로드
- const loadOptions = async () => {
- try {
- setLoading(true)
- const result = await getComboBoxOptions(codeGroup.id)
- if (result.success && result.data) {
- setOptions(result.data as ComboBoxOption[])
- } else {
- toast.error("옵션 목록을 불러오는데 실패했습니다.")
- }
- } catch (error) {
- console.error("옵션 로드 실패:", error)
- toast.error("옵션 목록을 불러오는데 실패했습니다.")
- } finally {
- setLoading(false)
- }
- }
-
- useEffect(() => {
- loadOptions()
- }, [codeGroup.id])
-
- // 기존 옵션 수정
- const handleUpdateOption = async (option: ComboBoxOption) => {
- if (!option.code.trim() || !option.description.trim()) {
- toast.error("Code와 Description은 필수 입력 항목입니다.")
- return
- }
-
- try {
- const result = await updateComboBoxOption({
- id: option.id,
- code: option.code.trim(),
- description: option.description.trim(),
- remark: option.remark || undefined,
- })
-
- if (result.success) {
- await loadOptions() // 목록 새로고침
- setEditingOption(null) // 편집 모드 종료
- toast.success("옵션이 수정되었습니다.")
- } else {
- toast.error("옵션 수정에 실패했습니다.")
- }
- } catch (error) {
- console.error("옵션 수정 실패:", error)
- toast.error("옵션 수정에 실패했습니다.")
- }
- }
-
- // 기존 옵션 삭제
- const handleDeleteOption = async (optionId: number) => {
- if (!confirm("정말로 이 옵션을 삭제하시겠습니까?")) {
- return
- }
-
- try {
- const result = await deleteComboBoxOption(optionId)
- if (result.success) {
- await loadOptions() // 목록 새로고침
- toast.success("옵션이 삭제되었습니다.")
- } else {
- toast.error("옵션 삭제에 실패했습니다.")
- }
- } catch (error) {
- console.error("옵션 삭제 실패:", error)
- toast.error("옵션 삭제에 실패했습니다.")
- }
- }
-
- // Document Class인지 확인 (Description이 "Document Class"인 경우)
- const isDocumentClass = codeGroup.description === "Document Class"
-
- return (
- <div className="bg-muted/20 border-t">
- <div className="space-y-0 ml-[60px]">
- {/* 커스텀 테이블 */}
- <div className="border overflow-hidden bg-white">
- <Table className="w-full table-fixed">
- <TableHeader>
- <TableRow className="bg-muted/30">
- <TableHead className="w-[20%] font-medium text-muted-foreground">Code</TableHead>
- <TableHead className="w-[30%] font-medium text-muted-foreground">Description</TableHead>
- <TableHead className="w-[25%] font-medium text-muted-foreground">Remark</TableHead>
- {isDocumentClass && (
- <TableHead className="w-[15%] font-medium text-muted-foreground">하위 옵션</TableHead>
- )}
- <TableHead className="w-[10%] font-medium text-muted-foreground">작업</TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- {/* 기존 옵션들 */}
- {options.map((option) => (
- <TableRow key={option.id} className="hover:bg-muted/30 transition-colors">
- <TableCell className="font-medium text-sm">
- {editingOption?.id === option.id ? (
- <Input
- value={editingOption.code}
- onChange={(e) => setEditingOption(prev => prev ? { ...prev, code: e.target.value } : null)}
- placeholder="Code (*)"
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- ) : (
- option.code
- )}
- </TableCell>
- <TableCell className="text-sm">
- {editingOption?.id === option.id ? (
- <Input
- value={editingOption.description}
- onChange={(e) => setEditingOption(prev => prev ? { ...prev, description: e.target.value } : null)}
- placeholder="Description (*)"
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- ) : (
- option.description
- )}
- </TableCell>
- <TableCell className="text-sm text-muted-foreground">
- {editingOption?.id === option.id ? (
- <Input
- value={editingOption.remark || ""}
- onChange={(e) => setEditingOption(prev => prev ? { ...prev, remark: e.target.value } : null)}
- placeholder="Remark"
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- ) : (
- option.remark || "-"
- )}
- </TableCell>
- {isDocumentClass && (
- <TableCell className="text-sm">
- <Button
- variant="outline"
- size="sm"
- onClick={() => setSelectedOptionForSubOptions(option)}
- className="h-6 px-2 text-xs"
- >
- <Settings className="h-3 w-3 mr-1" />
- 관리
- </Button>
- </TableCell>
- )}
- <TableCell className="text-sm">
- {editingOption?.id === option.id ? (
- <div className="flex gap-1">
- <Button
- onClick={() => handleUpdateOption(editingOption)}
- size="sm"
- variant="outline"
- className="h-6 px-2 text-xs"
- >
- 저장
- </Button>
- <Button
- onClick={() => setEditingOption(null)}
- size="sm"
- variant="ghost"
- className="h-6 px-2 text-xs"
- >
- 취소
- </Button>
- </div>
- ) : (
- <DropdownMenu>
- <DropdownMenuTrigger asChild>
- <Button
- variant="ghost"
- className="h-6 w-6 p-0"
- >
- <MoreHorizontal className="h-3 w-3" />
- </Button>
- </DropdownMenuTrigger>
- <DropdownMenuContent align="end">
- <DropdownMenuItem onClick={() => setEditingOption(option)}>
- 수정
- </DropdownMenuItem>
- <DropdownMenuSeparator />
- <DropdownMenuItem onClick={() => handleDeleteOption(option.id)}>
- 삭제
- </DropdownMenuItem>
- </DropdownMenuContent>
- </DropdownMenu>
- )}
- </TableCell>
- </TableRow>
- ))}
-
- {options.length === 0 && (
- <TableRow>
- <TableCell colSpan={isDocumentClass ? 5 : 4} className="text-center text-muted-foreground py-8">
- 등록된 옵션이 없습니다.
- </TableCell>
- </TableRow>
- )}
- </TableBody>
- </Table>
- </div>
- </div>
-
- {/* Document Class 하위 옵션 관리 시트 */}
- {selectedOptionForSubOptions && (
- <DocumentClassOptionsSheet
- open={!!selectedOptionForSubOptions}
- onOpenChange={(open) => !open && setSelectedOptionForSubOptions(null)}
- comboBoxOption={selectedOptionForSubOptions}
- onSuccess={() => {
- setSelectedOptionForSubOptions(null)
- // 필요시 하위 옵션 목록 새로고침
- }}
- />
- )}
- </div>
- )
-} \ No newline at end of file
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 e5780e9e..0e46c0ed 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
@@ -114,40 +114,43 @@ export function getColumns({ setRowAction }: GetColumnsProps): ColumnDef<ComboBo
accessorKey: "code",
enableResizing: true,
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="코드" />
+ <DataTableColumnHeaderSimple column={column} title="code" />
),
meta: {
- excelHeader: "코드",
+ excelHeader: "code",
type: "text",
},
cell: ({ row }) => row.getValue("code") ?? "",
minSize: 80
},
{
- accessorKey: "description",
+ accessorKey: "remark",
enableResizing: true,
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="값" />
+ <DataTableColumnHeaderSimple column={column} title="remark" />
),
meta: {
- excelHeader: "값",
+ excelHeader: "remark",
type: "text",
},
- cell: ({ row }) => row.getValue("description") ?? "",
+ cell: ({ row }) => row.getValue("remark") ?? "",
minSize: 80
},
{
- accessorKey: "remark",
+ accessorKey: "updatedAt",
enableResizing: true,
header: ({ column }) => (
- <DataTableColumnHeaderSimple column={column} title="비고" />
+ <DataTableColumnHeaderSimple column={column} title="updated_at" />
),
meta: {
- excelHeader: "비고",
- type: "text",
+ excelHeader: "updated_at",
+ type: "date",
},
- cell: ({ row }) => row.getValue("remark") ?? "",
- minSize: 80
+ cell: ({ row }) => {
+ const date = row.getValue("updatedAt") as Date
+ return date ? new Date(date).toLocaleDateString('ko-KR') : ""
+ },
+ minSize: 100
}
]
diff --git a/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-toolbar.tsx b/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-toolbar.tsx
deleted file mode 100644
index 77cbea01..00000000
--- a/lib/docu-list-rule/combo-box-settings/table/combo-box-settings-table-toolbar.tsx
+++ /dev/null
@@ -1,33 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { type Table } from "@tanstack/react-table"
-
-import { DeleteComboBoxSettingsDialog } from "./delete-combo-box-settings-dialog"
-import { codeGroups } from "@/db/schema/codeGroups"
-
-interface ComboBoxSettingsTableToolbarActionsProps {
- table: Table<typeof codeGroups.$inferSelect>
- onSuccess?: () => void
-}
-
-export function ComboBoxSettingsTableToolbarActions({ table, onSuccess }: ComboBoxSettingsTableToolbarActionsProps) {
- return (
- <div className="flex items-center gap-2">
- {/** 1) 선택된 로우가 있으면 삭제 다이얼로그 */}
- {table.getFilteredSelectedRowModel().rows.length > 0 ? (
- <DeleteComboBoxSettingsDialog
- codeGroups={table
- .getFilteredSelectedRowModel()
- .rows.map((row) => row.original)}
- onSuccess={() => {
- table.toggleAllRowsSelected(false)
- onSuccess?.()
- }}
- />
- ) : null}
-
-
- </div>
- )
-} \ No newline at end of file
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 f6216363..42ce1a19 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
@@ -1,15 +1,12 @@
"use client"
import * as React from "react"
-import { useRouter } from "next/navigation"
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 type { DataTableAdvancedFilterField } from "@/types/table"
-import { getComboBoxCodeGroups } from "../service"
import { getColumns } from "./combo-box-settings-table-columns"
import { ComboBoxOptionsDetailSheet } from "./combo-box-options-detail-sheet"
-import { ComboBoxSettingsTableToolbarActions } from "./combo-box-settings-table-toolbar"
import { codeGroups } from "@/db/schema/docu-list-rule"
interface ComboBoxSettingsTableProps {
@@ -17,21 +14,15 @@ interface ComboBoxSettingsTableProps {
}
export function ComboBoxSettingsTable({ promises }: ComboBoxSettingsTableProps) {
- const router = useRouter()
+
const rawData = React.use(promises!)
const [isDetailSheetOpen, setIsDetailSheetOpen] = React.useState(false)
const [selectedCodeGroup, setSelectedCodeGroup] = React.useState<typeof codeGroups.$inferSelect | null>(null)
-
- const refreshData = React.useCallback(() => {
- // 전체 페이지 새로고침 대신 router.refresh() 사용 (성능 개선)
- router.refresh()
- }, [router])
-
// Detail 버튼 클릭 핸들러
- const handleDetail = (codeGroup: typeof codeGroups.$inferSelect) => {
+ const handleDetail = React.useCallback((codeGroup: typeof codeGroups.$inferSelect) => {
setSelectedCodeGroup(codeGroup)
setIsDetailSheetOpen(true)
- }
+ }, [])
const columns = React.useMemo(() => getColumns({ onDetail: handleDetail }), [handleDetail])
@@ -44,8 +35,6 @@ export function ComboBoxSettingsTable({ promises }: ComboBoxSettingsTableProps)
id: "controlType", label: "Control Type", type: "select", options: [
{ label: "Textbox", value: "textbox" },
{ label: "Combobox", value: "combobox" },
- { label: "Date", value: "date" },
- { label: "Number", value: "number" },
]
},
{
@@ -58,14 +47,12 @@ export function ComboBoxSettingsTable({ promises }: ComboBoxSettingsTableProps)
]
const { table } = useDataTable({
- data: rawData[0].data as any,
+ data: rawData[0].data as typeof codeGroups.$inferSelect[],
columns,
pageCount: rawData[0].pageCount,
enablePinning: true,
enableAdvancedFilter: true,
- manualSorting: false,
initialState: {
- sorting: [{ id: "createdAt", desc: true }],
columnPinning: { right: ["actions"] },
columnFilters: [
{
@@ -86,7 +73,7 @@ export function ComboBoxSettingsTable({ promises }: ComboBoxSettingsTableProps)
table={table}
filterFields={advancedFilterFields}
>
- <ComboBoxSettingsTableToolbarActions table={table} onSuccess={refreshData} />
+
</DataTableAdvancedToolbar>
</DataTable>
diff --git a/lib/docu-list-rule/combo-box-settings/table/delete-combo-box-settings-dialog.tsx b/lib/docu-list-rule/combo-box-settings/table/delete-combo-box-settings-dialog.tsx
deleted file mode 100644
index 28788bd7..00000000
--- a/lib/docu-list-rule/combo-box-settings/table/delete-combo-box-settings-dialog.tsx
+++ /dev/null
@@ -1,85 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { useRouter } from "next/navigation"
-import { AlertTriangle } from "lucide-react"
-
-import { Button } from "@/components/ui/button"
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogFooter,
- DialogHeader,
- DialogTitle,
-} from "@/components/ui/dialog"
-import { deleteCodeGroup } from "@/lib/docu-list-rule/code-groups/service"
-import { codeGroups } from "@/db/schema/codeGroups"
-
-interface DeleteComboBoxSettingsDialogProps {
- codeGroups: typeof codeGroups.$inferSelect[]
- onSuccess?: () => void
-}
-
-export function DeleteComboBoxSettingsDialog({
- codeGroups,
- onSuccess,
-}: DeleteComboBoxSettingsDialogProps) {
- const router = useRouter()
- const [isDeleting, setIsDeleting] = React.useState(false)
-
- const handleDelete = React.useCallback(async () => {
- if (codeGroups.length === 0) return
-
- setIsDeleting(true)
- try {
- for (const codeGroup of codeGroups) {
- await deleteCodeGroup(codeGroup.id)
- }
-
- router.refresh()
- onSuccess?.()
- } catch (error) {
- console.error("Error deleting code groups:", error)
- } finally {
- setIsDeleting(false)
- }
- }, [codeGroups, router, onSuccess])
-
- if (codeGroups.length === 0) {
- return null
- }
-
- return (
- <Dialog>
- <DialogContent>
- <DialogHeader>
- <DialogTitle className="flex items-center gap-2">
- <AlertTriangle className="h-5 w-5 text-destructive" />
- Code Group 삭제
- </DialogTitle>
- <DialogDescription>
- 선택한 Code Group{codeGroups.length > 1 ? "들" : ""}을 삭제하시겠습니까?
- <br />
- 이 작업은 되돌릴 수 없습니다.
- </DialogDescription>
- </DialogHeader>
- <DialogFooter>
- <Button
- variant="outline"
- disabled={isDeleting}
- >
- 취소
- </Button>
- <Button
- variant="destructive"
- onClick={handleDelete}
- disabled={isDeleting}
- >
- {isDeleting ? "삭제 중..." : "삭제"}
- </Button>
- </DialogFooter>
- </DialogContent>
- </Dialog>
- )
-} \ No newline at end of file
diff --git a/lib/docu-list-rule/combo-box-settings/table/document-class-options-sheet.tsx b/lib/docu-list-rule/combo-box-settings/table/document-class-options-sheet.tsx
deleted file mode 100644
index 8585d9a3..00000000
--- a/lib/docu-list-rule/combo-box-settings/table/document-class-options-sheet.tsx
+++ /dev/null
@@ -1,436 +0,0 @@
-"use client"
-
-import * as React from "react"
-import { useState, useEffect } from "react"
-import { Plus, Trash2, Save, X } from "lucide-react"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import {
- Sheet,
- SheetContent,
- SheetDescription,
- SheetHeader,
- SheetTitle,
-} from "@/components/ui/sheet"
-import {
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableHeader,
- TableRow,
-} from "@/components/ui/table"
-import { toast } from "sonner"
-import {
- getDocumentClassOptions,
- createDocumentClassOption,
- updateDocumentClassOption,
- deleteDocumentClassOption
-} from "../service"
-
-interface DocumentClassOptionsSheetProps {
- open: boolean
- onOpenChange: (open: boolean) => void
- comboBoxOption: {
- id: number
- code: string
- description: string
- }
- onSuccess: () => void
-}
-
-interface DocumentClassOption {
- id: number
- comboBoxSettingId: number
- optionValue: string
- optionCode: string | null
- sortOrder: number
- isActive: boolean
- createdAt: Date
- updatedAt: Date
-}
-
-interface NewOptionRow {
- id: string
- optionValue: string
- optionCode: string
- sortOrder: number
-}
-
-export function DocumentClassOptionsSheet({
- open,
- onOpenChange,
- comboBoxOption,
- onSuccess
-}: DocumentClassOptionsSheetProps) {
- const [options, setOptions] = useState<DocumentClassOption[]>([])
- const [loading, setLoading] = useState(true)
- const [newOptionRows, setNewOptionRows] = useState<NewOptionRow[]>([])
- const [editingOption, setEditingOption] = useState<DocumentClassOption | null>(null)
-
- // 하위 옵션 목록 로드
- const loadOptions = async () => {
- try {
- setLoading(true)
- const result = await getDocumentClassOptions(comboBoxOption.id)
- if (result.success && result.data) {
- setOptions(result.data)
- } else {
- toast.error("하위 옵션 목록을 불러오는데 실패했습니다.")
- }
- } catch (error) {
- console.error("하위 옵션 로드 실패:", error)
- toast.error("하위 옵션 목록을 불러오는데 실패했습니다.")
- } finally {
- setLoading(false)
- }
- }
-
- useEffect(() => {
- if (open) {
- loadOptions()
- }
- }, [open, comboBoxOption.id])
-
- // 새 행 추가
- const addNewRow = () => {
- const newRow: NewOptionRow = {
- id: `new-${Date.now()}-${Math.random()}`,
- optionValue: "",
- optionCode: "",
- sortOrder: options.length + newOptionRows.length + 1,
- }
- setNewOptionRows(prev => [...prev, newRow])
- }
-
- // 새 행 삭제
- const removeNewRow = (id: string) => {
- setNewOptionRows(prev => prev.filter(row => row.id !== id))
- }
-
- // 새 행 업데이트
- const updateNewRow = (id: string, field: keyof NewOptionRow, value: string | number) => {
- setNewOptionRows(prev =>
- prev.map(row =>
- row.id === id ? { ...row, [field]: value } : row
- )
- )
- }
-
- // 새 하위 옵션 저장
- const handleSaveNewOptions = async () => {
- const validRows = newOptionRows.filter(row => row.optionValue.trim())
-
- if (validRows.length === 0) {
- toast.error("최소 하나의 옵션 값을 입력해주세요.")
- return
- }
-
- try {
- let successCount = 0
- let errorCount = 0
-
- for (const row of validRows) {
- try {
- const result = await createDocumentClassOption({
- comboBoxSettingId: comboBoxOption.id,
- optionValue: row.optionValue.trim(),
- optionCode: row.optionCode.trim() || undefined,
- sortOrder: row.sortOrder,
- })
-
- if (result.success) {
- successCount++
- } else {
- errorCount++
- }
- } catch (error) {
- console.error("하위 옵션 추가 실패:", error)
- errorCount++
- }
- }
-
- if (successCount > 0) {
- toast.success(`${successCount}개의 하위 옵션이 추가되었습니다.${errorCount > 0 ? ` (${errorCount}개 실패)` : ''}`)
- setNewOptionRows([])
- await loadOptions()
- onSuccess()
- } else {
- toast.error("모든 하위 옵션 추가에 실패했습니다.")
- }
- } catch (error) {
- console.error("하위 옵션 추가 실패:", error)
- toast.error("하위 옵션 추가에 실패했습니다.")
- }
- }
-
- // 기존 하위 옵션 수정
- const handleUpdateOption = async (option: DocumentClassOption) => {
- if (!option.optionValue.trim()) {
- toast.error("옵션 값은 필수 입력 항목입니다.")
- return
- }
-
- try {
- const result = await updateDocumentClassOption({
- id: option.id,
- optionValue: option.optionValue.trim(),
- optionCode: option.optionCode || undefined,
- sortOrder: option.sortOrder,
- isActive: option.isActive,
- })
-
- if (result.success) {
- await loadOptions()
- setEditingOption(null)
- toast.success("하위 옵션이 수정되었습니다.")
- } else {
- toast.error("하위 옵션 수정에 실패했습니다.")
- }
- } catch (error) {
- console.error("하위 옵션 수정 실패:", error)
- toast.error("하위 옵션 수정에 실패했습니다.")
- }
- }
-
- // 하위 옵션 삭제
- const handleDeleteOption = async (optionId: number) => {
- if (!confirm("정말로 이 하위 옵션을 삭제하시겠습니까?")) {
- return
- }
-
- try {
- const result = await deleteDocumentClassOption(optionId)
- if (result.success) {
- await loadOptions()
- toast.success("하위 옵션이 삭제되었습니다.")
- } else {
- toast.error("하위 옵션 삭제에 실패했습니다.")
- }
- } catch (error) {
- console.error("하위 옵션 삭제 실패:", error)
- toast.error("하위 옵션 삭제에 실패했습니다.")
- }
- }
-
- return (
- <Sheet open={open} onOpenChange={onOpenChange}>
- <SheetContent className="w-[600px] sm:max-w-[600px] overflow-y-auto">
- <SheetHeader>
- <SheetTitle>하위 옵션 관리</SheetTitle>
- <SheetDescription>
- {comboBoxOption.description} ({comboBoxOption.code})의 하위 옵션을 관리합니다.
- </SheetDescription>
- </SheetHeader>
-
- <div className="space-y-4 mt-6">
- {/* 새 하위 옵션 추가 */}
- <div className="space-y-2">
- <div className="flex items-center justify-between">
- <h4 className="text-sm font-medium">새 하위 옵션 추가</h4>
- <Button
- type="button"
- variant="outline"
- size="sm"
- onClick={addNewRow}
- className="h-8"
- >
- <Plus className="h-4 w-4 mr-1" />
- 옵션 추가
- </Button>
- </div>
-
- {newOptionRows.length > 0 && (
- <div className="border rounded-lg overflow-hidden">
- <Table>
- <TableHeader>
- <TableRow className="bg-muted/30">
- <TableHead className="w-[40%]">옵션 값 *</TableHead>
- <TableHead className="w-[30%]">옵션 코드</TableHead>
- <TableHead className="w-[20%]">순서</TableHead>
- <TableHead className="w-[10%]"></TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- {newOptionRows.map((row) => (
- <TableRow key={row.id} className="hover:bg-muted/30">
- <TableCell>
- <Input
- value={row.optionValue}
- onChange={(e) => updateNewRow(row.id, "optionValue", e.target.value)}
- placeholder="옵션 값"
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- </TableCell>
- <TableCell>
- <Input
- value={row.optionCode}
- onChange={(e) => updateNewRow(row.id, "optionCode", e.target.value)}
- placeholder="옵션 코드 (선택)"
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- </TableCell>
- <TableCell>
- <Input
- type="number"
- value={row.sortOrder}
- onChange={(e) => updateNewRow(row.id, "sortOrder", parseInt(e.target.value) || 0)}
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- </TableCell>
- <TableCell>
- <Button
- onClick={() => removeNewRow(row.id)}
- size="sm"
- variant="ghost"
- className="h-6 w-6 p-0"
- >
- <X className="h-3 w-3" />
- </Button>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- <div className="p-3 border-t">
- <Button
- onClick={handleSaveNewOptions}
- size="sm"
- className="h-8"
- >
- <Save className="h-4 w-4 mr-1" />
- 저장
- </Button>
- </div>
- </div>
- )}
- </div>
-
- {/* 기존 하위 옵션 목록 */}
- <div className="space-y-2">
- <h4 className="text-sm font-medium">기존 하위 옵션</h4>
- <div className="border rounded-lg overflow-hidden">
- <Table>
- <TableHeader>
- <TableRow className="bg-muted/30">
- <TableHead className="w-[35%]">옵션 값</TableHead>
- <TableHead className="w-[25%]">옵션 코드</TableHead>
- <TableHead className="w-[15%]">순서</TableHead>
- <TableHead className="w-[15%]">상태</TableHead>
- <TableHead className="w-[10%]">작업</TableHead>
- </TableRow>
- </TableHeader>
- <TableBody>
- {loading ? (
- <TableRow>
- <TableCell colSpan={5} className="text-center py-8">
- 로딩 중...
- </TableCell>
- </TableRow>
- ) : options.length === 0 ? (
- <TableRow>
- <TableCell colSpan={5} className="text-center text-muted-foreground py-8">
- 등록된 하위 옵션이 없습니다.
- </TableCell>
- </TableRow>
- ) : (
- options.map((option) => (
- <TableRow key={option.id} className="hover:bg-muted/30">
- <TableCell className="text-sm">
- {editingOption?.id === option.id ? (
- <Input
- value={editingOption.optionValue}
- onChange={(e) => setEditingOption(prev => prev ? { ...prev, optionValue: e.target.value } : null)}
- placeholder="옵션 값"
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- ) : (
- option.optionValue
- )}
- </TableCell>
- <TableCell className="text-sm">
- {editingOption?.id === option.id ? (
- <Input
- value={editingOption.optionCode || ""}
- onChange={(e) => setEditingOption(prev => prev ? { ...prev, optionCode: e.target.value } : null)}
- placeholder="옵션 코드"
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- ) : (
- option.optionCode || "-"
- )}
- </TableCell>
- <TableCell className="text-sm">
- {editingOption?.id === option.id ? (
- <Input
- type="number"
- value={editingOption.sortOrder}
- onChange={(e) => setEditingOption(prev => prev ? { ...prev, sortOrder: parseInt(e.target.value) || 0 } : null)}
- className="border-0 focus-visible:ring-1 bg-transparent h-8"
- />
- ) : (
- option.sortOrder
- )}
- </TableCell>
- <TableCell className="text-sm">
- <span className={`px-2 py-1 rounded text-xs ${
- option.isActive
- ? "bg-green-100 text-green-800"
- : "bg-red-100 text-red-800"
- }`}>
- {option.isActive ? "활성" : "비활성"}
- </span>
- </TableCell>
- <TableCell className="text-sm">
- {editingOption?.id === option.id ? (
- <div className="flex gap-1">
- <Button
- onClick={() => handleUpdateOption(editingOption)}
- size="sm"
- variant="outline"
- className="h-6 px-2 text-xs"
- >
- 저장
- </Button>
- <Button
- onClick={() => setEditingOption(null)}
- size="sm"
- variant="ghost"
- className="h-6 px-2 text-xs"
- >
- 취소
- </Button>
- </div>
- ) : (
- <div className="flex gap-1">
- <Button
- onClick={() => setEditingOption(option)}
- size="sm"
- variant="outline"
- className="h-6 px-2 text-xs"
- >
- 수정
- </Button>
- <Button
- onClick={() => handleDeleteOption(option.id)}
- size="sm"
- variant="ghost"
- className="h-6 px-2 text-xs text-red-600 hover:text-red-700"
- >
- <Trash2 className="h-3 w-3" />
- </Button>
- </div>
- )}
- </TableCell>
- </TableRow>
- ))
- )}
- </TableBody>
- </Table>
- </div>
- </div>
- </div>
- </SheetContent>
- </Sheet>
- )
-} \ No newline at end of file
diff --git a/lib/docu-list-rule/combo-box-settings/validation.ts b/lib/docu-list-rule/combo-box-settings/validation.ts
index a83651be..ca8e9192 100644
--- a/lib/docu-list-rule/combo-box-settings/validation.ts
+++ b/lib/docu-list-rule/combo-box-settings/validation.ts
@@ -1,12 +1,34 @@
-import { createSearchParamsCache } from "nuqs/server";
-import { parseAsInteger, parseAsString, parseAsArrayOf, parseAsStringEnum } from "nuqs/server";
-import { getSortingStateParser, getFiltersStateParser } from "@/lib/parsers";
+import {
+ createSearchParamsCache,
+ parseAsArrayOf,
+ parseAsInteger,
+ parseAsString,
+ parseAsStringEnum,
+} from "nuqs/server"
+import * as z from "zod"
-export const searchParamsComboBoxOptionsCache = createSearchParamsCache({
+import { getFiltersStateParser, getSortingStateParser } from "@/lib/parsers"
+import { codeGroups } from "@/db/schema/docu-list-rule";
+
+export const searchParamsComboBoxSettingsCache = createSearchParamsCache({
+ flags: parseAsArrayOf(z.enum(["advancedTable", "floatingBar"])).withDefault(
+ []
+ ),
page: parseAsInteger.withDefault(1),
perPage: parseAsInteger.withDefault(10),
- sort: getSortingStateParser<any>(),
- filters: getFiltersStateParser(),
- search: parseAsString.withDefault(""),
+ sort: getSortingStateParser<typeof codeGroups.$inferSelect>().withDefault([
+ { id: "groupId", desc: false },
+ ]),
+
+ groupId: parseAsString.withDefault(""),
+ description: parseAsString.withDefault(""),
+ controlType: parseAsString.withDefault(""),
+ isActive: parseAsString.withDefault(""),
+
+ // advanced filter
+ filters: getFiltersStateParser().withDefault([]),
joinOperator: parseAsStringEnum(["and", "or"]).withDefault("and"),
-}); \ No newline at end of file
+ search: parseAsString.withDefault(""),
+})
+
+export type GetComboBoxSettingsSchema = Awaited<ReturnType<typeof searchParamsComboBoxSettingsCache.parse>> \ No newline at end of file