Merge remote-tracking branch 'origin/iipbs/st/dev'

This commit is contained in:
2026-09-07 17:26:39 +09:00
23 changed files with 385 additions and 1000 deletions
+3 -3
View File
@@ -955,7 +955,7 @@ const DataGrid = ({
try {
gv.commit?.();
} catch {
/* ignore */
console.error('commit error');
}
const ds = gv.getDataSource();
if (!ds) return rowDataRef.current.map((r) => ({ ...r }));
@@ -1954,7 +1954,7 @@ useEffect(() => {
}
| undefined;
if (!gv?.getDataSource) return [];
try { gv.commit?.(); } catch { /* ignore */ }
try { gv.commit?.(); } catch { console.error('commit error'); }
const ds = gv.getDataSource();
if (!ds) return [];
const cnt = ds.getRowCount();
@@ -2022,7 +2022,7 @@ useEffect(() => {
column.footer.styleName = styleName;
}
} catch {
/* ignore */
console.error('총계 스타일 재적용/초기화 error');
}
});
gridView.refresh?.();
+4 -3
View File
@@ -236,12 +236,13 @@ const SearchForm = (props: SearchFormProps = {}) => {
const sggOptions = sggList?.data ?? [];
const isDoSelected = isDoSido(selectedSido);
const firstConcreteSggOption = firstConcreteSgg(sggOptions);
// 도: 빈 값일 때만 첫 시군구로 보이게. ALL은 사용자가 고른 전체 유지
const gugunSelectValue =
isDoSelected && isAllGugunValue(values.gugun)
isDoSelected && !values.gugun
? (firstConcreteSggOption?.gugun ?? undefined)
: values.gugun || undefined;
// 도: 전체(ALL/"")가 아니라 그 다음 시군구. 그 외: 전체
// 도: 초기/시도 변경(빈 값)만 첫 시군구. 전체(ALL)는 유지. 그 외: 전체
useEffect(() => {
if (!mergedProps.gugun) return;
if (props.defaultRegionAll) return;
@@ -263,7 +264,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
if (!firstConcreteSggOption) return;
setValues((prev) => {
if (!isAllGugunValue(prev.gugun)) return prev;
if (prev.gugun) return prev;
return {
...prev,
gugun: firstConcreteSggOption.gugun,
@@ -104,7 +104,7 @@ function safeGridViewRefresh(gv: { refresh?: () => void } | null | undefined) {
try {
gv.refresh();
} catch {
/* noop */
console.error('refresh error');
}
}
@@ -68,7 +68,7 @@ function setRowCheckByItemIndex(
try {
gv?.checkItem?.(itemIndex, checked, false);
} catch {
/* RealGrid 체크 API 미지원 등 */
console.error('RealGrid 체크 API error');
}
}
@@ -91,7 +91,7 @@ function uncheckGridRow(gridRef: MutableRefObject<RealGridReact | null>, itemInd
try {
(gridRef.current?.gridView as GridViewWithCheckApi | undefined)?.checkItem?.(itemIndex, false, false);
} catch {
/* ignore */
console.error('RealGrid 체크 API error');
}
}
@@ -13,9 +13,7 @@ import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type {
SeweragePipeInputFactInstallStatusSaveRequest,
SeweragePipeInputFactInstallStatusSubmitRequest,
SeweragePipeInputManholeStatusItem,
SeweragePipeInputManholeStatusSaveRequest,
SeweragePipeInputManholeStatusSaveYearlyStatItem,
SeweragePipeInputManholeStatusSubmitRequest,
SeweragePipeInputPipeDiameterStatusSaveRequest,
SeweragePipeInputPipeDiameterStatusSubmitRequest,
@@ -221,11 +219,6 @@ const applyPipeTypeStatusEditableColumns = (
});
};
const getManholeLastDataYear = () => {
const lastDataYear = new Date().getFullYear() - 1;
return Math.max(2014, lastDataYear);
};
/** 2-3 관종별: verificationResult.prlgWhol → 총계 계(pipeByPrstWhol) */
function remapPipeTypePrlgWholVerification(
result?: SeweragePipeInputVerificationResult | null,
@@ -280,43 +273,6 @@ function normalizePipeTypeStatusRow(
};
}
/** 맨홀 신 응답(yearlyStats) -> 그리드 yy{14..25}* 필드로 정규화 */
function normalizeManholeStatusRow(
row: SeweragePipeInputManholeStatusItem,
ctpvNm: string,
): Record<string, string | number | SeweragePipeInputVerificationResult | undefined> {
const maxYy = getManholeLastDataYear() % 100;
const { yearlyStats, ...rest } = row;
const yearFields: Record<string, number> = {};
for (let yy = 14; yy <= maxYy; yy++) {
yearFields[`yy${yy}WholMnhlCnt`] = 0;
yearFields[`yy${yy}Step1MnhlCnt`] = 0;
yearFields[`yy${yy}Step2MnhlCnt`] = 0;
yearFields[`yy${yy}Step3MnhlCnt`] = 0;
yearFields[`yy${yy}Step4MnhlCnt`] = 0;
yearFields[`yy${yy}Step5MnhlCnt`] = 0;
}
for (const y of yearlyStats ?? []) {
const year = Number(y.manhYr);
if (!Number.isFinite(year)) continue;
const yy = year % 100;
if (yy < 14 || yy > maxYy) continue;
yearFields[`yy${yy}WholMnhlCnt`] = Number(y.wholMnhlCnt ?? 0);
yearFields[`yy${yy}Step1MnhlCnt`] = Number(y.step1MnhlCnt ?? 0);
yearFields[`yy${yy}Step2MnhlCnt`] = Number(y.step2MnhlCnt ?? 0);
yearFields[`yy${yy}Step3MnhlCnt`] = Number(y.step3MnhlCnt ?? 0);
yearFields[`yy${yy}Step4MnhlCnt`] = Number(y.step4MnhlCnt ?? 0);
yearFields[`yy${yy}Step5MnhlCnt`] = Number(y.step5MnhlCnt ?? 0);
}
return {
ctpvNm,
...rest,
...yearFields,
};
}
/** ST00020006(연결)은 행이 좌·우 그리드 키에만 있음 */
type GridRowWithVerification = Record<string, string | number> & {
verificationResult?: SeweragePipeInputVerificationResult;
@@ -884,9 +840,10 @@ const SewagePipeInp = () => {
const response = await statisticsInputSewPipeApiService.getManholeStatus(searchParam);
if (response.status === 'success') {
const commonInfo = response.data.commonInfo;
const convertedData = filterFetched(response.data.manholeStatusList).map((d) =>
normalizeManholeStatusRow(d, commonInfo.ctpvNm),
);
const convertedData = filterFetched(response.data.manholeStatusList).map((d) => ({
ctpvNm: commonInfo.ctpvNm,
...d,
}));
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));
@@ -1434,36 +1391,17 @@ const SewagePipeInp = () => {
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const minYy = 14;
const maxYy = getManholeLastDataYear() % 100;
const submitList = currentData.map((d) => {
const yearlyStats: SeweragePipeInputManholeStatusSaveYearlyStatItem[] = [];
for (let yy = minYy; yy <= maxYy; yy++) {
const year = 2000 + yy;
const wholKey = `yy${yy}WholMnhlCnt`;
const step1Key = `yy${yy}Step1MnhlCnt`;
const step2Key = `yy${yy}Step2MnhlCnt`;
const step3Key = `yy${yy}Step3MnhlCnt`;
const step4Key = `yy${yy}Step4MnhlCnt`;
const step5Key = `yy${yy}Step5MnhlCnt`;
yearlyStats.push({
manhYr: String(year),
wholMnhlCnt: formatNumber(d[wholKey] as string | number | undefined, wholKey),
step1MnhlCnt: formatNumber(d[step1Key] as string | number | undefined, step1Key),
step2MnhlCnt: formatNumber(d[step2Key] as string | number | undefined, step2Key),
step3MnhlCnt: formatNumber(d[step3Key] as string | number | undefined, step3Key),
step4MnhlCnt: formatNumber(d[step4Key] as string | number | undefined, step4Key),
step5MnhlCnt: formatNumber(d[step5Key] as string | number | undefined, step5Key),
});
}
return {
emphsMngSeCd: nullableTrimmedString(d.emphsMngSeCd),
admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: nullableTrimmedString(d.admdstNm),
yearlyStats,
rmrk: nullableTrimmedString(d.rmrk),
};
});
const submitList = currentData.map((d) => ({
admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: nullableTrimmedString(d.admdstNm),
emphsMngSeCd: nullableTrimmedString(d.emphsMngSeCd),
frlvlMnhlCnt: formatNumber(d.frlvlMnhlCnt, "frlvlMnhlCnt"),
sclvlMnhlCnt: formatNumber(d.sclvlMnhlCnt, "sclvlMnhlCnt"),
thlvlMnhlCnt: formatNumber(d.thlvlMnhlCnt, "thlvlMnhlCnt"),
folvlMnhlCnt: formatNumber(d.folvlMnhlCnt, "folvlMnhlCnt"),
filvlMnhlCnt: formatNumber(d.filvlMnhlCnt, "filvlMnhlCnt"),
rmrk: nullableTrimmedString(d.rmrk),
}));
const requestData: SeweragePipeInputManholeStatusSubmitRequest = {
...submitBase,
submitList,
@@ -1989,37 +1927,17 @@ const SewagePipeInp = () => {
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const minYy = 14;
const maxYy = getManholeLastDataYear() % 100;
const saveList = currentData.map((d) => {
const yearlyStats: SeweragePipeInputManholeStatusSaveYearlyStatItem[] = [];
for (let yy = minYy; yy <= maxYy; yy++) {
const year = 2000 + yy;
const wholKey = `yy${yy}WholMnhlCnt`;
const step1Key = `yy${yy}Step1MnhlCnt`;
const step2Key = `yy${yy}Step2MnhlCnt`;
const step3Key = `yy${yy}Step3MnhlCnt`;
const step4Key = `yy${yy}Step4MnhlCnt`;
const step5Key = `yy${yy}Step5MnhlCnt`;
yearlyStats.push({
manhYr: String(year),
wholMnhlCnt: formatNumber(d[wholKey] as string | number | undefined, wholKey),
step1MnhlCnt: formatNumber(d[step1Key] as string | number | undefined, step1Key),
step2MnhlCnt: formatNumber(d[step2Key] as string | number | undefined, step2Key),
step3MnhlCnt: formatNumber(d[step3Key] as string | number | undefined, step3Key),
step4MnhlCnt: formatNumber(d[step4Key] as string | number | undefined, step4Key),
step5MnhlCnt: formatNumber(d[step5Key] as string | number | undefined, step5Key),
});
}
return {
emphsMngSeCd: nullableTrimmedString(d.emphsMngSeCd),
admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: nullableTrimmedString(d.admdstNm),
yearlyStats,
rmrk: nullableTrimmedString(d.rmrk),
};
});
const saveList = currentData.map((d) => ({
admdstCd: nullableTrimmedString(d.admdstCd),
admdstNm: nullableTrimmedString(d.admdstNm),
emphsMngSeCd: nullableTrimmedString(d.emphsMngSeCd),
frlvlMnhlCnt: formatNumber(d.frlvlMnhlCnt, "frlvlMnhlCnt"),
sclvlMnhlCnt: formatNumber(d.sclvlMnhlCnt, "sclvlMnhlCnt"),
thlvlMnhlCnt: formatNumber(d.thlvlMnhlCnt, "thlvlMnhlCnt"),
folvlMnhlCnt: formatNumber(d.folvlMnhlCnt, "folvlMnhlCnt"),
filvlMnhlCnt: formatNumber(d.filvlMnhlCnt, "filvlMnhlCnt"),
rmrk: nullableTrimmedString(d.rmrk),
}));
const userInfo = await getUserInfo();
const param: SeweragePipeInputManholeStatusSaveRequest = {
@@ -103,7 +103,7 @@ export function ReviewOpinionDialog({
resetForm();
onOpenChange(false);
} catch {
/* 저장 실패 시 부모에서 alert 후 throw — 다이얼로그는 유지 */
console.error('검토 의견 저장 error');
} finally {
setIsSaving(false);
}
@@ -32,7 +32,7 @@ import type {
ColumnLayoutItem,
} from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { getST00020003RvwConfig, getST00020005RvwConfig,SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
import { getST00020003RvwConfig,SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
import { toGridRows } from "@/components/util/grid/gridFunction";
import { buildReviewCellStyleCallbackByTab } from "@/components/util/grid/reviewEmptyCurrCellStyle";
@@ -151,11 +151,9 @@ const SewagePipeRvw = () => {
() => {
const statsYr = lastSearchValues?.year ?? new Date().getFullYear();
const st00020003Config = getST00020003RvwConfig(statsYr);
const st00020005Config = getST00020005RvwConfig(statsYr);
return SewagePipeColumnInfoListRvw.map((tab) => {
const source =
tab.tabKey === "ST00020003" ? st00020003Config
: tab.tabKey === "ST00020005" ? st00020005Config
: tab;
return {
...source,
@@ -11,7 +11,7 @@ import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import type { ColumnInfo } from "@/components/type/DataGrid.type";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { getST00020003Config, getST00020005Config,SewagePipeColumnInfoListTot } from "@/components/util/column/total/SewagePipeTotColumn";
import { getST00020003Config, SewagePipeColumnInfoListTot } from "@/components/util/column/total/SewagePipeTotColumn";
import { toGridRows } from "@/components/util/grid/gridFunction";
const firstTabKey = SewagePipeColumnInfoListTot[0]?.tabKey ?? "ST00020001";
@@ -205,12 +205,10 @@ const SewagePipeTot = () => {
{columnInfo.map((info) => {
const tabKey = info.tabKey;
// ★ [동적 처리] ST00020003·ST00020005는 함수를 통해 컬럼 정보를 실시간으로 생성
// ★ [동적 처리] ST00020003은 함수를 통해 컬럼 정보를 실시간으로 생성
const currentYear = lastSearchedValuesRef.current?.year || new Date().getFullYear();
const finalTabConfig =
tabKey === "ST00020003" ? getST00020003Config(currentYear) :
tabKey === "ST00020005" ? getST00020005Config(currentYear) :
info;
tabKey === "ST00020003" ? getST00020003Config(currentYear) : info;
if (!finalTabConfig.columns || !finalTabConfig.columnLayout) {
return null;
@@ -9,7 +9,12 @@ import { useAlertStore } from "@/shared/lib";
import { NoData } from "@/shared/ui/no-data";
import { CodeSelectWithAll } from "@/entities/code";
import { generalverificationApiService } from "@/entities/statistics/api/general-verification-service";
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
import type { GeneralverificationList } from "@/entities/statistics/model/general-verification-interface";
import {
getInputPeriodVerificationManageBlockedMessage,
isInputPeriodCurrent,
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
import {
Button,
DataTable,
@@ -47,6 +52,10 @@ const GeneralVerificationManagement = () => {
const [isExporting, setIsExporting] = useState<boolean>(false);
const [selectedVrfcRow, setSelectedVrfcRow] = useState<number | null>(null);
const [showRegistForm, setShowRegistForm] = useState<boolean>(false);
const [inputPeriodBlocked, setInputPeriodBlocked] = useState(false);
const [inputPeriodBlockedMessage, setInputPeriodBlockedMessage] = useState<
string | null
>(null);
const { setMessage: alert } = useAlertStore((state) => state);
@@ -91,8 +100,18 @@ const GeneralVerificationManagement = () => {
};
const columns = useMemo(
() => getGeneralVerificationColumns(() => handleSearch()),
[searchValues.statsCd, searchValues.vrfcClsfCd, searchValues.useYn],
() =>
getGeneralVerificationColumns(() => handleSearch(), {
mutationBlocked: inputPeriodBlocked,
blockedMessage: inputPeriodBlockedMessage,
}),
[
searchValues.statsCd,
searchValues.vrfcClsfCd,
searchValues.useYn,
inputPeriodBlocked,
inputPeriodBlockedMessage,
],
);
const table = useReactTable({
@@ -162,6 +181,24 @@ const GeneralVerificationManagement = () => {
useEffect(() => {
void handleSearch();
void (async () => {
try {
const statsYr = String(new Date().getFullYear() - 1);
const response =
await statisticsInputSewSupplyRateApi.checkInputPeriod(statsYr);
if (response.status !== "success") return;
const blocked = isInputPeriodCurrent(response.data);
setInputPeriodBlocked(blocked);
setInputPeriodBlockedMessage(
blocked
? getInputPeriodVerificationManageBlockedMessage(response.data)
: null,
);
} catch {
setInputPeriodBlocked(false);
setInputPeriodBlockedMessage(null);
}
})();
}, []);
return (
@@ -263,13 +300,25 @@ const GeneralVerificationManagement = () => {
<i className="diveicon di-file-excel" />
<span>내보내기</span>
</Button>
<RegistDialog
open={showRegistForm}
onOpenChange={setShowRegistForm}
onSuccess={() => void handleSearch()}
>
<Button>등록</Button>
</RegistDialog>
{inputPeriodBlocked ? (
<span
title={
inputPeriodBlockedMessage ?? undefined
}
>
<Button disabled type="button">
등록
</Button>
</span>
) : (
<RegistDialog
open={showRegistForm}
onOpenChange={setShowRegistForm}
onSuccess={() => void handleSearch()}
>
<Button>등록</Button>
</RegistDialog>
)}
</div>
</div>
<div className="card !mx-2 !my-4 h-full overflow-auto !p-0">
@@ -294,7 +343,11 @@ const GeneralVerificationManagement = () => {
)}
</div>
</div>
<DetailComponent selectedVrfcRow={selectedVrfcRow} />
<DetailComponent
selectedVrfcRow={selectedVrfcRow}
mutationBlocked={inputPeriodBlocked}
blockedMessage={inputPeriodBlockedMessage}
/>
</div>
</>
);
@@ -391,9 +391,15 @@ const buildRuleBlocksFromFormulas = (
interface DetailComponentProps {
selectedVrfcRow: number | null;
mutationBlocked?: boolean;
blockedMessage?: string | null;
}
const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
const DetailComponent = ({
selectedVrfcRow,
mutationBlocked = false,
blockedMessage = null,
}: DetailComponentProps) => {
const [detailInfo, setDetailInfo] = useState<VerifyDetailData | null>(null);
/** 수식 / 조건·논리 연산자를 순서대로 담는 UI 블록 (서로 강제 교차하지 않음) */
const [ruleBlocks, setRuleBlocks] = useState<RuleBlock[]>([
@@ -487,6 +493,13 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
};
const handleSaveClick = async () => {
if (mutationBlocked) {
alert(
blockedMessage ??
"입력기간에는 검증항목을 등록하거나 수정할 수 없습니다.",
);
return;
}
const trgtStatsCd = detailInfo?.commonInfo.trgtStatsCd;
if (!trgtStatsCd) {
alert("대상 통계항목(trgtStatsCd) 정보가 없습니다.");
@@ -623,6 +636,13 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
formula?: VerifyDetailResponseDto | null,
blockId?: string,
) => {
if (mutationBlocked) {
alert(
blockedMessage ??
"입력기간에는 검증항목을 등록하거나 수정할 수 없습니다.",
);
return;
}
setSelectedFormula(formula ?? null);
setSelectedFormulaBlockId(blockId ?? null);
setShowFomulaDialog(true);
@@ -671,6 +691,13 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
};
const handleResetClick = async () => {
if (mutationBlocked) {
alert(
blockedMessage ??
"입력기간에는 검증항목을 등록하거나 수정할 수 없습니다.",
);
return;
}
const currentSignature = JSON.stringify(
normalizeFormulaListForCompare(formulaList, groupByFlags),
);
@@ -689,6 +716,13 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
};
const handleAddClause = () => {
if (mutationBlocked) {
alert(
blockedMessage ??
"입력기간에는 검증항목을 등록하거나 수정할 수 없습니다.",
);
return;
}
setRuleBlocks((prev) => [
...prev,
{
@@ -702,6 +736,13 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
/** 항상 목록 맨 뒤(마지막 수식 뒤 포함)에 조건·논리 연산자 Select 추가 */
const handleAddConnector = () => {
if (mutationBlocked) {
alert(
blockedMessage ??
"입력기간에는 검증항목을 등록하거나 수정할 수 없습니다.",
);
return;
}
if (connectorOperators.length === 0) {
alert("조건·논리 연산자 목록을 불러오지 못했습니다.");
return;
@@ -720,6 +761,13 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
};
const handleRemoveBlock = (blockId: string) => {
if (mutationBlocked) {
alert(
blockedMessage ??
"입력기간에는 검증항목을 등록하거나 수정할 수 없습니다.",
);
return;
}
setRuleBlocks((prev) => {
const next = prev.filter((b) => b.id !== blockId);
if (next.some((b) => b.kind === "formula")) return next;
@@ -831,6 +879,7 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
<div className="h-px flex-1 bg-[#e2e5ea]" />
<Select
value={block.value}
disabled={mutationBlocked}
onValueChange={(v) => {
setRuleBlocks(
(prev) => {
@@ -907,6 +956,7 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
size="icon"
color="red"
className="size-[3.2rem] shrink-0 rounded-full"
disabled={mutationBlocked}
onClick={() =>
handleRemoveBlock(
block.id,
@@ -933,6 +983,7 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
blockIndex,
)}
groupBy={block.groupBy}
disabled={mutationBlocked}
onGroupByChange={(
checked,
) => {
@@ -1045,6 +1096,12 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
<Button
type="button"
className="h-[3.6rem] gap-1.5 px-4"
disabled={mutationBlocked}
title={
mutationBlocked
? (blockedMessage ?? undefined)
: undefined
}
onClick={handleAddClause}
>
<i className="ri-play-list-add-fill text-[16px]" />
@@ -1054,6 +1111,12 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
type="button"
color="gray"
className="h-[3.6rem] gap-1.5 px-4"
disabled={mutationBlocked}
title={
mutationBlocked
? (blockedMessage ?? undefined)
: undefined
}
onClick={handleAddConnector}
>
<i className="ri-git-commit-line text-[16px]" />
@@ -1083,13 +1146,24 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
<Button
type="button"
color="red"
title="리셋"
title={
mutationBlocked
? (blockedMessage ?? "리셋")
: "리셋"
}
disabled={mutationBlocked}
onClick={handleResetClick}
>
<i className="diveicon di-arrow-reset" />
</Button>
<Button
type="button"
disabled={mutationBlocked}
title={
mutationBlocked
? (blockedMessage ?? undefined)
: undefined
}
onClick={handleSaveClick}
>
등록
@@ -19,6 +19,7 @@ interface FormulaComponentsProps {
index?: number,
) => void;
vrfcClsfCd?: string;
disabled?: boolean;
}
const zoneClassName: Record<ClauseZone, string> = {
@@ -45,6 +46,7 @@ const FormulaComponents = ({
onOperatorChange,
handleFomulaDialog,
vrfcClsfCd,
disabled = false,
}: FormulaComponentsProps) => {
const cmprOpert = data?.cmprOpert;
const displayFormula = data
@@ -60,6 +62,7 @@ const FormulaComponents = ({
size="icon"
className="formulaDeleteBtn size-[2.6rem] shrink-0 rounded-full !bg-[#f24e5b] hover:!bg-[#d63c48]"
color="red"
disabled={disabled}
onClick={onDelete}
>
<i className="ri-subtract-fill text-[16px]" />
@@ -70,21 +73,27 @@ const FormulaComponents = ({
id={`formulaInput-${index ?? 0}`}
value={displayFormula}
title={displayFormula}
onClick={() => handleFomulaDialog?.(true, data, index)}
onClick={() => {
if (disabled) return;
handleFomulaDialog?.(true, data, index);
}}
readOnly
disabled={disabled}
/>
<div className="w-[200px] shrink-0 [&_button]:!h-[3.6rem] [&_button]:!w-[200px] [&_button]:min-w-[200px] [&_button]:max-w-[200px]">
<OperatorsComponents
cmprOpert={cmprOpert}
onValueChange={onOperatorChange}
disabled={disabled}
/>
</div>
{vrfcClsfCd !== "TOTAL" &&
<label className="flex w-[96px] shrink-0 items-center justify-end gap-1.5 text-[#4b5563] cursor-pointer">
<label className={`flex w-[96px] shrink-0 items-center justify-end gap-1.5 text-[#4b5563] ${disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer"}`}>
<input
type="checkbox"
className="cursor-pointer accent-[#3a6df0]"
checked={groupBy}
disabled={disabled}
onChange={(e) => onGroupByChange?.(e.target.checked)}
/>
group by
@@ -26,13 +26,15 @@ interface OperatorsComponentsProps {
onValueChange?: (value: string) => void;
placeholder?: string;
opertSeCd?: string;
disabled?: boolean;
}
const OperatorsComponents = ({
cmprOpert,
onValueChange,
placeholder = "연산자 없음",
opertSeCd = "opertSeCd=CMPR"
opertSeCd = "opertSeCd=CMPR",
disabled = false,
}: OperatorsComponentsProps) => {
const [operators, setOperators] = useState<OperatorItem[]>([]);
const [localValue, setLocalValue] = useState<string>(cmprOpert ?? "");
@@ -75,6 +77,7 @@ const OperatorsComponents = ({
return (
<Select
value={value}
disabled={disabled}
onValueChange={(v) => {
setLocalValue(v);
onValueChange?.(v);
@@ -50,6 +50,9 @@ interface RegistDialogProps {
vrfcSn?: number;
/** 수정/등록 성공 시 부모에서 목록 재조회 등 실행 */
onSuccess?: () => void;
/** 입력기간이면 수정 불가(삭제는 가능) */
mutationBlocked?: boolean;
blockedMessage?: string | null;
}
const RegistDialog = memo(
@@ -59,10 +62,13 @@ const RegistDialog = memo(
onOpenChange,
vrfcSn,
onSuccess,
mutationBlocked = false,
blockedMessage = null,
...props
}: RegistDialogProps) => {
const isVrfcSn = vrfcSn != null;
const editBlocked = mutationBlocked && isVrfcSn;
const [checkVrfcNm, setCheckVrfcNm] = useState<boolean>(false);
const { confirm } = useConfirm();
const alert = useAlertStore((state) => state.setMessage);
@@ -219,6 +225,13 @@ const RegistDialog = memo(
}, [vrfcNm, open, isVrfcSn]);
const onSubmit = async (data: GeneralVerificationMasterSubmitFormData) => {
if (mutationBlocked) {
alert(
blockedMessage ??
"입력기간에는 검증항목을 등록하거나 수정할 수 없습니다.",
);
return;
}
const currentVrfcNm = (data.vrfcNm ?? '').trim();
const originalVrfcNm = originalVrfcNmRef.current;
@@ -321,7 +334,7 @@ const RegistDialog = memo(
onValueChange={field.onChange}
placeholder="전체"
className="w-[16rem]"
disabled={isVrfcSn}
disabled={isVrfcSn || editBlocked}
/>
</FormControl>
{form.formState.errors.trgtStatsCd && (
@@ -348,7 +361,7 @@ const RegistDialog = memo(
value={field.value ?? ""}
className="!text-[1.3rem]"
placeholder="검증명을 입력해주세요"
disabled={(trgtStatsCd ?? '') === ''}
disabled={editBlocked || (trgtStatsCd ?? '') === ''}
autoComplete="off"
/>
</FormControl>
@@ -380,6 +393,7 @@ const RegistDialog = memo(
<Select
value={field.value ?? ""}
onValueChange={field.onChange}
disabled={editBlocked}
>
<SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택" />
@@ -416,6 +430,7 @@ const RegistDialog = memo(
placeholder="소수점자리수를 입력해주세요"
type="number"
min={0}
disabled={editBlocked}
/>
</FormControl>
<p className="text-gray-500 text-base !mt-0">
@@ -443,7 +458,7 @@ const RegistDialog = memo(
<Select
value={field.value ?? ""}
onValueChange={field.onChange}
disabled={Number(dcptDigtCnt ?? 0) === 0}
disabled={editBlocked || Number(dcptDigtCnt ?? 0) === 0}
>
<SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택" />
@@ -484,13 +499,14 @@ const RegistDialog = memo(
{...field}
name="useYn"
onValueChange={field.onChange}
disabled={editBlocked}
>
<div className="flex items-center space-x-2">
<RadioGroupItem value="Y" id="useY" />
<RadioGroupItem value="Y" id="useY" disabled={editBlocked} />
<Label htmlFor="useY">사용</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="N" id="useN" />
<RadioGroupItem value="N" id="useN" disabled={editBlocked} />
<Label htmlFor="useN">사용안함</Label>
</div>
</RadioGroup>
@@ -599,9 +615,17 @@ const RegistDialog = memo(
삭제
</Button>
}
<Button size="lg" type="submit">
{vrfcSn != null ? '수정' : '등록'}
</Button>
{editBlocked ? (
<span title={blockedMessage ?? undefined}>
<Button size="lg" type="submit" disabled>
수정
</Button>
</span>
) : (
<Button size="lg" type="submit">
{vrfcSn != null ? '수정' : '등록'}
</Button>
)}
<DialogClose asChild>
<Button size="lg" color="white">
취소
@@ -5,130 +5,18 @@ import { footerInt } from "@/components/util/grid/footerSummary";
// 하수도 통계 관리 > 02.하수관로 > tab 5. 맨홀 현황
// SeweragePipeInputManholeStatusItem + commonInfo.ctpvNm (그리드 행에 병합)
// API: yy14=2015년 이전(2014년 데이터) … yy{(현재연도-1)%100} (필드 접두 yy + 연도 끝자리)
const MANHOLE_START_YEAR = 2014;
const MANHOLE_LAST_DATA_YEAR = Math.max(MANHOLE_START_YEAR, new Date().getFullYear() - 1);
const MANHOLE_YY_MIN = MANHOLE_START_YEAR % 100;
const MANHOLE_YY_MAX = MANHOLE_LAST_DATA_YEAR % 100;
const calendarYearFromYy = (yy: number) => 2000 + yy;
/** yy14(2014) 구간은 헤더에 「2015년 이전」 표기 (합계·검토 컬럼과 동일) */
const manholeYearHeaderText = (yy: number): string => {
const y = calendarYearFromYy(yy);
return y === MANHOLE_START_YEAR ? "2015년 이전" : `${y}년`;
};
const intCol = (name: string, headerText: string): ColumnConfig => ({
const intCol = (name: string, headerText: string, editable = true): ColumnConfig => ({
name,
fieldName: name,
header: { text: headerText },
footer: footerInt,
// editable: false,
editable,
valueType: ValueType.NUMBER,
dataFormat: "#,##0",
...(editable ? {} : { styleName: "rg-cell-total" }),
});
const yearlySumFields = (suffix: "WholMnhlCnt" | "Step1MnhlCnt" | "Step2MnhlCnt" | "Step3MnhlCnt" | "Step4MnhlCnt" | "Step5MnhlCnt") =>
Array.from({ length: MANHOLE_YY_MAX - MANHOLE_YY_MIN + 1 }, (_, i) => `yy${MANHOLE_YY_MIN + i}${suffix}`);
function totalBlockColumns(): ColumnConfig[] {
return [
{
...intCol("totalWholMnhlCnt", "계"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("WholMnhlCnt"), type: "sum" as const },
},
{
...intCol("totalStep1MnhlCnt", "주철"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("Step1MnhlCnt"), type: "sum" as const },
},
{
...intCol("totalStep2MnhlCnt", "주철(조화)"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("Step2MnhlCnt"), type: "sum" as const },
},
{
...intCol("totalStep3MnhlCnt", "콘크리트"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("Step3MnhlCnt"), type: "sum" as const },
},
{
...intCol("totalStep4MnhlCnt", "기타"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("Step4MnhlCnt"), type: "sum" as const },
},
{
...intCol("totalStep5MnhlCnt", "추락방지시설설치맨홀(개)"),
styleName: "rg-cell-total",
editable: false,
computed: { fields: yearlySumFields("Step5MnhlCnt"), type: "sum" as const },
},
];
}
function yyBlockColumns(yy: number): ColumnConfig[] {
const p = `yy${yy}`;
return [
{
...intCol(`${p}WholMnhlCnt`, "계"),
styleName: "rg-cell-total",
editable: false,
computed: {
fields: [
`${p}Step1MnhlCnt`,
`${p}Step2MnhlCnt`,
`${p}Step3MnhlCnt`,
`${p}Step4MnhlCnt`,
`${p}Step5MnhlCnt`,
],
type: "sum" as const,
},
},
intCol(`${p}Step1MnhlCnt`, "주철"),
intCol(`${p}Step2MnhlCnt`, "주철(조화)"),
intCol(`${p}Step3MnhlCnt`, "콘크리트"),
intCol(`${p}Step4MnhlCnt`, "기타"),
intCol(`${p}Step5MnhlCnt`, "추락방지시설설치맨홀(개)"),
];
}
function yyYearLayout(yy: number): ColumnLayoutItem {
const y = calendarYearFromYy(yy);
const p = `yy${yy}`;
return {
name: `year${y}`,
hideChildHeaders: false,
header: { text: manholeYearHeaderText(yy) },
items: [
{
name: `year${y}Material`,
hideChildHeaders: false,
header: { text: "맨홀뚜껑 재질(개)" },
items: [
`${p}WholMnhlCnt`,
`${p}Step1MnhlCnt`,
`${p}Step2MnhlCnt`,
`${p}Step3MnhlCnt`,
`${p}Step4MnhlCnt`,
],
},
`${p}Step5MnhlCnt`,
],
};
}
const yearLayoutItems: ColumnLayoutItem[] = [];
for (let yy = MANHOLE_YY_MIN; yy <= MANHOLE_YY_MAX; yy++) {
yearLayoutItems.push(yyYearLayout(yy));
}
export const ManholeStatusInpColumn = {
columns: [
{
@@ -160,12 +48,19 @@ export const ManholeStatusInpColumn = {
editable: false,
styleName: "rg-cell-readonly",
footer: { text: "합계" },
// editable: false,
},
...totalBlockColumns(),
...Array.from({ length: MANHOLE_YY_MAX - MANHOLE_YY_MIN + 1 }, (_, i) =>
yyBlockColumns(MANHOLE_YY_MIN + i)
).flat(),
{
...intCol("wholMnhlCnt", "계", false),
computed: {
fields: ["frlvlMnhlCnt", "sclvlMnhlCnt", "thlvlMnhlCnt", "folvlMnhlCnt"],
type: "sum" as const,
},
},
intCol("frlvlMnhlCnt", "주철"),
intCol("sclvlMnhlCnt", "주철(조화)"),
intCol("thlvlMnhlCnt", "콘크리트"),
intCol("folvlMnhlCnt", "기타"),
intCol("filvlMnhlCnt", "추락방지시설설치맨홀(개)"),
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
{
name: "admdstCd",
@@ -194,31 +89,17 @@ export const ManholeStatusInpColumn = {
"admdstNm",
"emphsMngSeNm",
{
name: "yearlyTotal",
name: "mtrlGroup",
hideChildHeaders: false,
header: { text: "연도별총계" },
items: [
{
name: "yearlyTotalMaterial",
hideChildHeaders: false,
header: { text: "맨홀뚜껑 재질(개)" },
items: [
"totalWholMnhlCnt",
"totalStep1MnhlCnt",
"totalStep2MnhlCnt",
"totalStep3MnhlCnt",
"totalStep4MnhlCnt",
],
},
"totalStep5MnhlCnt",
],
header: { text: "맨홀뚜껑 재질(개)" },
items: ["wholMnhlCnt", "frlvlMnhlCnt", "sclvlMnhlCnt", "thlvlMnhlCnt", "folvlMnhlCnt"],
},
...yearLayoutItems,
"filvlMnhlCnt",
"rmrk",
"admdstCd",
"emphsMngSeCd",
] as ColumnLayoutItem[],
rowStartNum: 3,
rowStartNum: 2,
fixedColumns: 2,
tabKey: "ST00020005",
tabLabel: "맨홀 현황",
@@ -11,6 +11,7 @@ import RegistDialog from "@/components/statistics/verification/generalVerificati
export const getGeneralVerificationColumns = (
onUpdateSuccess?: () => void,
options?: { mutationBlocked?: boolean; blockedMessage?: string | null },
): ColumnDef<GeneralverificationList>[] => [
{
id: "no",
@@ -53,6 +54,8 @@ export const getGeneralVerificationColumns = (
<GeneralVerificationCopyButton
row={row.original}
onSuccess={onUpdateSuccess}
mutationBlocked={options?.mutationBlocked}
blockedMessage={options?.blockedMessage}
/>
</div>
),
@@ -65,6 +68,8 @@ export const getGeneralVerificationColumns = (
<GeneralVerificationManageButton
vrfcSn={row.original.vrfcSn}
onSuccess={onUpdateSuccess}
mutationBlocked={options?.mutationBlocked}
blockedMessage={options?.blockedMessage}
/>
</div>
),
@@ -74,9 +79,13 @@ export const getGeneralVerificationColumns = (
const GeneralVerificationManageButton = ({
vrfcSn,
onSuccess,
mutationBlocked,
blockedMessage,
}: {
vrfcSn: number;
onSuccess?: () => void;
mutationBlocked?: boolean;
blockedMessage?: string | null;
}) => {
const [dialogOpen, setDialogOpen] = useState(false);
return (
@@ -85,6 +94,8 @@ const GeneralVerificationManageButton = ({
onOpenChange={setDialogOpen}
vrfcSn={vrfcSn}
onSuccess={onSuccess}
mutationBlocked={mutationBlocked}
blockedMessage={blockedMessage}
>
<Button size="sm">관리</Button>
</RegistDialog>
@@ -94,11 +105,24 @@ const GeneralVerificationManageButton = ({
const GeneralVerificationCopyButton = ({
row,
onSuccess,
mutationBlocked,
blockedMessage,
}: {
row: GeneralverificationList;
onSuccess?: () => void;
mutationBlocked?: boolean;
blockedMessage?: string | null;
}) => {
const [dialogOpen, setDialogOpen] = useState(false);
if (mutationBlocked) {
return (
<span title={blockedMessage ?? undefined}>
<Button size="sm" color="white" disabled type="button">
복사
</Button>
</span>
);
}
return (
<CopyDialog
open={dialogOpen}
@@ -434,18 +434,16 @@ const rawList = [
]
},
// ST00020005 맨홀 현황은 연도 범위가 동적이므로 rawList에 포함하지 않고
// getST00020005RvwConfig() 로 런타임에 생성합니다.
{
tabKey: "ST00020005_PLACEHOLDER",
tabLabel: "맨홀 현황",
rowStartNum: 3,
tabKey: 'ST00020005',
tabLabel: '맨홀 현황',
rowStartNum: 2,
fixedColumns: 3,
columns: [
{
name: "ctpvNm",
fieldName: "ctpvNm",
header: { text: "시도" },
name: 'ctpvNm',
fieldName: 'ctpvNm',
header: { text: '시도' },
mergeCells: true,
sortdisable: true,
footer: [
@@ -455,11 +453,11 @@ const rawList = [
const dataSource = grid.getDataSource();
const uniqueValues = new Set<string>();
for (let i = 0; i < dataSource.getRowCount(); i++) {
const val = dataSource.getValue(i, "displayYear");
const val = dataSource.getValue(i, 'displayYear');
if (val) uniqueValues.add(String(val));
}
const divisionValues = Array.from(uniqueValues);
return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + "합계" : "합계";
return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계';
}
},
{
@@ -468,18 +466,18 @@ const rawList = [
const dataSource = grid.getDataSource();
const uniqueValues = new Set<string>();
for (let i = 0; i < dataSource.getRowCount(); i++) {
const val = dataSource.getValue(i, "displayYear");
const val = dataSource.getValue(i, 'displayYear');
if (val) uniqueValues.add(String(val));
}
const divisionValues = Array.from(uniqueValues);
return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + "합계" : "합계";
return divisionValues.length > divisionIndex ? divisionValues[divisionIndex] + '합계' : '합계';
}
}
]
},
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, mergeCells: true, sortdisable: true },
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역명" }, mergeCells: true, sortdisable: true },
{ name: "emphsMngSeNm", fieldName: "emphsMngSeNm", header: { text: "집중강우\n중점관리\n구역구분" }, mergeCells: true },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, mergeCells: true, sortdisable: true },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, mergeCells: true, sortdisable: true },
{ name: 'emphsMngSeNm', fieldName: 'emphsMngSeNm', header: { text: '집중강우\n중점관리\n구역구분' }, mergeCells: true },
{
name: 'displayYear',
fieldName: 'displayYear',
@@ -514,233 +512,38 @@ const rawList = [
}
]
},
{ name: "totalWholMnhlCnt", fieldName: "totalWholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totalStep1MnhlCnt", fieldName: "totalStep1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totalStep2MnhlCnt", fieldName: "totalStep2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totalStep3MnhlCnt", fieldName: "totalStep3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totalStep4MnhlCnt", fieldName: "totalStep4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totalStep5MnhlCnt", fieldName: "totalStep5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2015년 이전 (yy14) ──────────────────────────────────
{ name: "yy14WholMnhlCnt", fieldName: "yy14WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step1MnhlCnt", fieldName: "yy14Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step2MnhlCnt", fieldName: "yy14Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step3MnhlCnt", fieldName: "yy14Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step4MnhlCnt", fieldName: "yy14Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step5MnhlCnt", fieldName: "yy14Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2015년 (yy15) ───────────────────────────────────────
{ name: "yy15WholMnhlCnt", fieldName: "yy15WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step1MnhlCnt", fieldName: "yy15Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step2MnhlCnt", fieldName: "yy15Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step3MnhlCnt", fieldName: "yy15Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step4MnhlCnt", fieldName: "yy15Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step5MnhlCnt", fieldName: "yy15Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2016년 (yy16) ───────────────────────────────────────
{ name: "yy16WholMnhlCnt", fieldName: "yy16WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step1MnhlCnt", fieldName: "yy16Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step2MnhlCnt", fieldName: "yy16Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step3MnhlCnt", fieldName: "yy16Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step4MnhlCnt", fieldName: "yy16Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step5MnhlCnt", fieldName: "yy16Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2017년 (yy17) ───────────────────────────────────────
{ name: "yy17WholMnhlCnt", fieldName: "yy17WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step1MnhlCnt", fieldName: "yy17Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step2MnhlCnt", fieldName: "yy17Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step3MnhlCnt", fieldName: "yy17Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step4MnhlCnt", fieldName: "yy17Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step5MnhlCnt", fieldName: "yy17Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2018년 (yy18) ───────────────────────────────────────
{ name: "yy18WholMnhlCnt", fieldName: "yy18WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step1MnhlCnt", fieldName: "yy18Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step2MnhlCnt", fieldName: "yy18Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step3MnhlCnt", fieldName: "yy18Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step4MnhlCnt", fieldName: "yy18Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step5MnhlCnt", fieldName: "yy18Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2019년 (yy19) ───────────────────────────────────────
{ name: "yy19WholMnhlCnt", fieldName: "yy19WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step1MnhlCnt", fieldName: "yy19Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step2MnhlCnt", fieldName: "yy19Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step3MnhlCnt", fieldName: "yy19Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step4MnhlCnt", fieldName: "yy19Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step5MnhlCnt", fieldName: "yy19Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2020년 (yy20) ───────────────────────────────────────
{ name: "yy20WholMnhlCnt", fieldName: "yy20WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step1MnhlCnt", fieldName: "yy20Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step2MnhlCnt", fieldName: "yy20Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step3MnhlCnt", fieldName: "yy20Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step4MnhlCnt", fieldName: "yy20Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step5MnhlCnt", fieldName: "yy20Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2021년 (yy21) ───────────────────────────────────────
{ name: "yy21WholMnhlCnt", fieldName: "yy21WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step1MnhlCnt", fieldName: "yy21Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step2MnhlCnt", fieldName: "yy21Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step3MnhlCnt", fieldName: "yy21Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step4MnhlCnt", fieldName: "yy21Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step5MnhlCnt", fieldName: "yy21Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2022년 (yy22) ───────────────────────────────────────
{ name: "yy22WholMnhlCnt", fieldName: "yy22WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step1MnhlCnt", fieldName: "yy22Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step2MnhlCnt", fieldName: "yy22Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step3MnhlCnt", fieldName: "yy22Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step4MnhlCnt", fieldName: "yy22Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step5MnhlCnt", fieldName: "yy22Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2023년 (yy23) ───────────────────────────────────────
{ name: "yy23WholMnhlCnt", fieldName: "yy23WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step1MnhlCnt", fieldName: "yy23Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step2MnhlCnt", fieldName: "yy23Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step3MnhlCnt", fieldName: "yy23Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step4MnhlCnt", fieldName: "yy23Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step5MnhlCnt", fieldName: "yy23Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2024년 (yy24) ───────────────────────────────────────
{ name: "yy24WholMnhlCnt", fieldName: "yy24WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step1MnhlCnt", fieldName: "yy24Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step2MnhlCnt", fieldName: "yy24Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step3MnhlCnt", fieldName: "yy24Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step4MnhlCnt", fieldName: "yy24Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step5MnhlCnt", fieldName: "yy24Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2025년 (yy25) ───────────────────────────────────────
{ name: "yy25WholMnhlCnt", fieldName: "yy25WholMnhlCnt", header: { text: "계" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step1MnhlCnt", fieldName: "yy25Step1MnhlCnt", header: { text: "주철" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step2MnhlCnt", fieldName: "yy25Step2MnhlCnt", header: { text: "주철(조화)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step3MnhlCnt", fieldName: "yy25Step3MnhlCnt", header: { text: "콘크리트" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step4MnhlCnt", fieldName: "yy25Step4MnhlCnt", header: { text: "기타" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step5MnhlCnt", fieldName: "yy25Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" }, editable: false },
{ name: 'wholMnhlCnt', fieldName: 'wholMnhlCnt', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'frlvlMnhlCnt', fieldName: 'frlvlMnhlCnt', header: { text: '주철' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'sclvlMnhlCnt', fieldName: 'sclvlMnhlCnt', header: { text: '주철(조화)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'thlvlMnhlCnt', fieldName: 'thlvlMnhlCnt', header: { text: '콘크리트' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'folvlMnhlCnt', fieldName: 'folvlMnhlCnt', header: { text: '기타' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'filvlMnhlCnt', fieldName: 'filvlMnhlCnt', header: { text: '추락방지시설\n설치맨홀(개)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } },
],
columnLayout: [
{
name: "addrGroup",
header: { text: "행정구역" },
name: 'addrGroup',
header: { text: '행정구역' },
items: [
{ column: "ctpvNm", footerUserSpans: [{ colspan: 5 }, { colspan: 5 }] },
"sggNm",
"admdstNm",
{ column: 'ctpvNm', footerUserSpans: [{ colspan: 5 }, { colspan: 5 }] },
'sggNm',
'admdstNm',
],
},
"emphsMngSeNm",
"displayYear",
'emphsMngSeNm',
'displayYear',
{
name: "totGroup",
header: { text: "총계" },
items: [
{ name: "totMtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["totalWholMnhlCnt", "totalStep1MnhlCnt", "totalStep2MnhlCnt", "totalStep3MnhlCnt", "totalStep4MnhlCnt"] },
"totalStep5MnhlCnt",
],
name: 'mtrlGroup',
header: { text: '맨홀뚜껑 재질(개)' },
items: ['wholMnhlCnt', 'frlvlMnhlCnt', 'sclvlMnhlCnt', 'thlvlMnhlCnt', 'folvlMnhlCnt'],
},
{
name: "yy14Group",
header: { text: "2015년 이전" }, // ← _14Y
items: [
{ name: "yy14MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy14WholMnhlCnt", "yy14Step1MnhlCnt", "yy14Step2MnhlCnt", "yy14Step3MnhlCnt", "yy14Step4MnhlCnt"] },
"yy14Step5MnhlCnt",
],
},
{
name: "yy15Group",
header: { text: "2015년" }, // ← _15Y
items: [
{ name: "yy15MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy15WholMnhlCnt", "yy15Step1MnhlCnt", "yy15Step2MnhlCnt", "yy15Step3MnhlCnt", "yy15Step4MnhlCnt"] },
"yy15Step5MnhlCnt",
],
},
{
name: "yy16Group",
header: { text: "2016년" }, // ← _16Y
items: [
{ name: "yy16MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy16WholMnhlCnt", "yy16Step1MnhlCnt", "yy16Step2MnhlCnt", "yy16Step3MnhlCnt", "yy16Step4MnhlCnt"] },
"yy16Step5MnhlCnt",
],
},
{
name: "yy17Group",
header: { text: "2017년" },
items: [
{ name: "yy17MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy17WholMnhlCnt", "yy17Step1MnhlCnt", "yy17Step2MnhlCnt", "yy17Step3MnhlCnt", "yy17Step4MnhlCnt"] },
"yy17Step5MnhlCnt",
],
},
{
name: "yy18Group",
header: { text: "2018년" },
items: [
{ name: "yy18MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy18WholMnhlCnt", "yy18Step1MnhlCnt", "yy18Step2MnhlCnt", "yy18Step3MnhlCnt", "yy18Step4MnhlCnt"] },
"yy18Step5MnhlCnt",
],
},
{
name: "yy19Group",
header: { text: "2019년" },
items: [
{ name: "yy19MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy19WholMnhlCnt", "yy19Step1MnhlCnt", "yy19Step2MnhlCnt", "yy19Step3MnhlCnt", "yy19Step4MnhlCnt"] },
"yy19Step5MnhlCnt",
],
},
{
name: "yy20Group",
header: { text: "2020년" },
items: [
{ name: "yy20MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy20WholMnhlCnt", "yy20Step1MnhlCnt", "yy20Step2MnhlCnt", "yy20Step3MnhlCnt", "yy20Step4MnhlCnt"] },
"yy20Step5MnhlCnt",
],
},
{
name: "yy21Group",
header: { text: "2021년" },
items: [
{ name: "yy21MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy21WholMnhlCnt", "yy21Step1MnhlCnt", "yy21Step2MnhlCnt", "yy21Step3MnhlCnt", "yy21Step4MnhlCnt"] },
"yy21Step5MnhlCnt",
],
},
{
name: "yy22Group",
header: { text: "2022년" },
items: [
{ name: "yy22MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy22WholMnhlCnt", "yy22Step1MnhlCnt", "yy22Step2MnhlCnt", "yy22Step3MnhlCnt", "yy22Step4MnhlCnt"] },
"yy22Step5MnhlCnt",
],
},
{
name: "yy23Group",
header: { text: "2023년" },
items: [
{ name: "yy23MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy23WholMnhlCnt", "yy23Step1MnhlCnt", "yy23Step2MnhlCnt", "yy23Step3MnhlCnt", "yy23Step4MnhlCnt"] },
"yy23Step5MnhlCnt",
],
},
{
name: "yy24Group",
header: { text: "2024년" },
items: [
{ name: "yy24MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy24WholMnhlCnt", "yy24Step1MnhlCnt", "yy24Step2MnhlCnt", "yy24Step3MnhlCnt", "yy24Step4MnhlCnt"] },
"yy24Step5MnhlCnt",
],
},
{
name: "yy25Group",
header: { text: "2025년" },
items: [
{ name: "yy25MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy25WholMnhlCnt", "yy25Step1MnhlCnt", "yy25Step2MnhlCnt", "yy25Step3MnhlCnt", "yy25Step4MnhlCnt"] },
"yy25Step5MnhlCnt",
],
},
"rmrk",
],
'filvlMnhlCnt',
'rmrk',
]
},
];
/** ST00020003 관종별 현황 — 검토 화면용 동적 컬럼 생성
* 필드명: prlg_whol_{year}, lyng_prlg_{year}, dmltn_prlg_{year}
* (total의 getST00020003Config와 동일 필드, oldPipeRt 제외)
@@ -813,100 +616,8 @@ export const getST00020003RvwConfig = (statsYr: string | number) => {
};
};
/** ST00020005 맨홀 현황 — 검토 화면용 동적 컬럼 생성
* 필드명: wholMnhlCnt{year}, step1~5MnhlCnt{year}
* (total의 getST00020005Config와 동일 필드)
*/
export const getST00020005RvwConfig = (statsYr: string | number) => {
const firstYear = 2014;
const lastYear = Number(statsYr) || dayjs().year();
const yearRange = Array.from({ length: lastYear - firstYear + 1 }, (_, i) => firstYear + i);
const makeFooterValueCallback = (divisionIndex: number) => ({
valueCallback: (grid: GridView) => {
const dataSource = grid.getDataSource();
const uniqueValues = new Set<string>();
for (let i = 0; i < dataSource.getRowCount(); i++) {
const val = dataSource.getValue(i, 'displayYear');
if (val) uniqueValues.add(String(val));
}
const divisionValues = Array.from(uniqueValues);
return divisionValues.length > divisionIndex
? divisionValues[divisionIndex] + '합계'
: '합계';
},
});
const mnhlYearCols = (year: number) => [
{ name: `wholMnhlCnt${year}`, fieldName: `wholMnhlCnt${year}`, header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `step1MnhlCnt${year}`, fieldName: `step1MnhlCnt${year}`, header: { text: '주철' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `step2MnhlCnt${year}`, fieldName: `step2MnhlCnt${year}`, header: { text: '주철(조화)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `step3MnhlCnt${year}`, fieldName: `step3MnhlCnt${year}`, header: { text: '콘크리트' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `step4MnhlCnt${year}`, fieldName: `step4MnhlCnt${year}`, header: { text: '기타' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: `step5MnhlCnt${year}`, fieldName: `step5MnhlCnt${year}`, header: { text: '추락방지시설\n설치맨홀(개)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
];
const mnhlYearLayout = (year: number) => ({
name: `yr${year}Group`,
header: { text: year === firstYear ? '2015년 이전' : `${year}년` },
items: [
{
name: `yr${year}MtrlGroup`,
header: { text: '맨홀뚜껑 재질(개)' },
items: [`wholMnhlCnt${year}`, `step1MnhlCnt${year}`, `step2MnhlCnt${year}`, `step3MnhlCnt${year}`, `step4MnhlCnt${year}`],
},
`step5MnhlCnt${year}`,
],
});
return {
tabKey: 'ST00020005',
tabLabel: '맨홀 현황',
rowStartNum: 3,
fixedColumns: 3,
columns: [
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true, footer: [makeFooterValueCallback(0), makeFooterValueCallback(1)] },
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true, sortdisable: true },
{ name: 'emphsMngSeNm', fieldName: 'emphsMngSeNm', header: { text: '집중강우\n중점관리\n구역구분' }, editable: false, styleName: 'rg-cell-readonly', mergeCells: true },
{ name: 'displayYear', fieldName: 'displayYear', header: { text: '구분' }, editable: false, styleName: 'rg-cell-readonly', sortdisable: true, footer: [makeFooterValueCallback(0), makeFooterValueCallback(1)] },
{ name: 'totWholMnhlCnt', fieldName: 'totWholMnhlCnt', header: { text: '계' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'totStep1MnhlCnt', fieldName: 'totStep1MnhlCnt', header: { text: '주철' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'totStep2MnhlCnt', fieldName: 'totStep2MnhlCnt', header: { text: '주철(조화)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'totStep3MnhlCnt', fieldName: 'totStep3MnhlCnt', header: { text: '콘크리트' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'totStep4MnhlCnt', fieldName: 'totStep4MnhlCnt', header: { text: '기타' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
{ name: 'totStep5MnhlCnt', fieldName: 'totStep5MnhlCnt', header: { text: '추락방지시설\n설치맨홀(개)' }, footer: footerIntArrayByDivision, valueType: ValueType.NUMBER, dataFormat: '#,##0', editable: false, styleName: 'rg-cell-readonly' },
...yearRange.flatMap(mnhlYearCols),
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' }, editable: false, styleName: 'rg-cell-readonly' },
],
columnLayout: [
{
name: 'addrGroup',
header: { text: '행정구역' },
items: [
{ column: 'ctpvNm', footerUserSpans: [{ colspan: 5 }, { colspan: 5 }] },
'sggNm',
'admdstNm',
],
},
'emphsMngSeNm',
'displayYear',
{
name: 'totGroup',
header: { text: '연도별총계' },
items: [
{ name: 'totMtrlGroup', header: { text: '맨홀뚜껑 재질(개)' }, items: ['totWholMnhlCnt', 'totStep1MnhlCnt', 'totStep2MnhlCnt', 'totStep3MnhlCnt', 'totStep4MnhlCnt'] },
'totStep5MnhlCnt',
],
},
...yearRange.map(mnhlYearLayout),
'rmrk',
],
};
};
export const SewagePipeColumnInfoListRvw = rawList
.filter((tab) => tab.tabKey !== 'ST00020003_PLACEHOLDER' && tab.tabKey !== 'ST00020005_PLACEHOLDER')
.filter((tab) => tab.tabKey !== 'ST00020003_PLACEHOLDER')
.map((tab) => ({
...tab,
columns: tab.columns.map((col) => ({
@@ -925,15 +636,5 @@ const st00020003RvwPlaceholder = {
columnLayout: [] as ReturnType<typeof getST00020003RvwConfig>['columnLayout'],
};
const st00020005RvwPlaceholder = {
tabKey: 'ST00020005',
tabLabel: '맨홀 현황',
rowStartNum: 3,
fixedColumns: 3,
columns: [] as ReturnType<typeof getST00020005RvwConfig>['columns'],
columnLayout: [] as ReturnType<typeof getST00020005RvwConfig>['columnLayout'],
};
// ST00020003: index 2, ST00020005: 끝에 추가
// ST00020003: index 2
SewagePipeColumnInfoListRvw.splice(2, 0, st00020003RvwPlaceholder);
SewagePipeColumnInfoListRvw.push(st00020005RvwPlaceholder);
@@ -700,232 +700,34 @@ const rawSewagePipeTotList = [
],
},
// ST00020005 맨홀 현황은 연도 범위가 동적이므로 rawSewagePipeTotList에 포함하지 않고
// getST00020005Config() 로 런타임에 생성합니다.
{
tabKey: "ST00020005_PLACEHOLDER",
tabLabel: "맨홀 현황",
rowStartNum: 2,
fixedColumns: 2,
columns: [
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역명" }, editable: false },
{ name: "emphsMngSeNm", fieldName: "emphsMngSeNm", header: { text: "집중강우\n중점관리\n구역구분" }, editable: false },
{ name: "totWholMnhlCnt", fieldName: "totWholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep1MnhlCnt", fieldName: "totStep1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep2MnhlCnt", fieldName: "totStep2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep3MnhlCnt", fieldName: "totStep3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep4MnhlCnt", fieldName: "totStep4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep5MnhlCnt", fieldName: "totStep5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2015년 이전 (yy14) ──────────────────────────────────
{ name: "yy14WholMnhlCnt", fieldName: "yy14WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step1MnhlCnt", fieldName: "yy14Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step2MnhlCnt", fieldName: "yy14Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step3MnhlCnt", fieldName: "yy14Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step4MnhlCnt", fieldName: "yy14Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy14Step5MnhlCnt", fieldName: "yy14Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2015년 (yy15) ───────────────────────────────────────
{ name: "yy15WholMnhlCnt", fieldName: "yy15WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step1MnhlCnt", fieldName: "yy15Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step2MnhlCnt", fieldName: "yy15Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step3MnhlCnt", fieldName: "yy15Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step4MnhlCnt", fieldName: "yy15Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy15Step5MnhlCnt", fieldName: "yy15Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2016년 (yy16) ───────────────────────────────────────
{ name: "yy16WholMnhlCnt", fieldName: "yy16WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step1MnhlCnt", fieldName: "yy16Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step2MnhlCnt", fieldName: "yy16Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step3MnhlCnt", fieldName: "yy16Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step4MnhlCnt", fieldName: "yy16Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy16Step5MnhlCnt", fieldName: "yy16Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2017년 (yy17) ───────────────────────────────────────
{ name: "yy17WholMnhlCnt", fieldName: "yy17WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step1MnhlCnt", fieldName: "yy17Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step2MnhlCnt", fieldName: "yy17Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step3MnhlCnt", fieldName: "yy17Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step4MnhlCnt", fieldName: "yy17Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy17Step5MnhlCnt", fieldName: "yy17Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2018년 (yy18) ───────────────────────────────────────
{ name: "yy18WholMnhlCnt", fieldName: "yy18WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step1MnhlCnt", fieldName: "yy18Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step2MnhlCnt", fieldName: "yy18Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step3MnhlCnt", fieldName: "yy18Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step4MnhlCnt", fieldName: "yy18Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy18Step5MnhlCnt", fieldName: "yy18Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2019년 (yy19) ───────────────────────────────────────
{ name: "yy19WholMnhlCnt", fieldName: "yy19WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step1MnhlCnt", fieldName: "yy19Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step2MnhlCnt", fieldName: "yy19Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step3MnhlCnt", fieldName: "yy19Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step4MnhlCnt", fieldName: "yy19Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy19Step5MnhlCnt", fieldName: "yy19Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2020년 (yy20) ───────────────────────────────────────
{ name: "yy20WholMnhlCnt", fieldName: "yy20WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step1MnhlCnt", fieldName: "yy20Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step2MnhlCnt", fieldName: "yy20Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step3MnhlCnt", fieldName: "yy20Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step4MnhlCnt", fieldName: "yy20Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy20Step5MnhlCnt", fieldName: "yy20Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2021년 (yy21) ───────────────────────────────────────
{ name: "yy21WholMnhlCnt", fieldName: "yy21WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step1MnhlCnt", fieldName: "yy21Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step2MnhlCnt", fieldName: "yy21Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step3MnhlCnt", fieldName: "yy21Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step4MnhlCnt", fieldName: "yy21Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy21Step5MnhlCnt", fieldName: "yy21Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2022년 (yy22) ───────────────────────────────────────
{ name: "yy22WholMnhlCnt", fieldName: "yy22WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step1MnhlCnt", fieldName: "yy22Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step2MnhlCnt", fieldName: "yy22Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step3MnhlCnt", fieldName: "yy22Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step4MnhlCnt", fieldName: "yy22Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy22Step5MnhlCnt", fieldName: "yy22Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2023년 (yy23) ───────────────────────────────────────
{ name: "yy23WholMnhlCnt", fieldName: "yy23WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step1MnhlCnt", fieldName: "yy23Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step2MnhlCnt", fieldName: "yy23Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step3MnhlCnt", fieldName: "yy23Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step4MnhlCnt", fieldName: "yy23Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy23Step5MnhlCnt", fieldName: "yy23Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2024년 (yy24) ───────────────────────────────────────
{ name: "yy24WholMnhlCnt", fieldName: "yy24WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step1MnhlCnt", fieldName: "yy24Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step2MnhlCnt", fieldName: "yy24Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step3MnhlCnt", fieldName: "yy24Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step4MnhlCnt", fieldName: "yy24Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy24Step5MnhlCnt", fieldName: "yy24Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
// ── 2025년 (yy25) ───────────────────────────────────────
{ name: "yy25WholMnhlCnt", fieldName: "yy25WholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step1MnhlCnt", fieldName: "yy25Step1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step2MnhlCnt", fieldName: "yy25Step2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step3MnhlCnt", fieldName: "yy25Step3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step4MnhlCnt", fieldName: "yy25Step4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "yy25Step5MnhlCnt", fieldName: "yy25Step5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
],
columnLayout: [
{ name: "addrGroup", header: { text: "행정구역" }, items: ["ctpvNm", "sggNm"] },
"emphsMngSeNm",
{
name: "totGroup",
header: { text: "연도별총계" },
items: [
{ name: "totMtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["totWholMnhlCnt", "totStep1MnhlCnt", "totStep2MnhlCnt", "totStep3MnhlCnt", "totStep4MnhlCnt"] },
"totStep5MnhlCnt",
tabKey: "ST00020005",
tabLabel: "맨홀 현황",
rowStartNum: 2,
fixedColumns: 2,
columns: [
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역명" }, editable: false },
{ name: "emphsMngSeNm", fieldName: "emphsMngSeNm", header: { text: "집중강우\n중점관리\n구역구분" }, editable: false },
{ name: "wholMnhlCnt", fieldName: "wholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "frlvlMnhlCnt", fieldName: "frlvlMnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "sclvlMnhlCnt", fieldName: "sclvlMnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "thlvlMnhlCnt", fieldName: "thlvlMnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "folvlMnhlCnt", fieldName: "folvlMnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "filvlMnhlCnt", fieldName: "filvlMnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
],
columnLayout: [
{ name: "addrGroup", header: { text: "행정구역" }, items: ["ctpvNm", "sggNm", "admdstNm"] },
"emphsMngSeNm",
{
name: "mtrlGroup",
header: { text: "맨홀뚜껑 재질(개)" },
items: ["wholMnhlCnt", "frlvlMnhlCnt", "sclvlMnhlCnt", "thlvlMnhlCnt", "folvlMnhlCnt"],
},
"filvlMnhlCnt",
],
},
{
name: "yy14Group",
header: { text: "2015년 이전" }, // ← _14Y
items: [
{ name: "yy14MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy14WholMnhlCnt", "yy14Step1MnhlCnt", "yy14Step2MnhlCnt", "yy14Step3MnhlCnt", "yy14Step4MnhlCnt"] },
"yy14Step5MnhlCnt",
],
},
{
name: "yy15Group",
header: { text: "2015년" }, // ← _15Y
items: [
{ name: "yy15MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy15WholMnhlCnt", "yy15Step1MnhlCnt", "yy15Step2MnhlCnt", "yy15Step3MnhlCnt", "yy15Step4MnhlCnt"] },
"yy15Step5MnhlCnt",
],
},
{
name: "yy16Group",
header: { text: "2016년" }, // ← _16Y
items: [
{ name: "yy16MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy16WholMnhlCnt", "yy16Step1MnhlCnt", "yy16Step2MnhlCnt", "yy16Step3MnhlCnt", "yy16Step4MnhlCnt"] },
"yy16Step5MnhlCnt",
],
},
{
name: "yy17Group",
header: { text: "2017년" },
items: [
{ name: "yy17MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy17WholMnhlCnt", "yy17Step1MnhlCnt", "yy17Step2MnhlCnt", "yy17Step3MnhlCnt", "yy17Step4MnhlCnt"] },
"yy17Step5MnhlCnt",
],
},
{
name: "yy18Group",
header: { text: "2018년" },
items: [
{ name: "yy18MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy18WholMnhlCnt", "yy18Step1MnhlCnt", "yy18Step2MnhlCnt", "yy18Step3MnhlCnt", "yy18Step4MnhlCnt"] },
"yy18Step5MnhlCnt",
],
},
{
name: "yy19Group",
header: { text: "2019년" },
items: [
{ name: "yy19MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy19WholMnhlCnt", "yy19Step1MnhlCnt", "yy19Step2MnhlCnt", "yy19Step3MnhlCnt", "yy19Step4MnhlCnt"] },
"yy19Step5MnhlCnt",
],
},
{
name: "yy20Group",
header: { text: "2020년" },
items: [
{ name: "yy20MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy20WholMnhlCnt", "yy20Step1MnhlCnt", "yy20Step2MnhlCnt", "yy20Step3MnhlCnt", "yy20Step4MnhlCnt"] },
"yy20Step5MnhlCnt",
],
},
{
name: "yy21Group",
header: { text: "2021년" },
items: [
{ name: "yy21MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy21WholMnhlCnt", "yy21Step1MnhlCnt", "yy21Step2MnhlCnt", "yy21Step3MnhlCnt", "yy21Step4MnhlCnt"] },
"yy21Step5MnhlCnt",
],
},
{
name: "yy22Group",
header: { text: "2022년" },
items: [
{ name: "yy22MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy22WholMnhlCnt", "yy22Step1MnhlCnt", "yy22Step2MnhlCnt", "yy22Step3MnhlCnt", "yy22Step4MnhlCnt"] },
"yy22Step5MnhlCnt",
],
},
{
name: "yy23Group",
header: { text: "2023년" },
items: [
{ name: "yy23MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy23WholMnhlCnt", "yy23Step1MnhlCnt", "yy23Step2MnhlCnt", "yy23Step3MnhlCnt", "yy23Step4MnhlCnt"] },
"yy23Step5MnhlCnt",
],
},
{
name: "yy24Group",
header: { text: "2024년" },
items: [
{ name: "yy24MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy24WholMnhlCnt", "yy24Step1MnhlCnt", "yy24Step2MnhlCnt", "yy24Step3MnhlCnt", "yy24Step4MnhlCnt"] },
"yy24Step5MnhlCnt",
],
},
{
name: "yy25Group",
header: { text: "2025년" },
items: [
{ name: "yy25MtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["yy25WholMnhlCnt", "yy25Step1MnhlCnt", "yy25Step2MnhlCnt", "yy25Step3MnhlCnt", "yy25Step4MnhlCnt"] },
"yy25Step5MnhlCnt",
],
}
],
},
];
// 2. 동적 컬럼(ST00020003) 생성 함수
@@ -987,79 +789,7 @@ export const getST00020003Config = (statsYr: string | number) => {
};
};
/** ST00020005 — 맨홀 현황 동적 컬럼 생성
* 응답 필드: wholMnhlCnt{year}, step1~5MnhlCnt{year}
* firstYear: 데이터 시작 연도(기본 2014), lastYear: 조회 연도(statsYr)
*/
export const getST00020005Config = (statsYr: string | number) => {
const firstYear = 2014;
const lastYear = Number(statsYr) || new Date().getFullYear();
const mnhlYearCols = (year: number) => [
{ name: `wholMnhlCnt${year}`, fieldName: `wholMnhlCnt${year}`, header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: `step1MnhlCnt${year}`, fieldName: `step1MnhlCnt${year}`, header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: `step2MnhlCnt${year}`, fieldName: `step2MnhlCnt${year}`, header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: `step3MnhlCnt${year}`, fieldName: `step3MnhlCnt${year}`, header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: `step4MnhlCnt${year}`, fieldName: `step4MnhlCnt${year}`, header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: `step5MnhlCnt${year}`, fieldName: `step5MnhlCnt${year}`, header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
];
const mnhlYearLayout = (year: number) => ({
name: `yr${year}Group`,
header: { text: year === firstYear ? `${firstYear}년 이전` : `${year}년` },
items: [
{
name: `yr${year}MtrlGroup`,
header: { text: "맨홀뚜껑 재질(개)" },
items: [
`wholMnhlCnt${year}`,
`step1MnhlCnt${year}`,
`step2MnhlCnt${year}`,
`step3MnhlCnt${year}`,
`step4MnhlCnt${year}`,
],
},
`step5MnhlCnt${year}`,
],
});
const yearRange = Array.from({ length: lastYear - firstYear + 1 }, (_, i) => firstYear + i);
return {
tabKey: "ST00020005",
tabLabel: "맨홀 현황",
rowStartNum: 2,
fixedColumns: 2,
columns: [
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" }, editable: false },
{ name: "emphsMngSeNm", fieldName: "emphsMngSeNm", header: { text: "집중강우\n중점관리\n구역구분" }, editable: false },
{ name: "totWholMnhlCnt", fieldName: "totWholMnhlCnt", header: { text: "계" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep1MnhlCnt", fieldName: "totStep1MnhlCnt", header: { text: "주철" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep2MnhlCnt", fieldName: "totStep2MnhlCnt", header: { text: "주철(조화)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep3MnhlCnt", fieldName: "totStep3MnhlCnt", header: { text: "콘크리트" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep4MnhlCnt", fieldName: "totStep4MnhlCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
{ name: "totStep5MnhlCnt", fieldName: "totStep5MnhlCnt", header: { text: "추락방지시설\n설치맨홀(개)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0", editable: false },
...yearRange.flatMap(mnhlYearCols),
],
columnLayout: [
{ name: "addrGroup", header: { text: "행정구역" }, items: ["ctpvNm", "sggNm"] },
"emphsMngSeNm",
{
name: "totGroup",
header: { text: "연도별총계" },
items: [
{ name: "totMtrlGroup", header: { text: "맨홀뚜껑 재질(개)" }, items: ["totWholMnhlCnt", "totStep1MnhlCnt", "totStep2MnhlCnt", "totStep3MnhlCnt", "totStep4MnhlCnt"] },
"totStep5MnhlCnt",
],
},
...yearRange.map(mnhlYearLayout),
],
};
};
export const SewagePipeColumnInfoListTot: ColumnInfoList[] = rawSewagePipeTotList
.filter((tab) => tab.tabKey !== "ST00020005_PLACEHOLDER")
.map((tab) => ({ ...tab, columns: tab.columns.map((col) => ({ ...col, editable: false as const })) }));
const st00020003PlaceholderTab: ColumnInfoList = {
@@ -1071,14 +801,4 @@ const st00020003PlaceholderTab: ColumnInfoList = {
columnLayout: [],
};
const st00020005PlaceholderTab: ColumnInfoList = {
tabKey: "ST00020005",
tabLabel: "맨홀 현황",
rowStartNum: 2,
fixedColumns: 2,
columns: [],
columnLayout: [],
};
SewagePipeColumnInfoListTot.splice(2, 0, st00020003PlaceholderTab);
SewagePipeColumnInfoListTot.push(st00020005PlaceholderTab);
SewagePipeColumnInfoListTot.splice(2, 0, st00020003PlaceholderTab);
@@ -412,43 +412,19 @@ export interface SeweragePipeInputPipeDiameterStatusSubmitRequest extends Sewera
submitList: SeweragePipeInputPipeDiameterStatusSaveItem[];
}
/** 맨홀 현황 — 연도별 통계 항목 */
export interface SeweragePipeInputManholeYearlyStatItem {
manhYr: string;
wholMnhlCnt: number;
step1MnhlCnt: number;
step2MnhlCnt: number;
step3MnhlCnt: number;
step4MnhlCnt: number;
step5MnhlCnt: number;
}
type SeweragePipeInputManholeLegacyYy = 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25;
type SeweragePipeInputManholeLegacySuffix =
| "WholMnhlCnt"
| "Step1MnhlCnt"
| "Step2MnhlCnt"
| "Step3MnhlCnt"
| "Step4MnhlCnt"
| "Step5MnhlCnt";
type SeweragePipeInputManholeLegacyYearField =
`yy${SeweragePipeInputManholeLegacyYy}${SeweragePipeInputManholeLegacySuffix}`;
/** 맨홀 현황 — 목록 항목 */
export interface SeweragePipeInputManholeStatusItem
extends Partial<Record<SeweragePipeInputManholeLegacyYearField, number>> {
export interface SeweragePipeInputManholeStatusItem {
sggNm: string;
admdstCd: string;
admdstNm: string;
emphsMngSeCd: string;
emphsMngSeNm: string;
totalWholMnhlCnt: number;
totalStep1MnhlCnt: number;
totalStep2MnhlCnt: number;
totalStep3MnhlCnt: number;
totalStep4MnhlCnt: number;
totalStep5MnhlCnt: number;
yearlyStats?: SeweragePipeInputManholeYearlyStatItem[];
wholMnhlCnt: number;
frlvlMnhlCnt: number;
sclvlMnhlCnt: number;
thlvlMnhlCnt: number;
folvlMnhlCnt: number;
filvlMnhlCnt: number;
rmrk: string;
verificationResult?: SeweragePipeInputVerificationResult;
}
@@ -467,23 +443,16 @@ export interface SeweragePipeInputManholeResponse {
message?: string;
}
/** 맨홀 현황 저장 — yearlyStats 항목 */
export interface SeweragePipeInputManholeStatusSaveYearlyStatItem {
manhYr: string;
wholMnhlCnt: number;
step1MnhlCnt: number;
step2MnhlCnt: number;
step3MnhlCnt: number;
step4MnhlCnt: number;
step5MnhlCnt: number;
}
/** 맨홀 현황 저장 — saveList 항목 */
export interface SeweragePipeInputManholeStatusSaveItem {
emphsMngSeCd: string | null;
admdstCd: string | null;
admdstNm: string | null;
yearlyStats: SeweragePipeInputManholeStatusSaveYearlyStatItem[];
emphsMngSeCd: string | null;
frlvlMnhlCnt: number;
sclvlMnhlCnt: number;
thlvlMnhlCnt: number;
folvlMnhlCnt: number;
filvlMnhlCnt: number;
rmrk: string | null;
}
@@ -169,9 +169,31 @@ export interface CheckInputPeriodResponse {
data: CheckInputPeriodItem[];
}
/** 입력기간(INPT) 중 `current`인 항목 (없으면 null) */
export function getCurrentInputPeriod(
periods: CheckInputPeriodItem[] | null | undefined,
): CheckInputPeriodItem | null {
return (periods ?? []).find((p) => p.seType === "INPT" && p.current === true) ?? null;
}
/** 입력기간(INPT) 중 현재 구간이면 true */
export function isInputPeriodCurrent(periods: CheckInputPeriodItem[] | null | undefined): boolean {
return (periods ?? []).some((p) => p.seType === "INPT" && p.current === true);
return getCurrentInputPeriod(periods) !== null;
}
/**
* 입력기간이면 일반검증 항목 등록·수정 차단 안내, 아니면 null
*/
export function getInputPeriodVerificationManageBlockedMessage(
periods: CheckInputPeriodItem[] | null | undefined,
): string | null {
const v = getCurrentInputPeriod(periods);
if (!v) return null;
const from = formatCheckInputYmdAsDash(v.bgngYmd);
const to = formatCheckInputYmdAsDash(v.endYmd);
const range =
from && to ? `${from} ~ ${to}` : [from, to].filter(Boolean).join(" ~ ");
return `입력기간에는 검증항목을 등록하거나 수정할 수 없습니다.\n(입력기간: ${range})`;
}
/** `YYYYMMDD` → `yyyy-mm-dd` (그 외 형식은 그대로 반환) */
@@ -256,19 +256,14 @@ export interface SewagePipeReviewManholeItem {
displayYear: string;
rowType: 'OLD' | 'CURR';
emphsMngSeCd: string;
emphsMngSeNm?: string;
totWholMnhlCnt: number;
totStep1MnhlCnt: number;
totStep2MnhlCnt: number;
totStep3MnhlCnt: number;
totStep4MnhlCnt: number;
totStep5MnhlCnt: number;
emphsMngSeNm?: string | null;
wholMnhlCnt: number;
frlvlMnhlCnt: number;
sclvlMnhlCnt: number;
thlvlMnhlCnt: number;
folvlMnhlCnt: number;
filvlMnhlCnt: number;
rmrk: string | null;
/** 연도별 동적 필드 (wholMnhlCnt{year} ~ step5MnhlCnt{year}) */
[key: string]: string | number | null | undefined;
}
export interface SewagePipeManholeResponseData {
@@ -162,8 +162,7 @@ export interface SewagePipePipeDiamStatusResponse {
/**
* 맨홀 현황 응답 아이템 인터페이스
* 연도별 동적 필드: wholMnhlCnt{year}, step1~5MnhlCnt{year}
* ex) wholMnhlCnt2014, step1MnhlCnt2025
* rowType: SIDO_SUM(시도 합계/총계), BIZ_SUM(집중강우중점관리구역·그외)
*/
export interface FcltyManholeSummaryItem {
ctpvNm: string;
@@ -174,16 +173,12 @@ export interface FcltyManholeSummaryItem {
rowType: string;
emphsMngSeCd: string;
emphsMngSeNm: string;
totWholMnhlCnt: number;
totStep1MnhlCnt: number;
totStep2MnhlCnt: number;
totStep3MnhlCnt: number;
totStep4MnhlCnt: number;
totStep5MnhlCnt: number;
/** 연도별 동적 필드 (wholMnhlCnt{year} ~ step5MnhlCnt{year}) */
[key: string]: string | number;
wholMnhlCnt: number;
frlvlMnhlCnt: number;
sclvlMnhlCnt: number;
thlvlMnhlCnt: number;
folvlMnhlCnt: number;
filvlMnhlCnt: number;
}
/**
@@ -24,8 +24,8 @@ function LoginPage() {
await authService.login({ userId, password });
try {
await statisticsApi.refreshAllCommonCodes();
} catch {
// 로그인 성공 후 캐시 동기화 실패는 진입을 막지 않음
} catch(error) {
console.error('로그인 실패');
}
window.location.href = "/statistics";
} catch (error) {