Merge remote-tracking branch 'origin/iipbs/st/dev' into feature/sein
This commit is contained in:
@@ -1,54 +1,32 @@
|
||||
"use client";
|
||||
|
||||
|
||||
|
||||
import { Suspense, useCallback, useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { usePathname } from "next/navigation";
|
||||
|
||||
|
||||
import { usePathname, useRouter } from "next/navigation";
|
||||
|
||||
import {
|
||||
|
||||
getTabStoreState as getGridTabStoreState,
|
||||
|
||||
useTabStore as gridTabStore,
|
||||
|
||||
} from "@rsone/components";
|
||||
|
||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||
|
||||
|
||||
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
|
||||
import { getCookie, useAlertStore, useMenuStore } from "@/shared/lib";
|
||||
|
||||
import { isBbsNoticeMenu } from "@/shared/lib/bbs-notice-menu";
|
||||
import {
|
||||
menuMatchesStatisticsTabId,
|
||||
resolveStatisticsTabId,
|
||||
} from "@/shared/lib/statistics-menu-url";
|
||||
import { useStatisticsTabUrlSync } from "@/shared/lib/use-statistics-tab-url-sync";
|
||||
|
||||
import { isVerificationSelectionReportMenu } from "@/shared/lib/verification-selection-report";
|
||||
|
||||
import { StatisticsPageLayout } from "@/shared/ui/statistics-page-layout";
|
||||
|
||||
import { tokenManager } from "@/entities/auth";
|
||||
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
|
||||
import { MenuApi } from "@/entities/menu";
|
||||
|
||||
import { BaseLayout } from "@common/components";
|
||||
|
||||
import { getConfig } from "@common/config";
|
||||
|
||||
|
||||
|
||||
import { getTabStoreState, useTabStore } from "@/store/tab-store";
|
||||
|
||||
|
||||
|
||||
import VerificationSelectionReport from "@/components/statistics/VerificationSelectionReport";
|
||||
|
||||
|
||||
import { getTabStoreState, useTabStore } from "@/store/tab-store";
|
||||
|
||||
const appConfig = getConfig();
|
||||
|
||||
@@ -58,42 +36,23 @@ const appSiteId = getCookie("site-id-key") ?? "";
|
||||
|
||||
const isLoggedIn = !!userId;
|
||||
|
||||
|
||||
|
||||
function WithBaseLayoutInner({
|
||||
|
||||
children,
|
||||
|
||||
}: {
|
||||
|
||||
children: React.ReactNode;
|
||||
|
||||
}) {
|
||||
|
||||
function WithBaseLayoutInner({ children }: { children: React.ReactNode }) {
|
||||
const pathname = usePathname();
|
||||
const router = useRouter();
|
||||
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
|
||||
/** 탭 추가 없이 메뉴만 눌렀을 때 패널 유지 (다른 메뉴 클릭해도 유지) */
|
||||
|
||||
const [isVerificationSelectionReportPanelOpen, setReportPanelOpen] =
|
||||
|
||||
useState(false);
|
||||
|
||||
|
||||
|
||||
const setCurrentMenuInfo = useMenuStore(
|
||||
|
||||
(state) => state.setCurrentMenuInfo,
|
||||
|
||||
);
|
||||
|
||||
const currentMenuInfo = useMenuStore((state) => state.currentMenuInfo);
|
||||
|
||||
|
||||
|
||||
const addTab = useTabStore((state) => state.addTab);
|
||||
|
||||
const setActiveTab = useTabStore((state) => state.setActiveTab);
|
||||
@@ -108,16 +67,11 @@ function WithBaseLayoutInner({
|
||||
|
||||
const addTabForGrid = gridTabStore((state) => state.addTab);
|
||||
|
||||
const setActiveTabForGrid = gridTabStore((state) => state.setActiveTab);
|
||||
|
||||
const setMenuListForGrid = gridTabStore((state) => state.setMenuList);
|
||||
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
|
||||
|
||||
|
||||
const { syncTabToUrl } = useStatisticsTabUrlSync({
|
||||
|
||||
pathname,
|
||||
|
||||
menuList,
|
||||
@@ -129,41 +83,27 @@ function WithBaseLayoutInner({
|
||||
addTabForGrid,
|
||||
|
||||
setCurrentMenuInfo,
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
const { data: menuListData } = useQuery({
|
||||
|
||||
queryKey: [ENDPOINT.CMS_SERVICE.MENU_ROLES],
|
||||
|
||||
queryFn: () => MenuApi.getMenuRoles(appSiteId),
|
||||
|
||||
enabled: isLoggedIn,
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (menuListData) {
|
||||
|
||||
setMenuList(menuListData);
|
||||
|
||||
setMenuListForGrid(menuListData);
|
||||
|
||||
}
|
||||
|
||||
}, [menuListData, setMenuList, setMenuListForGrid]);
|
||||
|
||||
|
||||
|
||||
// 새로고침 후 persist로 앱 탭만 복원된 경우 rsone 그리드 스토어에 탭을 맞춤
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (!menuList) return;
|
||||
|
||||
const appTabs = getTabStoreState().tabs;
|
||||
@@ -172,20 +112,13 @@ function WithBaseLayoutInner({
|
||||
|
||||
if (getGridTabStoreState().tabs.length > 0) return;
|
||||
|
||||
|
||||
|
||||
for (const tab of appTabs) {
|
||||
|
||||
getGridTabStoreState().addTab(tab.menuItem);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
const activeId = getTabStoreState().activeTabId;
|
||||
|
||||
if (activeId) {
|
||||
|
||||
setActiveTab(activeId);
|
||||
|
||||
const activeTab = appTabs.find((t) => t.id === activeId);
|
||||
@@ -193,155 +126,104 @@ function WithBaseLayoutInner({
|
||||
const gridActiveId = activeTab?.menuItem.menuEngNm;
|
||||
|
||||
if (gridActiveId) {
|
||||
|
||||
getGridTabStoreState().setActiveTab(gridActiveId);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}, [menuList, tabs, setActiveTab]);
|
||||
|
||||
|
||||
|
||||
// 활성 탭의 메뉴를 찾아서 urlPath를 가져옴. 탭이 전부 닫히면 ''를 넘겨 LNB에서 메뉴 강조 제거
|
||||
|
||||
const currentPath = useMemo(() => {
|
||||
|
||||
if (tabs.length === 0) return "";
|
||||
|
||||
|
||||
|
||||
if (activeTabId && menuList) {
|
||||
|
||||
// menuList에서 활성 탭의 메뉴 찾기
|
||||
|
||||
const findMenuByEngNm = (items: MenuItem[]): MenuItem | null => {
|
||||
|
||||
for (const item of items) {
|
||||
|
||||
if (item.urlPath === activeTabId) {
|
||||
|
||||
if (menuMatchesStatisticsTabId(item, activeTabId)) {
|
||||
return item;
|
||||
|
||||
}
|
||||
|
||||
if (item.children) {
|
||||
|
||||
const found = findMenuByEngNm(item.children);
|
||||
|
||||
if (found) return found;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
const activeMenu = findMenuByEngNm(menuList);
|
||||
|
||||
|
||||
|
||||
if (activeMenu?.urlPath) {
|
||||
|
||||
return activeMenu.urlPath;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 탭에서 직접 찾기
|
||||
|
||||
if (activeTabId) {
|
||||
|
||||
const currentTabs = getTabStoreState().tabs;
|
||||
|
||||
const activeTab = currentTabs.find((t) => t.id === activeTabId);
|
||||
|
||||
if (activeTab?.menuItem?.urlPath) {
|
||||
|
||||
return activeTab.menuItem.urlPath;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// currentMenuInfo에서 가져오기
|
||||
|
||||
if (currentMenuInfo?.urlPath) {
|
||||
|
||||
return currentMenuInfo.urlPath;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 기본값으로 pathname 사용
|
||||
|
||||
return pathname ?? "";
|
||||
|
||||
}, [tabs.length, activeTabId, menuList, currentMenuInfo, pathname]);
|
||||
|
||||
|
||||
|
||||
const handleMenuClick = useCallback(
|
||||
|
||||
(item: MenuItem) => {
|
||||
if (item.urlPath.includes("/cms/")) return;
|
||||
|
||||
setCurrentMenuInfo(item);
|
||||
|
||||
|
||||
|
||||
if (isVerificationSelectionReportMenu(item)) {
|
||||
|
||||
setReportPanelOpen(true);
|
||||
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
|
||||
if (isBbsNoticeMenu(item)) {
|
||||
if (item.urlPath) {
|
||||
router.push(item.urlPath);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const addTabResult: boolean = addTab(item);
|
||||
|
||||
|
||||
|
||||
// packages/rsone의 스토어에도 동기화
|
||||
|
||||
if (addTabResult) {
|
||||
|
||||
addTabForGrid(item);
|
||||
|
||||
if (item.urlPath) {
|
||||
|
||||
syncTabToUrl(item.urlPath);
|
||||
|
||||
syncTabToUrl(resolveStatisticsTabId(item.urlPath));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!addTabResult) {
|
||||
|
||||
alert(`최대 추가 할 수 있는 화면 수(10개)를 \n초과 하였습니다.
|
||||
|
||||
기존 화면을 닫은 후 이용해주세요.`);
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
[
|
||||
|
||||
setCurrentMenuInfo,
|
||||
|
||||
addTab,
|
||||
@@ -354,70 +236,38 @@ function WithBaseLayoutInner({
|
||||
|
||||
syncTabToUrl,
|
||||
|
||||
router,
|
||||
],
|
||||
|
||||
);
|
||||
|
||||
|
||||
|
||||
return (
|
||||
|
||||
<BaseLayout
|
||||
|
||||
queryClient={queryClient}
|
||||
|
||||
tokenManager={tokenManager}
|
||||
|
||||
currentPath={currentPath}
|
||||
|
||||
handleMenuClick={handleMenuClick}
|
||||
|
||||
>
|
||||
|
||||
<div className="flex flex-1 h-full">
|
||||
|
||||
<div className="flex h-full flex-1">
|
||||
{isVerificationSelectionReportPanelOpen && (
|
||||
|
||||
<VerificationSelectionReport
|
||||
|
||||
onClose={() => setReportPanelOpen(false)}
|
||||
|
||||
/>
|
||||
|
||||
)}
|
||||
|
||||
<StatisticsPageLayout>{children}</StatisticsPageLayout>
|
||||
|
||||
</div>
|
||||
|
||||
</BaseLayout>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default function WithBaseLayout({
|
||||
|
||||
children,
|
||||
|
||||
}: {
|
||||
|
||||
children: React.ReactNode;
|
||||
|
||||
}) {
|
||||
|
||||
return (
|
||||
|
||||
<Suspense fallback={null}>
|
||||
|
||||
<WithBaseLayoutInner>{children}</WithBaseLayoutInner>
|
||||
|
||||
</Suspense>
|
||||
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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, },
|
||||
|
||||
+7
-7
@@ -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: '비고' } },
|
||||
|
||||
+52
@@ -5,12 +5,20 @@ import { ENDPOINT } from "@/shared/config";
|
||||
import {
|
||||
type CauserBurdenApiResponse,
|
||||
type CauserBurdenRequestParams,
|
||||
type CauserBurdenSaveRequest,
|
||||
type CauserBurdenSaveResponse,
|
||||
type SewerageChargeApiResponse,
|
||||
type SewerageChargeRequestParams,
|
||||
type SewerageChargeSaveRequest,
|
||||
type SewerageChargeSaveResponse,
|
||||
type SewerageFinanceSettleApiResponse,
|
||||
type SewerageFinanceSettleRequestParams,
|
||||
type SewerageFinanceSettleSaveRequest,
|
||||
type SewerageFinanceSettleSaveResponse,
|
||||
type SewerageUsageFeeApiResponse,
|
||||
type SewerageUsageFeeRequestParams,
|
||||
type SewerageUsageFeeSaveRequest,
|
||||
type SewerageUsageFeeSaveResponse,
|
||||
type StatisticsInputSewerageFinanceService,
|
||||
} from "../../model/input/statistics-input-sewerage-finance-interface";
|
||||
|
||||
@@ -30,6 +38,17 @@ export class StatisticsInputSewerageFinanceApiService
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 하수도재정 결산 저장 — ST00090001 */
|
||||
public async saveSewerageFinanceSettle(
|
||||
request: SewerageFinanceSettleSaveRequest,
|
||||
): Promise<SewerageFinanceSettleSaveResponse> {
|
||||
const response = await stClient.put<SewerageFinanceSettleSaveResponse>(
|
||||
`${BASE}/settle/save`,
|
||||
request,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 하수도 요금 조회 — ST00090002 */
|
||||
public async getSewerageChargeList(
|
||||
params: SewerageChargeRequestParams,
|
||||
@@ -41,6 +60,17 @@ export class StatisticsInputSewerageFinanceApiService
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 하수도 요금 저장 — ST00090002 */
|
||||
public async saveSewerageCharge(
|
||||
request: SewerageChargeSaveRequest,
|
||||
): Promise<SewerageChargeSaveResponse> {
|
||||
const response = await stClient.put<SewerageChargeSaveResponse>(
|
||||
`${BASE}/charge/save`,
|
||||
request,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 하수도 사용료 조회 — ST00090003 */
|
||||
public async getSewerageUsageFeeList(
|
||||
params: SewerageUsageFeeRequestParams,
|
||||
@@ -52,6 +82,17 @@ export class StatisticsInputSewerageFinanceApiService
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 하수도 사용료 저장 — ST00090003 */
|
||||
public async saveSewerageUsageFee(
|
||||
request: SewerageUsageFeeSaveRequest,
|
||||
): Promise<SewerageUsageFeeSaveResponse> {
|
||||
const response = await stClient.put<SewerageUsageFeeSaveResponse>(
|
||||
`${BASE}/usage-fee/save`,
|
||||
request,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 원인자 부담금 조회 — ST00090004 */
|
||||
public async getCauserBurdenList(
|
||||
params: CauserBurdenRequestParams,
|
||||
@@ -62,6 +103,17 @@ export class StatisticsInputSewerageFinanceApiService
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/** 원인자 부담금 저장 — ST00090004 */
|
||||
public async saveCauserBurden(
|
||||
request: CauserBurdenSaveRequest,
|
||||
): Promise<CauserBurdenSaveResponse> {
|
||||
const response = await stClient.put<CauserBurdenSaveResponse>(
|
||||
`${BASE}/causer-burden/save`,
|
||||
request,
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const statisticsInputSewerageFinanceApi =
|
||||
|
||||
+189
@@ -86,6 +86,63 @@ export interface SewerageFinanceSettleRequestParams {
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ST00090001 저장 API `saveList[]` 한 행.
|
||||
* `SewerageFinanceSettleInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 요청 `ctpvNm` 시도명).
|
||||
*/
|
||||
export interface SewerageFinanceSettleSaveItem {
|
||||
admdstCd: string;
|
||||
sggNm: string;
|
||||
admdstNm: string;
|
||||
cnterGovSprtAmt: number;
|
||||
mctpvLcexpAmt: number;
|
||||
wareaSggLclAmt: number;
|
||||
sewerUseAmt: number;
|
||||
orgprBrdnAmt: number;
|
||||
etcIncmAmt: number;
|
||||
crfwAmt: number;
|
||||
lbltAmt: number;
|
||||
tmplntFcltAmt: number;
|
||||
tmplntMendngAmt: number;
|
||||
tmplntOperKeepAmt: number;
|
||||
tmplntEtcAmt: number;
|
||||
swagePplFcltAmt: number;
|
||||
swagePplMendngAmt: number;
|
||||
swagePplOperKeepAmt: number;
|
||||
swagePplEtcAmt: number;
|
||||
swageImpdFcltAmt: number;
|
||||
swageImpdMendngAmt: number;
|
||||
swageImpdOperKeepAmt: number;
|
||||
swageImpdFcltEtcAmt: number;
|
||||
etcAcdntCrfwAmt: number;
|
||||
etcNrcpAmt: number;
|
||||
etcAmt: number;
|
||||
rmrk: string | null;
|
||||
fnnByWholPjtcoAmt: number;
|
||||
usgByWholPjtcoAmt: number;
|
||||
tmplntWholAmt: number;
|
||||
wholSwagePplAmt: number;
|
||||
wholSwageImpdFcltAmt: number;
|
||||
etcWholAmt: number;
|
||||
}
|
||||
|
||||
/** ST00090001 저장 API 요청 */
|
||||
export interface SewerageFinanceSettleSaveRequest {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
sewerBzmnCd: string;
|
||||
userId: string;
|
||||
superAdmin?: string;
|
||||
saveList: SewerageFinanceSettleSaveItem[];
|
||||
}
|
||||
|
||||
export interface SewerageFinanceSettleSaveResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data?: string;
|
||||
}
|
||||
|
||||
/** 하수도 요금(ST00090002) — `SewerageChargeInpColumn.tabKey` */
|
||||
export const SEWERAGE_CHARGE_STATS_CD = "ST00090002" as const;
|
||||
|
||||
@@ -135,6 +192,40 @@ export interface SewerageChargeRequestParams {
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ST00090002 저장 API `saveList[]` 한 행.
|
||||
* `SewerageChargeInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 요청 `ctpvNm` 시도명).
|
||||
*/
|
||||
export interface SewerageChargeSaveItem {
|
||||
admdstCd: string;
|
||||
sggNm: string;
|
||||
admdstNm: string;
|
||||
anlLevyQnt: number;
|
||||
levyAmt: number;
|
||||
grssPrmcoAmt: number;
|
||||
rmrk: string | null;
|
||||
avgUntprcAmt: number;
|
||||
grssUnitPrmcoAmt: number;
|
||||
rlztnRt: number;
|
||||
}
|
||||
|
||||
/** ST00090002 저장 API 요청 */
|
||||
export interface SewerageChargeSaveRequest {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
sewerBzmnCd: string;
|
||||
userId: string;
|
||||
superAdmin?: string;
|
||||
saveList: SewerageChargeSaveItem[];
|
||||
}
|
||||
|
||||
export interface SewerageChargeSaveResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data?: string;
|
||||
}
|
||||
|
||||
/** 하수도 사용료(ST00090003) — `SewerageUsageFeeInpColumn.tabKey` */
|
||||
export const SEWERAGE_USAGE_FEE_STATS_CD = "ST00090003" as const;
|
||||
|
||||
@@ -201,6 +292,57 @@ export interface SewerageUsageFeeRequestParams {
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ST00090003 저장 API `saveList[]` 한 행.
|
||||
* `SewerageUsageFeeInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 요청 `ctpvNm` 시도명).
|
||||
*/
|
||||
export interface SewerageUsageFeeSaveItem {
|
||||
admdstCd: string;
|
||||
sggNm: string;
|
||||
admdstNm: string;
|
||||
hshusLevyAmt: number;
|
||||
hshusLevyQnt: number;
|
||||
hshusUntprcAmt: number;
|
||||
taskFrLevyAmt: number;
|
||||
taskFrLevyQnt: number;
|
||||
taskFrUntprcAmt: number;
|
||||
fbuLevyAmt: number;
|
||||
fbuLevyQnt: number;
|
||||
fbuUntprcAmt: number;
|
||||
bthhsFrSpcs1LevyAmt: number;
|
||||
bthhsFrSpcs1LevyQnt: number;
|
||||
bthhsFrSpcs1UntprcAmt: number;
|
||||
bthhsFrSpcs2LevyAmt: number;
|
||||
bthhsFrSpcs2LevyQnt: number;
|
||||
bthhsFrSpcs2UntprcAmt: number;
|
||||
induLevyAmt: number;
|
||||
induLevyQnt: number;
|
||||
induUntprcAmt: number;
|
||||
etcLevyAmt: number;
|
||||
etcLevyQnt: number;
|
||||
etcLevyUntprcAmt: number;
|
||||
rmrk: string | null;
|
||||
levyAmt: number;
|
||||
levyQnt: number;
|
||||
}
|
||||
|
||||
/** ST00090003 저장 API 요청 */
|
||||
export interface SewerageUsageFeeSaveRequest {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
sewerBzmnCd: string;
|
||||
userId: string;
|
||||
superAdmin?: string;
|
||||
saveList: SewerageUsageFeeSaveItem[];
|
||||
}
|
||||
|
||||
export interface SewerageUsageFeeSaveResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data?: string;
|
||||
}
|
||||
|
||||
/** 원인자 부담금(ST00090004) — `CauserBurdenInpColumn.tabKey` */
|
||||
export const CAUSER_BURDEN_STATS_CD = "ST00090004" as const;
|
||||
|
||||
@@ -252,6 +394,41 @@ export interface CauserBurdenRequestParams {
|
||||
mode?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* ST00090004 저장 API `saveList[]` 한 행.
|
||||
* `CauserBurdenInpColumn` fieldName과 동일 (`ctpvNm` 그리드 값은 요청 `ctpvNm` 시도명).
|
||||
*/
|
||||
export interface CauserBurdenSaveItem {
|
||||
admdstCd: string;
|
||||
sggNm: string;
|
||||
admdstNm: string;
|
||||
levyEvntcsCnt: number;
|
||||
levyAmt: number;
|
||||
mltvEqpmEvntcsCnt: number;
|
||||
mltvEqpmAmt: number;
|
||||
othrCstrnOthrActrCnt: number;
|
||||
othrCstrnOthrActrAmt: number;
|
||||
rmrk: string | null;
|
||||
trgtByWholClctnAmt: number;
|
||||
}
|
||||
|
||||
/** ST00090004 저장 API 요청 */
|
||||
export interface CauserBurdenSaveRequest {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
sewerBzmnCd: string;
|
||||
userId: string;
|
||||
superAdmin?: string;
|
||||
saveList: CauserBurdenSaveItem[];
|
||||
}
|
||||
|
||||
export interface CauserBurdenSaveResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data?: string;
|
||||
}
|
||||
|
||||
/** 하수도재정 조회 공통 요청 파라미터 */
|
||||
export interface SewerageFinanceRequestParams {
|
||||
ctpvNm?: string;
|
||||
@@ -265,13 +442,25 @@ export interface StatisticsInputSewerageFinanceService {
|
||||
getSewerageFinanceSettleList(
|
||||
params: SewerageFinanceSettleRequestParams,
|
||||
): Promise<SewerageFinanceSettleApiResponse>;
|
||||
saveSewerageFinanceSettle(
|
||||
request: SewerageFinanceSettleSaveRequest,
|
||||
): Promise<SewerageFinanceSettleSaveResponse>;
|
||||
getSewerageChargeList(
|
||||
params: SewerageChargeRequestParams,
|
||||
): Promise<SewerageChargeApiResponse>;
|
||||
saveSewerageCharge(
|
||||
request: SewerageChargeSaveRequest,
|
||||
): Promise<SewerageChargeSaveResponse>;
|
||||
getSewerageUsageFeeList(
|
||||
params: SewerageUsageFeeRequestParams,
|
||||
): Promise<SewerageUsageFeeApiResponse>;
|
||||
saveSewerageUsageFee(
|
||||
request: SewerageUsageFeeSaveRequest,
|
||||
): Promise<SewerageUsageFeeSaveResponse>;
|
||||
getCauserBurdenList(
|
||||
params: CauserBurdenRequestParams,
|
||||
): Promise<CauserBurdenApiResponse>;
|
||||
saveCauserBurden(
|
||||
request: CauserBurdenSaveRequest,
|
||||
): Promise<CauserBurdenSaveResponse>;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,11 @@ import { SkeletonPage } from "@/shared/ui/skeleton-page";
|
||||
import "@rsone/components/index.css";
|
||||
|
||||
import Dashboard from "@/components/statistics/Dashboard";
|
||||
import { isBbsNoticeMenu } from "@/shared/lib/bbs-notice-menu";
|
||||
import {
|
||||
parseStatisticsMenuUrlPath,
|
||||
resolveStatisticsCategory,
|
||||
} from "@/shared/lib/statistics-menu-url";
|
||||
import { isVerificationSelectionReportMenu } from "@/shared/lib/verification-selection-report";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -18,7 +23,9 @@ const StatisticsPage = () => {
|
||||
const tabsForBody = useMemo(
|
||||
() =>
|
||||
tabs.filter(
|
||||
(tab) => !isVerificationSelectionReportMenu(tab.menuItem),
|
||||
(tab) =>
|
||||
!isVerificationSelectionReportMenu(tab.menuItem) &&
|
||||
!isBbsNoticeMenu(tab.menuItem),
|
||||
),
|
||||
[tabs],
|
||||
);
|
||||
@@ -57,13 +64,17 @@ const StatisticsPage = () => {
|
||||
{tabsForBody.map((tab) => {
|
||||
// 렌더링 시점에 컴포넌트가 없으면 바로 로드
|
||||
if (!componentCache.current.has(tab.id)) {
|
||||
const componentName = tab.urlPath;
|
||||
const parentComponentName = tab.parentEngNm;
|
||||
const { componentName } = parseStatisticsMenuUrlPath(tab.urlPath);
|
||||
const category = resolveStatisticsCategory(
|
||||
tab.urlPath,
|
||||
tab.parentEngNm,
|
||||
);
|
||||
const resolvedComponentName = componentName || tab.urlPath;
|
||||
|
||||
const DynamicComponent = dynamic(
|
||||
() =>
|
||||
import(
|
||||
`@/components/statistics${parentComponentName ? `/${parentComponentName}` : ""}/${componentName}`
|
||||
`@/components/statistics/${category}/${resolvedComponentName}`
|
||||
),
|
||||
{ ssr: false, loading: () => <SkeletonPage /> },
|
||||
);
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
|
||||
export const BBS_NOTICE_MENU_MARKER = "bbsnotice";
|
||||
|
||||
export function isBbsNoticeMenu(item: MenuItem): boolean {
|
||||
const marker = BBS_NOTICE_MENU_MARKER;
|
||||
return (
|
||||
item.urlPath?.toLowerCase().includes(marker) ||
|
||||
item.menuEngNm?.toLowerCase().includes(marker) ||
|
||||
item.menuPath?.toLowerCase().includes(marker)
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
|
||||
export type ParsedStatisticsMenuUrl = {
|
||||
categoryFromPath: string | null;
|
||||
componentName: string | null;
|
||||
rawUrlPath: string;
|
||||
};
|
||||
|
||||
/** 메뉴 `urlPath`에서 통계 화면 카테고리·컴포넌트명을 파싱합니다. */
|
||||
export function parseStatisticsMenuUrlPath(urlPath: string): ParsedStatisticsMenuUrl {
|
||||
const trimmed = (urlPath ?? "").trim();
|
||||
if (!trimmed) {
|
||||
return { categoryFromPath: null, componentName: null, rawUrlPath: trimmed };
|
||||
}
|
||||
|
||||
let pathPart = trimmed;
|
||||
let queryString = "";
|
||||
const queryIndex = trimmed.indexOf("?");
|
||||
if (queryIndex >= 0) {
|
||||
pathPart = trimmed.slice(0, queryIndex);
|
||||
queryString = trimmed.slice(queryIndex + 1);
|
||||
}
|
||||
|
||||
const tabParam = new URLSearchParams(queryString).get("tab")?.trim() || null;
|
||||
const segments = pathPart.replace(/^\/+/, "").split("/").filter(Boolean);
|
||||
|
||||
let categoryFromPath: string | null = null;
|
||||
if (segments[0] === "statistics" && segments.length >= 2) {
|
||||
categoryFromPath = segments[1] ?? null;
|
||||
}
|
||||
|
||||
let componentName = tabParam;
|
||||
if (!componentName) {
|
||||
if (!pathPart.includes("/") && !queryString) {
|
||||
componentName = trimmed;
|
||||
} else if (segments.length === 1 && segments[0] !== "statistics") {
|
||||
componentName = segments[0] ?? null;
|
||||
} else if (segments[0] === "statistics" && segments.length >= 3) {
|
||||
componentName = segments[segments.length - 1] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
return { categoryFromPath, componentName, rawUrlPath: trimmed };
|
||||
}
|
||||
|
||||
/** 동적 import용 카테고리 폴더명 (`input`, `review` 등) */
|
||||
export function resolveStatisticsCategory(urlPath: string, parentEngNm: string): string {
|
||||
const { categoryFromPath } = parseStatisticsMenuUrlPath(urlPath);
|
||||
return categoryFromPath || parentEngNm || "";
|
||||
}
|
||||
|
||||
/** 탭 ID·URL `?tab=` 값 — 컴포넌트 파일명(확장자 제외) */
|
||||
export function resolveStatisticsTabId(urlPath: string): string {
|
||||
const { componentName, rawUrlPath } = parseStatisticsMenuUrlPath(urlPath);
|
||||
return componentName || rawUrlPath;
|
||||
}
|
||||
|
||||
/** `@/components/statistics/{category}/{Component}` 경로 */
|
||||
export function buildStatisticsComponentImportPath(
|
||||
urlPath: string,
|
||||
parentEngNm: string,
|
||||
): string {
|
||||
const { componentName } = parseStatisticsMenuUrlPath(urlPath);
|
||||
const category = resolveStatisticsCategory(urlPath, parentEngNm);
|
||||
const name = componentName || urlPath;
|
||||
return `@/components/statistics/${category}/${name}`;
|
||||
}
|
||||
|
||||
export function menuMatchesStatisticsTabId(menu: MenuItem, tabId: string): boolean {
|
||||
if (!tabId) return false;
|
||||
if (menu.urlPath === tabId) return true;
|
||||
return resolveStatisticsTabId(menu.urlPath) === tabId;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doNameList } from "@/components/util/column/input/DoList";
|
||||
import { menuMatchesStatisticsTabId } from "@/shared/lib/statistics-menu-url";
|
||||
import {
|
||||
SEWAGE_PIPE_INP_TAB_KEYS,
|
||||
SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS,
|
||||
@@ -97,7 +98,7 @@ export function findMenuItemByUrlPath(
|
||||
urlPath: string,
|
||||
): MenuItem | null {
|
||||
for (const item of menus) {
|
||||
if (item.urlPath === urlPath) return item;
|
||||
if (menuMatchesStatisticsTabId(item, urlPath)) return item;
|
||||
if (item.children?.length) {
|
||||
const found = findMenuItemByUrlPath(item.children, urlPath);
|
||||
if (found) return found;
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useEffect, useRef } from "react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
import { isBbsNoticeMenu } from "@/shared/lib/bbs-notice-menu";
|
||||
import { findMenuItemByUrlPath } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { getTabStoreState } from "@/store/tab-store";
|
||||
|
||||
@@ -61,7 +62,7 @@ export function useStatisticsTabUrlSync({
|
||||
if (getTabStoreState().activeTabId === tabParam) return;
|
||||
|
||||
const menu = findMenuItemByUrlPath(menuList, tabParam);
|
||||
if (!menu) return;
|
||||
if (!menu || isBbsNoticeMenu(menu)) return;
|
||||
|
||||
setCurrentMenuInfo(menu);
|
||||
const ok = addTab(menu);
|
||||
|
||||
@@ -5,7 +5,7 @@ import { TabBar } from "./tab-bar";
|
||||
function StatisticsPageLayout({ children }: { children: React.ReactNode }) {
|
||||
const { activeTabId, tabs } = useTabStore();
|
||||
|
||||
const pageTitle = tabs.find((t) => t.menuItem.urlPath === activeTabId);
|
||||
const pageTitle = tabs.find((t) => t.id === activeTabId);
|
||||
|
||||
const menuPaths = pageTitle?.path?.split(">").map((path) => path.trim());
|
||||
|
||||
|
||||
@@ -4,6 +4,8 @@ import { createJSONStorage, persist } from "zustand/middleware";
|
||||
import type { MenuItem } from "@/entities/menu";
|
||||
import { MenuAuthorityApi } from "@/entities/menu-authority";
|
||||
import type { PageMenuAuthority } from "@/entities/menu-authority/model/menu-authority-interface";
|
||||
import { isBbsNoticeMenu } from "@/shared/lib/bbs-notice-menu";
|
||||
import { resolveStatisticsTabId } from "@/shared/lib/statistics-menu-url";
|
||||
|
||||
export interface Tab {
|
||||
id: string; // 고유 ID (urlPath)
|
||||
@@ -101,11 +103,18 @@ function tabForPersist(tab: Tab): Tab {
|
||||
};
|
||||
}
|
||||
|
||||
function normalizePersistedTab(tab: Tab): Tab {
|
||||
const tabId = resolveStatisticsTabId(tab.urlPath || tab.id);
|
||||
return tabId === tab.id ? tab : { ...tab, id: tabId };
|
||||
}
|
||||
|
||||
function sanitizePersistedTabsAndActive(
|
||||
tabs: Tab[],
|
||||
activeTabId: string | null,
|
||||
): { tabs: Tab[]; activeTabId: string | null } {
|
||||
let nextTabs = Array.isArray(tabs) ? tabs : [];
|
||||
let nextTabs = Array.isArray(tabs)
|
||||
? tabs.filter((tab) => !isBbsNoticeMenu(tab.menuItem)).map(normalizePersistedTab)
|
||||
: [];
|
||||
if (nextTabs.length > MAX_TABS) nextTabs = nextTabs.slice(0, MAX_TABS);
|
||||
|
||||
if (nextTabs.length === 0) {
|
||||
@@ -130,8 +139,12 @@ const useTabStoreHook = create<TabStore>()(
|
||||
activeTabId: null,
|
||||
menuList: null,
|
||||
addTab: (menuItem: MenuItem) => {
|
||||
if (isBbsNoticeMenu(menuItem)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { tabs, menuList } = get();
|
||||
const tabId = menuItem.urlPath || "";
|
||||
const tabId = resolveStatisticsTabId(menuItem.urlPath || "");
|
||||
|
||||
const existingTab = tabs.find((tab) => tab.id === tabId);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user