공차등록성공

This commit is contained in:
leeheejin
2025-12-01 15:23:07 +09:00
parent be2550885a
commit 8d2ec8e737
5 changed files with 751 additions and 39 deletions

View File

@@ -90,7 +90,7 @@ export function LocationSwapSelectorConfigPanel({
}
}, [config?.dataSource?.tableName, config?.dataSource?.type]);
// 코드 카테고리 로드
// 코드 카테고리 로드 (API가 없을 수 있으므로 에러 무시)
useEffect(() => {
const loadCodeCategories = async () => {
try {
@@ -103,8 +103,11 @@ export function LocationSwapSelectorConfigPanel({
}))
);
}
} catch (error) {
console.error("코드 카테고리 로드 실패:", error);
} catch (error: any) {
// 404는 API가 없는 것이므로 무시
if (error?.response?.status !== 404) {
console.error("코드 카테고리 로드 실패:", error);
}
}
};
loadCodeCategories();
@@ -368,14 +371,14 @@ export function LocationSwapSelectorConfigPanel({
<Label> ()</Label>
{tableColumns.length > 0 ? (
<Select
value={config?.departureLabelField || ""}
onValueChange={(value) => handleChange("departureLabelField", value)}
value={config?.departureLabelField || "__none__"}
onValueChange={(value) => handleChange("departureLabelField", value === "__none__" ? "" : value)}
>
<SelectTrigger>
<SelectValue placeholder="컬럼 선택 (선택사항)" />
</SelectTrigger>
<SelectContent>
<SelectItem value=""></SelectItem>
<SelectItem value="__none__"></SelectItem>
{tableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}
@@ -395,14 +398,14 @@ export function LocationSwapSelectorConfigPanel({
<Label> ()</Label>
{tableColumns.length > 0 ? (
<Select
value={config?.destinationLabelField || ""}
onValueChange={(value) => handleChange("destinationLabelField", value)}
value={config?.destinationLabelField || "__none__"}
onValueChange={(value) => handleChange("destinationLabelField", value === "__none__" ? "" : value)}
>
<SelectTrigger>
<SelectValue placeholder="컬럼 선택 (선택사항)" />
</SelectTrigger>
<SelectContent>
<SelectItem value=""></SelectItem>
<SelectItem value="__none__"></SelectItem>
{tableColumns.map((col) => (
<SelectItem key={col.columnName} value={col.columnName}>
{col.columnLabel || col.columnName}

View File

@@ -110,6 +110,16 @@ export interface ButtonActionConfig {
geolocationExtraValue?: string | number | boolean; // 추가로 변경할 값 (예: "active")
geolocationExtraKeyField?: string; // 다른 테이블의 키 필드 (예: "vehicle_id")
geolocationExtraKeySourceField?: string; // 현재 폼에서 키 값을 가져올 필드 (예: "vehicle_id")
// 🆕 두 번째 테이블 설정 (위치정보 + 상태변경을 각각 다른 테이블에)
geolocationSecondTableEnabled?: boolean; // 두 번째 테이블 사용 여부
geolocationSecondTableName?: string; // 두 번째 테이블명 (예: "vehicles")
geolocationSecondMode?: "update" | "insert"; // 작업 모드 (기본: update)
geolocationSecondField?: string; // 두 번째 테이블에서 변경할 필드명 (예: "status")
geolocationSecondValue?: string | number | boolean; // 두 번째 테이블에서 변경할 값 (예: "inactive")
geolocationSecondKeyField?: string; // 두 번째 테이블의 키 필드 (예: "id") - UPDATE 모드에서만 사용
geolocationSecondKeySourceField?: string; // 현재 폼에서 키 값을 가져올 필드 (예: "vehicle_id") - UPDATE 모드에서만 사용
geolocationSecondInsertFields?: Record<string, any>; // INSERT 모드에서 추가로 넣을 필드들
// 필드 값 교환 관련 (출발지 ↔ 목적지)
swapFieldA?: string; // 교환할 첫 번째 필드명 (예: "departure")
@@ -121,6 +131,13 @@ export interface ButtonActionConfig {
updateTargetValue?: string | number | boolean; // 변경할 값 (예: "active")
updateAutoSave?: boolean; // 변경 후 자동 저장 여부 (기본: true)
updateMultipleFields?: Array<{ field: string; value: string | number | boolean }>; // 여러 필드 동시 변경
// 🆕 필드 값 변경 + 위치정보 수집 (update_field 액션에서 사용)
updateWithGeolocation?: boolean; // 위치정보도 함께 수집할지 여부
updateGeolocationLatField?: string; // 위도 저장 필드
updateGeolocationLngField?: string; // 경도 저장 필드
updateGeolocationAccuracyField?: string; // 정확도 저장 필드 (선택)
updateGeolocationTimestampField?: string; // 타임스탬프 저장 필드 (선택)
// 편집 관련 (수주관리 등 그룹별 다중 레코드 편집)
editMode?: "modal" | "navigate" | "inline"; // 편집 모드
@@ -217,6 +234,44 @@ export interface ButtonActionContext {
componentConfigs?: Record<string, any>; // 컴포넌트 ID → 컴포넌트 설정
}
/**
* 🆕 특수 키워드를 실제 값으로 변환하는 헬퍼 함수
* 지원하는 키워드:
* - __userId__ : 로그인한 사용자 ID
* - __userName__ : 로그인한 사용자 이름
* - __companyCode__ : 로그인한 사용자의 회사 코드
* - __screenId__ : 현재 화면 ID
* - __tableName__ : 현재 테이블명
*/
export function resolveSpecialKeyword(
sourceField: string | undefined,
context: ButtonActionContext
): any {
if (!sourceField) return undefined;
// 특수 키워드 처리
switch (sourceField) {
case "__userId__":
console.log("🔑 특수 키워드 변환: __userId__ →", context.userId);
return context.userId;
case "__userName__":
console.log("🔑 특수 키워드 변환: __userName__ →", context.userName);
return context.userName;
case "__companyCode__":
console.log("🔑 특수 키워드 변환: __companyCode__ →", context.companyCode);
return context.companyCode;
case "__screenId__":
console.log("🔑 특수 키워드 변환: __screenId__ →", context.screenId);
return context.screenId;
case "__tableName__":
console.log("🔑 특수 키워드 변환: __tableName__ →", context.tableName);
return context.tableName;
default:
// 일반 폼 데이터에서 가져오기
return context.formData?.[sourceField];
}
}
/**
* 버튼 액션 실행기
*/
@@ -3236,6 +3291,14 @@ export class ButtonActionExecutor {
private static async handleGeolocation(config: ButtonActionConfig, context: ButtonActionContext): Promise<boolean> {
try {
console.log("📍 위치정보 가져오기 액션 실행:", { config, context });
console.log("📍 [디버그] 추가 필드 설정값:", {
geolocationUpdateField: config.geolocationUpdateField,
geolocationExtraField: config.geolocationExtraField,
geolocationExtraValue: config.geolocationExtraValue,
geolocationExtraTableName: config.geolocationExtraTableName,
geolocationExtraKeyField: config.geolocationExtraKeyField,
geolocationExtraKeySourceField: config.geolocationExtraKeySourceField,
});
// 브라우저 Geolocation API 지원 확인
if (!navigator.geolocation) {
@@ -3296,26 +3359,35 @@ export class ButtonActionExecutor {
// 🆕 추가 필드 변경 (위치정보 + 상태변경)
let extraTableUpdated = false;
let secondTableUpdated = false;
if (config.geolocationUpdateField && config.geolocationExtraField && config.geolocationExtraValue !== undefined) {
const extraTableName = config.geolocationExtraTableName;
const extraTableName = config.geolocationExtraTableName || context.tableName; // 🆕 대상 테이블이 없으면 현재 테이블 사용
const currentTableName = config.geolocationTableName || context.tableName;
const keySourceField = config.geolocationExtraKeySourceField;
// 다른 테이블에 UPDATE하는 경우
if (extraTableName && extraTableName !== currentTableName) {
console.log("📍 다른 테이블 필드 변경:", {
// 🆕 특수 키워드가 설정되어 있으면 바로 DB UPDATE (같은 테이블이어도)
const hasSpecialKeyword = keySourceField?.startsWith("__") && keySourceField?.endsWith("__");
const isDifferentTable = extraTableName && extraTableName !== currentTableName;
// 다른 테이블이거나 특수 키워드가 설정된 경우 → 바로 DB UPDATE
if (isDifferentTable || hasSpecialKeyword) {
console.log("📍 DB 직접 UPDATE:", {
targetTable: extraTableName,
field: config.geolocationExtraField,
value: config.geolocationExtraValue,
keyField: config.geolocationExtraKeyField,
keySourceField: config.geolocationExtraKeySourceField,
keySourceField: keySourceField,
hasSpecialKeyword,
isDifferentTable,
});
// 키 값 가져오기
const keyValue = context.formData?.[config.geolocationExtraKeySourceField || ""];
// 키 값 가져오기 (특수 키워드 지원)
const keyValue = resolveSpecialKeyword(keySourceField, context);
if (keyValue && config.geolocationExtraKeyField) {
try {
// 다른 테이블 UPDATE API 호출
// DB UPDATE API 호출
const { apiClient } = await import("@/lib/api/client");
const response = await apiClient.put(`/dynamic-form/update-field`, {
tableName: extraTableName,
@@ -3327,30 +3399,131 @@ export class ButtonActionExecutor {
if (response.data?.success) {
extraTableUpdated = true;
console.log("✅ 다른 테이블 UPDATE 성공:", response.data);
console.log("✅ DB UPDATE 성공:", response.data);
} else {
console.error("❌ 다른 테이블 UPDATE 실패:", response.data);
console.error("❌ DB UPDATE 실패:", response.data);
toast.error(`${extraTableName} 테이블 업데이트에 실패했습니다.`);
}
} catch (apiError) {
console.error("❌ 다른 테이블 UPDATE API 오류:", apiError);
console.error("❌ DB UPDATE API 오류:", apiError);
toast.error(`${extraTableName} 테이블 업데이트 중 오류가 발생했습니다.`);
}
} else {
console.warn("⚠️ 키 값이 없어서 다른 테이블 UPDATE를 건너뜁니다:", {
keySourceField: config.geolocationExtraKeySourceField,
console.warn("⚠️ 키 값이 없어서 DB UPDATE를 건너뜁니다:", {
keySourceField: keySourceField,
keyValue,
});
}
} else {
// 같은 테이블 (현재 폼 데이터에 추가)
// 같은 테이블이고 특수 키워드가 없는 경우 (현재 폼 데이터에 추가)
updates[config.geolocationExtraField] = config.geolocationExtraValue;
console.log("📍 같은 테이블 추가 필드 변경:", {
console.log("📍 같은 테이블 추가 필드 변경 (폼 데이터):", {
field: config.geolocationExtraField,
value: config.geolocationExtraValue,
});
}
}
// 🆕 두 번째 테이블 INSERT 또는 UPDATE
if (config.geolocationSecondTableEnabled &&
config.geolocationSecondTableName) {
const secondMode = config.geolocationSecondMode || "update";
console.log("📍 두 번째 테이블 작업:", {
mode: secondMode,
targetTable: config.geolocationSecondTableName,
field: config.geolocationSecondField,
value: config.geolocationSecondValue,
keyField: config.geolocationSecondKeyField,
keySourceField: config.geolocationSecondKeySourceField,
});
try {
const { apiClient } = await import("@/lib/api/client");
if (secondMode === "insert") {
// INSERT 모드: 새 레코드 생성
const insertData: Record<string, any> = {
// 위치정보 포함 (선택적)
...(config.geolocationSecondInsertFields || {}),
};
// 기본 필드 추가
if (config.geolocationSecondField && config.geolocationSecondValue !== undefined) {
insertData[config.geolocationSecondField] = config.geolocationSecondValue;
}
// 위치정보도 두 번째 테이블에 저장하려면 추가
// (선택적으로 위도/경도도 저장)
if (config.geolocationSecondInsertFields?.includeLocation) {
insertData[latField] = latitude;
insertData[lngField] = longitude;
if (config.geolocationAccuracyField) {
insertData[config.geolocationAccuracyField] = accuracy;
}
if (config.geolocationTimestampField) {
insertData[config.geolocationTimestampField] = timestamp.toISOString();
}
}
// 현재 폼에서 키 값 가져와서 연결 (외래키) - 특수 키워드 지원
if (config.geolocationSecondKeySourceField && config.geolocationSecondKeyField) {
const keyValue = resolveSpecialKeyword(config.geolocationSecondKeySourceField, context);
if (keyValue) {
insertData[config.geolocationSecondKeyField] = keyValue;
}
}
console.log("📍 두 번째 테이블 INSERT 데이터:", insertData);
const response = await apiClient.post(`/dynamic-form/save`, {
tableName: config.geolocationSecondTableName,
data: insertData,
});
if (response.data?.success) {
secondTableUpdated = true;
console.log("✅ 두 번째 테이블 INSERT 성공:", response.data);
} else {
console.error("❌ 두 번째 테이블 INSERT 실패:", response.data);
toast.error(`${config.geolocationSecondTableName} 테이블 저장에 실패했습니다.`);
}
} else {
// UPDATE 모드: 기존 레코드 수정
if (config.geolocationSecondField && config.geolocationSecondValue !== undefined) {
// 특수 키워드 지원
const secondKeyValue = resolveSpecialKeyword(config.geolocationSecondKeySourceField, context);
if (secondKeyValue && config.geolocationSecondKeyField) {
const response = await apiClient.put(`/dynamic-form/update-field`, {
tableName: config.geolocationSecondTableName,
keyField: config.geolocationSecondKeyField,
keyValue: secondKeyValue,
updateField: config.geolocationSecondField,
updateValue: config.geolocationSecondValue,
});
if (response.data?.success) {
secondTableUpdated = true;
console.log("✅ 두 번째 테이블 UPDATE 성공:", response.data);
} else {
console.error("❌ 두 번째 테이블 UPDATE 실패:", response.data);
toast.error(`${config.geolocationSecondTableName} 테이블 업데이트에 실패했습니다.`);
}
} else {
console.warn("⚠️ 두 번째 테이블 키 값이 없어서 UPDATE를 건너뜁니다:", {
keySourceField: config.geolocationSecondKeySourceField,
keyValue: secondKeyValue,
});
}
}
}
} catch (apiError) {
console.error("❌ 두 번째 테이블 API 오류:", apiError);
toast.error(`${config.geolocationSecondTableName} 테이블 작업 중 오류가 발생했습니다.`);
}
}
// formData 업데이트
if (context.onFormDataChange) {
@@ -3371,6 +3544,11 @@ export class ButtonActionExecutor {
successMsg += `\n${config.geolocationExtraField}: ${config.geolocationExtraValue}`;
}
}
// 두 번째 테이블 변경이 있으면 메시지에 포함
if (secondTableUpdated && config.geolocationSecondTableName) {
successMsg += `\n[${config.geolocationSecondTableName}] ${config.geolocationSecondField}: ${config.geolocationSecondValue}`;
}
// 성공 메시지 표시
toast.success(successMsg);
@@ -3470,6 +3648,7 @@ export class ButtonActionExecutor {
/**
* 필드 값 변경 액션 처리 (예: status를 active로 변경)
* 🆕 위치정보 수집 기능 추가
*/
private static async handleUpdateField(config: ButtonActionConfig, context: ButtonActionContext): Promise<boolean> {
try {
@@ -3483,7 +3662,7 @@ export class ButtonActionExecutor {
const multipleFields = config.updateMultipleFields || [];
// 단일 필드 변경이나 다중 필드 변경 중 하나는 있어야 함
if (!targetField && multipleFields.length === 0) {
if (!targetField && multipleFields.length === 0 && !config.updateWithGeolocation) {
toast.error("변경할 필드가 설정되지 않았습니다.");
return false;
}
@@ -3510,6 +3689,69 @@ export class ButtonActionExecutor {
updates[field] = value;
});
// 🆕 위치정보 수집 (updateWithGeolocation이 true인 경우)
if (config.updateWithGeolocation) {
const latField = config.updateGeolocationLatField;
const lngField = config.updateGeolocationLngField;
if (!latField || !lngField) {
toast.error("위도/경도 저장 필드가 설정되지 않았습니다.");
return false;
}
// 브라우저 Geolocation API 지원 확인
if (!navigator.geolocation) {
toast.error("이 브라우저는 위치정보를 지원하지 않습니다.");
return false;
}
// 로딩 토스트 표시
const loadingToastId = toast.loading("위치 정보를 가져오는 중...");
try {
// 위치 정보 가져오기
const position = await new Promise<GeolocationPosition>((resolve, reject) => {
navigator.geolocation.getCurrentPosition(resolve, reject, {
enableHighAccuracy: true,
timeout: 10000,
maximumAge: 0,
});
});
toast.dismiss(loadingToastId);
const { latitude, longitude, accuracy } = position.coords;
const timestamp = new Date(position.timestamp);
console.log("📍 위치정보 획득:", { latitude, longitude, accuracy });
// 위치정보를 updates에 추가
updates[latField] = latitude;
updates[lngField] = longitude;
if (config.updateGeolocationAccuracyField && accuracy !== null) {
updates[config.updateGeolocationAccuracyField] = accuracy;
}
if (config.updateGeolocationTimestampField) {
updates[config.updateGeolocationTimestampField] = timestamp.toISOString();
}
} catch (geoError: any) {
toast.dismiss(loadingToastId);
// GeolocationPositionError 처리
if (geoError.code === 1) {
toast.error("위치 정보 접근이 거부되었습니다.");
} else if (geoError.code === 2) {
toast.error("위치 정보를 사용할 수 없습니다.");
} else if (geoError.code === 3) {
toast.error("위치 정보 요청 시간이 초과되었습니다.");
} else {
toast.error("위치 정보를 가져오는 중 오류가 발생했습니다.");
}
return false;
}
}
console.log("🔄 변경할 필드들:", updates);
// formData 업데이트
@@ -3523,6 +3765,67 @@ export class ButtonActionExecutor {
const autoSave = config.updateAutoSave !== false;
if (autoSave) {
// 🆕 키 필드 설정이 있는 경우 (특수 키워드 지원) - 직접 DB UPDATE
const keyField = config.updateKeyField;
const keySourceField = config.updateKeySourceField;
const targetTableName = config.updateTableName || tableName;
if (keyField && keySourceField) {
// 특수 키워드 변환 (예: __userId__ → 실제 사용자 ID)
const keyValue = resolveSpecialKeyword(keySourceField, context);
console.log("🔄 필드 값 변경 - 키 필드 사용:", {
targetTable: targetTableName,
keyField,
keySourceField,
keyValue,
updates,
});
if (!keyValue) {
console.warn("⚠️ 키 값이 없어서 업데이트를 건너뜁니다:", { keySourceField, keyValue });
toast.error("레코드를 식별할 키 값이 없습니다.");
return false;
}
try {
// 각 필드에 대해 개별 UPDATE 호출
const { apiClient } = await import("@/lib/api/client");
for (const [field, value] of Object.entries(updates)) {
console.log(`🔄 DB UPDATE: ${targetTableName}.${field} = ${value} WHERE ${keyField} = ${keyValue}`);
const response = await apiClient.put(`/dynamic-form/update-field`, {
tableName: targetTableName,
keyField: keyField,
keyValue: keyValue,
updateField: field,
updateValue: value,
});
if (!response.data?.success) {
console.error(`${field} 업데이트 실패:`, response.data);
toast.error(`${field} 업데이트에 실패했습니다.`);
return false;
}
}
console.log("✅ 모든 필드 업데이트 성공");
toast.success(config.successMessage || "상태가 변경되었습니다.");
// 테이블 새로고침 이벤트 발생
window.dispatchEvent(new CustomEvent("refreshTableData", {
detail: { tableName: targetTableName }
}));
return true;
} catch (apiError) {
console.error("❌ 필드 값 변경 API 호출 실패:", apiError);
toast.error(config.errorMessage || "상태 변경 중 오류가 발생했습니다.");
return false;
}
}
// onSave 콜백이 있으면 사용
if (onSave) {
console.log("🔄 필드 값 변경 후 자동 저장 (onSave 콜백)");
@@ -3537,7 +3840,7 @@ export class ButtonActionExecutor {
}
}
// API를 통한 직접 저장
// API를 통한 직접 저장 (기존 방식: formData에 PK가 있는 경우)
if (tableName && formData) {
console.log("🔄 필드 값 변경 후 자동 저장 (API 직접 호출)");
try {
@@ -3546,7 +3849,7 @@ export class ButtonActionExecutor {
const pkValue = formData[pkField] || formData.id;
if (!pkValue) {
toast.error("레코드 ID를 찾을 수 없습니다.");
toast.error("레코드 ID를 찾을 수 없습니다. 키 필드를 설정해주세요.");
return false;
}