diff options
| author | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-28 09:19:42 +0000 |
|---|---|---|
| committer | dujinkim <dujin.kim@dtsolution.co.kr> | 2025-07-28 09:19:42 +0000 |
| commit | 50ae0b8f02c034e60d4cbb504620dfa1575a836f (patch) | |
| tree | 24c661a0c7354e15ad56e2bded4d300bd7fd2b41 /lib/docu-list-rule/combo-box-settings/table/combo-box-options-expandable-row.tsx | |
| parent | 738f956aa61264ffa761e30398eca23393929f8c (diff) | |
(박서영) 설계 document Numbering Rule 개발-최겸 업로드
Diffstat (limited to 'lib/docu-list-rule/combo-box-settings/table/combo-box-options-expandable-row.tsx')
| -rw-r--r-- | lib/docu-list-rule/combo-box-settings/table/combo-box-options-expandable-row.tsx | 263 |
1 files changed, 263 insertions, 0 deletions
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 new file mode 100644 index 00000000..07b63de5 --- /dev/null +++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-expandable-row.tsx @@ -0,0 +1,263 @@ +"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 |
