Merge pull request 'lhj' (#290) from lhj into main
Reviewed-on: http://39.117.244.52:3000/kjs/ERP-node/pulls/290
This commit is contained in:
commit
a495088068
|
|
@ -53,6 +53,9 @@ export interface LocationSwapSelectorProps {
|
|||
formData?: Record<string, any>;
|
||||
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<LocationOption[]>(DEFAULT_OPTIONS);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [isSwapping, setIsSwapping] = useState(false);
|
||||
const [dbLoaded, setDbLoaded] = useState(false); // DB 로드 완료 여부
|
||||
|
||||
// 로컬 선택 상태 (Select 컴포넌트용)
|
||||
const [localDeparture, setLocalDeparture] = useState<string>("");
|
||||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -470,6 +470,58 @@ export function LocationSwapSelectorConfigPanel({
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{/* DB 초기값 로드 설정 */}
|
||||
<div className="space-y-2 border-t pt-4">
|
||||
<h4 className="text-sm font-medium">DB 초기값 로드</h4>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
새로고침 시에도 DB에 저장된 출발지/목적지를 자동으로 불러옵니다
|
||||
</p>
|
||||
|
||||
<div className="flex items-center justify-between">
|
||||
<Label>DB에서 초기값 로드</Label>
|
||||
<Switch
|
||||
checked={config?.loadFromDb !== false}
|
||||
onCheckedChange={(checked) => handleChange("loadFromDb", checked)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{config?.loadFromDb !== false && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label>조회 테이블</Label>
|
||||
<Select
|
||||
value={config?.dbTableName || "vehicles"}
|
||||
onValueChange={(value) => handleChange("dbTableName", value)}
|
||||
>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="테이블 선택" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="vehicles">vehicles (기본)</SelectItem>
|
||||
{tables.map((table) => (
|
||||
<SelectItem key={table.name} value={table.name}>
|
||||
{table.label}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label>키 필드</Label>
|
||||
<Input
|
||||
value={config?.dbKeyField || "user_id"}
|
||||
onChange={(e) => handleChange("dbKeyField", e.target.value)}
|
||||
placeholder="user_id"
|
||||
/>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
현재 사용자 ID로 조회할 필드 (기본: user_id)
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* 안내 */}
|
||||
<div className="rounded-md bg-blue-50 p-3 dark:bg-blue-950">
|
||||
<p className="text-xs text-blue-900 dark:text-blue-100">
|
||||
|
|
@ -480,6 +532,8 @@ export function LocationSwapSelectorConfigPanel({
|
|||
2. 출발지/도착지 값이 저장될 필드를 지정합니다
|
||||
<br />
|
||||
3. 교환 버튼을 클릭하면 출발지와 도착지가 바뀝니다
|
||||
<br />
|
||||
4. DB 초기값 로드를 활성화하면 새로고침 후에도 값이 유지됩니다
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -4273,39 +4273,80 @@ export class ButtonActionExecutor {
|
|||
try {
|
||||
console.log("🛑 [handleTrackingStop] 위치 추적 종료:", { config, context });
|
||||
|
||||
// 추적 중인지 확인
|
||||
if (!this.trackingIntervalId) {
|
||||
toast.warning("진행 중인 위치 추적이 없습니다.");
|
||||
return false;
|
||||
// 추적 중인지 확인 (새로고침 후에도 DB 상태 기반 종료 가능하도록 수정)
|
||||
const isTrackingActive = !!this.trackingIntervalId;
|
||||
|
||||
if (!isTrackingActive) {
|
||||
// 추적 중이 아니어도 DB 상태 변경은 진행 (새로고침 후 종료 지원)
|
||||
console.log("⚠️ [handleTrackingStop] trackingIntervalId 없음 - DB 상태 기반 종료 진행");
|
||||
} else {
|
||||
// 타이머 정리 (추적 중인 경우에만)
|
||||
clearInterval(this.trackingIntervalId);
|
||||
this.trackingIntervalId = null;
|
||||
}
|
||||
|
||||
// 타이머 정리
|
||||
clearInterval(this.trackingIntervalId);
|
||||
this.trackingIntervalId = null;
|
||||
|
||||
const tripId = this.currentTripId;
|
||||
|
||||
// 마지막 위치 저장 (trip_status를 completed로)
|
||||
const departure =
|
||||
this.trackingContext?.formData?.[this.trackingConfig?.trackingDepartureField || "departure"] || null;
|
||||
const arrival = 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 =
|
||||
this.trackingContext?.formData?.[this.trackingConfig?.trackingVehicleIdField || "vehicle_id"] || null;
|
||||
// 🆕 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);
|
||||
}
|
||||
}
|
||||
|
||||
await this.saveLocationToHistory(
|
||||
tripId,
|
||||
departure,
|
||||
arrival,
|
||||
departureName,
|
||||
destinationName,
|
||||
vehicleId,
|
||||
"completed",
|
||||
);
|
||||
// 마지막 위치 저장 (추적 중이었던 경우에만)
|
||||
if (isTrackingActive) {
|
||||
// DB 값 우선, 없으면 formData 사용
|
||||
const departure = dbDeparture ||
|
||||
this.trackingContext?.formData?.[this.trackingConfig?.trackingDepartureField || "departure"] || 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 = dbVehicleId ||
|
||||
this.trackingContext?.formData?.[this.trackingConfig?.trackingVehicleIdField || "vehicle_id"] || null;
|
||||
|
||||
// 🆕 거리/시간 계산 및 저장
|
||||
if (tripId) {
|
||||
await this.saveLocationToHistory(
|
||||
tripId,
|
||||
departure,
|
||||
arrival,
|
||||
departureName,
|
||||
destinationName,
|
||||
vehicleId,
|
||||
"completed",
|
||||
);
|
||||
}
|
||||
|
||||
// 🆕 거리/시간 계산 및 저장 (추적 중이었던 경우에만)
|
||||
if (isTrackingActive && tripId) {
|
||||
try {
|
||||
const tripStats = await this.calculateTripStats(tripId);
|
||||
console.log("📊 운행 통계:", tripStats);
|
||||
|
|
@ -4417,9 +4458,9 @@ export class ButtonActionExecutor {
|
|||
}
|
||||
}
|
||||
|
||||
// 상태 변경 (vehicles 테이블 등)
|
||||
const effectiveConfig = config.trackingStatusOnStop ? config : this.trackingConfig;
|
||||
const effectiveContext = context.userId ? context : this.trackingContext;
|
||||
// 상태 변경 (vehicles 테이블 등) - 새로고침 후에도 동작하도록 config 우선 사용
|
||||
const effectiveConfig = config.trackingStatusOnStop ? config : this.trackingConfig || config;
|
||||
const effectiveContext = context.userId ? context : this.trackingContext || context;
|
||||
|
||||
if (effectiveConfig?.trackingStatusOnStop && effectiveConfig?.trackingStatusField && effectiveContext) {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Reference in New Issue