"use client" import * as React from "react" import { zodResolver } from "@hookform/resolvers/zod" import { Loader2, Check, ChevronsUpDown } from "lucide-react" import { useForm } from "react-hook-form" import { toast } from "sonner" import { z } from "zod" import { Button } from "@/components/ui/button" import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form" import { Input } from "@/components/ui/input" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select" import { Sheet, SheetClose, SheetContent, SheetDescription, SheetFooter, SheetHeader, SheetTitle, } from "@/components/ui/sheet" import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover" import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, } from "@/components/ui/command" import { Badge } from "@/components/ui/badge" import { cn } from "@/lib/utils" import { Tag } from "@/db/schema/vendorData" import { updateTag, getSubfieldsByTagType, getClassOptions, TagTypeOption } from "@/lib/tags/service" // SubFieldDef 인터페이스 interface SubFieldDef { name: string label: string type: "select" | "text" options?: { value: string; label: string }[] expression?: string delimiter?: string } // 클래스 옵션 인터페이스 interface UpdatedClassOption { code: string label: string tagTypeCode: string tagTypeDescription?: string } // UpdateTagSchema 정의 const updateTagSchema = z.object({ class: z.string().min(1, "Class is required"), tagType: z.string().min(1, "Tag Type is required"), tagNo: z.string().min(1, "Tag Number is required"), description: z.string().optional(), // 추가 필드들은 동적으로 처리됨 }) // TypeScript 타입 정의 type UpdateTagSchema = z.infer & Record interface UpdateTagSheetProps extends React.ComponentPropsWithRef { tag: Tag | null selectedPackageId: number } export function UpdateTagSheet({ tag, selectedPackageId, ...props }: UpdateTagSheetProps) { const [isUpdatePending, startUpdateTransition] = React.useTransition() const [tagTypeList, setTagTypeList] = React.useState([]) const [selectedTagTypeCode, setSelectedTagTypeCode] = React.useState(null) const [subFields, setSubFields] = React.useState([]) const [classOptions, setClassOptions] = React.useState([]) const [classSearchTerm, setClassSearchTerm] = React.useState("") const [isLoadingClasses, setIsLoadingClasses] = React.useState(false) const [isLoadingSubFields, setIsLoadingSubFields] = React.useState(false) // ID management for popover elements const selectIdRef = React.useRef(0) const fieldIdsRef = React.useRef>({}) const classOptionIdsRef = React.useRef>({}) // Load class options when sheet opens React.useEffect(() => { const loadClassOptions = async () => { if (!props.open || !tag) return setIsLoadingClasses(true) try { const result = await getClassOptions(selectedPackageId) setClassOptions(result) } catch (err) { toast.error("클래스 옵션을 불러오는데 실패했습니다.") } finally { setIsLoadingClasses(false) } } loadClassOptions() }, [props.open, tag]) // Form setup const form = useForm({ resolver: zodResolver(updateTagSchema), defaultValues: { class: "", tagType: "", tagNo: "", description: "", }, }) // Load tag data into form when tag changes React.useEffect(() => { if (!tag) return // 필요한 필드만 선택적으로 추출 const formValues = { tagNo: tag.tagNo, tagType: tag.tagType, class: tag.class, description: tag.description || "" // 참고: 실제 태그 데이터에는 서브필드(functionCode, seqNumber 등)가 없음 }; // 폼 초기화 form.reset(formValues) // 태그 타입 코드 설정 (추가 필드 로딩을 위해) if (tag.tagType) { // 해당 태그 타입에 맞는 클래스 옵션을 찾아서 태그 타입 코드 설정 const foundClass = classOptions.find(opt => opt.label === tag.class) if (foundClass?.tagTypeCode) { setSelectedTagTypeCode(foundClass.tagTypeCode) loadSubFieldsByTagTypeCode(foundClass.tagTypeCode) } } }, [tag, classOptions, form]) // Load subfields by tag type code async function loadSubFieldsByTagTypeCode(tagTypeCode: string) { setIsLoadingSubFields(true) try { const { subFields: apiSubFields } = await getSubfieldsByTagType(tagTypeCode, selectedPackageId) const formattedSubFields: SubFieldDef[] = apiSubFields.map(field => ({ name: field.name, label: field.label, type: field.type, options: field.options || [], expression: field.expression ?? undefined, delimiter: field.delimiter ?? undefined, })) setSubFields(formattedSubFields) return true } catch (err) { toast.error("서브필드를 불러오는데 실패했습니다.") setSubFields([]) return false } finally { setIsLoadingSubFields(false) } } // Handle class selection async function handleSelectClass(classOption: UpdatedClassOption) { form.setValue("class", classOption.label, { shouldValidate: true }) if (classOption.tagTypeCode) { setSelectedTagTypeCode(classOption.tagTypeCode) // Set tag type const tagType = tagTypeList.find(t => t.id === classOption.tagTypeCode) if (tagType) { form.setValue("tagType", tagType.label, { shouldValidate: true }) } else if (classOption.tagTypeDescription) { form.setValue("tagType", classOption.tagTypeDescription, { shouldValidate: true }) } await loadSubFieldsByTagTypeCode(classOption.tagTypeCode) } } // Form submission handler function onSubmit(data: UpdateTagSchema) { startUpdateTransition(async () => { if (!tag) return try { // 기본 필드와 서브필드 데이터 결합 const tagData = { id: tag.id, tagType: data.tagType, class: data.class, tagNo: data.tagNo, description: data.description, ...Object.fromEntries( subFields.map(field => [field.name, data[field.name] || ""]) ), } const result = await updateTag(tagData, selectedPackageId) if ("error" in result) { toast.error(result.error) return } form.reset() props.onOpenChange?.(false) toast.success("태그가 성공적으로 업데이트되었습니다") } catch (error) { console.error("Error updating tag:", error) toast.error("태그 업데이트 중 오류가 발생했습니다") } }) } // Render class field function renderClassField(field: any) { const [popoverOpen, setPopoverOpen] = React.useState(false) const buttonId = React.useMemo( () => `class-button-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, [] ) const popoverContentId = React.useMemo( () => `class-popover-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, [] ) const commandId = React.useMemo( () => `class-command-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`, [] ) return ( Class 검색 결과가 없습니다. {classOptions.map((opt, optIndex) => { if (!classOptionIdsRef.current[opt.code]) { classOptionIdsRef.current[opt.code] = `class-${opt.code}-${Date.now()}-${Math.random() .toString(36) .slice(2, 9)}` } const optionId = classOptionIdsRef.current[opt.code] return ( { field.onChange(opt.label) setPopoverOpen(false) handleSelectClass(opt) }} value={opt.label} className="truncate" title={opt.label} > {opt.label} ) })} ) } // Render TagType field (readonly) function renderTagTypeField(field: any) { return ( Tag Type
) } // Render Tag Number field (readonly) function renderTagNoField(field: any) { return ( Tag Number
) } // Render form fields for each subfield function renderSubFields() { if (isLoadingSubFields) { return (
필드 로딩 중...
) } if (subFields.length === 0) { return null } return (
추가 필드
{subFields.map((sf, index) => ( ( {sf.label} {sf.type === "select" ? ( ) : ( )} {sf.expression && (

{sf.expression}

)}
)} /> ))}
) } // 컴포넌트 렌더링 return ( {/* */} 태그 수정 태그 정보를 업데이트하고 변경 사항을 저장하세요
{/* 기본 태그 정보 */}
{/* Class */} renderClassField(field)} /> {/* Tag Type */} renderTagTypeField(field)} /> {/* Tag Number */} renderTagNoField(field)} /> {/* Description */} ( Description )} />
{/* 서브필드 */} {renderSubFields()}
) }