Files
frontend/apps/sn/app/(map-layout)/map/components/agedPipe/agedPipeUtils.ts
T

324 lines
10 KiB
TypeScript

"use client";
import type { SelectDataType } from "@common/business_components";
import { formatDisplayData } from "@/components/lib/utils/formatDisplayValue";
import {
MS_SWAGE_PPL_CODES,
MS_SWAGE_PPL_USE_CODES,
} from "../common/table";
export const AGED_PIPE_LAYER_TYPE = "tb_ms_swage_ppl";
export const AGED_PIPE_DEFAULT_ELAPSED_YEARS = 30;
export const AGED_PIPE_PAGE_SIZE = 10;
export const AGED_PIPE_MIN_ZOOM = 13;
export const AGED_PIPE_GUIDE_MIN_ZOOM = 13.1;
export const AGED_PIPE_INVALID_INSTALL_YMD_PREFIXES = ["19000101"];
export type AgedPipeFilters = {
elapsedYears: number;
ctpvCd: string;
sggCd: string;
};
export type AgedPipeRow = {
fid: string;
lyrTypeNm: string;
rowNumber: number;
fcltMngNo: string;
instlYmd: string;
pdmtVl: string | number;
prlgVl: string | number;
usgCd: string;
mqltCd: string;
elapsedYears: number | string;
ctpvNm?: string;
sggNm?: string;
[key: string]: unknown;
};
type PipeSource = Record<string, unknown>;
const useMap = new Map(MS_SWAGE_PPL_USE_CODES.map((item) => [item.val.trim(), item.text]));
const materialMap = new Map(MS_SWAGE_PPL_CODES.map((item) => [item.val.trim(), item.text]));
export const AGED_PIPE_DEFAULT_FILTERS: AgedPipeFilters = {
elapsedYears: AGED_PIPE_DEFAULT_ELAPSED_YEARS,
ctpvCd: "0",
sggCd: "0",
};
const normalizeRegionFilterCode = (value: unknown, prefixLength: number) => {
const digits = String(value ?? "").replace(/[^0-9]/g, "");
if (digits === "" || digits === "0") return "0";
return digits.length > prefixLength ? digits.slice(0, prefixLength) : digits;
};
export const AGED_PIPE_LIST_HEADLIST = [
{ id: "rowNumber", title: "순번", widthPercent: 6 },
{ id: "fcltMngNo", title: "시설관리번호", widthPercent: 18 },
{ id: "instlYmd", title: "설치일자", widthPercent: 12 },
{ id: "pdmtVl", title: "관경(mm)", widthPercent: 10 },
{ id: "prlgVl", title: "연장(m)", widthPercent: 10 },
{ id: "usgCd", title: "용도", widthPercent: 12 },
{ id: "mqltCd", title: "재질", widthPercent: 18 },
{ id: "elapsedYears", title: "경과년도", widthPercent: 8, align: "right" },
];
export const AGED_PIPE_EXCEL_FIELDS = [
"ftreSmbolCd",
"fcltMngNo",
"pbadmsEmdCd",
"mapshtNo",
"mngInstCd",
"instlYmd",
"usgCd",
"mqltCd",
"fcltyShpCd",
"pdmtVl",
"wdthLen",
"vrtcLen",
"prlgVl",
"tmptDpth",
"scendDpth",
"avgGrdn",
"trflnCnt",
"exclnMltvArea",
"drtwMltvArea",
"rnyssnFvlcVl",
"clarFvlcVl",
"tmptMhlMngNo",
"scendMhlMngNo",
"instMngNo",
"fid",
"lyrTypeNm",
"swtrmtFcltCd",
"elapsedYears",
"ctpvNm",
"sggNm",
] as const;
export const AGED_PIPE_EXCEL_HEADER = {
ftreSmbolCd: "지형지물부호코드",
pbadmsEmdCd: "행정읍면동코드",
mapshtNo: "도엽번호",
mngInstCd: "관리기관코드",
wdthLen: "가로길이",
vrtcLen: "세로길이",
tmptDpth: "시점깊이",
scendDpth: "종점깊이",
avgGrdn: "평균구배",
trflnCnt: "차선수",
exclnMltvArea: "우수배수면적",
drtwMltvArea: "오수배수면적",
rnyssnFvlcVl: "우기유속값",
clarFvlcVl: "맑음유속값",
tmptMhlMngNo: "시점맨홀관리번호",
scendMhlMngNo: "종점맨홀관리번호",
instMngNo: "기관관리번호",
fid: "FID",
lyrTypeNm: "레이어타입명",
swtrmtFcltCd: "시설코드",
fcltyShpCd: "시설물형태코드",
fcltMngNo: "시설관리번호",
instlYmd: "설치일자",
pdmtVl: "관경(mm)",
prlgVl: "연장(m)",
usgCd: "용도",
mqltCd: "재질",
elapsedYears: "경과년도",
ctpvNm: "시도",
sggNm: "시군구",
};
const getExcelColumnLetter = (columnIndex: number) => {
let current = columnIndex;
let result = "";
while (current > 0) {
const remainder = (current - 1) % 26;
result = String.fromCharCode(65 + remainder) + result;
current = Math.floor((current - 1) / 26);
}
return result || "A";
};
export const AGED_PIPE_EXCEL_HEADER_RANGE = `A1:${getExcelColumnLetter(AGED_PIPE_EXCEL_FIELDS.length)}1`;
const pad2 = (value: number) => String(value).padStart(2, "0");
const normalizeYmd = (value: unknown) =>
String(value ?? "").replace(/[^0-9]/g, "").slice(0, 8);
const getToday = () => new Date();
export const normalizeAgedPipeFilters = (filters: AgedPipeFilters): AgedPipeFilters => {
const elapsedYears = Math.trunc(Number(filters.elapsedYears));
return {
...filters,
ctpvCd: normalizeRegionFilterCode(filters.ctpvCd, 2),
sggCd: normalizeRegionFilterCode(filters.sggCd, 5),
elapsedYears:
Number.isFinite(elapsedYears) && elapsedYears >= 1 && elapsedYears <= 100
? elapsedYears
: AGED_PIPE_DEFAULT_ELAPSED_YEARS,
};
};
export const validateElapsedYears = (value: number) =>
Number.isInteger(value) && value >= 1 && value <= 100;
export const getCutoffYmd = (elapsedYears: number) => {
const today = getToday();
const cutoff = new Date(today.getFullYear() - elapsedYears, today.getMonth(), today.getDate());
return `${cutoff.getFullYear()}${pad2(cutoff.getMonth() + 1)}${pad2(cutoff.getDate())}`;
};
export const getElapsedYears = (value: unknown) => {
const normalized = normalizeYmd(value);
if (normalized.length < 4 || isInvalidInstallYmd(value)) return null;
const year = Number(normalized.slice(0, 4));
const month = Number(normalized.slice(4, 6) || "1");
const day = Number(normalized.slice(6, 8) || "1");
if (!year) return null;
const today = getToday();
let elapsed = today.getFullYear() - year;
if (
today.getMonth() + 1 < month ||
(today.getMonth() + 1 === month && today.getDate() < day)
) {
elapsed -= 1;
}
return Math.max(elapsed, 0);
};
export const isInvalidInstallYmd = (value: unknown) => {
const normalized = normalizeYmd(value);
return (
normalized.length < 8 ||
AGED_PIPE_INVALID_INSTALL_YMD_PREFIXES.some((prefix) => normalized.startsWith(prefix))
);
};
export const getElapsedYearsDisplayText = (value: unknown) => {
const elapsedYears = getElapsedYears(value);
return elapsedYears == null ? "확인불가" : `${elapsedYears}년`;
};
export const buildAgedPipeCqlFilters = (filters: AgedPipeFilters) => {
const normalized = normalizeAgedPipeFilters(filters);
const cqlFilters = [
"instl_ymd IS NOT NULL",
...AGED_PIPE_INVALID_INSTALL_YMD_PREFIXES.map((prefix) => `instl_ymd NOT LIKE '${prefix}%'`),
`instl_ymd <= '${getCutoffYmd(normalized.elapsedYears)}'`,
];
if (normalized.sggCd !== "0") {
cqlFilters.push(`pbadms_emd_cd LIKE '${normalized.sggCd}%'`);
} else if (normalized.ctpvCd !== "0") {
cqlFilters.push(`pbadms_emd_cd LIKE '${normalized.ctpvCd}%'`);
}
return cqlFilters;
};
const getRegionCode = (source: PipeSource) =>
String(source.pbadmsEmdCd ?? source.pbadms_emd_cd ?? "").trim();
export const matchesAgedPipeFilters = (source: PipeSource, filters: AgedPipeFilters) => {
const normalized = normalizeAgedPipeFilters(filters);
const instlYmd = normalizeYmd(source.instl_ymd ?? source.instlYmd);
if (!instlYmd || isInvalidInstallYmd(instlYmd) || instlYmd > getCutoffYmd(normalized.elapsedYears)) return false;
const regionCode = getRegionCode(source);
if (normalized.sggCd !== "0") return regionCode.startsWith(normalized.sggCd);
if (normalized.ctpvCd !== "0") return regionCode.startsWith(normalized.ctpvCd);
return true;
};
const resolveRegionName = (
code: string,
directName: unknown,
options: SelectDataType[],
prefixLength: number,
) => {
const direct = String(directName ?? "").trim();
if (direct) return direct;
const prefix = code.slice(0, prefixLength);
return options.find((item) => String(item.val).trim() === prefix)?.text ?? "";
};
export const toDisplayPipeRow = (
source: PipeSource,
index: number,
page: number,
ctpvOptions: SelectDataType[],
sggOptions: SelectDataType[],
): AgedPipeRow => {
const formatted = formatDisplayData(source) as PipeSource;
const codePrefix = getRegionCode(source);
const instlYmd = formatted.instlYmd ?? source.instlYmd ?? formatted.instl_ymd ?? source.instl_ymd;
const invalidInstallYmd = isInvalidInstallYmd(source.instlYmd ?? source.instl_ymd ?? instlYmd);
const trimmedUse = String(source.usgCd ?? source.usg_cd ?? "").trim();
const trimmedMaterial = String(source.mqltCd ?? source.mqlt_cd ?? "").trim();
const pdmtVl = formatted.pdmtVl ?? source.pdmtVl ?? source.pdmt_vl ?? "-";
const prlgVl = formatted.prlgVl ?? source.prlgVl ?? source.prlg_vl ?? "-";
const fcltMngNo = String(formatted.fcltMngNo ?? source.fcltMngNo ?? source.fclt_mng_no ?? "-");
return {
...source,
...formatted,
fid: String(source.fid ?? ""),
lyrTypeNm: String(source.lyrTypeNm ?? AGED_PIPE_LAYER_TYPE),
rowNumber: (page - 1) * AGED_PIPE_PAGE_SIZE + index + 1,
fcltMngNo,
swtrmtFcltCd: source.swtrmtFcltCd ?? fcltMngNo,
instlYmd: invalidInstallYmd ? "확인불가" : String(instlYmd ?? "-"),
pdmtVl: typeof pdmtVl === "number" ? pdmtVl : String(pdmtVl),
prlgVl: typeof prlgVl === "number" ? prlgVl : String(prlgVl),
usgCd: useMap.get(trimmedUse) ?? String(formatted.usgCd ?? source.usgCd ?? source.usg_cd ?? "-"),
mqltCd:
materialMap.get(trimmedMaterial) ??
String(formatted.mqltCd ?? source.mqltCd ?? source.mqlt_cd ?? "-"),
elapsedYears: getElapsedYears(source.instlYmd ?? source.instl_ymd) ?? "확인불가",
ctpvNm: resolveRegionName(
codePrefix,
formatted.ctpvNm ?? source.ctpvNm ?? source.ctpv_nm,
ctpvOptions,
2,
),
sggNm: resolveRegionName(
codePrefix,
formatted.sggNm ?? source.sggNm ?? source.sgg_nm,
sggOptions,
5,
),
};
};
export const toAgedPipeExcelRow = (row: AgedPipeRow) => {
const exportRow: Record<string, unknown> = {};
AGED_PIPE_EXCEL_FIELDS.forEach((field) => {
exportRow[field] = row[field] ?? "-";
});
return exportRow;
};
export const isAgedPipeLayerNode = (node: any) => {
const data = node?.data ?? node;
const layerName = String(data?.lyrNm ?? node?.text ?? "");
const layerTypeName = String(data?.lyrTyNm ?? "");
return (
layerTypeName === "tb_old_pipe" ||
layerName.includes("노후관로")
);
};