diff --git a/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorComponent.tsx b/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorComponent.tsx index 3f1a723b..7a693ad5 100644 --- a/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorComponent.tsx +++ b/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorComponent.tsx @@ -53,6 +53,9 @@ export interface LocationSwapSelectorProps { formData?: Record; onFormDataChange?: (field: string, value: any) => void; + // ๐Ÿ†• ์‚ฌ์šฉ์ž ์ •๋ณด (DB์—์„œ ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ์šฉ) + userId?: string; + // componentConfig (ํ™”๋ฉด ๋””์ž์ด๋„ˆ์—์„œ ์ „๋‹ฌ) componentConfig?: { dataSource?: DataSourceConfig; @@ -65,6 +68,10 @@ export interface LocationSwapSelectorProps { showSwapButton?: boolean; swapButtonPosition?: "center" | "right"; variant?: "card" | "inline" | "minimal"; + // ๐Ÿ†• DB ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ ์„ค์ • + loadFromDb?: boolean; // DB์—์„œ ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ ์—ฌ๋ถ€ + dbTableName?: string; // ์กฐํšŒํ•  ํ…Œ์ด๋ธ”๋ช… (๊ธฐ๋ณธ: vehicles) + dbKeyField?: string; // ํ‚ค ํ•„๋“œ (๊ธฐ๋ณธ: user_id) }; } @@ -80,6 +87,7 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) formData = {}, onFormDataChange, componentConfig, + userId, } = props; // componentConfig์—์„œ ์„ค์ • ๊ฐ€์ ธ์˜ค๊ธฐ (์šฐ์„ ์ˆœ์œ„: componentConfig > props) @@ -93,6 +101,11 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) const destinationLabel = config.destinationLabel || props.destinationLabel || "๋„์ฐฉ์ง€"; const showSwapButton = config.showSwapButton !== false && props.showSwapButton !== false; const variant = config.variant || props.variant || "card"; + + // ๐Ÿ†• DB ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ ์„ค์ • + const loadFromDb = config.loadFromDb !== false; // ๊ธฐ๋ณธ๊ฐ’ true + const dbTableName = config.dbTableName || "vehicles"; + const dbKeyField = config.dbKeyField || "user_id"; // ๊ธฐ๋ณธ ์˜ต์…˜ (ํฌํ•ญ/๊ด‘์–‘) const DEFAULT_OPTIONS: LocationOption[] = [ @@ -104,6 +117,7 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) const [options, setOptions] = useState(DEFAULT_OPTIONS); const [loading, setLoading] = useState(false); const [isSwapping, setIsSwapping] = useState(false); + const [dbLoaded, setDbLoaded] = useState(false); // DB ๋กœ๋“œ ์™„๋ฃŒ ์—ฌ๋ถ€ // ๋กœ์ปฌ ์„ ํƒ ์ƒํƒœ (Select ์ปดํฌ๋„ŒํŠธ์šฉ) const [localDeparture, setLocalDeparture] = useState(""); @@ -193,8 +207,89 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) loadOptions(); }, [dataSource, isDesignMode]); - // formData์—์„œ ์ดˆ๊ธฐ๊ฐ’ ๋™๊ธฐํ™” + // ๐Ÿ†• DB์—์„œ ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ (์ƒˆ๋กœ๊ณ ์นจ ์‹œ์—๋„ ์ถœ๋ฐœ์ง€/๋ชฉ์ ์ง€ ์œ ์ง€) useEffect(() => { + const loadFromDatabase = async () => { + // ๋””์ž์ธ ๋ชจ๋“œ์ด๊ฑฐ๋‚˜, DB ๋กœ๋“œ ๋น„ํ™œ์„ฑํ™”์ด๊ฑฐ๋‚˜, userId๊ฐ€ ์—†์œผ๋ฉด ์Šคํ‚ต + if (isDesignMode || !loadFromDb || !userId) { + console.log("[LocationSwapSelector] DB ๋กœ๋“œ ์Šคํ‚ต:", { isDesignMode, loadFromDb, userId }); + return; + } + + // ์ด๋ฏธ ๋กœ๋“œํ–ˆ์œผ๋ฉด ์Šคํ‚ต + if (dbLoaded) { + return; + } + + try { + console.log("[LocationSwapSelector] DB์—์„œ ์ถœ๋ฐœ์ง€/๋ชฉ์ ์ง€ ๋กœ๋“œ ์‹œ์ž‘:", { dbTableName, dbKeyField, userId }); + + const response = await apiClient.post( + `/table-management/tables/${dbTableName}/data`, + { + page: 1, + size: 1, + search: { [dbKeyField]: userId }, + autoFilter: true, + } + ); + + const vehicleData = response.data?.data?.data?.[0] || response.data?.data?.rows?.[0]; + + if (vehicleData) { + const dbDeparture = vehicleData[departureField] || vehicleData.departure; + const dbDestination = vehicleData[destinationField] || vehicleData.arrival || vehicleData.destination; + + console.log("[LocationSwapSelector] DB์—์„œ ๋กœ๋“œ๋œ ๊ฐ’:", { dbDeparture, dbDestination }); + + // DB์— ๊ฐ’์ด ์žˆ์œผ๋ฉด ๋กœ์ปฌ ์ƒํƒœ ๋ฐ formData ์—…๋ฐ์ดํŠธ + if (dbDeparture && options.some(o => o.value === dbDeparture)) { + setLocalDeparture(dbDeparture); + onFormDataChange?.(departureField, dbDeparture); + + // ๋ผ๋ฒจ๋„ ์—…๋ฐ์ดํŠธ + if (departureLabelField) { + const opt = options.find(o => o.value === dbDeparture); + if (opt) { + onFormDataChange?.(departureLabelField, opt.label); + } + } + } + + if (dbDestination && options.some(o => o.value === dbDestination)) { + setLocalDestination(dbDestination); + onFormDataChange?.(destinationField, dbDestination); + + // ๋ผ๋ฒจ๋„ ์—…๋ฐ์ดํŠธ + if (destinationLabelField) { + const opt = options.find(o => o.value === dbDestination); + if (opt) { + onFormDataChange?.(destinationLabelField, opt.label); + } + } + } + } + + setDbLoaded(true); + } catch (error) { + console.error("[LocationSwapSelector] DB ๋กœ๋“œ ์‹คํŒจ:", error); + setDbLoaded(true); // ์‹คํŒจํ•ด๋„ ๋‹ค์‹œ ์‹œ๋„ํ•˜์ง€ ์•Š์Œ + } + }; + + // ์˜ต์…˜์ด ๋กœ๋“œ๋œ ํ›„์— DB ๋กœ๋“œ ์‹คํ–‰ + if (options.length > 0) { + loadFromDatabase(); + } + }, [userId, loadFromDb, dbTableName, dbKeyField, departureField, destinationField, options, isDesignMode, dbLoaded, onFormDataChange, departureLabelField, destinationLabelField]); + + // formData์—์„œ ์ดˆ๊ธฐ๊ฐ’ ๋™๊ธฐํ™” (DB ๋กœ๋“œ ํ›„์—๋„ formData ๋ณ€๊ฒฝ ์‹œ ๋ฐ˜์˜) + useEffect(() => { + // DB ๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜์ง€ ์•Š์•˜์œผ๋ฉด ์Šคํ‚ต (DB ๊ฐ’ ์šฐ์„ ) + if (loadFromDb && userId && !dbLoaded) { + return; + } + const depVal = formData[departureField]; const destVal = formData[destinationField]; @@ -204,7 +299,7 @@ export function LocationSwapSelectorComponent(props: LocationSwapSelectorProps) if (destVal && options.some(o => o.value === destVal)) { setLocalDestination(destVal); } - }, [formData, departureField, destinationField, options]); + }, [formData, departureField, destinationField, options, loadFromDb, userId, dbLoaded]); // ์ถœ๋ฐœ์ง€ ๋ณ€๊ฒฝ const handleDepartureChange = (selectedValue: string) => { diff --git a/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorConfigPanel.tsx b/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorConfigPanel.tsx index 4e21cddf..cd84806f 100644 --- a/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorConfigPanel.tsx +++ b/frontend/lib/registry/components/location-swap-selector/LocationSwapSelectorConfigPanel.tsx @@ -470,6 +470,58 @@ export function LocationSwapSelectorConfigPanel({ + {/* DB ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ ์„ค์ • */} +
+

DB ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ

+

+ ์ƒˆ๋กœ๊ณ ์นจ ์‹œ์—๋„ DB์— ์ €์žฅ๋œ ์ถœ๋ฐœ์ง€/๋ชฉ์ ์ง€๋ฅผ ์ž๋™์œผ๋กœ ๋ถˆ๋Ÿฌ์˜ต๋‹ˆ๋‹ค +

+ +
+ + handleChange("loadFromDb", checked)} + /> +
+ + {config?.loadFromDb !== false && ( + <> +
+ + +
+ +
+ + handleChange("dbKeyField", e.target.value)} + placeholder="user_id" + /> +

+ ํ˜„์žฌ ์‚ฌ์šฉ์ž ID๋กœ ์กฐํšŒํ•  ํ•„๋“œ (๊ธฐ๋ณธ: user_id) +

+
+ + )} +
+ {/* ์•ˆ๋‚ด */}

