출발지도착지 선택 가능하고 교환버튼 작동하게
This commit is contained in:
parent
d7ee63a857
commit
fd7a1bbf53
|
|
@ -94,21 +94,21 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
|
||||||
const showSwapButton = config.showSwapButton !== false && props.showSwapButton !== false;
|
const showSwapButton = config.showSwapButton !== false && props.showSwapButton !== false;
|
||||||
const variant = config.variant || props.variant || "card";
|
const variant = config.variant || props.variant || "card";
|
||||||
|
|
||||||
// 상태
|
|
||||||
const [options, setOptions] = useState<LocationOption[]>([]);
|
|
||||||
const [loading, setLoading] = useState(false);
|
|
||||||
const [isSwapping, setIsSwapping] = useState(false);
|
|
||||||
|
|
||||||
// 현재 선택된 값
|
|
||||||
const departureValue = formData[departureField] || "";
|
|
||||||
const destinationValue = formData[destinationField] || "";
|
|
||||||
|
|
||||||
// 기본 옵션 (포항/광양)
|
// 기본 옵션 (포항/광양)
|
||||||
const DEFAULT_OPTIONS: LocationOption[] = [
|
const DEFAULT_OPTIONS: LocationOption[] = [
|
||||||
{ value: "pohang", label: "포항" },
|
{ value: "pohang", label: "포항" },
|
||||||
{ value: "gwangyang", label: "광양" },
|
{ value: "gwangyang", label: "광양" },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// 상태
|
||||||
|
const [options, setOptions] = useState<LocationOption[]>(DEFAULT_OPTIONS);
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [isSwapping, setIsSwapping] = useState(false);
|
||||||
|
|
||||||
|
// 로컬 선택 상태 (Select 컴포넌트용)
|
||||||
|
const [localDeparture, setLocalDeparture] = useState<string>("");
|
||||||
|
const [localDestination, setLocalDestination] = useState<string>("");
|
||||||
|
|
||||||
// 옵션 로드
|
// 옵션 로드
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const loadOptions = async () => {
|
const loadOptions = async () => {
|
||||||
|
|
@ -124,13 +124,19 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
|
||||||
|
|
||||||
if (shouldUseStatic) {
|
if (shouldUseStatic) {
|
||||||
const staticOpts = dataSource.staticOptions || [];
|
const staticOpts = dataSource.staticOptions || [];
|
||||||
// 정적 옵션이 설정되어 있으면 사용
|
// 정적 옵션이 설정되어 있고, value가 유효한 경우 사용
|
||||||
if (staticOpts.length > 0 && staticOpts[0]?.value) {
|
// (value가 필드명과 같으면 잘못 설정된 것이므로 기본값 사용)
|
||||||
|
const isValidOptions = staticOpts.length > 0 &&
|
||||||
|
staticOpts[0]?.value &&
|
||||||
|
staticOpts[0].value !== departureField &&
|
||||||
|
staticOpts[0].value !== destinationField;
|
||||||
|
|
||||||
|
if (isValidOptions) {
|
||||||
console.log("[LocationSwapSelector] 정적 옵션 사용:", staticOpts);
|
console.log("[LocationSwapSelector] 정적 옵션 사용:", staticOpts);
|
||||||
setOptions(staticOpts);
|
setOptions(staticOpts);
|
||||||
} else {
|
} else {
|
||||||
// 기본값 (포항/광양)
|
// 기본값 (포항/광양)
|
||||||
console.log("[LocationSwapSelector] 기본 옵션 사용:", DEFAULT_OPTIONS);
|
console.log("[LocationSwapSelector] 기본 옵션 사용 (잘못된 설정 감지):", { staticOpts, DEFAULT_OPTIONS });
|
||||||
setOptions(DEFAULT_OPTIONS);
|
setOptions(DEFAULT_OPTIONS);
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|
@ -187,69 +193,109 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
|
||||||
loadOptions();
|
loadOptions();
|
||||||
}, [dataSource, isDesignMode]);
|
}, [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 = (value: string) => {
|
const handleDepartureChange = (selectedValue: string) => {
|
||||||
|
console.log("[LocationSwapSelector] 출발지 변경:", { selectedValue, options });
|
||||||
|
|
||||||
|
// 로컬 상태 업데이트
|
||||||
|
setLocalDeparture(selectedValue);
|
||||||
|
|
||||||
|
// 부모에게 전달
|
||||||
if (onFormDataChange) {
|
if (onFormDataChange) {
|
||||||
onFormDataChange(departureField, value);
|
onFormDataChange(departureField, selectedValue);
|
||||||
// 라벨 필드도 업데이트
|
// 라벨 필드도 업데이트
|
||||||
if (departureLabelField) {
|
if (departureLabelField) {
|
||||||
const selectedOption = options.find((opt) => opt.value === value);
|
const selectedOption = options.find((opt) => opt.value === selectedValue);
|
||||||
onFormDataChange(departureLabelField, selectedOption?.label || "");
|
if (selectedOption) {
|
||||||
|
onFormDataChange(departureLabelField, selectedOption.label);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 도착지 변경
|
// 도착지 변경
|
||||||
const handleDestinationChange = (value: string) => {
|
const handleDestinationChange = (selectedValue: string) => {
|
||||||
|
console.log("[LocationSwapSelector] 도착지 변경:", { selectedValue, options });
|
||||||
|
|
||||||
|
// 로컬 상태 업데이트
|
||||||
|
setLocalDestination(selectedValue);
|
||||||
|
|
||||||
|
// 부모에게 전달
|
||||||
if (onFormDataChange) {
|
if (onFormDataChange) {
|
||||||
onFormDataChange(destinationField, value);
|
onFormDataChange(destinationField, selectedValue);
|
||||||
// 라벨 필드도 업데이트
|
// 라벨 필드도 업데이트
|
||||||
if (destinationLabelField) {
|
if (destinationLabelField) {
|
||||||
const selectedOption = options.find((opt) => opt.value === value);
|
const selectedOption = options.find((opt) => opt.value === selectedValue);
|
||||||
onFormDataChange(destinationLabelField, selectedOption?.label || "");
|
if (selectedOption) {
|
||||||
|
onFormDataChange(destinationLabelField, selectedOption.label);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 출발지/도착지 교환
|
// 출발지/도착지 교환
|
||||||
const handleSwap = () => {
|
const handleSwap = () => {
|
||||||
if (!onFormDataChange) return;
|
|
||||||
|
|
||||||
setIsSwapping(true);
|
setIsSwapping(true);
|
||||||
|
|
||||||
// 값 교환
|
// 로컬 상태 교환
|
||||||
const tempDeparture = departureValue;
|
const tempDeparture = localDeparture;
|
||||||
const tempDestination = destinationValue;
|
const tempDestination = localDestination;
|
||||||
|
|
||||||
|
setLocalDeparture(tempDestination);
|
||||||
|
setLocalDestination(tempDeparture);
|
||||||
|
|
||||||
onFormDataChange(departureField, tempDestination);
|
// 부모에게 전달
|
||||||
onFormDataChange(destinationField, tempDeparture);
|
if (onFormDataChange) {
|
||||||
|
onFormDataChange(departureField, tempDestination);
|
||||||
|
onFormDataChange(destinationField, tempDeparture);
|
||||||
|
|
||||||
// 라벨도 교환
|
// 라벨도 교환
|
||||||
if (departureLabelField && destinationLabelField) {
|
if (departureLabelField && destinationLabelField) {
|
||||||
const tempDepartureLabel = formData[departureLabelField];
|
const depOption = options.find(o => o.value === tempDestination);
|
||||||
const tempDestinationLabel = formData[destinationLabelField];
|
const destOption = options.find(o => o.value === tempDeparture);
|
||||||
onFormDataChange(departureLabelField, tempDestinationLabel);
|
onFormDataChange(departureLabelField, depOption?.label || "");
|
||||||
onFormDataChange(destinationLabelField, tempDepartureLabel);
|
onFormDataChange(destinationLabelField, destOption?.label || "");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 애니메이션 효과
|
// 애니메이션 효과
|
||||||
setTimeout(() => setIsSwapping(false), 300);
|
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 추출
|
// 스타일에서 width, height 추출
|
||||||
const { width, height, ...restStyle } = style || {};
|
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 스타일 (이미지 참고)
|
// Card 스타일 (이미지 참고)
|
||||||
if (variant === "card") {
|
if (variant === "card") {
|
||||||
return (
|
return (
|
||||||
|
|
@ -263,27 +309,26 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
|
||||||
<div className="flex flex-1 flex-col items-center">
|
<div className="flex flex-1 flex-col items-center">
|
||||||
<span className="mb-1 text-xs text-muted-foreground">{departureLabel}</span>
|
<span className="mb-1 text-xs text-muted-foreground">{departureLabel}</span>
|
||||||
<Select
|
<Select
|
||||||
value={departureValue}
|
value={localDeparture || undefined}
|
||||||
onValueChange={handleDepartureChange}
|
onValueChange={handleDepartureChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-auto w-full max-w-[120px] border-0 bg-transparent p-0 text-center text-lg font-bold shadow-none focus:ring-0">
|
<SelectTrigger className={cn(
|
||||||
<SelectValue placeholder="선택">
|
"h-auto w-full max-w-[120px] border-0 bg-transparent p-0 text-center text-lg font-bold shadow-none focus:ring-0",
|
||||||
<span className={cn(isSwapping && "animate-pulse")}>
|
isSwapping && "animate-pulse"
|
||||||
{getDepartureLabel()}
|
)}>
|
||||||
</span>
|
{localDeparture ? (
|
||||||
</SelectValue>
|
<span>{getDepartureLabel()}</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-muted-foreground">선택</span>
|
||||||
|
)}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="popper" sideOffset={4}>
|
<SelectContent position="popper" sideOffset={4}>
|
||||||
{options.length > 0 ? (
|
{options.map((option) => (
|
||||||
options.map((option) => (
|
<SelectItem key={option.value} value={option.value}>
|
||||||
<SelectItem key={option.value} value={option.value}>
|
{option.label}
|
||||||
{option.label}
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="px-2 py-1.5 text-sm text-muted-foreground">옵션 없음</div>
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -308,27 +353,26 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
|
||||||
<div className="flex flex-1 flex-col items-center">
|
<div className="flex flex-1 flex-col items-center">
|
||||||
<span className="mb-1 text-xs text-muted-foreground">{destinationLabel}</span>
|
<span className="mb-1 text-xs text-muted-foreground">{destinationLabel}</span>
|
||||||
<Select
|
<Select
|
||||||
value={destinationValue}
|
value={localDestination || undefined}
|
||||||
onValueChange={handleDestinationChange}
|
onValueChange={handleDestinationChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-auto w-full max-w-[120px] border-0 bg-transparent p-0 text-center text-lg font-bold shadow-none focus:ring-0">
|
<SelectTrigger className={cn(
|
||||||
<SelectValue placeholder="선택">
|
"h-auto w-full max-w-[120px] border-0 bg-transparent p-0 text-center text-lg font-bold shadow-none focus:ring-0",
|
||||||
<span className={cn(isSwapping && "animate-pulse")}>
|
isSwapping && "animate-pulse"
|
||||||
{getDestinationLabel()}
|
)}>
|
||||||
</span>
|
{localDestination ? (
|
||||||
</SelectValue>
|
<span>{getDestinationLabel()}</span>
|
||||||
|
) : (
|
||||||
|
<span className="text-muted-foreground">선택</span>
|
||||||
|
)}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="popper" sideOffset={4}>
|
<SelectContent position="popper" sideOffset={4}>
|
||||||
{options.length > 0 ? (
|
{options.map((option) => (
|
||||||
options.map((option) => (
|
<SelectItem key={option.value} value={option.value}>
|
||||||
<SelectItem key={option.value} value={option.value}>
|
{option.label}
|
||||||
{option.label}
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="px-2 py-1.5 text-sm text-muted-foreground">옵션 없음</div>
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -348,23 +392,19 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<label className="mb-1 block text-xs text-muted-foreground">{departureLabel}</label>
|
<label className="mb-1 block text-xs text-muted-foreground">{departureLabel}</label>
|
||||||
<Select
|
<Select
|
||||||
value={departureValue}
|
value={localDeparture || undefined}
|
||||||
onValueChange={handleDepartureChange}
|
onValueChange={handleDepartureChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-10">
|
<SelectTrigger className="h-10">
|
||||||
<SelectValue placeholder="선택" />
|
{localDeparture ? getDepartureLabel() : <span className="text-muted-foreground">선택</span>}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="popper" sideOffset={4}>
|
<SelectContent position="popper" sideOffset={4}>
|
||||||
{options.length > 0 ? (
|
{options.map((option) => (
|
||||||
options.map((option) => (
|
<SelectItem key={option.value} value={option.value}>
|
||||||
<SelectItem key={option.value} value={option.value}>
|
{option.label}
|
||||||
{option.label}
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="px-2 py-1.5 text-sm text-muted-foreground">옵션 없음</div>
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -384,23 +424,19 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
|
||||||
<div className="flex-1">
|
<div className="flex-1">
|
||||||
<label className="mb-1 block text-xs text-muted-foreground">{destinationLabel}</label>
|
<label className="mb-1 block text-xs text-muted-foreground">{destinationLabel}</label>
|
||||||
<Select
|
<Select
|
||||||
value={destinationValue}
|
value={localDestination || undefined}
|
||||||
onValueChange={handleDestinationChange}
|
onValueChange={handleDestinationChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-10">
|
<SelectTrigger className="h-10">
|
||||||
<SelectValue placeholder="선택" />
|
{localDestination ? getDestinationLabel() : <span className="text-muted-foreground">선택</span>}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="popper" sideOffset={4}>
|
<SelectContent position="popper" sideOffset={4}>
|
||||||
{options.length > 0 ? (
|
{options.map((option) => (
|
||||||
options.map((option) => (
|
<SelectItem key={option.value} value={option.value}>
|
||||||
<SelectItem key={option.value} value={option.value}>
|
{option.label}
|
||||||
{option.label}
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="px-2 py-1.5 text-sm text-muted-foreground">옵션 없음</div>
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -416,23 +452,19 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
|
||||||
style={restStyle}
|
style={restStyle}
|
||||||
>
|
>
|
||||||
<Select
|
<Select
|
||||||
value={departureValue}
|
value={localDeparture || undefined}
|
||||||
onValueChange={handleDepartureChange}
|
onValueChange={handleDepartureChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-8 flex-1 text-sm">
|
<SelectTrigger className="h-8 flex-1 text-sm">
|
||||||
<SelectValue placeholder={departureLabel} />
|
{localDeparture ? getDepartureLabel() : <span className="text-muted-foreground">{departureLabel}</span>}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="popper" sideOffset={4}>
|
<SelectContent position="popper" sideOffset={4}>
|
||||||
{options.length > 0 ? (
|
{options.map((option) => (
|
||||||
options.map((option) => (
|
<SelectItem key={option.value} value={option.value}>
|
||||||
<SelectItem key={option.value} value={option.value}>
|
{option.label}
|
||||||
{option.label}
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="px-2 py-1.5 text-sm text-muted-foreground">옵션 없음</div>
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
|
|
||||||
|
|
@ -449,23 +481,19 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Select
|
<Select
|
||||||
value={destinationValue}
|
value={localDestination || undefined}
|
||||||
onValueChange={handleDestinationChange}
|
onValueChange={handleDestinationChange}
|
||||||
disabled={loading}
|
disabled={loading}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="h-8 flex-1 text-sm">
|
<SelectTrigger className="h-8 flex-1 text-sm">
|
||||||
<SelectValue placeholder={destinationLabel} />
|
{localDestination ? getDestinationLabel() : <span className="text-muted-foreground">{destinationLabel}</span>}
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent position="popper" sideOffset={4}>
|
<SelectContent position="popper" sideOffset={4}>
|
||||||
{options.length > 0 ? (
|
{options.map((option) => (
|
||||||
options.map((option) => (
|
<SelectItem key={option.value} value={option.value}>
|
||||||
<SelectItem key={option.value} value={option.value}>
|
{option.label}
|
||||||
{option.label}
|
</SelectItem>
|
||||||
</SelectItem>
|
))}
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<div className="px-2 py-1.5 text-sm text-muted-foreground">옵션 없음</div>
|
|
||||||
)}
|
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue