feat(repeat-screen-modal): 복수 외부 테이블 집계 지원 및 집계 설정 모달 UI 추가

- 여러 외부 테이블 데이터를 합산하여 집계 계산 지원
- 집계 설정 전용 모달(AggregationSettingsModal) 추가
- AggregationConfig에 hidden 속성 추가 (연산에만 사용, 표시 제외)
- 채번 규칙 API 에러 처리 개선 (조용히 무시, 로그 최소화)
This commit is contained in:
SeongHyun Kim 2025-12-10 10:37:33 +09:00
parent 5e97a3a5e9
commit ae6f022f88
7 changed files with 1623 additions and 134 deletions

View File

@ -607,7 +607,9 @@ class NumberingRuleService {
} }
const result = await pool.query(query, params); const result = await pool.query(query, params);
if (result.rowCount === 0) return null; if (result.rowCount === 0) {
return null;
}
const rule = result.rows[0]; const rule = result.rows[0];

View File

@ -317,6 +317,11 @@ apiClient.interceptors.response.use(
return Promise.reject(error); return Promise.reject(error);
} }
// 채번 규칙 미리보기 API 실패는 조용하게 처리 (화면 로드 시 자주 발생)
if (url?.includes("/numbering-rules/") && url?.includes("/preview")) {
return Promise.reject(error);
}
// 다른 에러들은 기존처럼 상세 로그 출력 // 다른 에러들은 기존처럼 상세 로그 출력
console.error("API 응답 오류:", { console.error("API 응답 오류:", {
status: status, status: status,
@ -324,7 +329,6 @@ apiClient.interceptors.response.use(
url: url, url: url,
data: error.response?.data, data: error.response?.data,
message: error.message, message: error.message,
headers: error.config?.headers,
}); });
// 401 에러 처리 // 401 에러 처리

View File

@ -109,11 +109,24 @@ export async function deleteNumberingRule(ruleId: string): Promise<ApiResponse<v
export async function previewNumberingCode( export async function previewNumberingCode(
ruleId: string ruleId: string
): Promise<ApiResponse<{ generatedCode: string }>> { ): Promise<ApiResponse<{ generatedCode: string }>> {
// ruleId 유효성 검사
if (!ruleId || ruleId === "undefined" || ruleId === "null") {
return { success: false, error: "채번 규칙 ID가 설정되지 않았습니다" };
}
try { try {
const response = await apiClient.post(`/numbering-rules/${ruleId}/preview`); const response = await apiClient.post(`/numbering-rules/${ruleId}/preview`);
if (!response.data) {
return { success: false, error: "서버 응답이 비어있습니다" };
}
return response.data; return response.data;
} catch (error: any) { } catch (error: any) {
return { success: false, error: error.message || "코드 미리보기 실패" }; const errorMessage =
error.response?.data?.error ||
error.response?.data?.message ||
error.message ||
"코드 미리보기 실패";
return { success: false, error: errorMessage };
} }
} }

View File

@ -590,18 +590,24 @@ export function RepeatScreenModalComponent({
if (!hasExternalAggregation) return; if (!hasExternalAggregation) return;
// contentRows에서 외부 테이블 데이터 소스가 있는 table 타입 행 찾기 // contentRows에서 외부 테이블 데이터 소스가 있는 모든 table 타입 행 찾기
const tableRowWithExternalSource = contentRows.find( const tableRowsWithExternalSource = contentRows.filter(
(row) => row.type === "table" && row.tableDataSource?.enabled (row) => row.type === "table" && row.tableDataSource?.enabled
); );
if (!tableRowWithExternalSource) return; if (tableRowsWithExternalSource.length === 0) return;
// 각 카드의 집계 재계산 // 각 카드의 집계 재계산
const updatedCards = groupedCardsData.map((card) => { const updatedCards = groupedCardsData.map((card) => {
const key = `${card._cardId}-${tableRowWithExternalSource.id}`; // 🆕 v3.11: 모든 외부 테이블 행의 데이터를 합침
// 🆕 v3.7: 삭제된 행은 집계에서 제외 const allExternalRows: any[] = [];
const externalRows = (extData[key] || []).filter((row) => !row._isDeleted); for (const tableRow of tableRowsWithExternalSource) {
const key = `${card._cardId}-${tableRow.id}`;
// 🆕 v3.7: 삭제된 행은 집계에서 제외
const rows = (extData[key] || []).filter((row) => !row._isDeleted);
allExternalRows.push(...rows);
}
const externalRows = allExternalRows;
// 집계 재계산 // 집계 재계산
const newAggregations: Record<string, number> = {}; const newAggregations: Record<string, number> = {};

View File

@ -289,6 +289,9 @@ export interface AggregationConfig {
resultField: string; // 결과 필드명 (예: "total_balance_qty") resultField: string; // 결과 필드명 (예: "total_balance_qty")
label: string; // 표시 라벨 (예: "총수주잔량") label: string; // 표시 라벨 (예: "총수주잔량")
// === 🆕 v3.10: 숨김 설정 ===
hidden?: boolean; // 레이아웃에서 숨김 (연산에만 사용, 기본: false)
// === 🆕 v3.9: 저장 설정 === // === 🆕 v3.9: 저장 설정 ===
saveConfig?: AggregationSaveConfig; // 연관 테이블 저장 설정 saveConfig?: AggregationSaveConfig; // 연관 테이블 저장 설정
} }

View File

@ -113,22 +113,20 @@ export const TextInputComponent: React.FC<TextInputComponentProps> = ({
// 채번 규칙은 비동기로 처리 // 채번 규칙은 비동기로 처리
if (testAutoGeneration.type === "numbering_rule") { if (testAutoGeneration.type === "numbering_rule") {
const ruleId = testAutoGeneration.options?.numberingRuleId; const ruleId = testAutoGeneration.options?.numberingRuleId;
if (ruleId) { if (ruleId && ruleId !== "undefined" && ruleId !== "null") {
try { try {
console.log("🚀 채번 규칙 API 호출 시작:", ruleId); const { previewNumberingCode } = await import("@/lib/api/numberingRule");
const { generateNumberingCode } = await import("@/lib/api/numberingRule"); const response = await previewNumberingCode(ruleId);
const response = await generateNumberingCode(ruleId);
console.log("✅ 채번 규칙 API 응답:", response);
if (response.success && response.data) { if (response.success && response.data) {
generatedValue = response.data.generatedCode; generatedValue = response.data.generatedCode;
} }
} catch (error) { // 실패 시 조용히 무시 (채번 규칙이 없어도 화면은 정상 로드)
console.error("❌ 채번 규칙 코드 생성 실패:", error); } catch {
// 네트워크 에러 등 예외 상황은 조용히 무시
} finally { } finally {
isGeneratingRef.current = false; // 생성 완료 isGeneratingRef.current = false;
} }
} else { } else {
console.warn("⚠️ 채번 규칙 ID가 없습니다");
isGeneratingRef.current = false; isGeneratingRef.current = false;
} }
} else { } else {