diff --git a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx index e6976844..662f6379 100644 --- a/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx +++ b/frontend/lib/registry/components/universal-form-modal/UniversalFormModalComponent.tsx @@ -19,7 +19,9 @@ import { AlertDialogTitle, } from "@/components/ui/alert-dialog"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; -import { ChevronDown, ChevronUp, ChevronRight, Plus, Trash2, Loader2 } from "lucide-react"; +import { ChevronDown, ChevronUp, ChevronRight, Plus, Trash2, Loader2, Check, ChevronsUpDown } from "lucide-react"; +import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; +import { Command, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; import { toast } from "sonner"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; @@ -42,6 +44,7 @@ import { TableSectionRenderer } from "./TableSectionRenderer"; /** * ๐Ÿ”— ์—ฐ์‡„ ๋“œ๋กญ๋‹ค์šด Select ํ•„๋“œ ์ปดํฌ๋„ŒํŠธ + * allowCustomInput์ด true์ด๋ฉด Combobox ํ˜•ํƒœ๋กœ ์ง์ ‘ ์ž…๋ ฅ ๊ฐ€๋Šฅ */ interface CascadingSelectFieldProps { fieldId: string; @@ -51,6 +54,7 @@ interface CascadingSelectFieldProps { onChange: (value: string) => void; placeholder?: string; disabled?: boolean; + allowCustomInput?: boolean; } const CascadingSelectField: React.FC = ({ @@ -61,12 +65,20 @@ const CascadingSelectField: React.FC = ({ onChange, placeholder, disabled, + allowCustomInput = false, }) => { + const [open, setOpen] = useState(false); + const [inputValue, setInputValue] = useState(value || ""); const { options, loading } = useCascadingDropdown({ config, parentValue, }); + // value๊ฐ€ ์™ธ๋ถ€์—์„œ ๋ณ€๊ฒฝ๋˜๋ฉด inputValue๋„ ๋™๊ธฐํ™” + useEffect(() => { + setInputValue(value || ""); + }, [value]); + const getPlaceholder = () => { if (!parentValue) { return config.emptyParentMessage || "์ƒ์œ„ ํ•ญ๋ชฉ์„ ๋จผ์ € ์„ ํƒํ•˜์„ธ์š”"; @@ -82,6 +94,79 @@ const CascadingSelectField: React.FC = ({ const isDisabled = disabled || !parentValue || loading; + // Combobox ํ˜•ํƒœ (์ง์ ‘ ์ž…๋ ฅ ํ—ˆ์šฉ) + if (allowCustomInput) { + return ( + + +
+ { + setInputValue(e.target.value); + onChange(e.target.value); + }} + placeholder={getPlaceholder()} + disabled={isDisabled} + className="w-full pr-8" + /> + +
+
+ + + + + + {!parentValue + ? config.emptyParentMessage || "์ƒ์œ„ ํ•ญ๋ชฉ์„ ๋จผ์ € ์„ ํƒํ•˜์„ธ์š”" + : config.noOptionsMessage || "์„ ํƒ ๊ฐ€๋Šฅํ•œ ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค"} + + + {options + .filter((option) => option.value && option.value !== "") + .map((option) => ( + { + setInputValue(option.label); + onChange(option.value); + setOpen(false); + }} + > + + {option.label} + + ))} + + + + +
+ ); + } + + // ๊ธฐ๋ณธ Select ํ˜•ํƒœ (๋ชฉ๋ก์—์„œ๋งŒ ์„ ํƒ) return ( { + setInputValue(e.target.value); + onChange(e.target.value); + }} + placeholder={loading ? "๋กœ๋”ฉ ์ค‘..." : placeholder} + disabled={disabled || loading} + className="w-full pr-8" + /> + + + + + + + + ์„ ํƒ ๊ฐ€๋Šฅํ•œ ํ•ญ๋ชฉ์ด ์—†์Šต๋‹ˆ๋‹ค + + {options + .filter((option) => option.value && option.value !== "") + .map((option) => ( + { + setInputValue(option.label); + onChange(option.value); + setOpen(false); + }} + > + + {option.label} + + ))} + + + + + + ); + } + + // ๊ธฐ๋ณธ Select ํ˜•ํƒœ (๋ชฉ๋ก์—์„œ๋งŒ ์„ ํƒ) return (