Compare commits

...

2 Commits

Author SHA1 Message Date
dohyeons 1170e34d4e Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into common/feat/dashboard-map 2025-12-16 14:44:48 +09:00
dohyeons aacedc004c 버튼 수정 2025-12-16 14:44:35 +09:00
2 changed files with 138 additions and 117 deletions

View File

@ -121,45 +121,45 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
return;
}
// 🆕 초기 로드 시 계산식 필드 자동 업데이트 (한 번만 실행)
// 🆕 초기 로드 시 계산식 필드 자동 업데이트 (한 번만 실행)
const calculatedFields = fields.filter((f) => f.type === "calculated");
if (calculatedFields.length > 0 && !initialCalcDoneRef.current) {
if (calculatedFields.length > 0 && !initialCalcDoneRef.current) {
const updatedValue = value.map((item) => {
const updatedItem = { ...item };
let hasChange = false;
const updatedItem = { ...item };
let hasChange = false;
calculatedFields.forEach((calcField) => {
const calculatedValue = calculateValue(calcField.formula, updatedItem);
if (calculatedValue !== null && updatedItem[calcField.name] !== calculatedValue) {
updatedItem[calcField.name] = calculatedValue;
hasChange = true;
const calculatedValue = calculateValue(calcField.formula, updatedItem);
if (calculatedValue !== null && updatedItem[calcField.name] !== calculatedValue) {
updatedItem[calcField.name] = calculatedValue;
hasChange = true;
}
});
// 🆕 기존 레코드임을 표시 (id가 있는 경우)
if (updatedItem.id) {
updatedItem._existingRecord = true;
}
return hasChange ? updatedItem : item;
});
// 🆕 기존 레코드임을 표시 (id가 있는 경우)
if (updatedItem.id) {
updatedItem._existingRecord = true;
}
setItems(updatedValue);
initialCalcDoneRef.current = true;
return hasChange ? updatedItem : item;
});
setItems(updatedValue);
initialCalcDoneRef.current = true;
// 계산된 값이 있으면 onChange 호출 (초기 1회만)
const dataWithMeta = config.targetTable
? updatedValue.map((item) => ({ ...item, _targetTable: config.targetTable }))
: updatedValue;
onChange?.(dataWithMeta);
} else {
// 🆕 기존 레코드 플래그 추가
// 계산된 값이 있으면 onChange 호출 (초기 1회만)
const dataWithMeta = config.targetTable
? updatedValue.map((item) => ({ ...item, _targetTable: config.targetTable }))
: updatedValue;
onChange?.(dataWithMeta);
} else {
// 🆕 기존 레코드 플래그 추가
const valueWithFlag = value.map((item) => ({
...item,
_existingRecord: !!item.id,
}));
setItems(valueWithFlag);
...item,
_existingRecord: !!item.id,
}));
setItems(valueWithFlag);
}
}, [value]);
@ -200,14 +200,25 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
const currentDeletedIds = deletedItemIdsRef.current;
console.log("🗑️ [RepeaterInput] 현재 삭제 목록:", currentDeletedIds);
const dataWithMeta = config.targetTable
? newItems.map((item, idx) => ({
// 🆕 빈 배열일 때도 삭제 ID를 전달해야 함
let dataWithMeta: any[];
if (config.targetTable) {
if (newItems.length > 0) {
// 항목이 있으면 첫 번째 항목에 삭제 ID 포함
dataWithMeta = newItems.map((item, idx) => ({
...item,
_targetTable: config.targetTable,
// 첫 번째 항목에만 삭제 ID 목록 포함
...(idx === 0 ? { _deletedItemIds: currentDeletedIds } : {}),
}))
: newItems;
}));
} else if (currentDeletedIds.length > 0) {
// 🆕 모든 항목 삭제 시 삭제 ID만 포함된 메타 객체 전달
dataWithMeta = [{ _targetTable: config.targetTable, _deletedItemIds: currentDeletedIds, _deleteOnly: true }];
} else {
dataWithMeta = [];
}
} else {
dataWithMeta = newItems;
}
console.log("🗑️ [RepeaterInput] onChange 호출 - dataWithMeta:", dataWithMeta);
onChange?.(dataWithMeta);
@ -372,11 +383,11 @@ export const RepeaterInput: React.FC<RepeaterInputProps> = ({
// 천 단위 구분자
let result =
format?.useThousandSeparator !== false
? formattedValue.toLocaleString("ko-KR", {
minimumFractionDigits: format?.minimumFractionDigits ?? 0,
maximumFractionDigits: format?.maximumFractionDigits ?? format?.decimalPlaces ?? 0,
})
: formattedValue.toString();
? formattedValue.toLocaleString("ko-KR", {
minimumFractionDigits: format?.minimumFractionDigits ?? 0,
maximumFractionDigits: format?.maximumFractionDigits ?? format?.decimalPlaces ?? 0,
})
: formattedValue.toString();
// 접두사/접미사 추가
if (format?.prefix) result = format.prefix + result;

View File

@ -802,6 +802,7 @@ export class ButtonActionExecutor {
}
}
// 🆕 배열이 비어있지 않거나, 하나라도 항목이 있으면 처리
if (Array.isArray(parsedValue) && parsedValue.length > 0) {
const firstItem = parsedValue[0];
const deletedItemIds = firstItem?._deletedItemIds;
@ -811,6 +812,7 @@ export class ButtonActionExecutor {
firstItemKeys: firstItem ? Object.keys(firstItem) : [],
deletedItemIds,
targetTable,
isDeleteOnly: firstItem?._deleteOnly,
});
if (deletedItemIds && deletedItemIds.length > 0 && targetTable) {
@ -868,6 +870,12 @@ export class ButtonActionExecutor {
// _targetTable이 없거나, _repeatScreenModal_ 키면 스킵 (다른 로직에서 처리)
if (!repeaterTargetTable || fieldKey.startsWith("_repeatScreenModal_")) continue;
// 🆕 _deleteOnly 플래그가 있으면 INSERT/UPDATE 건너뛰기 (삭제 전용)
if (firstItem?._deleteOnly) {
console.log(`⏭️ [handleSave] 삭제 전용 데이터 - INSERT/UPDATE 스킵: ${fieldKey}`);
continue;
}
console.log(`📦 [handleSave] RepeaterFieldGroup 데이터 저장: ${fieldKey}${repeaterTargetTable}`, {
itemCount: parsedData.length,
});
@ -2839,10 +2847,7 @@ export class ButtonActionExecutor {
* EditModal public으로
*
*/
public static async executeAfterSaveControl(
config: ButtonActionConfig,
context: ButtonActionContext,
): Promise<void> {
public static async executeAfterSaveControl(config: ButtonActionConfig, context: ButtonActionContext): Promise<void> {
console.log("🎯 저장 후 제어 실행:", {
enableDataflowControl: config.enableDataflowControl,
dataflowConfig: config.dataflowConfig,
@ -4306,19 +4311,20 @@ export class ButtonActionExecutor {
if (userId) {
try {
const { apiClient } = await import("@/lib/api/client");
const statusTableName = config.trackingStatusTableName || this.trackingConfig?.trackingStatusTableName || context.tableName || "vehicles";
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 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) {
@ -4335,14 +4341,18 @@ export class ButtonActionExecutor {
// 마지막 위치 저장 (추적 중이었던 경우에만)
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 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;
const vehicleId =
dbVehicleId ||
this.trackingContext?.formData?.[this.trackingConfig?.trackingVehicleIdField || "vehicle_id"] ||
null;
await this.saveLocationToHistory(
tripId,