"use client"; import { useState, useCallback, useEffect, useRef } from "react"; import { cn } from "@/lib/utils"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Search, ChevronRight } from "lucide-react"; import { usePopEvent } from "@/hooks/pop"; import type { PopSearchConfig, DatePresetOption, } from "./types"; import { DATE_PRESET_LABELS, computeDateRange } from "./types"; // ======================================== // 메인 컴포넌트 // ======================================== interface PopSearchComponentProps { config: PopSearchConfig; label?: string; screenId?: string; } const DEFAULT_CONFIG: PopSearchConfig = { inputType: "text", fieldName: "", placeholder: "검색어 입력", debounceMs: 500, triggerOnEnter: true, labelPosition: "top", labelText: "", labelVisible: true, }; export function PopSearchComponent({ config: rawConfig, label, screenId, }: PopSearchComponentProps) { const config = { ...DEFAULT_CONFIG, ...(rawConfig || {}) }; const { publish, setSharedData } = usePopEvent(screenId || ""); const [value, setValue] = useState(config.defaultValue ?? ""); const emitFilterChanged = useCallback( (newValue: unknown) => { if (!config.fieldName) return; setValue(newValue); setSharedData(`search_${config.fieldName}`, newValue); publish("filter_changed", { [config.fieldName]: newValue }); }, [config.fieldName, publish, setSharedData] ); const showLabel = config.labelVisible !== false && !!config.labelText; return (
{showLabel && ( {config.labelText} )}
); } // ======================================== // 서브타입 분기 렌더러 // ======================================== interface InputRendererProps { config: PopSearchConfig; value: unknown; onChange: (v: unknown) => void; } function SearchInputRenderer({ config, value, onChange }: InputRendererProps) { switch (config.inputType) { case "text": return ( ); case "select": return ( ); case "date-preset": return ; case "toggle": return ( ); case "modal-table": case "modal-card": case "modal-icon-grid": return ( ); default: return ; } } // ======================================== // text 서브타입: 디바운스 + Enter // ======================================== interface TextInputProps { config: PopSearchConfig; value: string; onChange: (v: unknown) => void; } function TextSearchInput({ config, value, onChange }: TextInputProps) { const [inputValue, setInputValue] = useState(value); const debounceRef = useRef | null>(null); useEffect(() => { setInputValue(value); }, [value]); useEffect(() => { return () => { if (debounceRef.current) clearTimeout(debounceRef.current); }; }, []); const handleChange = (e: React.ChangeEvent) => { const v = e.target.value; setInputValue(v); if (debounceRef.current) clearTimeout(debounceRef.current); const ms = config.debounceMs ?? 500; if (ms > 0) { debounceRef.current = setTimeout(() => onChange(v), ms); } }; const handleKeyDown = (e: React.KeyboardEvent) => { if (e.key === "Enter" && config.triggerOnEnter !== false) { if (debounceRef.current) clearTimeout(debounceRef.current); onChange(inputValue); } }; return (
); } // ======================================== // select 서브타입: 즉시 발행 // ======================================== interface SelectInputProps { config: PopSearchConfig; value: string; onChange: (v: unknown) => void; } function SelectSearchInput({ config, value, onChange }: SelectInputProps) { return ( ); } // ======================================== // date-preset 서브타입: 탭 버튼 + 즉시 발행 // ======================================== interface DatePresetInputProps { config: PopSearchConfig; value: unknown; onChange: (v: unknown) => void; } function DatePresetSearchInput({ config, value, onChange }: DatePresetInputProps) { const presets: DatePresetOption[] = config.datePresets || ["today", "this-week", "this-month"]; const currentPreset = value && typeof value === "object" && "preset" in (value as Record) ? (value as Record).preset : value; const handleSelect = (preset: DatePresetOption) => { if (preset === "custom") { onChange({ preset: "custom", from: "", to: "" }); return; } const range = computeDateRange(preset); if (range) onChange(range); }; return (
{presets.map((preset) => ( ))}
); } // ======================================== // toggle 서브타입: Switch + 즉시 발행 // ======================================== interface ToggleInputProps { value: boolean; onChange: (v: unknown) => void; } function ToggleSearchInput({ value, onChange }: ToggleInputProps) { return (
onChange(checked)} /> {value ? "ON" : "OFF"}
); } // ======================================== // modal-* 서브타입: readonly 입력 + 아이콘 (MVP: UI만) // ======================================== interface ModalInputProps { config: PopSearchConfig; value: string; } function ModalSearchInput({ config, value }: ModalInputProps) { return (
{value || config.placeholder || "선택..."}
); } // ======================================== // 미구현 서브타입 플레이스홀더 // ======================================== function PlaceholderInput({ inputType }: { inputType: string }) { return (
{inputType} (후속 구현 예정)
); }