253 lines
7.9 KiB
TypeScript
253 lines
7.9 KiB
TypeScript
import { ValueType } from "realgrid";
|
|
|
|
import type { ColumnConfig, ColumnInfo } from "@/components/type/DataGrid.type";
|
|
import type { StatisticsLogDetailRow } from "@/entities/statistics/model/statistics-interface";
|
|
|
|
export const LOG_DETAIL_DETAILS_FIELD = "details";
|
|
export const LOG_DETAIL_DETAILS_RAW_FIELD = "__detailsRaw";
|
|
|
|
export interface LogDetailVerificationColumn {
|
|
tblNm: string;
|
|
metaSn: number;
|
|
fieldNm: string;
|
|
engColNm: string;
|
|
dataScpCd: string;
|
|
kornColNm: string;
|
|
}
|
|
|
|
export interface LogDetailVerificationItem {
|
|
columns: LogDetailVerificationColumn[];
|
|
cmprOpert: string | null;
|
|
vrfcDtlSn: number;
|
|
refStatsCd: string | null;
|
|
eqtnCombSeq: number;
|
|
engArtopnEqtnCn: string;
|
|
kornArtopnEqtnCn: string;
|
|
}
|
|
|
|
const COMMON_FIELD_ORDER = ["rmrk", "sggNm", "admdstCd", "admdstNm"] as const;
|
|
|
|
const FIELD_HEADER: Record<string, string> = {
|
|
rmrk: "비고",
|
|
sggNm: "시군구",
|
|
admdstCd: "행정구역코드",
|
|
admdstNm: "행정구역명",
|
|
vrfcSn: "검증순번",
|
|
vrfcNm: "검증명",
|
|
trgtStatsCd: "대상통계코드",
|
|
trgtStatsNm: "대상통계명",
|
|
vrfcClsfCd: "검증분류코드",
|
|
vrfcClsfNm: "검증분류명",
|
|
dcptDigtCnt: "소수점자릿수",
|
|
dcptPrcsMthCd: "소수점처리방법코드",
|
|
dcptPrcsMthNm: "소수점처리방법명",
|
|
useYn: "사용여부",
|
|
regDt: "등록일시",
|
|
mdfcnDt: "수정일시",
|
|
[LOG_DETAIL_DETAILS_FIELD]: "상세",
|
|
};
|
|
|
|
/** 검증 규칙 로그 상세(vrfcSn 등) 컬럼 표시 순서 */
|
|
const VERIFICATION_FIELD_ORDER = [
|
|
"vrfcSn",
|
|
"vrfcNm",
|
|
"trgtStatsCd",
|
|
"trgtStatsNm",
|
|
"vrfcClsfCd",
|
|
"vrfcClsfNm",
|
|
"dcptDigtCnt",
|
|
"dcptPrcsMthCd",
|
|
"dcptPrcsMthNm",
|
|
"useYn",
|
|
"regDt",
|
|
"mdfcnDt",
|
|
] as const;
|
|
|
|
/** API `data`가 배열 또는 단일 객체일 때 그리드 행 배열로 정규화 */
|
|
export function normalizeLogDetailList(
|
|
data: StatisticsLogDetailRow[] | StatisticsLogDetailRow | null | undefined,
|
|
): StatisticsLogDetailRow[] {
|
|
if (Array.isArray(data)) return data;
|
|
if (data != null && typeof data === "object") return [data];
|
|
return [];
|
|
}
|
|
|
|
function parseLogDetailsValue(
|
|
value: unknown,
|
|
): LogDetailVerificationItem[] | null {
|
|
if (value == null || value === "") return null;
|
|
if (Array.isArray(value)) {
|
|
return value as LogDetailVerificationItem[];
|
|
}
|
|
if (typeof value === "string") {
|
|
try {
|
|
const parsed: unknown = JSON.parse(value);
|
|
return Array.isArray(parsed)
|
|
? (parsed as LogDetailVerificationItem[])
|
|
: null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/** details JSON 배열을 그리드 버튼 셀 + 숨김 raw 필드로 변환 */
|
|
export function prepareLogDetailRowsForGrid(
|
|
rows: StatisticsLogDetailRow[],
|
|
): Record<string, string | number>[] {
|
|
return rows.map((row) => {
|
|
const next: Record<string, string | number> = {};
|
|
|
|
for (const [key, value] of Object.entries(row)) {
|
|
if (key === LOG_DETAIL_DETAILS_FIELD) {
|
|
const parsed = parseLogDetailsValue(value);
|
|
if (parsed && parsed.length > 0) {
|
|
next[LOG_DETAIL_DETAILS_RAW_FIELD] = JSON.stringify(parsed);
|
|
next[LOG_DETAIL_DETAILS_FIELD] = "상세보기";
|
|
} else {
|
|
next[LOG_DETAIL_DETAILS_RAW_FIELD] = "";
|
|
next[LOG_DETAIL_DETAILS_FIELD] =
|
|
value === null || value === undefined
|
|
? ""
|
|
: typeof value === "object"
|
|
? ""
|
|
: String(value);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
next[key] =
|
|
value === null || value === undefined
|
|
? ""
|
|
: (value as string | number);
|
|
}
|
|
|
|
if (
|
|
LOG_DETAIL_DETAILS_FIELD in row &&
|
|
!(LOG_DETAIL_DETAILS_RAW_FIELD in next)
|
|
) {
|
|
next[LOG_DETAIL_DETAILS_RAW_FIELD] = "";
|
|
}
|
|
|
|
return next;
|
|
});
|
|
}
|
|
|
|
function collectFieldNames(rows: StatisticsLogDetailRow[]): string[] {
|
|
const keys = new Set<string>();
|
|
for (const row of rows) {
|
|
for (const key of Object.keys(row)) {
|
|
keys.add(key);
|
|
}
|
|
}
|
|
const verification = VERIFICATION_FIELD_ORDER.filter((k) => keys.has(k));
|
|
const common = COMMON_FIELD_ORDER.filter((k) => keys.has(k));
|
|
const ordered = new Set<string>([
|
|
...verification,
|
|
...common,
|
|
LOG_DETAIL_DETAILS_FIELD,
|
|
]);
|
|
const rest = [...keys]
|
|
.filter((k) => !ordered.has(k) && k !== LOG_DETAIL_DETAILS_RAW_FIELD)
|
|
.sort((a, b) => a.localeCompare(b, "ko"));
|
|
const details = keys.has(LOG_DETAIL_DETAILS_FIELD)
|
|
? [LOG_DETAIL_DETAILS_FIELD]
|
|
: [];
|
|
return [...verification, ...common, ...rest, ...details];
|
|
}
|
|
|
|
function isNumericField(rows: StatisticsLogDetailRow[], fieldName: string): boolean {
|
|
return rows.some((row) => {
|
|
const v = row[fieldName];
|
|
return typeof v === "number" && !Number.isNaN(v);
|
|
});
|
|
}
|
|
|
|
function buildColumns(rows: StatisticsLogDetailRow[]): ColumnConfig[] {
|
|
const fieldNames = collectFieldNames(rows);
|
|
if (fieldNames.length === 0) {
|
|
return COMMON_FIELD_ORDER.map((fieldName) => ({
|
|
name: fieldName,
|
|
fieldName,
|
|
header: { text: FIELD_HEADER[fieldName] ?? fieldName },
|
|
editable: false,
|
|
width: Math.min(340, Math.max(90, fieldName.length * 9 + 28))
|
|
}));
|
|
}
|
|
|
|
return fieldNames.map((fieldName) => {
|
|
const numeric = isNumericField(rows, fieldName);
|
|
if (fieldName === LOG_DETAIL_DETAILS_FIELD) {
|
|
return {
|
|
name: fieldName,
|
|
fieldName,
|
|
header: { text: FIELD_HEADER[fieldName] ?? fieldName },
|
|
editable: false,
|
|
width: 100,
|
|
styleName: "rg-cell-outline-action-btn",
|
|
};
|
|
}
|
|
return {
|
|
name: fieldName,
|
|
fieldName,
|
|
header: { text: FIELD_HEADER[fieldName] ?? fieldName },
|
|
editable: false,
|
|
valueType: numeric ? ValueType.NUMBER : undefined,
|
|
dataFormat: numeric ? "#,##0.###" : undefined,
|
|
width: Math.min(340, Math.max(90, fieldName.length * 9 + 28)),
|
|
visible: fieldName !== "admdstCd",
|
|
};
|
|
});
|
|
}
|
|
|
|
function appendHiddenDetailsRawColumn(columns: ColumnConfig[]): ColumnConfig[] {
|
|
const hasDetails = columns.some(
|
|
(column) => column.fieldName === LOG_DETAIL_DETAILS_FIELD,
|
|
);
|
|
if (!hasDetails) return columns;
|
|
|
|
return [
|
|
...columns,
|
|
{
|
|
name: LOG_DETAIL_DETAILS_RAW_FIELD,
|
|
fieldName: LOG_DETAIL_DETAILS_RAW_FIELD,
|
|
header: { text: "" },
|
|
editable: false,
|
|
visible: false,
|
|
},
|
|
];
|
|
}
|
|
|
|
export function buildLogHistoryDetailColumnInfo(
|
|
rows: StatisticsLogDetailRow[],
|
|
): ColumnInfo {
|
|
const columns = appendHiddenDetailsRawColumn(buildColumns(rows));
|
|
return {
|
|
columns,
|
|
columnLayout: columns
|
|
.filter((column) => column.visible !== false)
|
|
.map((column) => column.fieldName),
|
|
};
|
|
}
|
|
|
|
/** 그리드 field 정의와 행 키를 맞춤 (누락 필드 → "") */
|
|
export function alignLogDetailRowsToColumns(
|
|
rows: Record<string, string | number>[],
|
|
columnInfo: ColumnInfo,
|
|
): Record<string, string | number>[] {
|
|
const fieldNames = columnInfo.columns.map((c) => c.fieldName);
|
|
return rows.map((row) => {
|
|
const aligned: Record<string, string | number> = {};
|
|
for (const fieldName of fieldNames) {
|
|
const value = row[fieldName];
|
|
aligned[fieldName] =
|
|
value === null || value === undefined ? "" : value;
|
|
}
|
|
return aligned;
|
|
});
|
|
}
|
|
|
|
export const emptyLogHistoryDetailColumnInfo =
|
|
buildLogHistoryDetailColumnInfo([]);
|