[st] - 일정등록, 입력단 지난검증결과 알림 뱃지, 로그 상세조회, 검토단 cell style 적용

This commit is contained in:
2026-07-02 17:46:13 +09:00
parent 25417dcda1
commit cea2d39df8
66 changed files with 1615 additions and 206 deletions
@@ -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 };
}