"use client"; /** * πŸ”— 연쇄 λ“œλ‘­λ‹€μš΄(Cascading Dropdown) μ»΄ν¬λ„ŒνŠΈ * * λΆ€λͺ¨ ν•„λ“œμ˜ 값에 따라 μ˜΅μ…˜μ΄ λ™μ μœΌλ‘œ λ³€κ²½λ˜λŠ” λ“œλ‘­λ‹€μš΄μž…λ‹ˆλ‹€. * * @example * // μ°½κ³  β†’ μœ„μΉ˜ 연쇄 λ“œλ‘­λ‹€μš΄ * setFormData({ ...formData, location_code: value })} * placeholder="μœ„μΉ˜ 선택" * /> */ import React, { useEffect, useRef } from "react"; import { Loader2 } from "lucide-react"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { useCascadingDropdown, CascadingOption } from "@/hooks/useCascadingDropdown"; import { CascadingDropdownConfig } from "@/types/screen-management"; import { cn } from "@/lib/utils"; export interface CascadingDropdownProps { /** 연쇄 λ“œλ‘­λ‹€μš΄ μ„€μ • */ config: CascadingDropdownConfig; /** λΆ€λͺ¨ ν•„λ“œμ˜ ν˜„μž¬ κ°’ */ parentValue?: string | number | null; /** ν˜„μž¬ μ„ νƒλœ κ°’ */ value?: string; /** κ°’ λ³€κ²½ ν•Έλ“€λŸ¬ */ onChange?: (value: string, option?: CascadingOption) => void; /** ν”Œλ ˆμ΄μŠ€ν™€λ” */ placeholder?: string; /** λΉ„ν™œμ„±ν™” μ—¬λΆ€ */ disabled?: boolean; /** 읽기 μ „μš© μ—¬λΆ€ */ readOnly?: boolean; /** ν•„μˆ˜ μž…λ ₯ μ—¬λΆ€ */ required?: boolean; /** μΆ”κ°€ 클래슀λͺ… */ className?: string; /** μΆ”κ°€ μŠ€νƒ€μΌ */ style?: React.CSSProperties; /** 검색 κ°€λŠ₯ μ—¬λΆ€ */ searchable?: boolean; } export function CascadingDropdown({ config, parentValue, value, onChange, placeholder, disabled = false, readOnly = false, required = false, className, style, searchable = false, }: CascadingDropdownProps) { const prevParentValueRef = useRef(undefined); const { options, loading, error, getLabelByValue, } = useCascadingDropdown({ config, parentValue, }); // λΆ€λͺ¨ κ°’ λ³€κ²½ μ‹œ μžλ™ μ΄ˆκΈ°ν™” useEffect(() => { if (config.clearOnParentChange !== false) { if (prevParentValueRef.current !== undefined && prevParentValueRef.current !== parentValue && value) { // λΆ€λͺ¨ 값이 λ³€κ²½λ˜λ©΄ ν˜„μž¬ κ°’ μ΄ˆκΈ°ν™” onChange?.(""); } } prevParentValueRef.current = parentValue; }, [parentValue, config.clearOnParentChange, value, onChange]); // λΆ€λͺ¨ 값이 없을 λ•Œ λ©”μ‹œμ§€ const getPlaceholder = () => { if (!parentValue) { return config.emptyParentMessage || "μƒμœ„ ν•­λͺ©μ„ λ¨Όμ € μ„ νƒν•˜μ„Έμš”"; } if (loading) { return config.loadingMessage || "λ‘œλ”© 쀑..."; } if (options.length === 0) { return config.noOptionsMessage || "선택 κ°€λŠ₯ν•œ ν•­λͺ©μ΄ μ—†μŠ΅λ‹ˆλ‹€"; } return placeholder || "μ„ νƒν•˜μ„Έμš”"; }; // κ°’ λ³€κ²½ ν•Έλ“€λŸ¬ const handleValueChange = (newValue: string) => { if (readOnly) return; const selectedOption = options.find((opt) => opt.value === newValue); onChange?.(newValue, selectedOption); }; // λΉ„ν™œμ„±ν™” μƒνƒœ 계산 const isDisabled = disabled || readOnly || !parentValue || loading; return (
{/* μ—λŸ¬ λ©”μ‹œμ§€ */} {error && (

{error}

)}
); } export default CascadingDropdown;