diff options
Diffstat (limited to 'lib/docu-list-rule/combo-box-settings/table/combo-box-options-dialog.tsx')
| -rw-r--r-- | lib/docu-list-rule/combo-box-settings/table/combo-box-options-dialog.tsx | 234 |
1 files changed, 234 insertions, 0 deletions
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 new file mode 100644 index 00000000..6459ae14 --- /dev/null +++ b/lib/docu-list-rule/combo-box-settings/table/combo-box-options-dialog.tsx @@ -0,0 +1,234 @@ +"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 |
