[st] - tab & menu 로직 수정

This commit is contained in:
2026-06-15 16:34:02 +09:00
parent 44c5d66afa
commit f21dde8f7d
13 changed files with 690 additions and 23 deletions
@@ -8,11 +8,19 @@ import { statisticsInputSewerageFinanceApi } from "@/entities/statistics/api/inp
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type {
CauserBurdenItem,
CauserBurdenSaveItem,
CauserBurdenSaveRequest,
SewerageChargeItem,
SewerageChargeSaveItem,
SewerageChargeSaveRequest,
SewerageFinanceRequestParams,
SewerageFinanceSettleItem,
SewerageFinanceSettleSaveItem,
SewerageFinanceSettleSaveRequest,
SewerageFinanceVerificationResult,
SewerageUsageFeeItem,
SewerageUsageFeeSaveItem,
SewerageUsageFeeSaveRequest,
} from "@/entities/statistics/model/input/statistics-input-sewerage-finance-interface";
import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
import type { cancelSubmitRequest, SewerageStatusInfo } from "@/entities/statistics/model/statistics-interface";
@@ -141,6 +149,163 @@ function mapRegionalListToGridRows<
});
}
function normalizeGridString(value: unknown): string {
if (value === null || value === undefined) return "";
return typeof value === "string" ? value : String(value);
}
function normalizeGridRmrk(value: unknown): string | null {
if (value === null || value === undefined) return null;
const s = typeof value === "string" ? value : String(value);
return s.trim() === "" ? null : s;
}
function buildColumnFormatNumber(columns: { fieldName?: string; dataFormat?: string }[]) {
const columnFormatMap = new Map<string, string>();
columns.forEach((col) => {
if (col.fieldName && col.dataFormat) {
columnFormatMap.set(col.fieldName, col.dataFormat);
}
});
return (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === "") return 0;
const num =
typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
}
function resolveSewerageFinanceTab(tabKey: string) {
return {
isSettleTab: tabKey === SewerageFinanceSettleInpColumn.tabKey,
isChargeTab: tabKey === SewerageChargeInpColumn.tabKey,
isUsageFeeTab: tabKey === SewerageUsageFeeInpColumn.tabKey,
isCauserBurdenTab: tabKey === CauserBurdenInpColumn.tabKey,
};
}
function buildSewerageFinanceSettleSaveList(
currentData: Record<string, string | number>[],
): SewerageFinanceSettleSaveItem[] {
const formatNumber = buildColumnFormatNumber(SewerageFinanceSettleInpColumn.columns);
return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd),
sggNm: normalizeGridString(d.sggNm),
admdstNm: normalizeGridString(d.admdstNm),
cnterGovSprtAmt: formatNumber(d.cnterGovSprtAmt, "cnterGovSprtAmt"),
mctpvLcexpAmt: formatNumber(d.mctpvLcexpAmt, "mctpvLcexpAmt"),
wareaSggLclAmt: formatNumber(d.wareaSggLclAmt, "wareaSggLclAmt"),
sewerUseAmt: formatNumber(d.sewerUseAmt, "sewerUseAmt"),
orgprBrdnAmt: formatNumber(d.orgprBrdnAmt, "orgprBrdnAmt"),
etcIncmAmt: formatNumber(d.etcIncmAmt, "etcIncmAmt"),
crfwAmt: formatNumber(d.crfwAmt, "crfwAmt"),
lbltAmt: formatNumber(d.lbltAmt, "lbltAmt"),
tmplntFcltAmt: formatNumber(d.tmplntFcltAmt, "tmplntFcltAmt"),
tmplntMendngAmt: formatNumber(d.tmplntMendngAmt, "tmplntMendngAmt"),
tmplntOperKeepAmt: formatNumber(d.tmplntOperKeepAmt, "tmplntOperKeepAmt"),
tmplntEtcAmt: formatNumber(d.tmplntEtcAmt, "tmplntEtcAmt"),
swagePplFcltAmt: formatNumber(d.swagePplFcltAmt, "swagePplFcltAmt"),
swagePplMendngAmt: formatNumber(d.swagePplMendngAmt, "swagePplMendngAmt"),
swagePplOperKeepAmt: formatNumber(d.swagePplOperKeepAmt, "swagePplOperKeepAmt"),
swagePplEtcAmt: formatNumber(d.swagePplEtcAmt, "swagePplEtcAmt"),
swageImpdFcltAmt: formatNumber(d.swageImpdFcltAmt, "swageImpdFcltAmt"),
swageImpdMendngAmt: formatNumber(d.swageImpdMendngAmt, "swageImpdMendngAmt"),
swageImpdOperKeepAmt: formatNumber(d.swageImpdOperKeepAmt, "swageImpdOperKeepAmt"),
swageImpdFcltEtcAmt: formatNumber(d.swageImpdFcltEtcAmt, "swageImpdFcltEtcAmt"),
etcAcdntCrfwAmt: formatNumber(d.etcAcdntCrfwAmt, "etcAcdntCrfwAmt"),
etcNrcpAmt: formatNumber(d.etcNrcpAmt, "etcNrcpAmt"),
etcAmt: formatNumber(d.etcAmt, "etcAmt"),
rmrk: normalizeGridRmrk(d.rmrk),
fnnByWholPjtcoAmt: formatNumber(d.fnnByWholPjtcoAmt, "fnnByWholPjtcoAmt"),
usgByWholPjtcoAmt: formatNumber(d.usgByWholPjtcoAmt, "usgByWholPjtcoAmt"),
tmplntWholAmt: formatNumber(d.tmplntWholAmt, "tmplntWholAmt"),
wholSwagePplAmt: formatNumber(d.wholSwagePplAmt, "wholSwagePplAmt"),
wholSwageImpdFcltAmt: formatNumber(d.wholSwageImpdFcltAmt, "wholSwageImpdFcltAmt"),
etcWholAmt: formatNumber(d.etcWholAmt, "etcWholAmt"),
}));
}
function buildSewerageChargeSaveList(
currentData: Record<string, string | number>[],
): SewerageChargeSaveItem[] {
const formatNumber = buildColumnFormatNumber(SewerageChargeInpColumn.columns);
return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd),
sggNm: normalizeGridString(d.sggNm),
admdstNm: normalizeGridString(d.admdstNm),
anlLevyQnt: formatNumber(d.anlLevyQnt, "anlLevyQnt"),
levyAmt: formatNumber(d.levyAmt, "levyAmt"),
grssPrmcoAmt: formatNumber(d.grssPrmcoAmt, "grssPrmcoAmt"),
rmrk: normalizeGridRmrk(d.rmrk),
avgUntprcAmt: formatNumber(d.avgUntprcAmt, "avgUntprcAmt"),
grssUnitPrmcoAmt: formatNumber(d.grssUnitPrmcoAmt, "grssUnitPrmcoAmt"),
rlztnRt: formatNumber(d.rlztnRt, "rlztnRt"),
}));
}
function buildSewerageUsageFeeSaveList(
currentData: Record<string, string | number>[],
): SewerageUsageFeeSaveItem[] {
const formatNumber = buildColumnFormatNumber(SewerageUsageFeeInpColumn.columns);
return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd),
sggNm: normalizeGridString(d.sggNm),
admdstNm: normalizeGridString(d.admdstNm),
hshusLevyAmt: formatNumber(d.hshusLevyAmt, "hshusLevyAmt"),
hshusLevyQnt: formatNumber(d.hshusLevyQnt, "hshusLevyQnt"),
hshusUntprcAmt: formatNumber(d.hshusUntprcAmt, "hshusUntprcAmt"),
taskFrLevyAmt: formatNumber(d.taskFrLevyAmt, "taskFrLevyAmt"),
taskFrLevyQnt: formatNumber(d.taskFrLevyQnt, "taskFrLevyQnt"),
taskFrUntprcAmt: formatNumber(d.taskFrUntprcAmt, "taskFrUntprcAmt"),
fbuLevyAmt: formatNumber(d.fbuLevyAmt, "fbuLevyAmt"),
fbuLevyQnt: formatNumber(d.fbuLevyQnt, "fbuLevyQnt"),
fbuUntprcAmt: formatNumber(d.fbuUntprcAmt, "fbuUntprcAmt"),
bthhsFrSpcs1LevyAmt: formatNumber(d.bthhsFrSpcs1LevyAmt, "bthhsFrSpcs1LevyAmt"),
bthhsFrSpcs1LevyQnt: formatNumber(d.bthhsFrSpcs1LevyQnt, "bthhsFrSpcs1LevyQnt"),
bthhsFrSpcs1UntprcAmt: formatNumber(d.bthhsFrSpcs1UntprcAmt, "bthhsFrSpcs1UntprcAmt"),
bthhsFrSpcs2LevyAmt: formatNumber(d.bthhsFrSpcs2LevyAmt, "bthhsFrSpcs2LevyAmt"),
bthhsFrSpcs2LevyQnt: formatNumber(d.bthhsFrSpcs2LevyQnt, "bthhsFrSpcs2LevyQnt"),
bthhsFrSpcs2UntprcAmt: formatNumber(d.bthhsFrSpcs2UntprcAmt, "bthhsFrSpcs2UntprcAmt"),
induLevyAmt: formatNumber(d.induLevyAmt, "induLevyAmt"),
induLevyQnt: formatNumber(d.induLevyQnt, "induLevyQnt"),
induUntprcAmt: formatNumber(d.induUntprcAmt, "induUntprcAmt"),
etcLevyAmt: formatNumber(d.etcLevyAmt, "etcLevyAmt"),
etcLevyQnt: formatNumber(d.etcLevyQnt, "etcLevyQnt"),
etcLevyUntprcAmt: formatNumber(d.etcLevyUntprcAmt, "etcLevyUntprcAmt"),
rmrk: normalizeGridRmrk(d.rmrk),
levyAmt: formatNumber(d.levyAmt, "levyAmt"),
levyQnt: formatNumber(d.levyQnt, "levyQnt"),
}));
}
function buildCauserBurdenSaveList(
currentData: Record<string, string | number>[],
): CauserBurdenSaveItem[] {
const formatNumber = buildColumnFormatNumber(CauserBurdenInpColumn.columns);
return currentData.map((d) => ({
admdstCd: normalizeGridString(d.admdstCd),
sggNm: normalizeGridString(d.sggNm),
admdstNm: normalizeGridString(d.admdstNm),
levyEvntcsCnt: formatNumber(d.levyEvntcsCnt, "levyEvntcsCnt"),
levyAmt: formatNumber(d.levyAmt, "levyAmt"),
mltvEqpmEvntcsCnt: formatNumber(d.mltvEqpmEvntcsCnt, "mltvEqpmEvntcsCnt"),
mltvEqpmAmt: formatNumber(d.mltvEqpmAmt, "mltvEqpmAmt"),
othrCstrnOthrActrCnt: formatNumber(d.othrCstrnOthrActrCnt, "othrCstrnOthrActrCnt"),
othrCstrnOthrActrAmt: formatNumber(d.othrCstrnOthrActrAmt, "othrCstrnOthrActrAmt"),
rmrk: normalizeGridRmrk(d.rmrk),
trgtByWholClctnAmt: formatNumber(d.trgtByWholClctnAmt, "trgtByWholClctnAmt"),
}));
}
// 하수도 통계 관리 > 09. 하수도재정
const SewerageFinanceInp = () => {
const { setMessage: alert } = useAlertStore((state) => state);
@@ -541,7 +706,144 @@ const SewerageFinanceInp = () => {
};
const saveFunc = () => {
// 저장 로직
void (async () => {
const tabKey = activeTabRef.current;
const { isSettleTab, isChargeTab, isUsageFeeTab, isCauserBurdenTab } =
resolveSewerageFinanceTab(tabKey);
if (!isSettleTab && !isChargeTab && !isUsageFeeTab && !isCauserBurdenTab) {
alert("저장할 수 없는 탭입니다.");
return;
}
if (!lastSearchedValues || !searchParamRef.current) {
alert("먼저 조회를 진행해주세요.");
return;
}
if (!buildGugunOkForToolbar(lastSearchedValues)) {
alert("시군구를 선택해주세요.");
return;
}
if (!(await confirm("저장 하시겠습니까?"))) return;
const currentData = getTabGridRows(tabKey);
if (!currentData.length) {
alert("저장할 데이터가 없습니다.");
return;
}
const baseRequest = {
statsYr: lastSearchedValues.year ?? "",
ctpvNm: lastSearchedValues.sido ?? "",
sggNm:
lastSearchedValues.gugun === "ALL"
? ""
: (lastSearchedValues.gugun ?? ""),
sewerBzmnCd: lastSearchedValues.bsnmCd ?? "",
};
try {
setIsLoading(true);
const userInfo = await getUserInfo();
if (isSettleTab) {
const param: SewerageFinanceSettleSaveRequest = {
...baseRequest,
userId: userInfo.userUnqId,
saveList: buildSewerageFinanceSettleSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
};
const response =
await statisticsInputSewerageFinanceApi.saveSewerageFinanceSettle(param);
if (response.status === "success") {
await loadActiveTabData(
tabKey,
searchParamRef.current,
lastSearchedValues,
false,
false,
);
alert("저장되었습니다.");
} else {
alert(response.message || "데이터 저장 중 오류가 발생했습니다.");
}
return;
}
if (isChargeTab) {
const param: SewerageChargeSaveRequest = {
...baseRequest,
userId: userInfo.userUnqId,
saveList: buildSewerageChargeSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
};
const response =
await statisticsInputSewerageFinanceApi.saveSewerageCharge(param);
if (response.status === "success") {
await loadActiveTabData(
tabKey,
searchParamRef.current,
lastSearchedValues,
false,
false,
);
alert("저장되었습니다.");
} else {
alert(response.message || "데이터 저장 중 오류가 발생했습니다.");
}
return;
}
if (isUsageFeeTab) {
const param: SewerageUsageFeeSaveRequest = {
...baseRequest,
userId: userInfo.userUnqId,
saveList: buildSewerageUsageFeeSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
};
const response =
await statisticsInputSewerageFinanceApi.saveSewerageUsageFee(param);
if (response.status === "success") {
await loadActiveTabData(
tabKey,
searchParamRef.current,
lastSearchedValues,
false,
false,
);
alert("저장되었습니다.");
} else {
alert(response.message || "데이터 저장 중 오류가 발생했습니다.");
}
return;
}
const param: CauserBurdenSaveRequest = {
...baseRequest,
userId: userInfo.userUnqId,
saveList: buildCauserBurdenSaveList(currentData),
...(isAdminInfo ? { superAdmin: "superAdmin" } : {}),
};
const response = await statisticsInputSewerageFinanceApi.saveCauserBurden(param);
if (response.status === "success") {
await loadActiveTabData(
tabKey,
searchParamRef.current,
lastSearchedValues,
false,
false,
);
alert("저장되었습니다.");
} else {
alert(response.message || "데이터 저장 중 오류가 발생했습니다.");
}
} catch {
alert("데이터 저장 중 오류가 발생했습니다.");
} finally {
setIsLoading(false);
}
})();
};
const openVerificationHistoryDialog = () => {
@@ -58,7 +58,7 @@ export const SewerageChargeInpColumn = {
},
// 비고
{ name: 'rmrk', fieldName: '비고', header: { text: '비고' } },
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } },
// hidden
{ name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, },
@@ -59,37 +59,37 @@ export const SewerageUsageFeeInpColumn = {
// 가정용
{ name: 'hshusLevyAmt', fieldName: 'hshusLevyAmt', header: { text: '(백만원)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'hshusLevyQnt', fieldName: 'hshusLevyQnt', header: { text: '(천톤)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'hshusUntprcAmt', fieldName: 'hshusUntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'hshusUntprcAmt', fieldName: 'hshusUntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' },
// 업무용
{ name: 'taskFrLevyAmt', fieldName: 'taskFrLevyAmt', header: { text: '(백만원)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'taskFrLevyQnt', fieldName: 'taskFrLevyQnt', header: { text: '(천톤)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'taskFrUntprcAmt', fieldName: 'taskFrUntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'taskFrUntprcAmt', fieldName: 'taskFrUntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' },
// 영업용(일반용)
{ name: 'fbuLevyAmt', fieldName: 'fbuLevyAmt', header: { text: '(백만원)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'fbuLevyQnt', fieldName: 'fbuLevyQnt', header: { text: '(천톤)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'fbuUntprcAmt', fieldName: 'fbuUntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'fbuUntprcAmt', fieldName: 'fbuUntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' },
// 욕탕1종(대중탕용)
{ name: 'bthhsFrSpcs1LevyAmt', fieldName: 'bthhsFrSpcs1LevyAmt', header: { text: '(백만원)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'bthhsFrSpcs1LevyQnt', fieldName: 'bthhsFrSpcs1LevyQnt', header: { text: '(천톤)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'bthhsFrSpcs1UntprcAmt', fieldName: 'bthhsFrSpcs1UntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'bthhsFrSpcs1UntprcAmt', fieldName: 'bthhsFrSpcs1UntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' },
// 욕탕2종
{ name: 'bthhsFrSpcs2LevyAmt', fieldName: 'bthhsFrSpcs2LevyAmt', header: { text: '(백만원)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'bthhsFrSpcs2LevyQnt', fieldName: 'bthhsFrSpcs2LevyQnt', header: { text: '(천톤)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'bthhsFrSpcs2UntprcAmt', fieldName: 'bthhsFrSpcs2UntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'bthhsFrSpcs2UntprcAmt', fieldName: 'bthhsFrSpcs2UntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' },
// 산업 및 공업용
{ name: 'induLevyAmt', fieldName: 'induLevyAmt', header: { text: '(백만원)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'induLevyQnt', fieldName: 'induLevyQnt', header: { text: '(천톤)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'induUntprcAmt', fieldName: 'induUntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'induUntprcAmt', fieldName: 'induUntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' },
// 기타
{ name: 'etcLevyAmt', fieldName: 'etcLevyAmt', header: { text: '(백만원)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'etcLevyQnt', fieldName: 'etcLevyQnt', header: { text: '(천톤)' }, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'etcLevyUntprcAmt', fieldName: 'etcLevyUntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0' },
{ name: 'etcLevyUntprcAmt', fieldName: 'etcLevyUntprcAmt', header: { text: '(원/톤)' }, editable: false, footer: footerDecimal1, valueType: ValueType.NUMBER, dataFormat: '#,##0.0', styleName: 'rg-cell-readonly' },
// 비고
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } },