"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 [options, setOptions] = useState([]); const [loading, setLoading] = useState(false); const [isSwapping, setIsSwapping] = useState(false); // 현재 선택된 값 const departureValue = formData[departureField] || ""; const destinationValue = formData[destinationField] || ""; // 옵션 로드 useEffect(() => { const loadOptions = async () => { if (dataSource.type === "static") { setOptions(dataSource.staticOptions || []); return; } if (dataSource.type === "code" && dataSource.codeCategory) { // 코드 관리에서 가져오기 setLoading(true); try { const response = await apiClient.get(`/api/codes/${dataSource.codeCategory}`); if (response.data.success && response.data.data) { const codeOptions = response.data.data.map((code: any) => ({ value: code.code_value || code.codeValue, label: code.code_name || code.codeName, })); 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(`/api/dynamic/${dataSource.tableName}`, { params: { pageSize: 1000 }, }); if (response.data.success && response.data.data) { const tableOptions = response.data.data.map((row: any) => ({ value: row[dataSource.valueField || "id"], label: row[dataSource.labelField || "name"], })); setOptions(tableOptions); } } catch (error) { console.error("테이블 데이터 로드 실패:", error); } finally { setLoading(false); } } }; if (!isDesignMode) { loadOptions(); } else { // 디자인 모드에서는 샘플 데이터 setOptions([ { value: "seoul", label: "서울" }, { value: "busan", label: "부산" }, { value: "pohang", label: "포항" }, { value: "gwangyang", label: "광양" }, ]); } }, [dataSource, isDesignMode]); // 출발지 변경 const handleDepartureChange = (value: string) => { if (onFormDataChange) { onFormDataChange(departureField, value); // 라벨 필드도 업데이트 if (departureLabelField) { const selectedOption = options.find((opt) => opt.value === value); onFormDataChange(departureLabelField, selectedOption?.label || ""); } } }; // 도착지 변경 const handleDestinationChange = (value: string) => { if (onFormDataChange) { onFormDataChange(destinationField, value); // 라벨 필드도 업데이트 if (destinationLabelField) { const selectedOption = options.find((opt) => opt.value === value); onFormDataChange(destinationLabelField, selectedOption?.label || ""); } } }; // 출발지/도착지 교환 const handleSwap = () => { if (!onFormDataChange) return; setIsSwapping(true); // 값 교환 const tempDeparture = departureValue; const tempDestination = destinationValue; onFormDataChange(departureField, tempDestination); onFormDataChange(destinationField, tempDeparture); // 라벨도 교환 if (departureLabelField && destinationLabelField) { const tempDepartureLabel = formData[departureLabelField]; const tempDestinationLabel = formData[destinationLabelField]; onFormDataChange(departureLabelField, tempDestinationLabel); onFormDataChange(destinationLabelField, tempDepartureLabel); } // 애니메이션 효과 setTimeout(() => setIsSwapping(false), 300); }; // 선택된 라벨 가져오기 const getDepartureLabel = () => { const option = options.find((opt) => opt.value === departureValue); return option?.label || "선택"; }; const getDestinationLabel = () => { const option = options.find((opt) => opt.value === destinationValue); return option?.label || "선택"; }; // 스타일에서 width, height 추출 const { width, height, ...restStyle } = style || {}; // Card 스타일 (이미지 참고) if (variant === "card") { return (
{/* 출발지 */}
{departureLabel}
{/* 교환 버튼 */} {showSwapButton && ( )} {/* 도착지 */}
{destinationLabel}
); } // Inline 스타일 if (variant === "inline") { return (
{showSwapButton && ( )}
); } // Minimal 스타일 return (
{showSwapButton && ( )}
); }