출발지 목적지 선택

This commit is contained in:
leeheejin
2025-12-01 11:07:16 +09:00
parent c657d6f7a0
commit d7ee63a857
6 changed files with 284 additions and 65 deletions

View File

@@ -53,6 +53,7 @@ export const DividerLineComponent: React.FC<DividerLineComponentProps> = ({
};
// DOM에 전달하면 안 되는 React-specific props 필터링
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const {
selectedScreen,
onZoneComponentDrop,
@@ -70,8 +71,40 @@ export const DividerLineComponent: React.FC<DividerLineComponentProps> = ({
tableName: _tableName,
onRefresh: _onRefresh,
onClose: _onClose,
// 추가된 props 필터링
webType: _webType,
autoGeneration: _autoGeneration,
isInteractive: _isInteractive,
formData: _formData,
onFormDataChange: _onFormDataChange,
menuId: _menuId,
menuObjid: _menuObjid,
onSave: _onSave,
userId: _userId,
userName: _userName,
companyCode: _companyCode,
isInModal: _isInModal,
readonly: _readonly,
originalData: _originalData,
allComponents: _allComponents,
onUpdateLayout: _onUpdateLayout,
selectedRows: _selectedRows,
selectedRowsData: _selectedRowsData,
onSelectedRowsChange: _onSelectedRowsChange,
sortBy: _sortBy,
sortOrder: _sortOrder,
tableDisplayData: _tableDisplayData,
flowSelectedData: _flowSelectedData,
flowSelectedStepId: _flowSelectedStepId,
onFlowSelectedDataChange: _onFlowSelectedDataChange,
onConfigChange: _onConfigChange,
refreshKey: _refreshKey,
flowRefreshKey: _flowRefreshKey,
onFlowRefresh: _onFlowRefresh,
isPreview: _isPreview,
groupedData: _groupedData,
...domProps
} = props;
} = props as any;
return (
<div style={componentStyle} className={className} {...domProps}>

View File

@@ -103,11 +103,36 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
const departureValue = formData[departureField] || "";
const destinationValue = formData[destinationField] || "";
// 기본 옵션 (포항/광양)
const DEFAULT_OPTIONS: LocationOption[] = [
{ value: "pohang", label: "포항" },
{ value: "gwangyang", label: "광양" },
];
// 옵션 로드
useEffect(() => {
const loadOptions = async () => {
if (dataSource.type === "static") {
setOptions(dataSource.staticOptions || []);
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 || [];
// 정적 옵션이 설정되어 있으면 사용
if (staticOpts.length > 0 && staticOpts[0]?.value) {
console.log("[LocationSwapSelector] 정적 옵션 사용:", staticOpts);
setOptions(staticOpts);
} else {
// 기본값 (포항/광양)
console.log("[LocationSwapSelector] 기본 옵션 사용:", DEFAULT_OPTIONS);
setOptions(DEFAULT_OPTIONS);
}
return;
}
@@ -159,17 +184,7 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
}
};
if (!isDesignMode) {
loadOptions();
} else {
// 디자인 모드에서는 샘플 데이터
setOptions([
{ value: "seoul", label: "서울" },
{ value: "busan", label: "부산" },
{ value: "pohang", label: "포항" },
{ value: "gwangyang", label: "광양" },
]);
}
loadOptions();
}, [dataSource, isDesignMode]);
// 출발지 변경
@@ -250,7 +265,7 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
<Select
value={departureValue}
onValueChange={handleDepartureChange}
disabled={loading || isDesignMode}
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">
<SelectValue placeholder="선택">
@@ -259,12 +274,16 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
<SelectContent position="popper" sideOffset={4}>
{options.length > 0 ? (
options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))
) : (
<div className="px-2 py-1.5 text-sm text-muted-foreground"> </div>
)}
</SelectContent>
</Select>
</div>
@@ -276,7 +295,6 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
variant="ghost"
size="icon"
onClick={handleSwap}
disabled={isDesignMode || !departureValue || !destinationValue}
className={cn(
"mx-2 h-10 w-10 rounded-full border bg-background transition-transform hover:bg-muted",
isSwapping && "rotate-180"
@@ -292,7 +310,7 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
<Select
value={destinationValue}
onValueChange={handleDestinationChange}
disabled={loading || isDesignMode}
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">
<SelectValue placeholder="선택">
@@ -301,12 +319,16 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
<SelectContent position="popper" sideOffset={4}>
{options.length > 0 ? (
options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))
) : (
<div className="px-2 py-1.5 text-sm text-muted-foreground"> </div>
)}
</SelectContent>
</Select>
</div>
@@ -328,17 +350,21 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
<Select
value={departureValue}
onValueChange={handleDepartureChange}
disabled={loading || isDesignMode}
disabled={loading}
>
<SelectTrigger className="h-10">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
<SelectContent position="popper" sideOffset={4}>
{options.length > 0 ? (
options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))
) : (
<div className="px-2 py-1.5 text-sm text-muted-foreground"> </div>
)}
</SelectContent>
</Select>
</div>
@@ -349,7 +375,6 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
variant="outline"
size="icon"
onClick={handleSwap}
disabled={isDesignMode}
className="mt-5 h-10 w-10"
>
<ArrowLeftRight className="h-4 w-4" />
@@ -361,17 +386,21 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
<Select
value={destinationValue}
onValueChange={handleDestinationChange}
disabled={loading || isDesignMode}
disabled={loading}
>
<SelectTrigger className="h-10">
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
<SelectContent position="popper" sideOffset={4}>
{options.length > 0 ? (
options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))
) : (
<div className="px-2 py-1.5 text-sm text-muted-foreground"> </div>
)}
</SelectContent>
</Select>
</div>
@@ -389,17 +418,21 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
<Select
value={departureValue}
onValueChange={handleDepartureChange}
disabled={loading || isDesignMode}
disabled={loading}
>
<SelectTrigger className="h-8 flex-1 text-sm">
<SelectValue placeholder={departureLabel} />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
<SelectContent position="popper" sideOffset={4}>
{options.length > 0 ? (
options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))
) : (
<div className="px-2 py-1.5 text-sm text-muted-foreground"> </div>
)}
</SelectContent>
</Select>
@@ -409,7 +442,6 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
variant="ghost"
size="sm"
onClick={handleSwap}
disabled={isDesignMode}
className="h-8 w-8 p-0"
>
<ArrowLeftRight className="h-4 w-4" />
@@ -419,17 +451,21 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps)
<Select
value={destinationValue}
onValueChange={handleDestinationChange}
disabled={loading || isDesignMode}
disabled={loading}
>
<SelectTrigger className="h-8 flex-1 text-sm">
<SelectValue placeholder={destinationLabel} />
</SelectTrigger>
<SelectContent>
{options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
<SelectContent position="popper" sideOffset={4}>
{options.length > 0 ? (
options.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))
) : (
<div className="px-2 py-1.5 text-sm text-muted-foreground"> </div>
)}
</SelectContent>
</Select>
</div>

View File

@@ -139,13 +139,83 @@ export function LocationSwapSelectorConfigPanel({
<SelectValue placeholder="선택" />
</SelectTrigger>
<SelectContent>
<SelectItem value="static"> ()</SelectItem>
<SelectItem value="table"></SelectItem>
<SelectItem value="code"> </SelectItem>
<SelectItem value="static"> (/ )</SelectItem>
<SelectItem value="table"> </SelectItem>
<SelectItem value="code"> </SelectItem>
</SelectContent>
</Select>
</div>
{/* 고정 옵션 설정 (type이 static일 때) */}
{(!config?.dataSource?.type || config?.dataSource?.type === "static") && (
<div className="space-y-3 rounded-md bg-amber-50 p-3 dark:bg-amber-950">
<h4 className="text-sm font-medium"> </h4>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-2">
<Label> 1 ()</Label>
<Input
value={config?.dataSource?.staticOptions?.[0]?.value || ""}
onChange={(e) => {
const options = config?.dataSource?.staticOptions || [];
const newOptions = [...options];
newOptions[0] = { ...newOptions[0], value: e.target.value };
handleChange("dataSource.staticOptions", newOptions);
}}
placeholder="예: pohang"
className="h-8 text-xs"
/>
</div>
<div className="space-y-2">
<Label> 1 ()</Label>
<Input
value={config?.dataSource?.staticOptions?.[0]?.label || ""}
onChange={(e) => {
const options = config?.dataSource?.staticOptions || [];
const newOptions = [...options];
newOptions[0] = { ...newOptions[0], label: e.target.value };
handleChange("dataSource.staticOptions", newOptions);
}}
placeholder="예: 포항"
className="h-8 text-xs"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-2">
<div className="space-y-2">
<Label> 2 ()</Label>
<Input
value={config?.dataSource?.staticOptions?.[1]?.value || ""}
onChange={(e) => {
const options = config?.dataSource?.staticOptions || [];
const newOptions = [...options];
newOptions[1] = { ...newOptions[1], value: e.target.value };
handleChange("dataSource.staticOptions", newOptions);
}}
placeholder="예: gwangyang"
className="h-8 text-xs"
/>
</div>
<div className="space-y-2">
<Label> 2 ()</Label>
<Input
value={config?.dataSource?.staticOptions?.[1]?.label || ""}
onChange={(e) => {
const options = config?.dataSource?.staticOptions || [];
const newOptions = [...options];
newOptions[1] = { ...newOptions[1], label: e.target.value };
handleChange("dataSource.staticOptions", newOptions);
}}
placeholder="예: 광양"
className="h-8 text-xs"
/>
</div>
</div>
<p className="text-xs text-amber-700 dark:text-amber-300">
2 . (: 포항 )
</p>
</div>
)}
{/* 테이블 선택 (type이 table일 때) */}
{config?.dataSource?.type === "table" && (
<>

View File

@@ -12,7 +12,28 @@ export class LocationSwapSelectorRenderer extends AutoRegisteringComponentRender
static componentDefinition = LocationSwapSelectorDefinition;
render(): React.ReactElement {
return <LocationSwapSelectorComponent {...this.props} />;
const { component, formData, onFormDataChange, isDesignMode, style, ...restProps } = this.props;
// component.componentConfig에서 설정 가져오기
const componentConfig = component?.componentConfig || {};
console.log("[LocationSwapSelectorRenderer] render:", {
componentConfig,
formData,
isDesignMode
});
return (
<LocationSwapSelectorComponent
id={component?.id}
style={style}
isDesignMode={isDesignMode}
formData={formData}
onFormDataChange={onFormDataChange}
componentConfig={componentConfig}
{...restProps}
/>
);
}
}

View File

@@ -20,12 +20,15 @@ export const LocationSwapSelectorDefinition = createComponentDefinition({
defaultConfig: {
// 데이터 소스 설정
dataSource: {
type: "table", // "table" | "code" | "static"
type: "static", // "table" | "code" | "static"
tableName: "", // 장소 테이블명
valueField: "location_code", // 값 필드
labelField: "location_name", // 표시 필드
codeCategory: "", // 코드 관리 카테고리 (type이 "code"일 때)
staticOptions: [], // 정적 옵션 (type이 "static"일 때)
staticOptions: [
{ value: "pohang", label: "포항" },
{ value: "gwangyang", label: "광양" },
], // 정적 옵션 (type이 "static"일 때)
},
// 필드 매핑
departureField: "departure", // 출발지 저장 필드