282 lines
9.9 KiB
TypeScript
282 lines
9.9 KiB
TypeScript
import { DREDGING_EXTERNAL_MODE } from "./disclosure";
|
|
/** 준설 목록·집계 표의 컬럼·표시 변환. 서버 조회/React 상태와 분리한다. */
|
|
import type { DredgingRecord } from "./types";
|
|
import { MS_SWAGE_PPL_CODES } from "@/entities/facility/model/facility-code-fallbacks";
|
|
|
|
export const PAGE_SIZE = 10;
|
|
|
|
export const ALL_REGION_OPTION = { text: "전체", val: "0" };
|
|
|
|
export const pipeMaterialNameByCode = new Map(
|
|
MS_SWAGE_PPL_CODES.map((item) => [item.val.trim(), item.text]),
|
|
);
|
|
|
|
export const resolvePipeMaterialName = (
|
|
value: string,
|
|
names: ReadonlyMap<string, string> = pipeMaterialNameByCode,
|
|
) => {
|
|
const normalizedValue = value.trim();
|
|
if (!normalizedValue) return "확인불가";
|
|
const normalizedCode = normalizedValue.toUpperCase();
|
|
if (!/^MOP\d+$/.test(normalizedCode)) return normalizedValue;
|
|
return names.get(normalizedCode) ?? "확인불가";
|
|
};
|
|
|
|
// 백엔드·프록시 상세 오류 대신 사용자가 다음 행동을 알 수 있는 문구만 표시한다.
|
|
export const DREDGING_LOAD_ERROR_MESSAGE =
|
|
"준설현황을 불러오지 못했습니다. 잠시 후 다시 시도해주세요.";
|
|
|
|
export const currentYear = new Date().getFullYear();
|
|
|
|
export const formatLocalDate = (date: Date) => {
|
|
const year = date.getFullYear();
|
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
const day = String(date.getDate()).padStart(2, "0");
|
|
return `${year}-${month}-${day}`;
|
|
};
|
|
|
|
/** 준설일자 기본 검색기간을 오늘까지의 최근 1년으로 생성한다. */
|
|
export const createDefaultDredgingDateRange = () => {
|
|
const to = new Date();
|
|
const from = new Date(to);
|
|
from.setFullYear(from.getFullYear() - 1);
|
|
return {
|
|
fromDate: formatLocalDate(from),
|
|
toDate: formatLocalDate(to),
|
|
};
|
|
};
|
|
|
|
export const initialRecords: DredgingRecord[] = [
|
|
{
|
|
id: "mock-dredging-1",
|
|
dredgingJobSn: "mock-dredging-job-1",
|
|
pipeFid: "MOCK-PIPE-1",
|
|
mngInstCd: "4173000",
|
|
ctpv: "경기도",
|
|
sgg: "양평군",
|
|
treatmentZone: "양평처리구역",
|
|
fcltMngNo: "PPL-DEMO-001",
|
|
pipeDiameterMm: 450,
|
|
pipeLengthM: 82.4,
|
|
pipeMaterial: "콘크리트",
|
|
installationYear: 2004,
|
|
dredgingDate: `${currentYear}-05-14`,
|
|
photoCount: 3,
|
|
registeredDate: `${currentYear}-05-15`,
|
|
longitude: 127.4875,
|
|
latitude: 37.4916,
|
|
snappedLongitude: 127.48751,
|
|
snappedLatitude: 37.49159,
|
|
registrationMode: "PHOTO",
|
|
registrationSource: "EXIF",
|
|
matchDistanceMeters: 4.28,
|
|
isMock: true,
|
|
},
|
|
{
|
|
id: "mock-dredging-2",
|
|
dredgingJobSn: "mock-dredging-job-2",
|
|
pipeFid: "MOCK-PIPE-2",
|
|
mngInstCd: "1153000",
|
|
ctpv: "서울특별시",
|
|
sgg: "구로구",
|
|
treatmentZone: "서남처리구역",
|
|
fcltMngNo: "PPL-DEMO-002",
|
|
pipeDiameterMm: 900,
|
|
pipeLengthM: 116.7,
|
|
pipeMaterial: "PVC",
|
|
installationYear: 1992,
|
|
dredgingDate: `${currentYear - 1}-11-08`,
|
|
photoCount: 0,
|
|
registeredDate: `${currentYear - 1}-11-09`,
|
|
longitude: 126.8874,
|
|
latitude: 37.4954,
|
|
snappedLongitude: 126.88741,
|
|
snappedLatitude: 37.49539,
|
|
registrationMode: "MANUAL",
|
|
registrationSource: "MANUAL",
|
|
matchDistanceMeters: 8.15,
|
|
isMock: true,
|
|
},
|
|
];
|
|
|
|
/** 상세·수정 팝업이 동일한 관로·위치 정보를 받도록 쿼리 파라미터를 한곳에서 구성한다. */
|
|
export const buildDredgingPopupParams = (record: DredgingRecord) => ({
|
|
sourcePath: window.location.pathname,
|
|
id: record.id,
|
|
dredgingJobSn: record.dredgingJobSn,
|
|
targetSn: record.targetSn,
|
|
mappingStatus: record.mappingStatus,
|
|
pipeFid: record.pipeFid,
|
|
mngInstCd: record.mngInstCd,
|
|
ctpv: record.ctpv,
|
|
sgg: record.sgg,
|
|
treatmentZone: record.treatmentZone,
|
|
fcltMngNo: record.fcltMngNo,
|
|
pipeDiameterMm: record.pipeDiameterMm,
|
|
pipeLengthM: record.pipeLengthM,
|
|
pipeMaterial: record.pipeMaterial,
|
|
installationYear: record.installationYear,
|
|
dredgingDate: record.dredgingDate,
|
|
photoCount: record.photoCount,
|
|
registeredDate: record.registeredDate,
|
|
longitude: record.longitude,
|
|
latitude: record.latitude,
|
|
snappedLongitude: record.snappedLongitude,
|
|
snappedLatitude: record.snappedLatitude,
|
|
registrationMode:
|
|
record.registrationMode ??
|
|
(record.photoCount === 0 && record.registrationSource === "MANUAL"
|
|
? "MANUAL"
|
|
: "PHOTO"),
|
|
registrationSource: record.registrationSource,
|
|
matchDistanceMeters: record.matchDistanceMeters,
|
|
isMock: record.isMock,
|
|
});
|
|
|
|
export type TableRow = Record<string, unknown> & {
|
|
recordIds: string[];
|
|
};
|
|
|
|
export type TableHeadDefinition = {
|
|
id: string;
|
|
title: string;
|
|
align?: "left" | "center" | "right";
|
|
};
|
|
|
|
const fullMunicipalityHeadlist: TableHeadDefinition[] = [
|
|
{ id: "mappingStatusLabel", title: "관로 지정" },
|
|
{ id: "rowNumber", title: "순번" },
|
|
{ id: "ctpv", title: "시도" },
|
|
{ id: "sgg", title: "시군구" },
|
|
{ id: "treatmentZone", title: "처리구역" },
|
|
{ id: "fcltMngNo", title: "관로 번호" },
|
|
{ id: "pipeDiameterMm", title: "관경(mm)", align: "right" },
|
|
{ id: "pipeLengthM", title: "관로연장(m)", align: "right" },
|
|
{ id: "pipeMaterial", title: "관재질" },
|
|
{ id: "installationYear", title: "매설년도" },
|
|
{ id: "elapsedYears", title: "경과년도" },
|
|
{ id: "dredgingDate", title: "준설일자" },
|
|
{ id: "photoCount", title: "등록사진건수", align: "right" },
|
|
{ id: "registeredDate", title: "등록일자" },
|
|
];
|
|
|
|
export const municipalityHeadlist = DREDGING_EXTERNAL_MODE
|
|
? fullMunicipalityHeadlist.filter(column => ["rowNumber", "ctpv", "sgg", "dredgingDate", "photoCount", "registeredDate"].includes(column.id))
|
|
: fullMunicipalityHeadlist;
|
|
|
|
export const diameterHeadlist: TableHeadDefinition[] = [
|
|
{ id: "ctpv", title: "시도" },
|
|
{ id: "totalLengthKm", title: "관로연장(km)", align: "right" },
|
|
{ id: "diameter450", title: "450 이하", align: "right" },
|
|
{ id: "diameter600", title: "450~600", align: "right" },
|
|
{ id: "diameter700", title: "700", align: "right" },
|
|
{ id: "diameter900", title: "800~900", align: "right" },
|
|
{ id: "diameter1000", title: "1000", align: "right" },
|
|
{ id: "diameter1200", title: "1200", align: "right" },
|
|
{ id: "diameter1500", title: "1500 이상", align: "right" },
|
|
];
|
|
|
|
export const elapsedYearHeadlist: TableHeadDefinition[] = [
|
|
{ id: "ctpv", title: "시도" },
|
|
{ id: "totalLengthKm", title: "관로연장(km)", align: "right" },
|
|
{ id: "under10", title: "10년 미만", align: "right" },
|
|
{ id: "year10To20", title: "10년~20년", align: "right" },
|
|
{ id: "year20To30", title: "20년~30년", align: "right" },
|
|
{ id: "over30", title: "30년 이상", align: "right" },
|
|
{ id: "unknown", title: "확인불가", align: "right" },
|
|
];
|
|
|
|
export const roundKm = (meters: number) => Number((meters / 1000).toFixed(3));
|
|
|
|
export const parseLocalDate = (value: string) => new Date(`${value}T00:00:00`);
|
|
|
|
export const formatTableValue = (value: unknown) => {
|
|
if (typeof value === "number") return value.toLocaleString("ko-KR");
|
|
if (value === null || value === undefined) return "";
|
|
return String(value);
|
|
};
|
|
|
|
export const groupByCtpv = (records: DredgingRecord[]) => {
|
|
const groups = new Map<string, DredgingRecord[]>();
|
|
for (const record of records) {
|
|
groups.set(record.ctpv, [...(groups.get(record.ctpv) ?? []), record]);
|
|
}
|
|
return groups;
|
|
};
|
|
|
|
export const buildDiameterRows = (records: DredgingRecord[]): TableRow[] =>
|
|
Array.from(groupByCtpv(records), ([ctpv, items]) => {
|
|
const sumLength = (predicate: (record: DredgingRecord) => boolean) =>
|
|
roundKm(
|
|
items
|
|
.filter(predicate)
|
|
.reduce((sum, record) => sum + (record.pipeLengthM ?? 0), 0),
|
|
);
|
|
|
|
return {
|
|
ctpv,
|
|
totalLengthKm: roundKm(
|
|
items.reduce((sum, record) => sum + (record.pipeLengthM ?? 0), 0),
|
|
),
|
|
diameter450: sumLength((record) => (record.pipeDiameterMm ?? 0) <= 450),
|
|
diameter600: sumLength(
|
|
(record) => (record.pipeDiameterMm ?? 0) > 450 && (record.pipeDiameterMm ?? 0) <= 600,
|
|
),
|
|
diameter700: sumLength(
|
|
(record) => (record.pipeDiameterMm ?? 0) > 600 && (record.pipeDiameterMm ?? 0) <= 700,
|
|
),
|
|
diameter900: sumLength(
|
|
(record) => (record.pipeDiameterMm ?? 0) > 700 && (record.pipeDiameterMm ?? 0) <= 900,
|
|
),
|
|
diameter1000: sumLength(
|
|
(record) =>
|
|
(record.pipeDiameterMm ?? 0) > 900 && (record.pipeDiameterMm ?? 0) <= 1000,
|
|
),
|
|
diameter1200: sumLength(
|
|
(record) =>
|
|
(record.pipeDiameterMm ?? 0) > 1000 && (record.pipeDiameterMm ?? 0) <= 1200,
|
|
),
|
|
diameter1500: sumLength((record) => (record.pipeDiameterMm ?? 0) >= 1500),
|
|
recordIds: items.map((record) => record.id),
|
|
};
|
|
});
|
|
|
|
export const buildElapsedYearRows = (records: DredgingRecord[]): TableRow[] =>
|
|
Array.from(groupByCtpv(records), ([ctpv, items]) => {
|
|
const sumLength = (predicate: (record: DredgingRecord) => boolean) =>
|
|
roundKm(
|
|
items
|
|
.filter(predicate)
|
|
.reduce((sum, record) => sum + (record.pipeLengthM ?? 0), 0),
|
|
);
|
|
const elapsedYears = (record: DredgingRecord) =>
|
|
record.installationYear === null
|
|
? null
|
|
: currentYear - record.installationYear;
|
|
|
|
return {
|
|
ctpv,
|
|
totalLengthKm: roundKm(
|
|
items.reduce((sum, record) => sum + (record.pipeLengthM ?? 0), 0),
|
|
),
|
|
under10: sumLength((record) => {
|
|
const elapsed = elapsedYears(record);
|
|
return elapsed !== null && elapsed < 10;
|
|
}),
|
|
year10To20: sumLength((record) => {
|
|
const elapsed = elapsedYears(record);
|
|
return elapsed !== null && elapsed >= 10 && elapsed < 20;
|
|
}),
|
|
year20To30: sumLength((record) => {
|
|
const elapsed = elapsedYears(record);
|
|
return elapsed !== null && elapsed >= 20 && elapsed < 30;
|
|
}),
|
|
over30: sumLength((record) => {
|
|
const elapsed = elapsedYears(record);
|
|
return elapsed !== null && elapsed >= 30;
|
|
}),
|
|
unknown: sumLength((record) => elapsedYears(record) === null),
|
|
recordIds: items.map((record) => record.id),
|
|
};
|
|
});
|