Merge remote-tracking branch 'origin/iipbs/st/dev'
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
.verification-result-btn-wrap {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
}
|
||||
|
||||
.verification-result-btn-failed-dot {
|
||||
position: absolute;
|
||||
left: 0.35rem;
|
||||
top: 0.35rem;
|
||||
width: 0.55rem;
|
||||
height: 0.55rem;
|
||||
border-radius: 9999px;
|
||||
background: #ff6c64;
|
||||
box-shadow: 0 0 0 0 rgba(255, 108, 100, 0.75);
|
||||
animation: verification-result-btn-dot-ping 1s cubic-bezier(0, 0, 0.2, 1) infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes verification-result-btn-dot-ping {
|
||||
75%,
|
||||
100% {
|
||||
box-shadow: 0 0 0 0.5rem rgba(255, 108, 100, 0);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +1,34 @@
|
||||
import { Button } from "@common/components";
|
||||
|
||||
import "./VerificationResultButton.css";
|
||||
|
||||
interface VerificationResultButtonProps {
|
||||
VerificationResultButtonFunc: () => void;
|
||||
isVerificationFailed?: boolean;
|
||||
}
|
||||
|
||||
const VerificationResultButton = ({ VerificationResultButtonFunc }: VerificationResultButtonProps) => {
|
||||
const VerificationResultButton = ({
|
||||
VerificationResultButtonFunc,
|
||||
isVerificationFailed = false,
|
||||
}: VerificationResultButtonProps) => {
|
||||
return (
|
||||
<Button color="white" className="h-[3.2rem]" onClick={VerificationResultButtonFunc}>
|
||||
<i className="diveicon di-list" />
|
||||
<span>지난 검증 결과</span>
|
||||
</Button>
|
||||
<span className="verification-result-btn-wrap">
|
||||
<Button
|
||||
color="white"
|
||||
className="h-[3.2rem]"
|
||||
onClick={VerificationResultButtonFunc}
|
||||
>
|
||||
<i className="diveicon di-list" />
|
||||
<span>지난 검증 결과</span>
|
||||
</Button>
|
||||
{isVerificationFailed && (
|
||||
<span
|
||||
className="verification-result-btn-failed-dot"
|
||||
aria-hidden
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
export default VerificationResultButton;
|
||||
export default VerificationResultButton;
|
||||
|
||||
@@ -166,6 +166,30 @@
|
||||
@apply !bg-white !text-[#333];
|
||||
}
|
||||
|
||||
/* 하수관로 검토: CURR 구분 이후 미입력 — OLD 있음(노랑) / OLD 없음(하늘) */
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-warn,
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-warn,
|
||||
.rg-grid .rg-fixed-body .rg-table .rg-data-row td.rg-cell-readonly.rg-cell-review-curr-empty-warn,
|
||||
.rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-warn,
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-review-curr-empty-warn.rg-cell-review-curr-empty-warn,
|
||||
.rg-grid .rg-fixed-body .rg-data-cell.rg-cell-review-curr-empty-warn.rg-cell-review-curr-empty-warn,
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-warn,
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-warn,
|
||||
.rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-warn {
|
||||
@apply !bg-[#feee85] !text-[#333];
|
||||
}
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-new,
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-new,
|
||||
.rg-grid .rg-fixed-body .rg-table .rg-data-row td.rg-cell-readonly.rg-cell-review-curr-empty-new,
|
||||
.rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-readonly.rg-cell-review-curr-empty-new,
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-review-curr-empty-new.rg-cell-review-curr-empty-new,
|
||||
.rg-grid .rg-fixed-body .rg-data-cell.rg-cell-review-curr-empty-new.rg-cell-review-curr-empty-new,
|
||||
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-new,
|
||||
.rg-grid .rg-body .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-new,
|
||||
.rg-grid .rg-fixed-body .rg-data-row .rg-data-cell.rg-cell-rowtype-curr.rg-cell-review-curr-empty-new {
|
||||
@apply !bg-[#bae6fd] !text-[#333];
|
||||
}
|
||||
|
||||
/*
|
||||
* 검증 오류 셀(rg-cell-verify-error / -imr) 최우선 적용.
|
||||
* 클래스 이중화로 specificity를 한 단계 올려 동일 specificity의 다른 셀 클래스
|
||||
|
||||
@@ -39,10 +39,10 @@ function mergeCellStyleCallbackResult(
|
||||
return baseStyleName ? { styleName: baseStyleName } : {};
|
||||
}
|
||||
if (typeof extra === "string") {
|
||||
const styleName = [extra, baseStyleName].filter(Boolean).join(" ");
|
||||
const styleName = [baseStyleName, extra].filter(Boolean).join(" ");
|
||||
return styleName ? { styleName } : {};
|
||||
}
|
||||
const styleName = [extra.styleName, baseStyleName].filter(Boolean).join(" ");
|
||||
const styleName = [baseStyleName, extra.styleName].filter(Boolean).join(" ");
|
||||
return {
|
||||
...extra,
|
||||
...(styleName ? { styleName } : {}),
|
||||
@@ -812,13 +812,24 @@ useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
const gv = gridRef.current?.gridView;
|
||||
if (!gv || !cellStyleCallback) return;
|
||||
applyCellStyleCallbacksToColumns(
|
||||
gv,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
(gv as { refresh?: () => void }).refresh?.();
|
||||
|
||||
const apply = () => {
|
||||
applyCellStyleCallbacksToColumns(
|
||||
gv,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
(gv as { refresh?: () => void }).refresh?.();
|
||||
};
|
||||
|
||||
apply();
|
||||
// footer setColumns / setColumnLayout 비동기 처리 이후 styleCallback 재적용
|
||||
const timerId = window.setTimeout(apply, 300);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timerId);
|
||||
};
|
||||
}, [cellStyleCallback, columns, rowData]);
|
||||
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
@@ -1416,6 +1427,13 @@ useEffect(() => {
|
||||
| ((layout: unknown) => void)
|
||||
| undefined
|
||||
)?.(safeLayout);
|
||||
applyCellStyleCallbacksToColumns(
|
||||
gridRef.current.gridView,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
gridRef.current.gridView.refresh?.();
|
||||
} catch (e) {
|
||||
console.warn("setColumnLayout skipped due to invalid columns:", e);
|
||||
}
|
||||
@@ -1482,16 +1500,16 @@ useEffect(() => {
|
||||
col,
|
||||
),
|
||||
);
|
||||
applyFooterSettings(currentGridView);
|
||||
if (hasFooterUserSpans) {
|
||||
currentGridView.setColumnLayout?.(columnLayout);
|
||||
}
|
||||
applyCellStyleCallbacksToColumns(
|
||||
currentGridView,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
applyFooterSettings(currentGridView);
|
||||
if (hasFooterUserSpans) {
|
||||
currentGridView.setColumnLayout?.(columnLayout);
|
||||
}
|
||||
|
||||
// setColumns 호출 후 footer 배열의 valueCallback을 다시 설정
|
||||
scheduleFooterTask(() => {
|
||||
@@ -1535,16 +1553,16 @@ useEffect(() => {
|
||||
);
|
||||
}
|
||||
});
|
||||
applyFooterSettings(liveGridView);
|
||||
if (hasFooterUserSpans) {
|
||||
liveGridView.setColumnLayout?.(columnLayout);
|
||||
}
|
||||
applyCellStyleCallbacksToColumns(
|
||||
liveGridView,
|
||||
columns,
|
||||
rowDataRef,
|
||||
cellStyleCallbackRef,
|
||||
);
|
||||
applyFooterSettings(liveGridView);
|
||||
if (hasFooterUserSpans) {
|
||||
liveGridView.setColumnLayout?.(columnLayout);
|
||||
}
|
||||
liveGridView.refresh?.();
|
||||
}, 50);
|
||||
} catch (e) {
|
||||
|
||||
@@ -38,6 +38,7 @@ import type {
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
|
||||
import { statisticsInputExcretaTreatmentFacilityApi } from "@/entities/statistics/api/input/statistics-input-excreta-treatment-facility-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
@@ -59,6 +60,8 @@ import type {
|
||||
SewerageStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
@@ -325,6 +328,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
const [hasSearched, setHasSearched] = useState(false);
|
||||
const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null);
|
||||
const [statsCd, setStatsCd] = useState("");
|
||||
const [isVerificationFailed, setIsVerificationFailed] = useState(false);
|
||||
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
|
||||
@@ -454,11 +458,17 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
excretaTreatmentFacilityList,
|
||||
} = response.data;
|
||||
const list = excretaTreatmentFacilityList ?? [];
|
||||
const gridRows = mapExcretaTreatmentFacilityListToGridRows(list, commonInfo.ctpvNm);
|
||||
|
||||
setRowData(mapExcretaTreatmentFacilityListToGridRows(list, commonInfo.ctpvNm));
|
||||
setRowData(gridRows);
|
||||
setStatusInfo(nextStatusInfo);
|
||||
setStatsCd(commonInfo.statsCd);
|
||||
setIsNotApplicable(false);
|
||||
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
||||
if (overrides?.mode !== "PRE") {
|
||||
setIsNotApplicable(gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
setIsNotApplicable(false);
|
||||
}
|
||||
setSearchLoaded(true);
|
||||
return true;
|
||||
};
|
||||
@@ -512,6 +522,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
setHasSearched(false);
|
||||
setStatusInfo(null);
|
||||
setStatsCd("");
|
||||
setIsVerificationFailed(false);
|
||||
setIsNotApplicable(false);
|
||||
setInputPeriodEnterable(true);
|
||||
setSggOptions([]);
|
||||
@@ -974,10 +985,18 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
};
|
||||
})();
|
||||
|
||||
const { verificationSearchDefaults, searchFormRemountKey } =
|
||||
useStatisticsInputPendingNavigation({
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.EXCRETA_TREATMENT_FACILITY,
|
||||
onAutoSearch: (values) => handleSearch(values),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
fctName={true}
|
||||
onSubmit={handleSearch}
|
||||
onReset={handleReset}
|
||||
@@ -988,6 +1007,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={isVerificationFailed}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { statisticsInputPrivateSewageFacilityApi } from "@/entities/statistics/api/input/statistics-input-private-sewage-facility-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
@@ -53,6 +55,7 @@ import { PrivateSewageDesigStatusInpColumn } from "@/components/util/column/inpu
|
||||
import { SepticTankStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SepticTankStatusInpColumn";
|
||||
import { SewageTreatFactStatusInpColumn } from "@/components/util/column/input/privateSewageFacility/SewageTreatFactStatusInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
const PrivateSewageFacilityInpTabList = [
|
||||
@@ -338,6 +341,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
Map<string, SewerageStatusInfo | null>
|
||||
>(new Map());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -462,6 +468,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -487,6 +496,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -512,6 +524,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -528,6 +543,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -539,7 +555,10 @@ const PrivateSewageFacilityInp = () => {
|
||||
periodEnterableRef.current = true;
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
const handleSearch = async (
|
||||
values: SearchFormValues,
|
||||
opts?: { forcedTabKey?: string },
|
||||
) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
@@ -566,11 +585,17 @@ const PrivateSewageFacilityInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
|
||||
if (opts?.forcedTabKey) {
|
||||
activeTabRef.current = opts.forcedTabKey;
|
||||
setActiveTab(opts.forcedTabKey);
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
searchDoneRef.current = true;
|
||||
setHasSearched(true);
|
||||
@@ -578,7 +603,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
searchParamRef.current = searchParam;
|
||||
|
||||
await loadActiveTabData(
|
||||
activeTabRef.current,
|
||||
opts?.forcedTabKey ?? activeTabRef.current,
|
||||
searchParam,
|
||||
values,
|
||||
!yearChangedFromLastSearch,
|
||||
@@ -1131,10 +1156,23 @@ const PrivateSewageFacilityInp = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const { verificationSearchDefaults, searchFormRemountKey } =
|
||||
useStatisticsInputPendingNavigation({
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.PRIVATE_SEWAGE_FACILITY,
|
||||
onPending: (pending) => {
|
||||
activeTabRef.current = pending.tabKey;
|
||||
setActiveTab(pending.tabKey);
|
||||
},
|
||||
onAutoSearch: (values, tabKey) =>
|
||||
handleSearch(values, { forcedTabKey: tabKey }),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
onReset={handleReset}
|
||||
onValuesChange={setSearchValues}
|
||||
@@ -1144,6 +1182,9 @@ const PrivateSewageFacilityInp = () => {
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
isVerificationFailedByTab.get(activeTab) === true
|
||||
}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { useEffect, useRef, useState, type Dispatch, type SetStateAction } from
|
||||
import { useConfirm } from '@frontend-opensource/use-react-hooks';
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from '@/shared/lib';
|
||||
import { STATISTICS_INPUT_URL_PATH } from '@/shared/lib/stats-cd-input-navigation';
|
||||
import { useStatisticsInputPendingNavigation } from '@/shared/lib/use-statistics-input-pending-navigation';
|
||||
import { statisticsInputSewSupplyRateApi } from '@/entities/statistics/api/input/statistics-input-sew-supply-rate-service';
|
||||
import { statisticsInputPublicSewageFacilityApiService } from '@/entities/statistics/api/input/statistics-public-sewage-facility-input-service';
|
||||
import { statisticsApi } from '@/entities/statistics/api/statistics-service';
|
||||
@@ -46,6 +48,7 @@ import type { SearchFormValues } from '@/components/type/SearchForm.type';
|
||||
import { doList } from '@/components/util/column/input/DoList';
|
||||
import { PublicSewageFactInpColumnList } from '@/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactInpColumn';
|
||||
import { toGridRows } from '@/components/util/grid/gridFunction';
|
||||
import { patchVerificationFailedByTab } from '@/components/util/input/verification-failed-flag';
|
||||
import { useAdminStore } from '@/store/admin-store';
|
||||
|
||||
type GridRowWithVerification = Record<string, string | number> & {
|
||||
@@ -165,6 +168,9 @@ const PublicSewageFacilityInp = () => {
|
||||
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
|
||||
const [tabStatusInfoMap, setTabStatusInfoMap] = useState<Map<string, SewerageStatusInfo | null>>(new Map());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
/** 탭키별 `engColNm -> columnGuide` 캐시 */
|
||||
const [columnGuidesByTab, setColumnGuidesByTab] = useState<Record<string, Record<string, string>>>({});
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<Map<string, TabToolbarSnapshot>>(() => new Map());
|
||||
@@ -685,8 +691,10 @@ const PublicSewageFacilityInp = () => {
|
||||
|
||||
if (list.length === 0) {
|
||||
setShowIsNotApplicable(true);
|
||||
setIsNotApplicable(true);
|
||||
} else {
|
||||
setShowIsNotApplicable(false);
|
||||
setIsNotApplicable(false);
|
||||
}
|
||||
|
||||
const gridRows = toGridRows(convertedData);
|
||||
@@ -700,6 +708,9 @@ const PublicSewageFacilityInp = () => {
|
||||
next.set(tabKey, statsCd);
|
||||
return next;
|
||||
});
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('ST00060001', gridRows);
|
||||
@@ -744,6 +755,9 @@ const PublicSewageFacilityInp = () => {
|
||||
next.set(tabKey, statsCd);
|
||||
return next;
|
||||
});
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, response.data.commonInfo),
|
||||
);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('MainFacility', leftGridRows);
|
||||
@@ -874,7 +888,10 @@ const PublicSewageFacilityInp = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
const handleSearch = async (
|
||||
values: SearchFormValues,
|
||||
opts?: { forcedTabKey?: string },
|
||||
) => {
|
||||
if (!values.sido || values.sido === 'ALL') {
|
||||
alert('시도를 선택해주세요.');
|
||||
return;
|
||||
@@ -902,6 +919,7 @@ const PublicSewageFacilityInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
@@ -910,16 +928,17 @@ const PublicSewageFacilityInp = () => {
|
||||
setLastSearchedValues(values);
|
||||
searchDoneRef.current = true;
|
||||
setHasSearched(true);
|
||||
setIsNotApplicableByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set('ST00060001', false);
|
||||
return next;
|
||||
});
|
||||
|
||||
setIsLoading(true);
|
||||
const param = buildPublicSewageFacilityParams(values);
|
||||
searchParamRef.current = param;
|
||||
const tabKeyToLoad = yearChangedFromLastSearch ? firstTabKey : activeTabRef.current;
|
||||
const tabKeyToLoad =
|
||||
opts?.forcedTabKey ??
|
||||
(yearChangedFromLastSearch ? firstTabKey : activeTabRef.current);
|
||||
if (opts?.forcedTabKey) {
|
||||
activeTabRef.current = opts.forcedTabKey;
|
||||
setActiveTab(opts.forcedTabKey);
|
||||
}
|
||||
await loadPublicSewageFacilityActiveTabData(tabKeyToLoad, param, values);
|
||||
} catch {
|
||||
alert('데이터 조회 중 오류가 발생했습니다.');
|
||||
@@ -963,6 +982,7 @@ const PublicSewageFacilityInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
|
||||
setInputPeriodEnterable(true);
|
||||
@@ -1138,10 +1158,23 @@ const PublicSewageFacilityInp = () => {
|
||||
};
|
||||
})();
|
||||
|
||||
const { verificationSearchDefaults, searchFormRemountKey } =
|
||||
useStatisticsInputPendingNavigation({
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.PUBLIC_SEWAGE_FACILITY,
|
||||
onPending: (pending) => {
|
||||
activeTabRef.current = pending.tabKey;
|
||||
setActiveTab(pending.tabKey);
|
||||
},
|
||||
onAutoSearch: (values, tabKey) =>
|
||||
handleSearch(values, { forcedTabKey: tabKey }),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
fctName={true}
|
||||
onSubmit={handleSearch}
|
||||
onReset={handleReset}
|
||||
@@ -1153,6 +1186,9 @@ const PublicSewageFacilityInp = () => {
|
||||
{!isMainFacilityTab && (
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
isVerificationFailedByTab.get(activeTab) === true
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{showEnterableToolbarButtons && !isMainFacilityTab && (
|
||||
|
||||
@@ -38,6 +38,7 @@ import type {
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
|
||||
import { statisticsInputPumpingStationApi } from "@/entities/statistics/api/input/statistics-input-pumping-station-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
@@ -58,6 +59,8 @@ import type {
|
||||
SewerageStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
@@ -320,6 +323,7 @@ const PumpingStationInp = () => {
|
||||
const [hasSearched, setHasSearched] = useState(false);
|
||||
const [statusInfo, setStatusInfo] = useState<SewerageStatusInfo | null>(null);
|
||||
const [statsCd, setStatsCd] = useState("");
|
||||
const [isVerificationFailed, setIsVerificationFailed] = useState(false);
|
||||
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [sggOptions, setSggOptions] = useState<Sgg[]>([]);
|
||||
@@ -445,11 +449,18 @@ const PumpingStationInp = () => {
|
||||
const { commonInfo, statusInfo: nextStatusInfo, pumpingStationList } =
|
||||
response.data;
|
||||
const list = pumpingStationList ?? [];
|
||||
const gridRows = mapPumpingStationListToGridRows(list, commonInfo.ctpvNm);
|
||||
|
||||
setRowData(mapPumpingStationListToGridRows(list, commonInfo.ctpvNm));
|
||||
setPumpingStationDeleteList([]);
|
||||
setRowData(gridRows);
|
||||
setStatusInfo(nextStatusInfo);
|
||||
setStatsCd(commonInfo.statsCd);
|
||||
setIsNotApplicable(false);
|
||||
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
||||
if (overrides?.mode !== "PRE") {
|
||||
setIsNotApplicable(gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
setIsNotApplicable(false);
|
||||
}
|
||||
setSearchLoaded(true);
|
||||
return true;
|
||||
};
|
||||
@@ -503,6 +514,7 @@ const PumpingStationInp = () => {
|
||||
setHasSearched(false);
|
||||
setStatusInfo(null);
|
||||
setStatsCd("");
|
||||
setIsVerificationFailed(false);
|
||||
setIsNotApplicable(false);
|
||||
setInputPeriodEnterable(true);
|
||||
setSggOptions([]);
|
||||
@@ -957,10 +969,18 @@ const PumpingStationInp = () => {
|
||||
};
|
||||
})();
|
||||
|
||||
const { verificationSearchDefaults, searchFormRemountKey } =
|
||||
useStatisticsInputPendingNavigation({
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.PUMPING_STATION,
|
||||
onAutoSearch: (values) => handleSearch(values),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
fctName={true}
|
||||
onSubmit={handleSearch}
|
||||
onReset={handleReset}
|
||||
@@ -971,6 +991,7 @@ const PumpingStationInp = () => {
|
||||
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={isVerificationFailed}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -35,6 +35,7 @@ import { PrivateSewageTreatRelatedIndustryInpColumn } from "@/components/util/co
|
||||
import { PublicInstitutionExcretaIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionExcretaIndustryInpColumn";
|
||||
import { PublicInstitutionSewerIndustryInpColumn } from "@/components/util/column/input/sewageExcretaRelated/PublicInstitutionSewerIndustryInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { statisticsInputSewageExcretaRelatedApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-related-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -61,6 +62,8 @@ import type {
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
@@ -613,6 +616,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
|
||||
const [loadedTabKeys, setLoadedTabKeys] = useState<Set<string>>(new Set());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [isNotApplicableByTab, setIsNotApplicableByTab] = useState<Map<string, boolean>>(
|
||||
() => new Map(),
|
||||
);
|
||||
@@ -823,7 +829,16 @@ const SewageExcretaRelatedInp = () => {
|
||||
const requestParam = overrides?.mode ? { ...param, mode: overrides.mode } : param;
|
||||
const fromPreYear = overrides?.mode === "PRE";
|
||||
|
||||
setIsNotApplicableForTab(tabKey, false);
|
||||
const applyNotApplicableForLoadedTab = (
|
||||
gridRows: readonly Record<string, string | number>[],
|
||||
) => {
|
||||
if (!NOT_APPLICABLE_TAB_KEYS.has(tabKey)) return;
|
||||
if (!fromPreYear) {
|
||||
setIsNotApplicableForTab(tabKey, gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
setIsNotApplicableForTab(tabKey, false);
|
||||
}
|
||||
};
|
||||
|
||||
if (tabKey === PublicInstitutionSewerIndustryInpColumn.tabKey) {
|
||||
const response =
|
||||
@@ -852,6 +867,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -859,6 +877,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
gridRows.length > 0,
|
||||
searchContext,
|
||||
);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
markTabLoaded(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -891,6 +910,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -898,6 +920,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
gridRows.length > 0,
|
||||
searchContext,
|
||||
);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
markTabLoaded(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -918,6 +941,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -949,6 +975,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -979,6 +1008,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
loadedTabKeysRef.current.clear();
|
||||
setLoadedTabKeys(new Set());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setIsNotApplicableByTab(new Map());
|
||||
setDeleteListByTab(new Map());
|
||||
clearArticleOgnzSnTracking(
|
||||
@@ -990,7 +1020,10 @@ const SewageExcretaRelatedInp = () => {
|
||||
setVerificationHistoryOpen(false);
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
const handleSearch = async (
|
||||
values: SearchFormValues,
|
||||
opts?: { forcedTabKey?: string },
|
||||
) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
@@ -1025,6 +1058,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setIsNotApplicableByTab(new Map());
|
||||
setDeleteListByTab(new Map());
|
||||
@@ -1054,8 +1088,13 @@ const SewageExcretaRelatedInp = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts?.forcedTabKey) {
|
||||
activeTabRef.current = opts.forcedTabKey;
|
||||
setActiveTab(opts.forcedTabKey);
|
||||
}
|
||||
|
||||
const ok = await loadActiveTabData(
|
||||
activeTabRef.current,
|
||||
opts?.forcedTabKey ?? activeTabRef.current,
|
||||
searchParam,
|
||||
values,
|
||||
!yearChangedFromLastSearch,
|
||||
@@ -1894,10 +1933,23 @@ const SewageExcretaRelatedInp = () => {
|
||||
};
|
||||
})();
|
||||
|
||||
const { verificationSearchDefaults, searchFormRemountKey } =
|
||||
useStatisticsInputPendingNavigation({
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_EXCRETA_RELATED,
|
||||
onPending: (pending) => {
|
||||
activeTabRef.current = pending.tabKey;
|
||||
setActiveTab(pending.tabKey);
|
||||
},
|
||||
onAutoSearch: (values, tabKey) =>
|
||||
handleSearch(values, { forcedTabKey: tabKey }),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
onReset={handleReset}
|
||||
onValuesChange={setSearchValues}
|
||||
@@ -1907,6 +1959,9 @@ const SewageExcretaRelatedInp = () => {
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
isVerificationFailedByTab.get(activeTab) === true
|
||||
}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -49,6 +49,7 @@ import { SewageSlurgeProcessingFacStatusInpColumn } from "@/components/util/colu
|
||||
import { SlurgeProcessingFacStatusInpColumn } from "@/components/util/column/input/sewageExcretaSlurge/SlurgeProcessingFacStatusInpColumn";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { statisticsInputSewageExcretaSlurgeApi } from "@/entities/statistics/api/input/statistics-input-sewage-excreta-slurge-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -70,6 +71,8 @@ import { isInputPeriodCurrent } from "@/entities/statistics/model/input/statisti
|
||||
import type { ApiResponse, cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo, Sgg } from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
@@ -660,6 +663,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
Map<string, SewerageStatusInfo | null>
|
||||
>(new Map());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [_toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -895,7 +901,15 @@ const SewageExcretaSlurgeInp = () => {
|
||||
const requestParam = overrides?.mode ? { ...param, mode: overrides.mode } : param;
|
||||
const fromPreYear = overrides?.mode === "PRE";
|
||||
|
||||
setIsNotApplicableForTab(tabKey, false);
|
||||
const applyNotApplicableForLoadedTab = (
|
||||
gridRows: readonly Record<string, string | number>[],
|
||||
) => {
|
||||
if (!fromPreYear) {
|
||||
setIsNotApplicableForTab(tabKey, gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
setIsNotApplicableForTab(tabKey, false);
|
||||
}
|
||||
};
|
||||
|
||||
if (tabKey === SlurgeProcessingFacStatusInpColumn.tabKey) {
|
||||
const response =
|
||||
@@ -914,6 +928,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -921,6 +938,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
gridRows.length > 0,
|
||||
searchContext,
|
||||
);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
markTabLoaded(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -947,6 +965,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -954,6 +975,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
gridRows.length > 0,
|
||||
searchContext,
|
||||
);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
markTabLoaded(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -978,6 +1000,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -985,6 +1010,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
gridRows.length > 0,
|
||||
searchContext,
|
||||
);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
markTabLoaded(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -1014,6 +1040,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
tabKey,
|
||||
@@ -1021,6 +1050,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
gridRows.length > 0,
|
||||
searchContext,
|
||||
);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
markTabLoaded(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -1033,6 +1063,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -1056,7 +1087,10 @@ const SewageExcretaSlurgeInp = () => {
|
||||
sggOptionsSidoRef.current = null;
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
const handleSearch = async (
|
||||
values: SearchFormValues,
|
||||
opts?: { forcedTabKey?: string },
|
||||
) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
@@ -1087,6 +1121,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
loadedTabKeysRef.current.clear();
|
||||
setLoadedTabKeys(new Set());
|
||||
@@ -1113,13 +1148,20 @@ const SewageExcretaSlurgeInp = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (opts?.forcedTabKey) {
|
||||
activeTabRef.current = opts.forcedTabKey;
|
||||
setActiveTab(opts.forcedTabKey);
|
||||
}
|
||||
|
||||
const ok = await loadActiveTabData(
|
||||
activeTabRef.current,
|
||||
opts?.forcedTabKey ?? activeTabRef.current,
|
||||
searchParam,
|
||||
values,
|
||||
!yearChangedFromLastSearch,
|
||||
true,
|
||||
buildEmptyDataAlertOverrides(activeTabRef.current),
|
||||
buildEmptyDataAlertOverrides(
|
||||
opts?.forcedTabKey ?? activeTabRef.current,
|
||||
),
|
||||
);
|
||||
if (!ok) return;
|
||||
} catch {
|
||||
@@ -1988,10 +2030,23 @@ const SewageExcretaSlurgeInp = () => {
|
||||
};
|
||||
})();
|
||||
|
||||
const { verificationSearchDefaults, searchFormRemountKey } =
|
||||
useStatisticsInputPendingNavigation({
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_EXCRETA_SLURGE,
|
||||
onPending: (pending) => {
|
||||
activeTabRef.current = pending.tabKey;
|
||||
setActiveTab(pending.tabKey);
|
||||
},
|
||||
onAutoSearch: (values, tabKey) =>
|
||||
handleSearch(values, { forcedTabKey: tabKey }),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
fctName
|
||||
onSubmit={handleSearch}
|
||||
onReset={handleReset}
|
||||
@@ -2002,6 +2057,9 @@ const SewageExcretaSlurgeInp = () => {
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
isVerificationFailedByTab.get(activeTab) === true
|
||||
}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -69,6 +69,9 @@ import { PipeTypeStatusInpColumn } from "@/components/util/column/input/sewagePi
|
||||
import { RegionalFactStatusInpColumn } from "@/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn";
|
||||
import { SewagePipeInpColumnInfoList } from "@/components/util/column/input/sewagePipe/SewagePipeInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import {
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -360,6 +363,9 @@ const SewagePipeInp = () => {
|
||||
const [verificationResultDialogOpen, setVerificationResultDialogOpen] = useState(false);
|
||||
const [statsCd, setStatsCd] = useState<string>('');
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]);
|
||||
/** 탭키별 `engColNm -> columnGuide` 캐시 */
|
||||
@@ -470,6 +476,7 @@ const SewagePipeInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setLastSearchedYear(null);
|
||||
setLastSearchedValues(null);
|
||||
setHasSearched(false);
|
||||
@@ -582,6 +589,9 @@ const SewagePipeInp = () => {
|
||||
applyRowMap((m) => m.set('ST00020001', toGridRows(convertedData)));
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
@@ -623,6 +633,9 @@ const SewagePipeInp = () => {
|
||||
applyRowMap((m) => m.set('ST00020002', toGridRows(convertedData)));
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
@@ -646,6 +659,9 @@ const SewagePipeInp = () => {
|
||||
applyRowMap((m) => m.set('ST00020003', toGridRows(convertedData)));
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
@@ -670,6 +686,9 @@ const SewagePipeInp = () => {
|
||||
applyRowMap((m) => m.set('ST00020004', toGridRows(convertedData)));
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
@@ -693,6 +712,9 @@ const SewagePipeInp = () => {
|
||||
applyRowMap((m) => m.set('ST00020005', toGridRows(convertedData)));
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
@@ -723,6 +745,9 @@ const SewagePipeInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, response.data.statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
syncTopStatusIfStillOnTab(tabKey, commonInfo.statsCd);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(values);
|
||||
commitToolbarSnapshotForLoadedTab(
|
||||
@@ -774,6 +799,7 @@ const SewagePipeInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setStatsCd("");
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
}
|
||||
@@ -1896,6 +1922,9 @@ const SewagePipeInp = () => {
|
||||
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
isVerificationFailedByTab.get(activeTab) === true
|
||||
}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -56,6 +56,7 @@ import { doList } from "@/components/util/column/input/DoList";
|
||||
import { PipeDredgingStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/PipeDredgingStatusInpColumn";
|
||||
import { RepairStatusInpColumn } from "@/components/util/column/input/sewagePipeMaintain/RepairStatusInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -291,6 +292,9 @@ const SewagePipeMaintainInp = () => {
|
||||
Map<string, SewerageStatusInfo | null>
|
||||
>(new Map());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -441,6 +445,9 @@ const SewagePipeMaintainInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(searchContext);
|
||||
}
|
||||
@@ -474,6 +481,9 @@ const SewagePipeMaintainInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(searchContext);
|
||||
}
|
||||
@@ -868,6 +878,7 @@ const SewagePipeMaintainInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
setLastSearchedValues(null);
|
||||
@@ -912,6 +923,7 @@ const SewagePipeMaintainInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
@@ -1109,6 +1121,9 @@ const SewagePipeMaintainInp = () => {
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
isVerificationFailedByTab.get(activeTab) === true
|
||||
}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -42,6 +42,7 @@ import { doList } from "@/components/util/column/input/DoList";
|
||||
import { RainfallPumpStationInpColumn } from "@/components/util/column/input/sewageReservoirPump/RainfallPumpStationInpColumn";
|
||||
import { SewageReservoirInpColumn } from "@/components/util/column/input/sewageReservoirPump/SewageReservoirInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import {
|
||||
formatCompactYmdForGrid,
|
||||
normalizeOxYnValue,
|
||||
@@ -69,6 +70,8 @@ import type {
|
||||
SewerageStatusInfo,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
@@ -926,6 +929,9 @@ const SewageReservoirPumpInp = () => {
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [verificationHistoryOpen, setVerificationHistoryOpen] = useState(false);
|
||||
const [verificationResultDialogOpen, setVerificationResultDialogOpen] =
|
||||
useState(false);
|
||||
@@ -1046,8 +1052,7 @@ const SewageReservoirPumpInp = () => {
|
||||
updateLastSearchedValues: boolean,
|
||||
overrides?: { mode?: string },
|
||||
): Promise<boolean> => {
|
||||
// 서버 데이터로 새로 불러오는 시점이므로 현재 탭의 "해당없음" 체크는 초기화
|
||||
setIsNotApplicableForTab(tabKey, false);
|
||||
const fromPreYear = overrides?.mode === "PRE";
|
||||
if (tabKey === SEWAGE_RESERVOIR_STATS_CD) {
|
||||
const response =
|
||||
await statisticsInputSewageReservoirPumpApi.getSewageReservoirList(
|
||||
@@ -1072,6 +1077,9 @@ const SewageReservoirPumpInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(values);
|
||||
}
|
||||
@@ -1081,6 +1089,11 @@ const SewageReservoirPumpInp = () => {
|
||||
gridRows.length > 0,
|
||||
values,
|
||||
);
|
||||
if (!fromPreYear) {
|
||||
setIsNotApplicableForTab(tabKey, gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
setIsNotApplicableForTab(tabKey, false);
|
||||
}
|
||||
loadedTabKeysRef.current.add(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -1109,6 +1122,9 @@ const SewageReservoirPumpInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) {
|
||||
setLastSearchedValues(values);
|
||||
}
|
||||
@@ -1118,6 +1134,11 @@ const SewageReservoirPumpInp = () => {
|
||||
gridRows.length > 0,
|
||||
values,
|
||||
);
|
||||
if (!fromPreYear) {
|
||||
setIsNotApplicableForTab(tabKey, gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
setIsNotApplicableForTab(tabKey, false);
|
||||
}
|
||||
loadedTabKeysRef.current.add(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -1170,6 +1191,7 @@ const SewageReservoirPumpInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setLastSearchedValues(null);
|
||||
setSggOptions([]);
|
||||
@@ -1183,7 +1205,10 @@ const SewageReservoirPumpInp = () => {
|
||||
setActiveTab(firstTabKey);
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
const handleSearch = async (
|
||||
values: SearchFormValues,
|
||||
opts?: { forcedTabKey?: string },
|
||||
) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
@@ -1216,6 +1241,7 @@ const SewageReservoirPumpInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
loadedTabKeysRef.current.clear();
|
||||
setActiveTab(firstTabKey);
|
||||
@@ -1233,8 +1259,13 @@ const SewageReservoirPumpInp = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const tabKeyToLoad = yearChangedFromLastSearch ? firstTabKey : activeTabRef.current;
|
||||
if (yearChangedFromLastSearch) {
|
||||
const tabKeyToLoad =
|
||||
opts?.forcedTabKey ??
|
||||
(yearChangedFromLastSearch ? firstTabKey : activeTabRef.current);
|
||||
if (opts?.forcedTabKey) {
|
||||
activeTabRef.current = opts.forcedTabKey;
|
||||
setActiveTab(opts.forcedTabKey);
|
||||
} else if (yearChangedFromLastSearch) {
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
@@ -1484,10 +1515,23 @@ const SewageReservoirPumpInp = () => {
|
||||
};
|
||||
})();
|
||||
|
||||
const { verificationSearchDefaults, searchFormRemountKey } =
|
||||
useStatisticsInputPendingNavigation({
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_RESERVOIR_PUMP,
|
||||
onPending: (pending) => {
|
||||
activeTabRef.current = pending.tabKey;
|
||||
setActiveTab(pending.tabKey);
|
||||
},
|
||||
onAutoSearch: (values, tabKey) =>
|
||||
handleSearch(values, { forcedTabKey: tabKey }),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
fctName={true}
|
||||
onSubmit={handleSearch}
|
||||
onReset={handleReset}
|
||||
@@ -1498,6 +1542,9 @@ const SewageReservoirPumpInp = () => {
|
||||
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
isVerificationFailedByTab.get(activeTab) === true
|
||||
}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -3,6 +3,8 @@ import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
|
||||
import { useAlertStore, useGlobalProgressStore } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsInputSewerageFinanceApi } from "@/entities/statistics/api/input/statistics-input-sewerage-finance-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -58,6 +60,7 @@ import { SewerageChargeInpColumn } from "@/components/util/column/input/sewerage
|
||||
import { SewerageFinanceSettleInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageFinanceSettleInpColumn";
|
||||
import { SewerageUsageFeeInpColumn } from "@/components/util/column/input/sewerageFinance/SewerageUsageFeeInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
const SewerageFinanceInpTabList = [
|
||||
@@ -367,6 +370,9 @@ const SewerageFinanceInp = () => {
|
||||
Map<string, SewerageStatusInfo | null>
|
||||
>(new Map());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -482,6 +488,9 @@ const SewerageFinanceInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -505,6 +514,9 @@ const SewerageFinanceInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -528,6 +540,9 @@ const SewerageFinanceInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -551,6 +566,9 @@ const SewerageFinanceInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
return true;
|
||||
@@ -567,6 +585,7 @@ const SewerageFinanceInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -578,7 +597,10 @@ const SewerageFinanceInp = () => {
|
||||
periodEnterableRef.current = true;
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
const handleSearch = async (
|
||||
values: SearchFormValues,
|
||||
opts?: { forcedTabKey?: string },
|
||||
) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
@@ -605,11 +627,17 @@ const SewerageFinanceInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
|
||||
if (opts?.forcedTabKey) {
|
||||
activeTabRef.current = opts.forcedTabKey;
|
||||
setActiveTab(opts.forcedTabKey);
|
||||
}
|
||||
|
||||
setIsLoading(true);
|
||||
searchDoneRef.current = true;
|
||||
setHasSearched(true);
|
||||
@@ -617,7 +645,7 @@ const SewerageFinanceInp = () => {
|
||||
searchParamRef.current = searchParam;
|
||||
|
||||
await loadActiveTabData(
|
||||
activeTabRef.current,
|
||||
opts?.forcedTabKey ?? activeTabRef.current,
|
||||
searchParam,
|
||||
values,
|
||||
!yearChangedFromLastSearch,
|
||||
@@ -1194,10 +1222,23 @@ const SewerageFinanceInp = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const { verificationSearchDefaults, searchFormRemountKey } =
|
||||
useStatisticsInputPendingNavigation({
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWERAGE_FINANCE,
|
||||
onPending: (pending) => {
|
||||
activeTabRef.current = pending.tabKey;
|
||||
setActiveTab(pending.tabKey);
|
||||
},
|
||||
onAutoSearch: (values, tabKey) =>
|
||||
handleSearch(values, { forcedTabKey: tabKey }),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
onReset={handleReset}
|
||||
onValuesChange={setSearchValues}
|
||||
@@ -1207,6 +1248,9 @@ const SewerageFinanceInp = () => {
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
isVerificationFailedByTab.get(activeTab) === true
|
||||
}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -49,6 +49,7 @@ import { doList } from "@/components/util/column/input/DoList";
|
||||
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
|
||||
import { getFooterSummary } from "@/components/util/grid/footerSummary";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { isVerificationFailedFromCommonInfo } from "@/components/util/input/verification-failed-flag";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
@@ -131,6 +132,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
/** 예외제출 버튼으로 연 다이얼로그인지 (확인 시 executeSubmit 대신 SubmitExceptionFunc 경로) */
|
||||
const [isExceptionSubmitFlow, setIsExceptionSubmitFlow] = useState(false);
|
||||
const [statsCd, setStatsCd] = useState<string>('');
|
||||
const [isVerificationFailed, setIsVerificationFailed] = useState(false);
|
||||
/** `engColNm` → `columnGuide` (헤더 hover) */
|
||||
const [columnGuidesByEngCol, setColumnGuidesByEngCol] = useState<Record<string, string>>({});
|
||||
const [footerFromApi, setFooterFromApi] = useState<{ sewerBzmnWholPpltnCnt?: number; sewerBzmnDomplWholPpltnCnt?: number; sewerBzmnFrgnrWholPpltnCnt?: number; sewerBzmnArea?: number } | null>(null);
|
||||
@@ -240,6 +242,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
setLastSearchedValues(null);
|
||||
setFooterFromApi(null);
|
||||
setVerificationSearchDefaults(null);
|
||||
setIsVerificationFailed(false);
|
||||
setSearchFormRemountKey((k) => k + 1);
|
||||
};
|
||||
|
||||
@@ -275,6 +278,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
: null
|
||||
);
|
||||
setStatsCd(commonInfo.statsCd);
|
||||
setIsVerificationFailed(isVerificationFailedFromCommonInfo(commonInfo));
|
||||
if (statusInfo && statusInfo.laeSbmsnSttsCd) {
|
||||
if (['2', '3'].includes(statusInfo.laeSbmsnSttsCd)) {
|
||||
setIsEnterable(false);
|
||||
@@ -779,6 +783,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={isVerificationFailed}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -23,6 +23,7 @@ import { doList } from "@/components/util/column/input/DoList";
|
||||
import { ComplaintInpColumn } from "@/components/util/column/input/sewerageServiceLevel/ComplaintInpColumn";
|
||||
import { ResidentFriendlyFactInpColumn } from "@/components/util/column/input/sewerageServiceLevel/ResidentFriendlyFactInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { patchVerificationFailedByTab } from "@/components/util/input/verification-failed-flag";
|
||||
import { statisticsInputSewerageServiceLevelApi } from "@/entities/statistics/api/input/statistics-input-sewerage-service-level-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
@@ -44,6 +45,8 @@ import type {
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
@@ -300,6 +303,9 @@ const SewerageServiceLevelInp = () => {
|
||||
Map<string, SewerageStatusInfo | null>
|
||||
>(new Map());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -425,6 +431,9 @@ const SewerageServiceLevelInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
setSearchLoaded(true);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
@@ -457,9 +466,17 @@ const SewerageServiceLevelInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
setSearchLoaded(true);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
if (!fromPreYear) {
|
||||
setIsNotApplicable(gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
setIsNotApplicable(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -474,6 +491,7 @@ const SewerageServiceLevelInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -488,7 +506,10 @@ const SewerageServiceLevelInp = () => {
|
||||
publicSewageFacilitiesAvailableRef.current = false;
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
const handleSearch = async (
|
||||
values: SearchFormValues,
|
||||
opts?: { forcedTabKey?: string },
|
||||
) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
@@ -515,12 +536,20 @@ const SewerageServiceLevelInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setIsNotApplicable(false);
|
||||
activeTabRef.current = firstTabKey;
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
|
||||
if (opts?.forcedTabKey) {
|
||||
activeTabRef.current = opts.forcedTabKey;
|
||||
setActiveTab(opts.forcedTabKey);
|
||||
}
|
||||
|
||||
const tabForLoad = opts?.forcedTabKey ?? activeTabRef.current;
|
||||
|
||||
setIsLoading(true);
|
||||
searchDoneRef.current = true;
|
||||
setHasSearched(true);
|
||||
@@ -528,12 +557,12 @@ const SewerageServiceLevelInp = () => {
|
||||
searchParamRef.current = searchParam;
|
||||
|
||||
await loadActiveTabData(
|
||||
activeTabRef.current,
|
||||
tabForLoad,
|
||||
searchParam,
|
||||
values,
|
||||
!yearChangedFromLastSearch,
|
||||
true,
|
||||
buildEmptyDataAlertOverrides(activeTabRef.current),
|
||||
buildEmptyDataAlertOverrides(tabForLoad),
|
||||
);
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
@@ -1091,10 +1120,23 @@ const SewerageServiceLevelInp = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const { verificationSearchDefaults, searchFormRemountKey } =
|
||||
useStatisticsInputPendingNavigation({
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWERAGE_SERVICE_LEVEL,
|
||||
onPending: (pending) => {
|
||||
activeTabRef.current = pending.tabKey;
|
||||
setActiveTab(pending.tabKey);
|
||||
},
|
||||
onAutoSearch: (values, tabKey) =>
|
||||
handleSearch(values, { forcedTabKey: tabKey }),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
onReset={handleReset}
|
||||
onValuesChange={setSearchValues}
|
||||
@@ -1104,6 +1146,9 @@ const SewerageServiceLevelInp = () => {
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
isVerificationFailedByTab.get(activeTab) === true
|
||||
}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -38,6 +38,9 @@ import { RecycledWaterStatusInpColumn } from "@/components/util/column/input/wat
|
||||
import { TreatWaterReuseBusinessApprStatusInpColumn } from "@/components/util/column/input/waterReuse/TreatWaterReuseBusinessApprStatusInpColumn";
|
||||
import { TreatWaterReuseDesignConstStatusInpColumn } from "@/components/util/column/input/waterReuse/TreatWaterReuseDesignConstStatusInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import {
|
||||
patchVerificationFailedByTab,
|
||||
} from "@/components/util/input/verification-failed-flag";
|
||||
import { formatCompactYmdForGrid, toCompactYmdOrNull } from "@/components/util/grid/ymdFormat";
|
||||
import { statisticsInputWaterReuseApi } from "@/entities/statistics/api/input/statistics-input-water-reuse-service";
|
||||
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
||||
@@ -71,6 +74,8 @@ import type {
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import type { StatisticsInputSubmitInfoFormData } from "@/entities/statistics/model/statistics-input-submit-info-schema";
|
||||
import { useAlertStore, useGlobalProgressStore, useStatisticsInputToolbarGroupGate } from "@/shared/lib";
|
||||
import { STATISTICS_INPUT_URL_PATH } from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputPendingNavigation } from "@/shared/lib/use-statistics-input-pending-navigation";
|
||||
import { ConfirmDialog } from "@/shared/ui/confirm-dialog";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
|
||||
@@ -533,6 +538,9 @@ const WaterReuseInp = () => {
|
||||
Map<string, SewerageStatusInfo | null>
|
||||
>(new Map());
|
||||
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
|
||||
const [isVerificationFailedByTab, setIsVerificationFailedByTab] = useState<
|
||||
Map<string, boolean>
|
||||
>(new Map());
|
||||
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<
|
||||
Map<string, TabToolbarSnapshot>
|
||||
>(new Map());
|
||||
@@ -677,8 +685,19 @@ const WaterReuseInp = () => {
|
||||
overrides?: { mode?: string },
|
||||
): Promise<boolean> => {
|
||||
const requestParam = overrides?.mode ? { ...param, mode: overrides.mode } : param;
|
||||
const fromPreYear = overrides?.mode === "PRE";
|
||||
|
||||
const applyNotApplicableForLoadedTab = (
|
||||
gridRows: readonly Record<string, string | number>[],
|
||||
) => {
|
||||
if (!NO_APPLICABLE_TAB_KEYS.has(tabKey)) return;
|
||||
if (!fromPreYear) {
|
||||
setIsNotApplicableForTab(tabKey, gridRows.length === 0);
|
||||
} else if (gridRows.length > 0) {
|
||||
setIsNotApplicableForTab(tabKey, false);
|
||||
}
|
||||
};
|
||||
|
||||
setIsNotApplicableForTab(tabKey, false);
|
||||
if (ARTICLE_EDIT_TAB_KEYS.has(tabKey)) {
|
||||
setDeleteListByTab((prev) => {
|
||||
const next = new Map(prev);
|
||||
@@ -706,8 +725,12 @@ const WaterReuseInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
loadedTabKeysRef.current.add(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -730,8 +753,12 @@ const WaterReuseInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
loadedTabKeysRef.current.add(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -754,8 +781,12 @@ const WaterReuseInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
loadedTabKeysRef.current.add(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -780,8 +811,12 @@ const WaterReuseInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
loadedTabKeysRef.current.add(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -807,8 +842,12 @@ const WaterReuseInp = () => {
|
||||
});
|
||||
setTabStatusInfoMap((prev) => new Map(prev).set(tabKey, statusInfo));
|
||||
setTabStatsCdMap((prev) => new Map(prev).set(tabKey, commonInfo.statsCd));
|
||||
setIsVerificationFailedByTab((prev) =>
|
||||
patchVerificationFailedByTab(prev, tabKey, commonInfo),
|
||||
);
|
||||
if (updateLastSearchedValues) setLastSearchedValues(searchContext);
|
||||
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
|
||||
applyNotApplicableForLoadedTab(gridRows);
|
||||
loadedTabKeysRef.current.add(tabKey);
|
||||
return true;
|
||||
}
|
||||
@@ -862,6 +901,7 @@ const WaterReuseInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
activeTabRef.current = firstTabKey;
|
||||
@@ -878,7 +918,10 @@ const WaterReuseInp = () => {
|
||||
sggOptionsSidoRef.current = null;
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
const handleSearch = async (
|
||||
values: SearchFormValues,
|
||||
opts?: { forcedTabKey?: string },
|
||||
) => {
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
@@ -905,6 +948,7 @@ const WaterReuseInp = () => {
|
||||
setRowDataMap(new Map());
|
||||
setTabStatusInfoMap(new Map());
|
||||
setTabStatsCdMap(new Map());
|
||||
setIsVerificationFailedByTab(new Map());
|
||||
setToolbarSnapshotByTab(new Map());
|
||||
setDeleteListByTab(new Map());
|
||||
loadedTabKeysRef.current.clear();
|
||||
@@ -912,6 +956,13 @@ const WaterReuseInp = () => {
|
||||
setActiveTab(firstTabKey);
|
||||
}
|
||||
|
||||
if (opts?.forcedTabKey) {
|
||||
activeTabRef.current = opts.forcedTabKey;
|
||||
setActiveTab(opts.forcedTabKey);
|
||||
}
|
||||
|
||||
const tabForLoad = opts?.forcedTabKey ?? activeTabRef.current;
|
||||
|
||||
setIsLoading(true);
|
||||
searchDoneRef.current = true;
|
||||
setHasSearched(true);
|
||||
@@ -920,13 +971,13 @@ const WaterReuseInp = () => {
|
||||
|
||||
const loadedSgg = await loadSggOptions(values.sido);
|
||||
sggOptionsSidoRef.current = values.sido;
|
||||
if (ARTICLE_EDIT_TAB_KEYS.has(activeTabRef.current) && loadedSgg.length === 0) {
|
||||
if (ARTICLE_EDIT_TAB_KEYS.has(tabForLoad) && loadedSgg.length === 0) {
|
||||
alert("시군구 목록을 불러오지 못했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
await loadActiveTabData(
|
||||
activeTabRef.current,
|
||||
tabForLoad,
|
||||
searchParam,
|
||||
values,
|
||||
!yearChangedFromLastSearch,
|
||||
@@ -2009,10 +2060,23 @@ const WaterReuseInp = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const { verificationSearchDefaults, searchFormRemountKey } =
|
||||
useStatisticsInputPendingNavigation({
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.WATER_REUSE,
|
||||
onPending: (pending) => {
|
||||
activeTabRef.current = pending.tabKey;
|
||||
setActiveTab(pending.tabKey);
|
||||
},
|
||||
onAutoSearch: (values, tabKey) =>
|
||||
handleSearch(values, { forcedTabKey: tabKey }),
|
||||
});
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="relative">
|
||||
<SearchForm
|
||||
key={searchFormRemountKey}
|
||||
defaultValues={verificationSearchDefaults ?? undefined}
|
||||
onSubmit={handleSearch}
|
||||
onReset={handleReset}
|
||||
onValuesChange={setSearchValues}
|
||||
@@ -2022,6 +2086,9 @@ const WaterReuseInp = () => {
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<VerificationResultButton
|
||||
VerificationResultButtonFunc={openVerificationHistoryDialog}
|
||||
isVerificationFailed={
|
||||
isVerificationFailedByTab.get(activeTab) === true
|
||||
}
|
||||
/>
|
||||
{showEnterableToolbarButtons && (
|
||||
<>
|
||||
|
||||
@@ -165,6 +165,8 @@ const LogHistory = () => {
|
||||
const logList = logListRaw.map((l) => ({
|
||||
...l,
|
||||
sggNm: String(l.sggNm ?? "").trim() || "-",
|
||||
ctpvNm: String(l.ctpvNm ?? "").trim() || "-",
|
||||
statsYr: String(l.statsYr ?? "").trim() || "-",
|
||||
}));
|
||||
|
||||
setRowData(toGridRows(logList));
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import { GridFitStyle } from "realgrid";
|
||||
|
||||
import {
|
||||
@@ -15,17 +15,25 @@ import {
|
||||
} from "@common/components";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type {
|
||||
ColumnInfo,
|
||||
DataCellClickCallbackParams,
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
alignLogDetailRowsToColumns,
|
||||
buildLogHistoryDetailColumnInfo,
|
||||
emptyLogHistoryDetailColumnInfo,
|
||||
LOG_DETAIL_DETAILS_FIELD,
|
||||
LOG_DETAIL_DETAILS_RAW_FIELD,
|
||||
type LogDetailVerificationItem,
|
||||
prepareLogDetailRowsForGrid,
|
||||
} from "@/components/util/column/management/LogHistoryDetailColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import type { StatisticsLogDetailRow } from "@/entities/statistics/model/statistics-interface";
|
||||
import { UserApi } from "@/entities/user";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
import { LogHistoryDetailsNestedDialog } from "./LogHistoryDetailsNestedDialog";
|
||||
|
||||
export interface LogHistoryDetailDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
@@ -45,11 +53,19 @@ export function LogHistoryDetailDialog({
|
||||
const [columnInfo, setColumnInfo] = useState<ColumnInfo>(
|
||||
emptyLogHistoryDetailColumnInfo,
|
||||
);
|
||||
const [userName, setUserName] = useState("");
|
||||
const [detailsDialogOpen, setDetailsDialogOpen] = useState(false);
|
||||
const [selectedDetails, setSelectedDetails] = useState<
|
||||
LogDetailVerificationItem[]
|
||||
>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
setRowData([]);
|
||||
setColumnInfo(emptyLogHistoryDetailColumnInfo);
|
||||
setUserName("");
|
||||
setDetailsDialogOpen(false);
|
||||
setSelectedDetails([]);
|
||||
setIsLoading(false);
|
||||
return;
|
||||
}
|
||||
@@ -57,23 +73,42 @@ export function LogHistoryDetailDialog({
|
||||
if (!row) return;
|
||||
|
||||
const sn = row.statsLogSn;
|
||||
const rgtrId =
|
||||
row.rgtrId != null && String(row.rgtrId).trim() !== ""
|
||||
? String(row.rgtrId).trim()
|
||||
: "";
|
||||
let cancelled = false;
|
||||
|
||||
void (async () => {
|
||||
setIsLoading(true);
|
||||
setRowData([]);
|
||||
setUserName("");
|
||||
|
||||
try {
|
||||
const response = await statisticsApi.getLogDetail(
|
||||
sn !== undefined && sn !== null ? String(sn) : "",
|
||||
);
|
||||
const userLookupPromise =
|
||||
rgtrId !== ""
|
||||
? UserApi.findByUserUnqId(rgtrId).catch(() => {
|
||||
return null;
|
||||
})
|
||||
: Promise.resolve(null);
|
||||
|
||||
const [response, userResponse] = await Promise.all([
|
||||
statisticsApi.getLogDetail(
|
||||
sn !== undefined && sn !== null ? String(sn) : "",
|
||||
),
|
||||
userLookupPromise,
|
||||
]);
|
||||
|
||||
if (userResponse) {
|
||||
setUserName(userResponse.userName ?? "");
|
||||
}
|
||||
|
||||
if (cancelled) return;
|
||||
|
||||
if (response.status !== "success") {
|
||||
alert(
|
||||
response.message ??
|
||||
"로그 상세 정보를 불러오는데 실패했습니다.",
|
||||
"로그 상세 정보를 불러오는데 실패했습니다.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -83,7 +118,7 @@ export function LogHistoryDetailDialog({
|
||||
: [];
|
||||
const nextColumnInfo = buildLogHistoryDetailColumnInfo(list);
|
||||
const nextRows = alignLogDetailRowsToColumns(
|
||||
toGridRows(list as StatisticsLogDetailRow[]),
|
||||
prepareLogDetailRowsForGrid(list),
|
||||
nextColumnInfo,
|
||||
);
|
||||
setColumnInfo(nextColumnInfo);
|
||||
@@ -102,6 +137,25 @@ export function LogHistoryDetailDialog({
|
||||
};
|
||||
}, [open, row, alert]);
|
||||
|
||||
const handleDataCellClick = useCallback(
|
||||
({ fieldName, row }: DataCellClickCallbackParams) => {
|
||||
if (fieldName !== LOG_DETAIL_DETAILS_FIELD) return;
|
||||
|
||||
const raw = row[LOG_DETAIL_DETAILS_RAW_FIELD];
|
||||
if (raw == null || String(raw).trim() === "") return;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(String(raw)) as unknown;
|
||||
if (!Array.isArray(parsed) || parsed.length === 0) return;
|
||||
setSelectedDetails(parsed as LogDetailVerificationItem[]);
|
||||
setDetailsDialogOpen(true);
|
||||
} catch {
|
||||
alert("상세 데이터를 불러오는데 실패했습니다.");
|
||||
}
|
||||
},
|
||||
[alert],
|
||||
);
|
||||
|
||||
const gridRemountKey = useMemo(
|
||||
() => columnInfo.columns.map((c) => c.fieldName).join("\u0001"),
|
||||
[columnInfo],
|
||||
@@ -110,13 +164,14 @@ export function LogHistoryDetailDialog({
|
||||
const showGrid = !isLoading && rowData.length > 0;
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
className="max-w-[80%]"
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<>
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
className="max-w-[80%]"
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>로그 상세 정보</DialogTitle>
|
||||
<DialogTitle>로그 상세 정보(기능 실행자: {userName})</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="relative min-h-[32rem]">
|
||||
{isLoading && (
|
||||
@@ -136,7 +191,8 @@ export function LogHistoryDetailDialog({
|
||||
isExcelExport={false}
|
||||
enableSort={false}
|
||||
gridHeight={55}
|
||||
fitStyle={GridFitStyle.NONE}
|
||||
fitStyle={GridFitStyle.EVEN}
|
||||
onDataCellClick={handleDataCellClick}
|
||||
/>
|
||||
) : (
|
||||
!isLoading && (
|
||||
@@ -153,7 +209,13 @@ export function LogHistoryDetailDialog({
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
<LogHistoryDetailsNestedDialog
|
||||
open={detailsDialogOpen}
|
||||
onOpenChange={setDetailsDialogOpen}
|
||||
items={selectedDetails}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@common/components";
|
||||
|
||||
import type { LogDetailVerificationItem } from "@/components/util/column/management/LogHistoryDetailColumn";
|
||||
|
||||
export interface LogHistoryDetailsNestedDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
items: LogDetailVerificationItem[];
|
||||
}
|
||||
|
||||
export function LogHistoryDetailsNestedDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
items,
|
||||
}: LogHistoryDetailsNestedDialogProps) {
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent
|
||||
className="max-w-[72rem]"
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<DialogHeader>
|
||||
<DialogTitle>검증 상세</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="max-h-[60vh] space-y-4 overflow-y-auto pr-1">
|
||||
{items.length === 0 ? (
|
||||
<p className="py-8 text-center text-[1.3rem] text-muted-foreground">
|
||||
표시할 상세 데이터가 없습니다.
|
||||
</p>
|
||||
) : (
|
||||
items.map((item, index) => (
|
||||
<div
|
||||
key={`${item.vrfcDtlSn ?? "detail"}-${index}`}
|
||||
className="rounded-lg border border-slate-200 p-4"
|
||||
>
|
||||
<div className="mb-3 grid grid-cols-2 gap-x-6 gap-y-2 text-[1.3rem]">
|
||||
<p>
|
||||
<span className="font-semibold">
|
||||
조합순번:
|
||||
</span>{" "}
|
||||
{item.eqtnCombSeq ?? "-"}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold">
|
||||
검증상세순번:
|
||||
</span>{" "}
|
||||
{item.vrfcDtlSn ?? "-"}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold">
|
||||
비교연산:
|
||||
</span>{" "}
|
||||
{item.cmprOpert ?? "-"}
|
||||
</p>
|
||||
<p>
|
||||
<span className="font-semibold">
|
||||
참조통계코드:
|
||||
</span>{" "}
|
||||
{item.refStatsCd ?? "-"}
|
||||
</p>
|
||||
<p className="col-span-2">
|
||||
<span className="font-semibold">
|
||||
한글 수식:
|
||||
</span>{" "}
|
||||
{item.kornArtopnEqtnCn ?? "-"}
|
||||
</p>
|
||||
<p className="col-span-2">
|
||||
<span className="font-semibold">
|
||||
영문 수식:
|
||||
</span>{" "}
|
||||
{item.engArtopnEqtnCn ?? "-"}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button size="lg" color="white" type="button">
|
||||
닫기
|
||||
</Button>
|
||||
</DialogClose>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -33,6 +33,12 @@ function rowSeTypeToCode(row: Record<string, string | number>): "INPT" | "VRFC"
|
||||
return "INPT";
|
||||
}
|
||||
|
||||
function parseExtSchdlId(value: string | number | undefined): number | undefined {
|
||||
if (typeof value === "number" && Number.isFinite(value)) return value;
|
||||
const parsed = parseInt(String(value ?? ""), 10);
|
||||
return Number.isFinite(parsed) ? parsed : undefined;
|
||||
}
|
||||
|
||||
function parseYmdToDate(ymd: string): Date | undefined {
|
||||
const s = String(ymd ?? "").replace(/\D/g, "");
|
||||
if (s.length !== 8) return undefined;
|
||||
@@ -132,6 +138,7 @@ const StatisticsInputManagement = () => {
|
||||
const seCd = Number.isFinite(seCdNum) ? seCdNum : 0;
|
||||
const statsYr = String(row.statsYr ?? "");
|
||||
const seType = rowSeTypeToCode(row);
|
||||
const extSchdlId = parseExtSchdlId(row.extSchdlId);
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -139,6 +146,7 @@ const StatisticsInputManagement = () => {
|
||||
statsYr,
|
||||
seType,
|
||||
seCd,
|
||||
...(extSchdlId != null ? { extSchdlId } : {}),
|
||||
});
|
||||
|
||||
if (res.status !== "success" || !res.data) {
|
||||
@@ -147,8 +155,10 @@ const StatisticsInputManagement = () => {
|
||||
}
|
||||
|
||||
const detail = res.data;
|
||||
const detailExtSchdlId = parseExtSchdlId(detail.extSchdlId) ?? extSchdlId;
|
||||
setPeriodDialogMode("edit");
|
||||
setPeriodDialogInitial({
|
||||
extSchdlId: detailExtSchdlId,
|
||||
statsYr: String(detail.statsYr ?? statsYr),
|
||||
seType: detail.seType === "VRFC" ? "VRFC" : "INPT",
|
||||
seCd:
|
||||
|
||||
@@ -31,6 +31,7 @@ import {
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
export type StatisticsInputPeriodDialogValues = {
|
||||
extSchdlId?: number;
|
||||
statsYr: string;
|
||||
seType: "INPT" | "VRFC";
|
||||
seCd: number;
|
||||
@@ -146,6 +147,12 @@ export function StatisticsInputPeriodDialog({
|
||||
};
|
||||
if (!isCreate) {
|
||||
param.seCd = seCdParsed;
|
||||
if (
|
||||
initial?.extSchdlId != null &&
|
||||
Number.isFinite(initial.extSchdlId)
|
||||
) {
|
||||
param.extSchdlId = initial.extSchdlId;
|
||||
}
|
||||
}
|
||||
const res = await statisticsApi.saveStatsInputPeriod(param);
|
||||
if (res.status === "success") {
|
||||
@@ -191,6 +198,9 @@ export function StatisticsInputPeriodDialog({
|
||||
inptEndYmd: end,
|
||||
rgtrId: userInfo.userUnqId,
|
||||
};
|
||||
if (initial.extSchdlId != null && Number.isFinite(initial.extSchdlId)) {
|
||||
param.extSchdlId = initial.extSchdlId;
|
||||
}
|
||||
const res = await statisticsApi.deleteStatsInputPeriod(param);
|
||||
if (res.status === "success") {
|
||||
alert("삭제되었습니다.");
|
||||
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { ExcretaTreatmentFacilityInfoList } from "@/components/util/column/review/ExcretaTreatmentFacilityRvwColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleExcludeMergeCells, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const excretaTreatmentFacilityTab = ExcretaTreatmentFacilityInfoList[0];
|
||||
|
||||
@@ -68,8 +70,8 @@ const ExcretaTreatmentFacilityRvw = () => {
|
||||
}, []);
|
||||
|
||||
const cellStyleCallback = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleExcludeMergeCells(columnInfo.columns),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallback(columnInfo.columns, rowData),
|
||||
[columnInfo, rowData],
|
||||
);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
@@ -274,6 +276,7 @@ const ExcretaTreatmentFacilityRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { PrivateSewageFacilityInfoListRvw } from "@/components/util/column/review/PrivateSewageFacilityColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = PrivateSewageFacilityInfoListRvw[0]?.tabKey ?? "ST00080001";
|
||||
|
||||
@@ -86,8 +88,8 @@ const PrivateSewageFacilityRvw = () => {
|
||||
}, []);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap),
|
||||
[columnInfo, rowDataMap],
|
||||
);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
@@ -349,6 +351,7 @@ const PrivateSewageFacilityRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
|
||||
@@ -22,7 +22,9 @@ import {
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { buildReviewRowTypeCurrCellStyleExcludeMergeCells, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
import { PublicSewageFacilityInfoList } from "@/components/util/column/review/PublicSewageFacilityRvwColumn";
|
||||
|
||||
@@ -56,8 +58,8 @@ const PublicSewageFacilityRvw = () => {
|
||||
}, []);
|
||||
|
||||
const cellStyleCallback = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleExcludeMergeCells(columnInfo.columns),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallback(columnInfo.columns, rowData),
|
||||
[columnInfo, rowData],
|
||||
);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
@@ -248,15 +250,18 @@ const PublicSewageFacilityRvw = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
fixedColumns={columnInfo.fixedColumns}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
cellStyleCallback={cellStyleCallback}
|
||||
/>
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
fixedColumns={columnInfo.fixedColumns}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
cellStyleCallback={cellStyleCallback}
|
||||
/>
|
||||
</div>
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
|
||||
@@ -28,7 +28,9 @@ import {
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { PumpingStationInfoList } from "@/components/util/column/review/PumpingStationRvwColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleExcludeMergeCells, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
/**
|
||||
* 하수도 통계 검토 05.중계펌프장
|
||||
@@ -63,8 +65,8 @@ const PumpingStationRvw = () => {
|
||||
}, []);
|
||||
|
||||
const cellStyleCallback = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleExcludeMergeCells(columnInfo.columns),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallback(columnInfo.columns, rowData),
|
||||
[columnInfo, rowData],
|
||||
);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
@@ -132,7 +134,7 @@ const PumpingStationRvw = () => {
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
statsYr: values.year,
|
||||
pumpstNm: values.fctName,
|
||||
fcltNm: values.fctName,
|
||||
});
|
||||
|
||||
if (response.status === "success" && response.data) {
|
||||
@@ -264,6 +266,7 @@ const PumpingStationRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
const ReviewEmptyCurrLegend = () => (
|
||||
<div className="flex justify-end">
|
||||
<div className="flex items-center gap-[1.2rem] text-[1.4rem] bg-white w-fit py-2 px-4 border border-slate-200/80 rounded-md">
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="w-8 h-6 bg-[#feee85] border border-slate-200" />
|
||||
<span>전년도만 존재</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<div className="w-8 h-6 bg-[#bae6fd] border border-slate-200" />
|
||||
<span>현년도만 존재</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default ReviewEmptyCurrLegend;
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewageExcretaRelatedInfoListRvw } from "@/components/util/column/review/SewageExcretaRelatedRvwColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewageExcretaRelatedInfoListRvw[0]?.tabKey ?? "ST00120001";
|
||||
|
||||
@@ -89,8 +91,8 @@ const SewageExcretaRelatedRvw = () => {
|
||||
}, []);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap),
|
||||
[columnInfo, rowDataMap],
|
||||
);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
@@ -354,6 +356,7 @@ const SewageExcretaRelatedRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewageExcretaSludgeInfoListRvw } from "@/components/util/column/review/SewageExcretaSlurgeRvwColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewageExcretaSludgeInfoListRvw[0]?.tabKey ?? "ST00110001";
|
||||
|
||||
@@ -89,8 +91,8 @@ const SewageExcretaSlurgeRvw = () => {
|
||||
}, []);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap),
|
||||
[columnInfo, rowDataMap],
|
||||
);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
@@ -355,6 +357,7 @@ const SewageExcretaSlurgeRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
|
||||
@@ -27,7 +27,9 @@ import {
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeMaintainColumnInfoList } from "@/components/util/column/review/SewagePipeMaintainRvwColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewagePipeMaintainColumnInfoList[0]?.tabKey ?? "ST00030001";
|
||||
|
||||
@@ -104,8 +106,8 @@ const SewagePipeMaintainRvw = () => {
|
||||
}, []);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap),
|
||||
[columnInfo, rowDataMap],
|
||||
);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
@@ -367,6 +369,7 @@ const SewagePipeMaintainRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
|
||||
@@ -32,7 +32,9 @@ import type {
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const firstTabKey = SewagePipeColumnInfoListRvw[0]?.tabKey ?? "ST00020001";
|
||||
|
||||
@@ -158,8 +160,8 @@ const SewagePipeRvw = () => {
|
||||
);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap),
|
||||
[columnInfo, rowDataMap],
|
||||
);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
@@ -418,6 +420,7 @@ const SewagePipeRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
|
||||
@@ -25,7 +25,9 @@ import {
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewageReservoirPumpInfoListRvw } from "@/components/util/column/review/SewageReservoirPumpRvwColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewageReservoirPumpInfoListRvw[0]?.tabKey ?? "ST00040001";
|
||||
|
||||
@@ -52,7 +54,7 @@ function getListDataByTab(tabKey: string, responseData: unknown): object[] {
|
||||
|
||||
const data = responseData as Record<string, unknown>;
|
||||
if (tabKey === "ST00040001") {
|
||||
const list = data.sewerReservoirList ?? data.facilitiesList;
|
||||
const list = data.sewerReservoirList ?? data.reservoirList;
|
||||
return Array.isArray(list) ? (list as object[]) : [];
|
||||
}
|
||||
if (tabKey === "ST00040002") {
|
||||
@@ -108,8 +110,8 @@ const SewageReservoirPumpRvw = () => {
|
||||
}, []);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap),
|
||||
[columnInfo, rowDataMap],
|
||||
);
|
||||
|
||||
const activeStatsCd = activeStatsStatusInfo.statsCd ?? activeTab;
|
||||
@@ -373,6 +375,7 @@ const SewageReservoirPumpRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewerageFinanceInfoListRvw } from "@/components/util/column/review/SewerageFinanceRvwColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewerageFinanceInfoListRvw[0]?.tabKey ?? "ST00090001";
|
||||
|
||||
@@ -89,8 +91,8 @@ const SewerageFinanceRvw = () => {
|
||||
}, []);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap),
|
||||
[columnInfo, rowDataMap],
|
||||
);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
@@ -352,6 +354,7 @@ const SewerageFinanceRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
|
||||
@@ -18,7 +18,9 @@ import type {
|
||||
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { buildReviewRowTypeCurrCellStyleExcludeMergeCells, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallback } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
@@ -55,8 +57,8 @@ const SeweragePenetrationRateRvw = () => {
|
||||
}, []);
|
||||
|
||||
const cellStyleCallback = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleExcludeMergeCells(columnInfo.columns),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallback(columnInfo.columns, rowData),
|
||||
[columnInfo, rowData],
|
||||
);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
@@ -243,15 +245,18 @@ const SeweragePenetrationRateRvw = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
fixedColumns={columnInfo.fixedColumns}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
cellStyleCallback={cellStyleCallback}
|
||||
/>
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
fixedColumns={columnInfo.fixedColumns}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
cellStyleCallback={cellStyleCallback}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
|
||||
@@ -34,7 +34,9 @@ import type {
|
||||
} from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewerageServiceLevelInfoListRvw } from "@/components/util/column/review/SewerageServiceLevelRvwColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = SewerageServiceLevelInfoListRvw[0]?.tabKey ?? "ST00130001";
|
||||
|
||||
@@ -87,8 +89,8 @@ const SewerageServiceLevelRvw = () => {
|
||||
}, []);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap),
|
||||
[columnInfo, rowDataMap],
|
||||
);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
@@ -353,6 +355,7 @@ const SewerageServiceLevelRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
|
||||
@@ -30,7 +30,9 @@ import {
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { WaterReuseInfoListRvw } from "@/components/util/column/review/WaterReuseRvwColumn";
|
||||
import { buildReviewRowTypeCurrCellStyleCallbackByTab, toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
|
||||
import ReviewEmptyCurrLegend from "@/components/statistics/review/ReviewEmptyCurrLegend";
|
||||
|
||||
const firstTabKey = WaterReuseInfoListRvw[0]?.tabKey ?? "ST00100001";
|
||||
|
||||
@@ -92,8 +94,8 @@ const WaterReuseRvw = () => {
|
||||
}, []);
|
||||
|
||||
const reviewCellStyleCallbackByTab = useMemo(
|
||||
() => buildReviewRowTypeCurrCellStyleCallbackByTab(columnInfo),
|
||||
[columnInfo],
|
||||
() => buildReviewCellStyleCallbackByTab(columnInfo, rowDataMap),
|
||||
[columnInfo, rowDataMap],
|
||||
);
|
||||
|
||||
const reviewOpinionContext = useMemo<ReviewOpinionDialogContext | null>(() => {
|
||||
@@ -356,6 +358,7 @@ const WaterReuseRvw = () => {
|
||||
)}
|
||||
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<ReviewEmptyCurrLegend />
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
|
||||
@@ -63,11 +63,14 @@ const CopyDialog = ({
|
||||
try {
|
||||
setIsLoading(true);
|
||||
const userInfo = await getUserInfo();
|
||||
const response = await generalverificationApiService.CopyVrfcData({
|
||||
vrfcSn: row?.vrfcSn ?? null,
|
||||
targetStatsCd: selectedCode ?? '',
|
||||
rgtrId: userInfo.userUnqId
|
||||
});
|
||||
const response = await generalverificationApiService.CopyVrfcData(
|
||||
{
|
||||
vrfcSn: row?.vrfcSn ?? null,
|
||||
targetStatsCd: selectedCode ?? '',
|
||||
rgtrId: userInfo.userUnqId,
|
||||
},
|
||||
userInfo.userUnqId,
|
||||
);
|
||||
|
||||
if (response.status === 'success') {
|
||||
alert('복사되었습니다.');
|
||||
|
||||
@@ -177,7 +177,12 @@ const RegistDialog = memo(
|
||||
|
||||
if (await confirm(deleteMsg)) {
|
||||
try {
|
||||
const response = await generalverificationApiService.DeleteVrfcMaster(vrfcSn!);
|
||||
const userInfo = await getUserInfo();
|
||||
const response =
|
||||
await generalverificationApiService.DeleteVrfcMaster(
|
||||
vrfcSn!,
|
||||
userInfo.userUnqId,
|
||||
);
|
||||
|
||||
if (response.status === 'success') {
|
||||
alert('삭제되었습니다.');
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
/**
|
||||
* statistics/input 화면과 탭(·분할 그리드) 키 매칭용 상수.
|
||||
* 각 컴포넌트에 실제로 쓰이는 `tabKey`를 모두 포함합니다.
|
||||
* 각 컴포넌트에 실제로 쓰이는 `tabKey`·`statsCd`를 모두 포함합니다.
|
||||
*/
|
||||
|
||||
/** `SeweragePenetrationRateInp.tsx` — 단일 그리드 */
|
||||
export const SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS = ["ST00010001"] as const;
|
||||
|
||||
/** `SewagePipeInp.tsx` — 메인 탭 + `ConnectionStatusInpColumn` 좌·우 그리드 서브 키 */
|
||||
export const SEWAGE_PIPE_INP_TAB_KEYS = [
|
||||
"ST00020001",
|
||||
@@ -18,16 +21,108 @@ export const SEWAGE_PIPE_INP_TAB_KEYS = [
|
||||
/** `SewagePipeMaintainInp.tsx` — 보수·준설 탭 */
|
||||
export const SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS = ["ST00030001", "ST00030002"] as const;
|
||||
|
||||
/** `SeweragePenetrationRateInp.tsx` — 단일 그리드(통계코드) */
|
||||
export const SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS = ["ST00010001"] as const;
|
||||
/** `SewageReservoirPumpInp.tsx` */
|
||||
export const SEWAGE_RESERVOIR_PUMP_INP_TAB_KEYS = ["ST00040001", "ST00040002"] as const;
|
||||
|
||||
/** `PumpingStationInp.tsx` */
|
||||
export const PUMPING_STATION_INP_TAB_KEYS = ["ST00050001"] as const;
|
||||
|
||||
/** `PublicSewageFacilityInp.tsx` */
|
||||
export const PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS = ["ST00060001", "ST00060002"] as const;
|
||||
|
||||
/** `ExcretaTreatmentFacilityInp.tsx` */
|
||||
export const EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS = ["ST00070001"] as const;
|
||||
|
||||
/**
|
||||
* `PrivateSewageFacilityInp.tsx`
|
||||
* ST00080003은 tabKey(`privateSewageDesigStatusInp`)와 다름 — `INPUT_TAB_KEY_BY_STATS_CD` 참고
|
||||
*/
|
||||
export const PRIVATE_SEWAGE_FACILITY_INP_TAB_KEYS = [
|
||||
"ST00080001",
|
||||
"ST00080002",
|
||||
"ST00080003",
|
||||
] as const;
|
||||
|
||||
/** `SewerageFinanceInp.tsx` */
|
||||
export const SEWERAGE_FINANCE_INP_TAB_KEYS = [
|
||||
"ST00090001",
|
||||
"ST00090002",
|
||||
"ST00090003",
|
||||
"ST00090004",
|
||||
] as const;
|
||||
|
||||
/** `WaterReuseInp.tsx` */
|
||||
export const WATER_REUSE_INP_TAB_KEYS = [
|
||||
"ST00100001",
|
||||
"ST00100002",
|
||||
"ST00100003",
|
||||
"ST00100004",
|
||||
"ST00100005",
|
||||
] as const;
|
||||
|
||||
/** `SewageExcretaSlurgeInp.tsx` */
|
||||
export const SEWAGE_EXCRETA_SLURGE_INP_TAB_KEYS = [
|
||||
"ST00110001",
|
||||
"ST00110002",
|
||||
"ST00110003",
|
||||
"ST00110004",
|
||||
] as const;
|
||||
|
||||
/** `SewageExcretaRelatedInp.tsx` */
|
||||
export const SEWAGE_EXCRETA_RELATED_INP_TAB_KEYS = [
|
||||
"ST00120001",
|
||||
"ST00120002",
|
||||
"ST00120003",
|
||||
"ST00120004",
|
||||
] as const;
|
||||
|
||||
/** `SewerageServiceLevelInp.tsx` */
|
||||
export const SEWERAGE_SERVICE_LEVEL_INP_TAB_KEYS = [
|
||||
"ST00130001",
|
||||
"ST00130002",
|
||||
] as const;
|
||||
|
||||
/** statsCd와 입력 화면 activeTab(tabKey)이 다른 경우 */
|
||||
export const INPUT_TAB_KEY_BY_STATS_CD: Record<string, string> = {
|
||||
ST00080003: "privateSewageDesigStatusInp",
|
||||
};
|
||||
|
||||
export type SeweragePenetrationRateInpTabKey =
|
||||
(typeof SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS)[number];
|
||||
export type SewagePipeInpTabKey = (typeof SEWAGE_PIPE_INP_TAB_KEYS)[number];
|
||||
export type SewagePipeMaintainInpTabKey = (typeof SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS)[number];
|
||||
export type SeweragePenetrationRateInpTabKey = (typeof SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS)[number];
|
||||
export type SewagePipeMaintainInpTabKey =
|
||||
(typeof SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS)[number];
|
||||
export type SewageReservoirPumpInpTabKey =
|
||||
(typeof SEWAGE_RESERVOIR_PUMP_INP_TAB_KEYS)[number];
|
||||
export type PumpingStationInpTabKey = (typeof PUMPING_STATION_INP_TAB_KEYS)[number];
|
||||
export type PublicSewageFacilityInpTabKey =
|
||||
(typeof PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS)[number];
|
||||
export type ExcretaTreatmentFacilityInpTabKey =
|
||||
(typeof EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS)[number];
|
||||
export type PrivateSewageFacilityInpTabKey =
|
||||
(typeof PRIVATE_SEWAGE_FACILITY_INP_TAB_KEYS)[number];
|
||||
export type SewerageFinanceInpTabKey = (typeof SEWERAGE_FINANCE_INP_TAB_KEYS)[number];
|
||||
export type WaterReuseInpTabKey = (typeof WATER_REUSE_INP_TAB_KEYS)[number];
|
||||
export type SewageExcretaSlurgeInpTabKey =
|
||||
(typeof SEWAGE_EXCRETA_SLURGE_INP_TAB_KEYS)[number];
|
||||
export type SewageExcretaRelatedInpTabKey =
|
||||
(typeof SEWAGE_EXCRETA_RELATED_INP_TAB_KEYS)[number];
|
||||
export type SewerageServiceLevelInpTabKey =
|
||||
(typeof SEWERAGE_SERVICE_LEVEL_INP_TAB_KEYS)[number];
|
||||
|
||||
/** 컴포넌트 파일명 기준 묶음 (메뉴·라우트 매칭 시 참고) */
|
||||
export const STATISTICS_INPUT_TAB_KEYS_BY_SCREEN = {
|
||||
SeweragePenetrationRateInp: SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS,
|
||||
SewagePipeInp: SEWAGE_PIPE_INP_TAB_KEYS,
|
||||
SewagePipeMaintainInp: SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS,
|
||||
SeweragePenetrationRateInp: SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS,
|
||||
SewageReservoirPumpInp: SEWAGE_RESERVOIR_PUMP_INP_TAB_KEYS,
|
||||
PumpingStationInp: PUMPING_STATION_INP_TAB_KEYS,
|
||||
PublicSewageFacilityInp: PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS,
|
||||
ExcretaTreatmentFacilityInp: EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS,
|
||||
PrivateSewageFacilityInp: PRIVATE_SEWAGE_FACILITY_INP_TAB_KEYS,
|
||||
SewerageFinanceInp: SEWERAGE_FINANCE_INP_TAB_KEYS,
|
||||
WaterReuseInp: WATER_REUSE_INP_TAB_KEYS,
|
||||
SewageExcretaSlurgeInp: SEWAGE_EXCRETA_SLURGE_INP_TAB_KEYS,
|
||||
SewageExcretaRelatedInp: SEWAGE_EXCRETA_RELATED_INP_TAB_KEYS,
|
||||
SewerageServiceLevelInp: SEWERAGE_SERVICE_LEVEL_INP_TAB_KEYS,
|
||||
} as const;
|
||||
|
||||
@@ -3,6 +3,28 @@ 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> = {
|
||||
@@ -10,8 +32,71 @@ const FIELD_HEADER: Record<string, string> = {
|
||||
sggNm: "시군구",
|
||||
admdstCd: "행정구역코드",
|
||||
admdstNm: "행정구역명",
|
||||
[LOG_DETAIL_DETAILS_FIELD]: "상세",
|
||||
};
|
||||
|
||||
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) {
|
||||
@@ -47,6 +132,16 @@ function buildColumns(rows: StatisticsLogDetailRow[]): ColumnConfig[] {
|
||||
|
||||
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,
|
||||
@@ -60,13 +155,33 @@ function buildColumns(rows: StatisticsLogDetailRow[]): ColumnConfig[] {
|
||||
});
|
||||
}
|
||||
|
||||
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 = buildColumns(rows);
|
||||
const columns = appendHiddenDetailsRawColumn(buildColumns(rows));
|
||||
return {
|
||||
columns,
|
||||
columnLayout: columns.map((c) => c.fieldName),
|
||||
columnLayout: columns
|
||||
.filter((column) => column.visible !== false)
|
||||
.map((column) => column.fieldName),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -33,16 +33,19 @@ export const rawPublicSewageFacilityInfoList = {
|
||||
name: 'ctpvNm',
|
||||
fieldName: 'ctpvNm',
|
||||
header: { text: '시도' },
|
||||
mergeCells: true,
|
||||
sortdisable: true,
|
||||
footer: [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, true) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, true) },
|
||||
],
|
||||
},
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' } },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true },
|
||||
{
|
||||
name: 'displayYear',
|
||||
fieldName: 'displayYear',
|
||||
header: { text: '구분' },
|
||||
sortdisable: true,
|
||||
footer: [
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 0, false) },
|
||||
{ valueCallback: (grid: GridView) => getDivisionFooterLabel(grid, 1, false) },
|
||||
|
||||
@@ -61,6 +61,26 @@ export function buildReviewRowTypeCurrCellStyleCallbackByTab(
|
||||
return map;
|
||||
}
|
||||
|
||||
/** 여러 cellStyleCallback 결과를 styleName으로 병합 */
|
||||
export function composeCellStyleCallbacks(
|
||||
...callbacks: CellStyleCallback[]
|
||||
): CellStyleCallback {
|
||||
return (params) => {
|
||||
const styleNames: string[] = [];
|
||||
for (const cb of callbacks) {
|
||||
const result = cb(params);
|
||||
if (!result) continue;
|
||||
if (typeof result === "string") {
|
||||
styleNames.push(result);
|
||||
} else if (result.styleName) {
|
||||
styleNames.push(result.styleName);
|
||||
}
|
||||
}
|
||||
const styleName = styleNames.join(" ").trim();
|
||||
return styleName ? styleName : undefined;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 객체 배열을 그리드 row 형식으로 변환 (null/undefined → "").
|
||||
* 어떤 엔티티 타입이든 공통 사용 가능.
|
||||
|
||||
@@ -0,0 +1,183 @@
|
||||
import type { CellStyleCallback, ColumnConfig } from "@/components/type/DataGrid.type";
|
||||
import {
|
||||
buildReviewRowTypeCurrCellStyleExcludeMergeCells,
|
||||
composeCellStyleCallbacks,
|
||||
} from "@/components/util/grid/gridFunction";
|
||||
|
||||
const DISPLAY_YEAR_FIELD = "displayYear";
|
||||
|
||||
/** CURR 미입력 + OLD(전년) 데이터 있음 */
|
||||
export const REVIEW_CURR_EMPTY_WARN_STYLE = "rg-cell-review-curr-empty-warn";
|
||||
/** CURR 미입력 + OLD 행 없음 */
|
||||
export const REVIEW_CURR_EMPTY_NEW_STYLE = "rg-cell-review-curr-empty-new";
|
||||
|
||||
function getColumnFieldNames(columns: readonly Pick<ColumnConfig, "fieldName">[]): string[] {
|
||||
return columns.map((col) => col.fieldName).filter(Boolean);
|
||||
}
|
||||
|
||||
function getFieldNamesBeforeDivision(columns: readonly Pick<ColumnConfig, "fieldName">[]): string[] {
|
||||
const fields = getColumnFieldNames(columns);
|
||||
const divisionIndex = fields.indexOf(DISPLAY_YEAR_FIELD);
|
||||
if (divisionIndex <= 0) return [];
|
||||
return fields.slice(0, divisionIndex);
|
||||
}
|
||||
|
||||
function getFieldNamesAfterDivision(columns: readonly Pick<ColumnConfig, "fieldName">[]): string[] {
|
||||
const fields = getColumnFieldNames(columns);
|
||||
const divisionIndex = fields.indexOf(DISPLAY_YEAR_FIELD);
|
||||
if (divisionIndex < 0 || divisionIndex >= fields.length - 1) return [];
|
||||
return fields.slice(divisionIndex + 1);
|
||||
}
|
||||
|
||||
function getDivisionHighlightFieldNames(
|
||||
columns: readonly Pick<ColumnConfig, "fieldName" | "name">[],
|
||||
): Set<string> {
|
||||
const divisionColumn = columns.find((col) => col.fieldName === DISPLAY_YEAR_FIELD);
|
||||
return new Set(
|
||||
[DISPLAY_YEAR_FIELD, divisionColumn?.name, divisionColumn?.fieldName].filter(
|
||||
Boolean,
|
||||
) as string[],
|
||||
);
|
||||
}
|
||||
|
||||
function buildRowGroupKey(
|
||||
row: Record<string, string | number>,
|
||||
groupKeyFields: readonly string[],
|
||||
): string {
|
||||
return groupKeyFields.map((field) => String(row[field] ?? "")).join("\0");
|
||||
}
|
||||
|
||||
export function isEmptyOrZeroGridValue(value: string | number | undefined): boolean {
|
||||
if (value === null || value === undefined || value === "") return true;
|
||||
if (typeof value === "number") return value === 0;
|
||||
const trimmed = String(value).trim();
|
||||
if (trimmed === "") return true;
|
||||
const numeric = Number(trimmed.replace(/,/g, ""));
|
||||
return !Number.isNaN(numeric) && numeric === 0;
|
||||
}
|
||||
|
||||
export function isRowEmptyOrZeroAfterDivision(
|
||||
row: Record<string, string | number>,
|
||||
dataFieldNames: readonly string[],
|
||||
): boolean {
|
||||
if (dataFieldNames.length === 0) return false;
|
||||
return dataFieldNames.every((field) => isEmptyOrZeroGridValue(row[field]));
|
||||
}
|
||||
|
||||
type EmptyCurrHighlight =
|
||||
| typeof REVIEW_CURR_EMPTY_WARN_STYLE
|
||||
| typeof REVIEW_CURR_EMPTY_NEW_STYLE;
|
||||
|
||||
function buildOldRowByGroupKey(
|
||||
rows: readonly Record<string, string | number>[],
|
||||
groupKeyFields: readonly string[],
|
||||
): Map<string, Record<string, string | number>> {
|
||||
const map = new Map<string, Record<string, string | number>>();
|
||||
for (const row of rows) {
|
||||
if (String(row.rowType ?? "") !== "OLD") continue;
|
||||
map.set(buildRowGroupKey(row, groupKeyFields), row);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
function buildOldRowIndexByGroupKey(
|
||||
rows: readonly Record<string, string | number>[],
|
||||
groupKeyFields: readonly string[],
|
||||
): Map<string, number> {
|
||||
const map = new Map<string, number>();
|
||||
rows.forEach((row, index) => {
|
||||
if (String(row.rowType ?? "") !== "OLD") return;
|
||||
map.set(buildRowGroupKey(row, groupKeyFields), index);
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
function resolveEmptyCurrHighlight(
|
||||
currRow: Record<string, string | number>,
|
||||
oldRowByGroupKey: ReadonlyMap<string, Record<string, string | number>>,
|
||||
groupKeyFields: readonly string[],
|
||||
dataFieldNames: readonly string[],
|
||||
): EmptyCurrHighlight | undefined {
|
||||
if (String(currRow.rowType ?? "") !== "CURR") return undefined;
|
||||
if (!isRowEmptyOrZeroAfterDivision(currRow, dataFieldNames)) return undefined;
|
||||
|
||||
const groupKey = buildRowGroupKey(currRow, groupKeyFields);
|
||||
const oldRow = oldRowByGroupKey.get(groupKey);
|
||||
|
||||
if (!oldRow) {
|
||||
return REVIEW_CURR_EMPTY_NEW_STYLE;
|
||||
}
|
||||
if (isRowEmptyOrZeroAfterDivision(oldRow, dataFieldNames)) {
|
||||
return undefined;
|
||||
}
|
||||
return REVIEW_CURR_EMPTY_WARN_STYLE;
|
||||
}
|
||||
|
||||
/**
|
||||
* 검토 그리드 — CURR 행의 구분 이후 값이 모두 비어있거나 0일 때
|
||||
* 구분(displayYear): OLD 있음 → OLD·CURR 노란색, OLD 없음 → CURR 하늘색, 둘 다 없음 → 미적용
|
||||
*/
|
||||
export function buildReviewEmptyCurrCellStyleCallback(
|
||||
columns: readonly Pick<ColumnConfig, "fieldName" | "name">[],
|
||||
rows: readonly Record<string, string | number>[],
|
||||
): CellStyleCallback {
|
||||
const groupKeyFields = getFieldNamesBeforeDivision(columns);
|
||||
const dataFieldNames = getFieldNamesAfterDivision(columns);
|
||||
const highlightFieldNames = getDivisionHighlightFieldNames(columns);
|
||||
const oldRowByGroupKey = buildOldRowByGroupKey(rows, groupKeyFields);
|
||||
const oldRowIndexByGroupKey = buildOldRowIndexByGroupKey(rows, groupKeyFields);
|
||||
|
||||
const highlightByRowIndex = new Map<number, EmptyCurrHighlight>();
|
||||
rows.forEach((row, index) => {
|
||||
if (String(row.rowType ?? "") !== "CURR") return;
|
||||
|
||||
const highlight = resolveEmptyCurrHighlight(
|
||||
row,
|
||||
oldRowByGroupKey,
|
||||
groupKeyFields,
|
||||
dataFieldNames,
|
||||
);
|
||||
if (!highlight) return;
|
||||
|
||||
highlightByRowIndex.set(index, highlight);
|
||||
|
||||
if (highlight === REVIEW_CURR_EMPTY_WARN_STYLE) {
|
||||
const oldIndex = oldRowIndexByGroupKey.get(buildRowGroupKey(row, groupKeyFields));
|
||||
if (oldIndex !== undefined) {
|
||||
highlightByRowIndex.set(oldIndex, highlight);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return ({ rowIndex, fieldName }) => {
|
||||
if (highlightFieldNames.size === 0 || !highlightFieldNames.has(fieldName)) {
|
||||
return undefined;
|
||||
}
|
||||
return highlightByRowIndex.get(rowIndex);
|
||||
};
|
||||
}
|
||||
|
||||
export function buildReviewCellStyleCallback(
|
||||
columns: readonly Pick<ColumnConfig, "fieldName" | "name" | "mergeCells">[],
|
||||
rows: readonly Record<string, string | number>[],
|
||||
): CellStyleCallback {
|
||||
const base = buildReviewRowTypeCurrCellStyleExcludeMergeCells(columns);
|
||||
const emptyCurr = buildReviewEmptyCurrCellStyleCallback(columns, rows);
|
||||
return composeCellStyleCallbacks(base, emptyCurr);
|
||||
}
|
||||
|
||||
export function buildReviewCellStyleCallbackByTab(
|
||||
tabs: readonly {
|
||||
tabKey: string;
|
||||
columns?: readonly Pick<ColumnConfig, "fieldName" | "name" | "mergeCells">[];
|
||||
}[],
|
||||
rowDataMap: ReadonlyMap<string, readonly Record<string, string | number>[]>,
|
||||
): Map<string, CellStyleCallback> {
|
||||
const map = new Map<string, CellStyleCallback>();
|
||||
for (const tab of tabs) {
|
||||
const columns = tab.columns ?? [];
|
||||
const rows = rowDataMap.get(tab.tabKey) ?? [];
|
||||
map.set(tab.tabKey, buildReviewCellStyleCallback(columns, rows));
|
||||
}
|
||||
return map;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import type { StatisticsInputCommonInfoBase } from "@/entities/statistics/model/statistics-interface";
|
||||
|
||||
export function isVerificationFailedFromCommonInfo(
|
||||
commonInfo: Pick<StatisticsInputCommonInfoBase, "isVerificationFailed"> | null | undefined,
|
||||
): boolean {
|
||||
return commonInfo?.isVerificationFailed === true;
|
||||
}
|
||||
|
||||
export function patchVerificationFailedByTab(
|
||||
prev: Map<string, boolean>,
|
||||
tabKey: string,
|
||||
commonInfo: Pick<StatisticsInputCommonInfoBase, "isVerificationFailed"> | null | undefined,
|
||||
): Map<string, boolean> {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, isVerificationFailedFromCommonInfo(commonInfo));
|
||||
return next;
|
||||
}
|
||||
@@ -75,10 +75,16 @@ export class GeneralverificationApiService implements GeneralverificationService
|
||||
}
|
||||
|
||||
public async DeleteVrfcMaster(
|
||||
param: number,
|
||||
vrfcSn: number,
|
||||
userUnqId: string,
|
||||
): Promise<DeleteVrfcMasterResponse> {
|
||||
const response = await stClient.delete<DeleteVrfcMasterResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${param}`,
|
||||
`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${vrfcSn}`,
|
||||
{
|
||||
headers: {
|
||||
userId: userUnqId,
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
@@ -141,9 +147,16 @@ export class GeneralverificationApiService implements GeneralverificationService
|
||||
|
||||
public async CopyVrfcData(
|
||||
param: CopyVerifyDataRequest,
|
||||
userUnqId: string,
|
||||
): Promise<RegistVrfcMasterResponse> {
|
||||
const response = await stClient.post<RegistVrfcMasterResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.INPUTVERIFY}/${param.vrfcSn}/copy?targetStatsCd=${param.targetStatsCd}&rgtrId=${param.rgtrId}`,
|
||||
undefined,
|
||||
{
|
||||
headers: {
|
||||
userId: userUnqId,
|
||||
},
|
||||
},
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -258,7 +258,7 @@ export class StatisticsManagementApiService {
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
sewerBzmnCd?: string;
|
||||
pumpstNm?: string;
|
||||
fcltNm?: string;
|
||||
}
|
||||
): Promise<MediationPumpStationApiResponse> {
|
||||
const response = await stClient.get<MediationPumpStationApiResponse>(
|
||||
|
||||
@@ -93,7 +93,7 @@ export class StatisticsApiService implements StatisticsService {
|
||||
): Promise<StatsInputPeriodListApiResponse> {
|
||||
const response = await stClient.get<StatsInputPeriodListApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info/list`,
|
||||
{ params },
|
||||
withAuthHeader({ params }),
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
@@ -101,7 +101,7 @@ export class StatisticsApiService implements StatisticsService {
|
||||
public async getStatsInputPeriodDetail(params: StatsInputPeriodDetailRequest): Promise<StatsInputPeriodDetailResponse> {
|
||||
const response = await stClient.get<StatsInputPeriodDetailResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info/detail`,
|
||||
{ params },
|
||||
withAuthHeader({ params }),
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
@@ -113,6 +113,7 @@ export class StatisticsApiService implements StatisticsService {
|
||||
const response = await stClient.post<StatsInputPeriodSaveApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info`,
|
||||
param,
|
||||
withAuthHeader(),
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
@@ -123,7 +124,7 @@ export class StatisticsApiService implements StatisticsService {
|
||||
): Promise<StatsInputPeriodDeleteApiResponse> {
|
||||
const response = await stClient.delete<StatsInputPeriodDeleteApiResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.DASHBOARD}/schedule-info`,
|
||||
{ data: param },
|
||||
withAuthHeader({ data: param }),
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
@@ -275,7 +275,10 @@ export interface GeneralverificationService {
|
||||
RegistVrfcMaster(
|
||||
param: RegistVrfcMasterRequest,
|
||||
): Promise<RegistVrfcMasterResponse>;
|
||||
DeleteVrfcMaster(param: number): Promise<DeleteVrfcMasterResponse>;
|
||||
DeleteVrfcMaster(
|
||||
vrfcSn: number,
|
||||
userUnqId: string,
|
||||
): Promise<DeleteVrfcMasterResponse>;
|
||||
getVrfcOperators(): Promise<VrfcOperatorsResponse>;
|
||||
getVrfcDetail(param: number): Promise<GeneralverificationDetailResponse>;
|
||||
getVerifyColumnList(
|
||||
@@ -292,6 +295,7 @@ export interface GeneralverificationService {
|
||||
): Promise<RegistVrfcMasterResponse>;
|
||||
CopyVrfcData(
|
||||
param: CopyVerifyDataRequest,
|
||||
userUnqId: string,
|
||||
): Promise<RegistVrfcMasterResponse>;
|
||||
getVerifyResultList(
|
||||
param: VerifyResultListRequest,
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
/** 분뇨처리시설(ST00070001) — `ExcretaTreatFactInpColumn` */
|
||||
export const EXCRETA_TREATMENT_FACILITY_STATS_CD = "ST00070001" as const;
|
||||
@@ -18,7 +18,7 @@ export type ExcretaTreatmentFacilityVerificationResult = Record<
|
||||
>;
|
||||
|
||||
/** 분뇨처리시설 조회 `data.commonInfo` */
|
||||
export interface ExcretaTreatmentFacilityCommonInfo {
|
||||
export interface ExcretaTreatmentFacilityCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+4
-4
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
/** 처리용량별 오수처리시설 현황(ST00080001) — `SewageTreatFactStatusInpColumn.tabKey` */
|
||||
export const SEWAGE_TREAT_FACT_STATUS_STATS_CD = "ST00080001" as const;
|
||||
@@ -18,7 +18,7 @@ export type PrivateSewageFacilityVerificationResult = Record<
|
||||
>;
|
||||
|
||||
/** ST00080001 조회 `data.commonInfo` */
|
||||
export interface SewageTreatFactStatusCommonInfo {
|
||||
export interface SewageTreatFactStatusCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
@@ -78,7 +78,7 @@ export interface SewageTreatFactStatusRequestParams {
|
||||
export const SEPTIC_TANK_STATUS_STATS_CD = "ST00080002" as const;
|
||||
|
||||
/** ST00080002 조회 `data.commonInfo` */
|
||||
export interface SepticTankStatusCommonInfo {
|
||||
export interface SepticTankStatusCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
@@ -156,7 +156,7 @@ export interface SepticTankStatusRequestParams {
|
||||
export const PRIVATE_SEWAGE_DESIG_STATUS_STATS_CD = "ST00080003" as const;
|
||||
|
||||
/** ST00080003 조회 `data.commonInfo` */
|
||||
export interface PrivateSewageDesigStatusCommonInfo {
|
||||
export interface PrivateSewageDesigStatusCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
/** 펌프장(ST00050001) — `PumpingStationInpColumn` tabKey */
|
||||
export const PUMPING_STATION_STATS_CD = "ST00050001" as const;
|
||||
@@ -18,7 +18,7 @@ export type PumpingStationVerificationResult = Record<
|
||||
>;
|
||||
|
||||
/** 펌프장 조회 `data.commonInfo` */
|
||||
export interface PumpingStationCommonInfo {
|
||||
export interface PumpingStationCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
/** 하수·분뇨관련산업 — 공공기관 하수분야(ST00120001) `PublicInstitutionSewerIndustryInpColumn.tabKey` */
|
||||
export const SEWAGE_EXCRETA_PUBLIC_INSTITUTION_SEWER_STATS_CD = "ST00120001" as const;
|
||||
@@ -18,7 +18,7 @@ export type SewageExcretaRelatedVerificationResult = Record<
|
||||
>;
|
||||
|
||||
/** 조회 `data.commonInfo` */
|
||||
export interface SewageExcretaRelatedCommonInfo {
|
||||
export interface SewageExcretaRelatedCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
/** 슬러지 처리 — 찌꺼기(슬러지)처리시설현황(ST00110001) `SlurgeProcessingFacStatusInpColumn.tabKey` */
|
||||
export const SEWAGE_EXCRETA_SLURGE_PROCESSING_FAC_STATUS_STATS_CD = "ST00110001" as const;
|
||||
@@ -27,7 +27,7 @@ export type SewageExcretaSlurgeVerificationResult = Record<
|
||||
>;
|
||||
|
||||
/** 조회 `data.commonInfo` */
|
||||
export interface SewageExcretaSlurgeCommonInfo {
|
||||
export interface SewageExcretaSlurgeCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
export interface SewagePipeMaintainRepairVerificationResultItem {
|
||||
vrfcCd: string;
|
||||
@@ -13,7 +13,7 @@ export type SewagePipeMaintainRepairVerificationResult = Record<
|
||||
SewagePipeMaintainRepairVerificationResultItem[]
|
||||
>;
|
||||
|
||||
export interface SewagePipeMaintainRepairCommonInfo {
|
||||
export interface SewagePipeMaintainRepairCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
/** 하수저류시설 Tab1 — `SewageReservoirInpColumn.tabKey` */
|
||||
export const SEWAGE_RESERVOIR_STATS_CD = "ST00040001" as const;
|
||||
@@ -18,7 +18,7 @@ export type SewageReservoirVerificationResult = Record<
|
||||
>;
|
||||
|
||||
/** 하수저류시설 조회 `data.commonInfo` */
|
||||
export interface SewageReservoirCommonInfo {
|
||||
export interface SewageReservoirCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
@@ -86,7 +86,7 @@ export type SewageReservoirApiResponse = ApiResponse<SewageReservoirResponseData
|
||||
};
|
||||
|
||||
/** 빗물펌프장 Tab2 `data.commonInfo` */
|
||||
export interface RainfallPumpStationCommonInfo {
|
||||
export interface RainfallPumpStationCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+5
-5
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
/** 하수도재정 결산(ST00090001) — `SewerageFinanceSettleInpColumn.tabKey` */
|
||||
export const SEWERAGE_FINANCE_SETTLE_STATS_CD = "ST00090001" as const;
|
||||
@@ -18,7 +18,7 @@ export type SewerageFinanceVerificationResult = Record<
|
||||
>;
|
||||
|
||||
/** ST00090001 조회 `data.commonInfo` */
|
||||
export interface SewerageFinanceSettleCommonInfo {
|
||||
export interface SewerageFinanceSettleCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
@@ -202,7 +202,7 @@ export interface SewerageFinanceSubmitResponse {
|
||||
export const SEWERAGE_CHARGE_STATS_CD = "ST00090002" as const;
|
||||
|
||||
/** ST00090002 조회 `data.commonInfo` */
|
||||
export interface SewerageChargeCommonInfo {
|
||||
export interface SewerageChargeCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
@@ -292,7 +292,7 @@ export type SewerageChargeSubmitItem = SewerageChargeSaveItem;
|
||||
export const SEWERAGE_USAGE_FEE_STATS_CD = "ST00090003" as const;
|
||||
|
||||
/** ST00090003 조회 `data.commonInfo` */
|
||||
export interface SewerageUsageFeeCommonInfo {
|
||||
export interface SewerageUsageFeeCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
@@ -416,7 +416,7 @@ export type SewerageUsageFeeSubmitItem = SewerageUsageFeeSaveItem;
|
||||
export const CAUSER_BURDEN_STATS_CD = "ST00090004" as const;
|
||||
|
||||
/** ST00090004 조회 `data.commonInfo` */
|
||||
export interface CauserBurdenCommonInfo {
|
||||
export interface CauserBurdenCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
/** 하수도 서비스 수준 — 민원(ST00130001) `ComplaintInpColumn.tabKey` */
|
||||
export const SEWERAGE_SERVICE_LEVEL_COMPLAINT_STATS_CD = "ST00130001" as const;
|
||||
@@ -18,7 +18,7 @@ export type SewerageServiceLevelVerificationResult = Record<
|
||||
>;
|
||||
|
||||
/** ST00130001 조회 `data.commonInfo` */
|
||||
export interface SewerageServiceLevelComplaintCommonInfo {
|
||||
export interface SewerageServiceLevelComplaintCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
@@ -72,7 +72,7 @@ export interface SewerageServiceLevelComplaintRequestParams {
|
||||
export const SEWERAGE_SERVICE_LEVEL_RESIDENT_FRIENDLY_FACT_STATS_CD = "ST00130002" as const;
|
||||
|
||||
/** ST00130002 조회 `data.commonInfo` */
|
||||
export interface SewerageServiceLevelResidentFriendlyFactCommonInfo {
|
||||
export interface SewerageServiceLevelResidentFriendlyFactCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
/** 물 재이용 — 공공하수처리수 재이용(ST00100001) `PublicSewageTreatWaterReuseInpColumn.tabKey` */
|
||||
export const WATER_REUSE_PUBLIC_SEWAGE_TREAT_STATS_CD = "ST00100001" as const;
|
||||
@@ -30,7 +30,7 @@ export type WaterReuseVerificationResult = Record<
|
||||
>;
|
||||
|
||||
/** 조회 `data.commonInfo` */
|
||||
export interface WaterReuseCommonInfo {
|
||||
export interface WaterReuseCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import type { SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
export interface PublicSewageFacilityRequestParams {
|
||||
ctpvNm?: string;
|
||||
@@ -22,7 +22,7 @@ export type PublicSewageFacilityVerificationResult = Record<
|
||||
PublicSewageFacilityVerifyResultItem[]
|
||||
>;
|
||||
|
||||
export interface PublicSewageFacilityCommonInfo {
|
||||
export interface PublicSewageFacilityCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import type { SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
export interface SeweragePipeRequestParams {
|
||||
ctpvNm?: string;
|
||||
@@ -20,7 +20,7 @@ export interface VerifyResultItem {
|
||||
/** 필드명 -> 검증 결과 배열 */
|
||||
export type SeweragePipeInputVerificationResult = Record<string, VerifyResultItem[]>;
|
||||
|
||||
export interface SeweragePipeInputCommonInfo {
|
||||
export interface SeweragePipeInputCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sewerBzmnCd: string;
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import type { ApiResponse, SewerageStatusInfo } from "../statistics-interface";
|
||||
import type { ApiResponse, SewerageStatusInfo, StatisticsInputCommonInfoBase } from "../statistics-interface";
|
||||
|
||||
// 하수도 보급률 데이터 항목
|
||||
export interface SewerageSupplyRateCommonInfo {
|
||||
export interface SewerageSupplyRateCommonInfo extends StatisticsInputCommonInfoBase {
|
||||
sewerBzmnArea: number;
|
||||
sewerBzmnCd: string;
|
||||
sewerBzmnFrgnrWholPpltnCnt: number;
|
||||
|
||||
+2
-2
@@ -44,7 +44,7 @@ export interface ExcretaTreatmentFacilityReviewItem {
|
||||
}
|
||||
|
||||
export interface ExcretaTreatmentFacilityReviewResponseData {
|
||||
excretaTreatmentFacilityList: ExcretaTreatmentFacilityReviewItem[];
|
||||
excretaTreatmentList: ExcretaTreatmentFacilityReviewItem[];
|
||||
statsStatusInfo?: StatsStatusInfo;
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ export function extractExcretaTreatmentFacilityReviewList(
|
||||
): ExcretaTreatmentFacilityReviewItem[] {
|
||||
if (!data) return [];
|
||||
if (Array.isArray(data)) return data;
|
||||
return data.excretaTreatmentFacilityList ?? [];
|
||||
return data.excretaTreatmentList ?? [];
|
||||
}
|
||||
|
||||
export function extractExcretaTreatmentFacilityReviewStatsStatusInfo(
|
||||
|
||||
@@ -49,6 +49,7 @@ export type StatisticsColumnGuideListResponse = ApiResponse<StatisticsColumnGuid
|
||||
|
||||
/** 통계 입력 관리 등 — 연도·구분·분류(입력/검증)별 입력 기간 한 행 */
|
||||
export interface StatsInputPeriodItem {
|
||||
extSchdlId?: number;
|
||||
statsYr: string;
|
||||
seCd: number;
|
||||
seType: string;
|
||||
@@ -69,6 +70,7 @@ export interface StatsInputPeriodDetailRequest {
|
||||
statsYr: string;
|
||||
seType: string;
|
||||
seCd: number;
|
||||
extSchdlId?: number;
|
||||
}
|
||||
|
||||
export interface StatsInputPeriodDetailResponse {
|
||||
@@ -79,6 +81,7 @@ export interface StatsInputPeriodDetailResponse {
|
||||
|
||||
/** 통계 입력 기간 등록·수정 등 요청 본문 */
|
||||
export interface StatsInputPeriodSaveRequest {
|
||||
extSchdlId?: number;
|
||||
statsYr: string;
|
||||
seType: string;
|
||||
seCd?: number;
|
||||
@@ -94,6 +97,7 @@ export type StatsInputPeriodSaveApiResponse = ApiResponse<unknown>;
|
||||
|
||||
/** 통계 입력 기간 삭제 요청 */
|
||||
export interface StatsInputPeriodDeleteRequest {
|
||||
extSchdlId?: number;
|
||||
statsYr: string;
|
||||
seType: string;
|
||||
seCd: number;
|
||||
@@ -105,6 +109,11 @@ export interface StatsInputPeriodDeleteRequest {
|
||||
/** 통계 입력 기간 삭제 응답 */
|
||||
export type StatsInputPeriodDeleteApiResponse = ApiResponse<unknown>;
|
||||
|
||||
/** 입력 조회 `data.commonInfo` 공통 필드 */
|
||||
export interface StatisticsInputCommonInfoBase {
|
||||
isVerificationFailed: boolean;
|
||||
}
|
||||
|
||||
/** 하수 입력/보급률 등 조회 `data.statusInfo` 공통 (LAE 제출·담당자 정보) */
|
||||
export interface SewerageStatusInfo {
|
||||
laePicNm: string | null;
|
||||
|
||||
+1
-1
@@ -84,6 +84,6 @@ export interface MediationPumpStationService {
|
||||
sewerBzmnCd?: string;
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
pumpstNm?: string;
|
||||
fcltNm?: string;
|
||||
}): Promise<MediationPumpStationApiResponse>;
|
||||
}
|
||||
|
||||
@@ -3,9 +3,20 @@ 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 {
|
||||
EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS,
|
||||
INPUT_TAB_KEY_BY_STATS_CD,
|
||||
PRIVATE_SEWAGE_FACILITY_INP_TAB_KEYS,
|
||||
PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS,
|
||||
PUMPING_STATION_INP_TAB_KEYS,
|
||||
SEWAGE_EXCRETA_RELATED_INP_TAB_KEYS,
|
||||
SEWAGE_EXCRETA_SLURGE_INP_TAB_KEYS,
|
||||
SEWAGE_PIPE_INP_TAB_KEYS,
|
||||
SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS,
|
||||
SEWAGE_RESERVOIR_PUMP_INP_TAB_KEYS,
|
||||
SEWERAGE_FINANCE_INP_TAB_KEYS,
|
||||
SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS,
|
||||
SEWERAGE_SERVICE_LEVEL_INP_TAB_KEYS,
|
||||
WATER_REUSE_INP_TAB_KEYS,
|
||||
} from "@/components/util/MenuMatchList";
|
||||
|
||||
/** 검증 결과에서 입력 화면 자동 조회에 사용하는 조건 */
|
||||
@@ -35,15 +46,21 @@ export function verificationSearchToFormValues(
|
||||
|
||||
/** `statistics-page` 동적 import 경로의 파일명(확장자 제외)과 일치해야 함 */
|
||||
export const STATISTICS_INPUT_URL_PATH = {
|
||||
SEWERAGE_PENETRATION_RATE: "SeweragePenetrationRateInp",
|
||||
SEWAGE_PIPE: "SewagePipeInp",
|
||||
SEWAGE_PIPE_MAINTAIN: "SewagePipeMaintainInp",
|
||||
SEWERAGE_PENETRATION_RATE: "SeweragePenetrationRateInp",
|
||||
SEWAGE_RESERVOIR_PUMP: "SewageReservoirPumpInp",
|
||||
PUMPING_STATION: "PumpingStationInp",
|
||||
PUBLIC_SEWAGE_FACILITY: "PublicSewageFacilityInp",
|
||||
EXCRETA_TREATMENT_FACILITY: "ExcretaTreatmentFacilityInp",
|
||||
PRIVATE_SEWAGE_FACILITY: "PrivateSewageFacilityInp",
|
||||
SEWERAGE_FINANCE: "SewerageFinanceInp",
|
||||
WATER_REUSE: "WaterReuseInp",
|
||||
SEWAGE_EXCRETA_SLURGE: "SewageExcretaSlurgeInp",
|
||||
SEWAGE_EXCRETA_RELATED: "SewageExcretaRelatedInp",
|
||||
SEWERAGE_SERVICE_LEVEL: "SewerageServiceLevelInp",
|
||||
} as const;
|
||||
|
||||
/** `PublicSewageFacilityInp.tsx` — 공공하수처리시설 탭 */
|
||||
const PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS = ["ST00060001"] as const;
|
||||
|
||||
function normalizeSewagePipeTabKey(tabKey: string): string {
|
||||
if (
|
||||
tabKey === "adminAreaConnectionInp" ||
|
||||
@@ -54,20 +71,66 @@ function normalizeSewagePipeTabKey(tabKey: string): string {
|
||||
return tabKey;
|
||||
}
|
||||
|
||||
function resolveInputTabKey(urlPath: string, statsCd: string): string {
|
||||
if (urlPath === STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE) {
|
||||
return normalizeSewagePipeTabKey(statsCd);
|
||||
}
|
||||
return INPUT_TAB_KEY_BY_STATS_CD[statsCd] ?? statsCd;
|
||||
}
|
||||
|
||||
const NAV_ROWS: { urlPath: string; tabKeys: readonly string[] }[] = [
|
||||
{ urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE, tabKeys: SEWAGE_PIPE_INP_TAB_KEYS },
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN,
|
||||
tabKeys: SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWERAGE_PENETRATION_RATE,
|
||||
tabKeys: SEWERAGE_PENETRATION_RATE_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE,
|
||||
tabKeys: SEWAGE_PIPE_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE_MAINTAIN,
|
||||
tabKeys: SEWAGE_PIPE_MAINTAIN_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_RESERVOIR_PUMP,
|
||||
tabKeys: SEWAGE_RESERVOIR_PUMP_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.PUMPING_STATION,
|
||||
tabKeys: PUMPING_STATION_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.PUBLIC_SEWAGE_FACILITY,
|
||||
tabKeys: PUBLIC_SEWAGE_FACILITY_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.EXCRETA_TREATMENT_FACILITY,
|
||||
tabKeys: EXCRETA_TREATMENT_FACILITY_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.PRIVATE_SEWAGE_FACILITY,
|
||||
tabKeys: PRIVATE_SEWAGE_FACILITY_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWERAGE_FINANCE,
|
||||
tabKeys: SEWERAGE_FINANCE_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.WATER_REUSE,
|
||||
tabKeys: WATER_REUSE_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_EXCRETA_SLURGE,
|
||||
tabKeys: SEWAGE_EXCRETA_SLURGE_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWAGE_EXCRETA_RELATED,
|
||||
tabKeys: SEWAGE_EXCRETA_RELATED_INP_TAB_KEYS,
|
||||
},
|
||||
{
|
||||
urlPath: STATISTICS_INPUT_URL_PATH.SEWERAGE_SERVICE_LEVEL,
|
||||
tabKeys: SEWERAGE_SERVICE_LEVEL_INP_TAB_KEYS,
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
@@ -83,11 +146,10 @@ export function resolveInputPageNavigationFromStatsCd(statsCd: string): {
|
||||
|
||||
for (const row of NAV_ROWS) {
|
||||
if (row.tabKeys.includes(trimmed)) {
|
||||
const tabKey =
|
||||
row.urlPath === STATISTICS_INPUT_URL_PATH.SEWAGE_PIPE
|
||||
? normalizeSewagePipeTabKey(trimmed)
|
||||
: trimmed;
|
||||
return { urlPath: row.urlPath, tabKey };
|
||||
return {
|
||||
urlPath: row.urlPath,
|
||||
tabKey: resolveInputTabKey(row.urlPath, trimmed),
|
||||
};
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import {
|
||||
verificationSearchToFormValues,
|
||||
type StatisticsInputPendingSearch,
|
||||
} from "@/shared/lib/stats-cd-input-navigation";
|
||||
import { useStatisticsInputFocusStore } from "@/store/statistics-input-focus-store";
|
||||
import { useTabStore } from "@/store/tab-store";
|
||||
|
||||
type PendingNavigation = {
|
||||
tabKey: string;
|
||||
search: StatisticsInputPendingSearch | null;
|
||||
};
|
||||
|
||||
type UseStatisticsInputPendingNavigationOptions = {
|
||||
urlPath: string;
|
||||
onPending?: (pending: PendingNavigation) => void;
|
||||
onAutoSearch?: (
|
||||
values: SearchFormValues,
|
||||
tabKey: string,
|
||||
) => void | Promise<void>;
|
||||
};
|
||||
|
||||
/** 이상치 선별 보고서 등에서 넘어온 입력 화면·탭·검색조건을 적용합니다. */
|
||||
export function useStatisticsInputPendingNavigation({
|
||||
urlPath,
|
||||
onPending,
|
||||
onAutoSearch,
|
||||
}: UseStatisticsInputPendingNavigationOptions) {
|
||||
const [verificationSearchDefaults, setVerificationSearchDefaults] =
|
||||
useState<SearchFormValues | null>(null);
|
||||
const [searchFormRemountKey, setSearchFormRemountKey] = useState(0);
|
||||
const autoSearchRef = useRef<{
|
||||
values: SearchFormValues;
|
||||
tabKey: string;
|
||||
} | null>(null);
|
||||
const handlersRef = useRef({ onPending, onAutoSearch });
|
||||
handlersRef.current = { onPending, onAutoSearch };
|
||||
|
||||
const activeTabId = useTabStore((s) => s.activeTabId);
|
||||
const inputFocusNonce = useStatisticsInputFocusStore((s) => s.nonce);
|
||||
|
||||
useLayoutEffect(() => {
|
||||
if (activeTabId !== urlPath) return;
|
||||
const pending = useStatisticsInputFocusStore
|
||||
.getState()
|
||||
.takePendingForUrl(urlPath);
|
||||
if (!pending) return;
|
||||
|
||||
handlersRef.current.onPending?.(pending);
|
||||
|
||||
if (pending.search) {
|
||||
const values = verificationSearchToFormValues(pending.search);
|
||||
setVerificationSearchDefaults(values);
|
||||
autoSearchRef.current = { values, tabKey: pending.tabKey };
|
||||
setSearchFormRemountKey((k) => k + 1);
|
||||
}
|
||||
}, [activeTabId, inputFocusNonce, urlPath]);
|
||||
|
||||
useEffect(() => {
|
||||
const job = autoSearchRef.current;
|
||||
if (!job) return;
|
||||
autoSearchRef.current = null;
|
||||
void handlersRef.current.onAutoSearch?.(job.values, job.tabKey);
|
||||
}, [searchFormRemountKey]);
|
||||
|
||||
return { verificationSearchDefaults, searchFormRemountKey };
|
||||
}
|
||||
Reference in New Issue
Block a user