"use client"; import React, { useState, useEffect } from "react"; import { ArrowLeftRight, ChevronDown } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { cn } from "@/lib/utils"; import { apiClient } from "@/lib/api/client"; interface LocationOption { value: string; label: string; } interface DataSourceConfig { type: "table" | "code" | "static"; tableName?: string; valueField?: string; labelField?: string; codeCategory?: string; staticOptions?: LocationOption[]; } export interface LocationSwapSelectorProps { // 기본 props id?: string; style?: React.CSSProperties; isDesignMode?: boolean; // 데이터 소스 설정 dataSource?: DataSourceConfig; // 필드 매핑 departureField?: string; destinationField?: string; departureLabelField?: string; destinationLabelField?: string; // UI 설정 departureLabel?: string; destinationLabel?: string; showSwapButton?: boolean; swapButtonPosition?: "center" | "right"; variant?: "card" | "inline" | "minimal"; // 폼 데이터 formData?: Record; onFormDataChange?: (field: string, value: any) => void; // componentConfig (화면 디자이너에서 전달) componentConfig?: { dataSource?: DataSourceConfig; departureField?: string; destinationField?: string; departureLabelField?: string; destinationLabelField?: string; departureLabel?: string; destinationLabel?: string; showSwapButton?: boolean; swapButtonPosition?: "center" | "right"; variant?: "card" | "inline" | "minimal"; }; } /** * LocationSwapSelector 컴포넌트 * 출발지/도착지 선택 및 교환 기능 */ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) { const { id, style, isDesignMode = false, formData = {}, onFormDataChange, componentConfig, } = props; // componentConfig에서 설정 가져오기 (우선순위: componentConfig > props) const config = componentConfig || {}; const dataSource = config.dataSource || props.dataSource || { type: "static", staticOptions: [] }; const departureField = config.departureField || props.departureField || "departure"; const destinationField = config.destinationField || props.destinationField || "destination"; const departureLabelField = config.departureLabelField || props.departureLabelField; const destinationLabelField = config.destinationLabelField || props.destinationLabelField; const departureLabel = config.departureLabel || props.departureLabel || "출발지"; const destinationLabel = config.destinationLabel || props.destinationLabel || "도착지"; const showSwapButton = config.showSwapButton !== false && props.showSwapButton !== false; const variant = config.variant || props.variant || "card"; // 기본 옵션 (포항/광양) const DEFAULT_OPTIONS: LocationOption[] = [ { value: "pohang", label: "포항" }, { value: "gwangyang", label: "광양" }, ]; // 상태 const [options, setOptions] = useState(DEFAULT_OPTIONS); const [loading, setLoading] = useState(false); const [isSwapping, setIsSwapping] = useState(false); // 로컬 선택 상태 (Select 컴포넌트용) const [localDeparture, setLocalDeparture] = useState(""); const [localDestination, setLocalDestination] = useState(""); // 옵션 로드 useEffect(() => { const loadOptions = async () => { console.log("[LocationSwapSelector] 옵션 로드 시작:", { dataSource, isDesignMode }); // 정적 옵션 처리 (기본값) // type이 없거나 static이거나, table인데 tableName이 없는 경우 const shouldUseStatic = !dataSource.type || dataSource.type === "static" || (dataSource.type === "table" && !dataSource.tableName) || (dataSource.type === "code" && !dataSource.codeCategory); if (shouldUseStatic) { const staticOpts = dataSource.staticOptions || []; // 정적 옵션이 설정되어 있고, value가 유효한 경우 사용 // (value가 필드명과 같으면 잘못 설정된 것이므로 기본값 사용) const isValidOptions = staticOpts.length > 0 && staticOpts[0]?.value && staticOpts[0].value !== departureField && staticOpts[0].value !== destinationField; if (isValidOptions) { console.log("[LocationSwapSelector] 정적 옵션 사용:", staticOpts); setOptions(staticOpts); } else { // 기본값 (포항/광양) console.log("[LocationSwapSelector] 기본 옵션 사용 (잘못된 설정 감지):", { staticOpts, DEFAULT_OPTIONS }); setOptions(DEFAULT_OPTIONS); } return; } if (dataSource.type === "code" && dataSource.codeCategory) { // 코드 관리에서 가져오기 setLoading(true); try { const response = await apiClient.get(`/code-management/codes`, { params: { categoryCode: dataSource.codeCategory }, }); if (response.data.success && response.data.data) { const codeOptions = response.data.data.map((code: any) => ({ value: code.code_value || code.codeValue || code.code, label: code.code_name || code.codeName || code.name, })); setOptions(codeOptions); } } catch (error) { console.error("코드 로드 실패:", error); } finally { setLoading(false); } return; } if (dataSource.type === "table" && dataSource.tableName) { // 테이블에서 가져오기 setLoading(true); try { const response = await apiClient.get(`/dynamic-form/list/${dataSource.tableName}`, { params: { page: 1, pageSize: 1000 }, }); if (response.data.success && response.data.data) { // data가 배열인지 또는 data.rows인지 확인 const rows = Array.isArray(response.data.data) ? response.data.data : response.data.data.rows || []; const tableOptions = rows.map((row: any) => ({ value: String(row[dataSource.valueField || "id"] || ""), label: String(row[dataSource.labelField || "name"] || ""), })); setOptions(tableOptions); } } catch (error) { console.error("테이블 데이터 로드 실패:", error); } finally { setLoading(false); } } }; loadOptions(); }, [dataSource, isDesignMode]); // formData에서 초기값 동기화 useEffect(() => { const depVal = formData[departureField]; const destVal = formData[destinationField]; if (depVal && options.some(o => o.value === depVal)) { setLocalDeparture(depVal); } if (destVal && options.some(o => o.value === destVal)) { setLocalDestination(destVal); } }, [formData, departureField, destinationField, options]); // 출발지 변경 const handleDepartureChange = (selectedValue: string) => { console.log("[LocationSwapSelector] 출발지 변경:", { selectedValue, departureField, hasOnFormDataChange: !!onFormDataChange, options }); // 로컬 상태 업데이트 setLocalDeparture(selectedValue); // 부모에게 전달 if (onFormDataChange) { console.log(`[LocationSwapSelector] onFormDataChange 호출: ${departureField} = ${selectedValue}`); onFormDataChange(departureField, selectedValue); // 라벨 필드도 업데이트 if (departureLabelField) { const selectedOption = options.find((opt) => opt.value === selectedValue); if (selectedOption) { console.log(`[LocationSwapSelector] onFormDataChange 호출: ${departureLabelField} = ${selectedOption.label}`); onFormDataChange(departureLabelField, selectedOption.label); } } } else { console.warn("[LocationSwapSelector] ⚠️ onFormDataChange가 없습니다!"); } }; // 도착지 변경 const handleDestinationChange = (selectedValue: string) => { console.log("[LocationSwapSelector] 도착지 변경:", { selectedValue, destinationField, hasOnFormDataChange: !!onFormDataChange, options }); // 로컬 상태 업데이트 setLocalDestination(selectedValue); // 부모에게 전달 if (onFormDataChange) { console.log(`[LocationSwapSelector] onFormDataChange 호출: ${destinationField} = ${selectedValue}`); onFormDataChange(destinationField, selectedValue); // 라벨 필드도 업데이트 if (destinationLabelField) { const selectedOption = options.find((opt) => opt.value === selectedValue); if (selectedOption) { console.log(`[LocationSwapSelector] onFormDataChange 호출: ${destinationLabelField} = ${selectedOption.label}`); onFormDataChange(destinationLabelField, selectedOption.label); } } } else { console.warn("[LocationSwapSelector] ⚠️ onFormDataChange가 없습니다!"); } }; // 출발지/도착지 교환 const handleSwap = () => { setIsSwapping(true); // 로컬 상태 교환 const tempDeparture = localDeparture; const tempDestination = localDestination; setLocalDeparture(tempDestination); setLocalDestination(tempDeparture); // 부모에게 전달 if (onFormDataChange) { onFormDataChange(departureField, tempDestination); onFormDataChange(destinationField, tempDeparture); // 라벨도 교환 if (departureLabelField && destinationLabelField) { const depOption = options.find(o => o.value === tempDestination); const destOption = options.find(o => o.value === tempDeparture); onFormDataChange(departureLabelField, depOption?.label || ""); onFormDataChange(destinationLabelField, destOption?.label || ""); } } // 애니메이션 효과 setTimeout(() => setIsSwapping(false), 300); }; // 스타일에서 width, height 추출 const { width, height, ...restStyle } = style || {}; // 선택된 라벨 가져오기 const getDepartureLabel = () => { const opt = options.find(o => o.value === localDeparture); return opt?.label || ""; }; const getDestinationLabel = () => { const opt = options.find(o => o.value === localDestination); return opt?.label || ""; }; // 디버그 로그 console.log("[LocationSwapSelector] 렌더:", { localDeparture, localDestination, options: options.map(o => `${o.value}:${o.label}`), }); // Card 스타일 (이미지 참고) if (variant === "card") { return (
{/* 출발지 */}
{departureLabel}
{/* 교환 버튼 */} {showSwapButton && ( )} {/* 도착지 */}
{destinationLabel}
); } // Inline 스타일 if (variant === "inline") { return (
{showSwapButton && ( )}
); } // Minimal 스타일 return (
{showSwapButton && ( )}
); }