"use client"; import * as React from "react"; import { z } from "zod"; import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { Check, ChevronsUpDown, Loader, LockIcon } from "lucide-react"; import { toast } from "sonner"; import { useRouter } from "next/navigation"; import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage, FormDescription, } from "@/components/ui/form"; import { Popover, PopoverTrigger, PopoverContent, } from "@/components/ui/popover" import { Command, CommandInput, CommandList, CommandGroup, CommandItem, CommandEmpty, } from "@/components/ui/command" import { DataTableColumnJSON } from "./form-data-table-columns"; import { updateFormDataInDB } from "@/lib/forms/services"; import { cn } from "@/lib/utils"; interface UpdateTagSheetProps extends React.ComponentPropsWithoutRef { open: boolean; onOpenChange: (open: boolean) => void; columns: DataTableColumnJSON[]; rowData: Record | null; formCode: string; contractItemId: number; editableFieldsMap?: Map; // 새로 추가 /** 업데이트 성공 시 호출될 콜백 */ onUpdateSuccess?: (updatedValues: Record) => void; } export function UpdateTagSheet({ open, onOpenChange, columns, rowData, formCode, contractItemId, editableFieldsMap = new Map(), // 기본값 설정 onUpdateSuccess, ...props }: UpdateTagSheetProps) { const [isPending, startTransition] = React.useTransition(); const router = useRouter(); // 현재 TAG의 편집 가능한 필드 목록 가져오기 const editableFields = React.useMemo(() => { if (!rowData?.TAG_NO || !editableFieldsMap.has(rowData.TAG_NO)) { return []; } return editableFieldsMap.get(rowData.TAG_NO) || []; }, [rowData?.TAG_NO, editableFieldsMap]); // 필드가 편집 가능한지 판별하는 함수 const isFieldEditable = React.useCallback((column: DataTableColumnJSON) => { // 1. SHI-only 필드는 편집 불가 if (column.shi === true) { return false; } // 2. TAG_NO와 TAG_DESC는 기본적으로 편집 가능 (필요에 따라 수정 가능) if (column.key === "TAG_NO" || column.key === "TAG_DESC") { return true; } // 3. editableFieldsMap이 있으면 해당 리스트에 있는지 확인 if (rowData?.TAG_NO && editableFieldsMap.has(rowData.TAG_NO)) { return editableFields.includes(column.key); } // 4. editableFieldsMap 정보가 없으면 기본적으로 편집 불가 (안전한 기본값) return false; }, [rowData?.TAG_NO, editableFieldsMap, editableFields]); // 읽기 전용 필드인지 판별하는 함수 (편집 가능의 반대) const isFieldReadOnly = React.useCallback((column: DataTableColumnJSON) => { return !isFieldEditable(column); }, [isFieldEditable]); // 읽기 전용 사유를 반환하는 함수 const getReadOnlyReason = React.useCallback((column: DataTableColumnJSON) => { if (column.shi === true) { return "SHI-only field (managed by SHI system)"; } if (column.key !== "TAG_NO" && column.key !== "TAG_DESC") { if (!rowData?.TAG_NO || !editableFieldsMap.has(rowData.TAG_NO)) { return "No editable fields information for this TAG"; } if (!editableFields.includes(column.key)) { return "Not editable for this TAG class"; } } return "Read-only field"; }, [rowData?.TAG_NO, editableFieldsMap, editableFields]); // 1) zod 스키마 const dynamicSchema = React.useMemo(() => { const shape: Record> = {}; for (const col of columns) { if (col.type === "NUMBER") { shape[col.key] = z .union([z.coerce.number(), z.nan()]) .transform((val) => (isNaN(val) ? undefined : val)) .optional(); } else { shape[col.key] = z.string().optional(); } } return z.object(shape); }, [columns]); // 2) form init const form = useForm({ resolver: zodResolver(dynamicSchema), defaultValues: React.useMemo(() => { if (!rowData) return {}; const defaults: Record = {}; for (const col of columns) { defaults[col.key] = rowData[col.key] ?? ""; } return defaults; }, [rowData, columns]), }); React.useEffect(() => { if (!rowData) { form.reset({}); return; } const defaults: Record = {}; for (const col of columns) { defaults[col.key] = rowData[col.key] ?? ""; } form.reset(defaults); }, [rowData, columns, form]); async function onSubmit(values: Record) { startTransition(async () => { try { // 제출 전에 읽기 전용 필드를 원본 값으로 복원 const finalValues = { ...values }; for (const col of columns) { if (isFieldReadOnly(col)) { // 읽기 전용 필드는 원본 값으로 복원 finalValues[col.key] = rowData?.[col.key] ?? ""; } } const { success, message } = await updateFormDataInDB( formCode, contractItemId, finalValues ); if (!success) { toast.error(message); return; } // Success handling toast.success("Updated successfully!"); // Create a merged object of original rowData and new values const updatedData = { ...rowData, ...finalValues, TAG_NO: rowData?.TAG_NO, }; // Call the success callback onUpdateSuccess?.(updatedData); // Refresh the entire route to get fresh data router.refresh(); // Close the sheet onOpenChange(false); } catch (error) { console.error("Error updating form data:", error); toast.error("An unexpected error occurred while updating"); } }); } // 편집 가능한 필드 개수 계산 const editableFieldCount = React.useMemo(() => { return columns.filter(col => isFieldEditable(col)).length; }, [columns, isFieldEditable]); return ( Update Row - {rowData?.TAG_NO || 'Unknown TAG'} Modify the fields below and save changes. Fields with are read-only.
{editableFieldCount} of {columns.length} fields are editable for this TAG.
{columns.map((col) => { const isReadOnly = isFieldReadOnly(col); const readOnlyReason = isReadOnly ? getReadOnlyReason(col) : ""; return ( { switch (col.type) { case "NUMBER": return ( {col.displayLabel || col.label} {isReadOnly && ( )} { const num = parseFloat(e.target.value); field.onChange(isNaN(num) ? "" : num); }} value={field.value ?? ""} className={cn( isReadOnly && "bg-gray-100 text-gray-600 cursor-not-allowed border-gray-300" )} /> {isReadOnly && ( {readOnlyReason} )} ); case "LIST": return ( {col.label} {isReadOnly && ( )} No option found. {col.options?.map((opt) => ( { field.onChange(opt); }} > {opt} ))} {isReadOnly && ( {readOnlyReason} )} ); case "STRING": default: return ( {col.label} {isReadOnly && ( )} {isReadOnly && ( {readOnlyReason} )} ); } }} /> ); })}
); }