@@ -480,6 +532,8 @@ export function LocationSwapSelectorConfigPanel({ 2. ์ถœ๋ฐœ์ง€/๋„์ฐฉ์ง€ ๊ฐ’์ด ์ €์žฅ๋  ํ•„๋“œ๋ฅผ ์ง€์ •ํ•ฉ๋‹ˆ๋‹ค
3. ๊ตํ™˜ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์ถœ๋ฐœ์ง€์™€ ๋„์ฐฉ์ง€๊ฐ€ ๋ฐ”๋€๋‹ˆ๋‹ค +
+ 4. DB ์ดˆ๊ธฐ๊ฐ’ ๋กœ๋“œ๋ฅผ ํ™œ์„ฑํ™”ํ•˜๋ฉด ์ƒˆ๋กœ๊ณ ์นจ ํ›„์—๋„ ๊ฐ’์ด ์œ ์ง€๋ฉ๋‹ˆ๋‹ค

diff --git a/frontend/lib/utils/buttonActions.ts b/frontend/lib/utils/buttonActions.ts index 4d5915e9..e1573998 100644 --- a/frontend/lib/utils/buttonActions.ts +++ b/frontend/lib/utils/buttonActions.ts @@ -4130,14 +4130,51 @@ export class ButtonActionExecutor { const tripId = this.currentTripId; + // ๐Ÿ†• DB์—์„œ ์ถœ๋ฐœ์ง€/๋ชฉ์ ์ง€ ์กฐํšŒ (์šด์ „์ž๊ฐ€ ์ค‘๊ฐ„์— ๋ฐ”๊ฟ”๋„ ์›๋ž˜ ๊ฐ’ ์‚ฌ์šฉ) + let dbDeparture: string | null = null; + let dbArrival: string | null = null; + let dbVehicleId: string | null = null; + + const userId = context.userId || this.trackingUserId; + if (userId) { + try { + const { apiClient } = await import("@/lib/api/client"); + const statusTableName = config.trackingStatusTableName || this.trackingConfig?.trackingStatusTableName || context.tableName || "vehicles"; + const keyField = config.trackingStatusKeyField || this.trackingConfig?.trackingStatusKeyField || "user_id"; + + // DB์—์„œ ํ˜„์žฌ ์ฐจ๋Ÿ‰ ์ •๋ณด ์กฐํšŒ + const vehicleResponse = await apiClient.post( + `/table-management/tables/${statusTableName}/data`, + { + page: 1, + size: 1, + search: { [keyField]: userId }, + autoFilter: true, + }, + ); + + const vehicleData = vehicleResponse.data?.data?.data?.[0] || vehicleResponse.data?.data?.rows?.[0]; + if (vehicleData) { + dbDeparture = vehicleData.departure || null; + dbArrival = vehicleData.arrival || null; + dbVehicleId = vehicleData.id || vehicleData.vehicle_id || null; + console.log("๐Ÿ“ [handleTrackingStop] DB์—์„œ ์ถœ๋ฐœ์ง€/๋ชฉ์ ์ง€ ์กฐํšŒ:", { dbDeparture, dbArrival, dbVehicleId }); + } + } catch (dbError) { + console.warn("โš ๏ธ [handleTrackingStop] DB ์กฐํšŒ ์‹คํŒจ, formData ์‚ฌ์šฉ:", dbError); + } + } + // ๋งˆ์ง€๋ง‰ ์œ„์น˜ ์ €์žฅ (์ถ”์  ์ค‘์ด์—ˆ๋˜ ๊ฒฝ์šฐ์—๋งŒ) if (isTrackingActive) { - const departure = + // DB ๊ฐ’ ์šฐ์„ , ์—†์œผ๋ฉด formData ์‚ฌ์šฉ + const departure = dbDeparture || this.trackingContext?.formData?.[this.trackingConfig?.trackingDepartureField || "departure"] || null; - const arrival = this.trackingContext?.formData?.[this.trackingConfig?.trackingArrivalField || "arrival"] || null; + const arrival = dbArrival || + this.trackingContext?.formData?.[this.trackingConfig?.trackingArrivalField || "arrival"] || null; const departureName = this.trackingContext?.formData?.["departure_name"] || null; const destinationName = this.trackingContext?.formData?.["destination_name"] || null; - const vehicleId = + const vehicleId = dbVehicleId || this.trackingContext?.formData?.[this.trackingConfig?.trackingVehicleIdField || "vehicle_id"] || null; await this.saveLocationToHistory(