cm: 하수관로현황
This commit is contained in:
@@ -52,11 +52,14 @@ export function middleware(req: NextRequest) {
|
||||
}
|
||||
|
||||
|
||||
if (pathname.startsWith("/sewerage-service")) {
|
||||
if (pathname.startsWith("/server/sewerage-service")) {
|
||||
const requestHeaders = new Headers(req.headers);
|
||||
const token = req.cookies.get(CLAIM_NAME)?.value;
|
||||
const userId = req.cookies.get(AUTH_USER_ID)?.value;
|
||||
const newUrl = `${serverApiUrl2}${pathname}`;
|
||||
|
||||
// const decodedQuery = toDecodedQueryString(searchParams);
|
||||
const newUrl = `${serverApiUrl2}${pathname.replace("/server/", "")}${searchParams ? `?${searchParams}` : ""}`;
|
||||
// const newUrl = `${serverApiUrl2}${pathname}`;
|
||||
|
||||
if (token) requestHeaders.set(CLAIM_NAME, token);
|
||||
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
|
||||
@@ -93,7 +96,6 @@ export function middleware(req: NextRequest) {
|
||||
const token = req.cookies.get(CLAIM_NAME)?.value;
|
||||
const userId = req.cookies.get(AUTH_USER_ID)?.value;
|
||||
const newUrl = `${serverApiUrl2}${pathname.replace("/server/", "")}`;
|
||||
console.log("==========================================" + newUrl);
|
||||
|
||||
if (token) requestHeaders.set(CLAIM_NAME, token);
|
||||
if (userId) requestHeaders.set(AUTH_USER_ID, userId);
|
||||
|
||||
@@ -14,8 +14,14 @@ export type ReuseStateActions = {
|
||||
|
||||
export type ReuseStore = ReuseState & ReuseStateActions;
|
||||
|
||||
const initialData: ReuseStatusContent[] = [
|
||||
{ yr: '2021', sewage: 7335803, reclaimed: 254805510.3, rainwater: 14049224.5 },
|
||||
{ yr: '2022', sewage: 7378907, reclaimed: 243457788.8, rainwater: 15128093.4 },
|
||||
{ yr: '2023', sewage: 7537151, reclaimed: 222475997, rainwater: 13944732.5 },
|
||||
{ yr: '2024', sewage: 7479136, reclaimed: 265625917.7, rainwater: 13305309.5 },
|
||||
];
|
||||
export const defaultInitState: ReuseState = {
|
||||
reuseStatus: null,
|
||||
reuseStatus: initialData,
|
||||
};
|
||||
|
||||
export const createReuseStore = (initState: ReuseState = defaultInitState) =>
|
||||
@@ -30,8 +36,10 @@ export const reuseStore = createReuseStore();
|
||||
export class OperationApiService implements OperationService {
|
||||
// 여러 컴포넌트에서 호출해도 서버 호출은 1번만 되도록 캐싱
|
||||
public async getReuseStatus(): Promise<ReuseStatusContent[]> {
|
||||
// API 호출 시에는 항상 최신 데이터를 가져오도록 캐싱 로직을 제거하거나 수정할 수 있습니다.
|
||||
// 여기서는 기존 캐싱 로직을 유지하되, 초기 데이터가 아닌 경우에만 API를 호출하도록 합니다.
|
||||
const { reuseStatus, setReuseStatus } = reuseStore.getState();
|
||||
if (reuseStatus && reuseStatus.length > 0) {
|
||||
if (reuseStatus && reuseStatus.length > 0 && reuseStatus !== initialData) {
|
||||
return reuseStatus;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
// apps/cm/src/pages/dashboard/api/sn-service.ts
|
||||
import { client } from "@/shared/api";
|
||||
import { createStore } from "zustand/vanilla";
|
||||
import { ENDPOINT } from "@/shared/config";
|
||||
import type { PplLengthContent, SewerageService } from "../model/sn-interface";
|
||||
|
||||
export type SnState = {
|
||||
snStatus: PplLengthContent[] | null;
|
||||
};
|
||||
|
||||
export type SnStateActions = {
|
||||
setSnStatus: (snStatus: PplLengthContent[] | null) => void;
|
||||
};
|
||||
|
||||
export type SnStore = SnState & SnStateActions;
|
||||
|
||||
export const defaultInitState: SnState = {
|
||||
snStatus: null,
|
||||
};
|
||||
|
||||
export const createSnStore = (initState: SnState = defaultInitState) =>
|
||||
createStore<SnStore>()((set) => ({
|
||||
...initState,
|
||||
setSnStatus: (snStatus) => set(() => ({ snStatus })),
|
||||
}));
|
||||
|
||||
// 전역으로 공유할 store 인스턴스
|
||||
// export const snStore = createSnStore();
|
||||
|
||||
export class SewerageApiService implements SewerageService {
|
||||
// 기간별 관로 총길이
|
||||
public async getPplLength(instlYmdFrom: string, instlYmdTo: string): Promise<PplLengthContent[]> {
|
||||
const response = await client.get<{
|
||||
PplLengthList: PplLengthContent[];
|
||||
}>(ENDPOINT.SEWERAGE_SERVICE.PPL_LENGTH, {
|
||||
params: {
|
||||
instlYmdFrom,
|
||||
instlYmdTo,
|
||||
},
|
||||
});
|
||||
|
||||
const list = (response.data.PplLengthList ?? []).map((item) => ({
|
||||
...item,
|
||||
sumPrlgVl: Number(item.sumPrlgVl),
|
||||
}));
|
||||
return list;
|
||||
}
|
||||
|
||||
// 기간별 용도별 관로 총길이 : useCd - SBA004 오수관, SBA003 우수관
|
||||
public async getPplUseLength(instlYmdFrom: string, instlYmdTo: string, useCd: string): Promise<PplLengthContent[]> {
|
||||
const response = await client.get<{
|
||||
PplLengthList: PplLengthContent[];
|
||||
}>(ENDPOINT.SEWERAGE_SERVICE.PPL_USE_LENGTH, {
|
||||
params: {
|
||||
instlYmdFrom,
|
||||
instlYmdTo,
|
||||
useCd,
|
||||
},
|
||||
});
|
||||
|
||||
const list = (response.data.PplLengthList ?? []).map((item) => ({
|
||||
...item,
|
||||
sumPrlgVl: Number(item.sumPrlgVl),
|
||||
}));
|
||||
return list;
|
||||
}
|
||||
}
|
||||
|
||||
export const SewerageApi = new SewerageApiService();
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface PplLengthContent {
|
||||
zoneNm: string;
|
||||
sumPrlgVl: number;
|
||||
}
|
||||
|
||||
export interface SewerageService {
|
||||
getPplLength(ymdFrom: string, ymdTo: string, useCd: string): Promise<PplLengthContent[]>;
|
||||
getPplUseLength(ymdFrom: string, ymdTo: string, useCd: string): Promise<PplLengthContent[]>;
|
||||
}
|
||||
@@ -15,16 +15,6 @@ const ReuseStatus: React.FC = () => {
|
||||
(state) => state.reuseStatus ) as ReuseStatusContent[];
|
||||
|
||||
useEffect(() => {
|
||||
// const fetchData = async () => {
|
||||
// try {
|
||||
// const data = await OperationApi.getReuseStatus();
|
||||
// setReuseStatusData(data || []); // API 응답이 null/undefined일 경우 빈 배열로 설정
|
||||
// } catch (error) {
|
||||
// console.error('물재이용 현황 데이터 조회에 실패했습니다.', error);
|
||||
// }
|
||||
// };
|
||||
// fetchData();
|
||||
// 여러 컴포넌트에서 호출해도 실제 API는 1번만
|
||||
OperationApi.getReuseStatus().catch((e) => {
|
||||
console.error("물재이용 현황 데이터 조회 실패", e);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
/**
|
||||
* 노후 하수관로 현황
|
||||
*/
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
@@ -14,44 +15,84 @@ import {
|
||||
} from "chart.js";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
import { Button } from "@common/components";
|
||||
import type { PplLengthContent } from "../model/sn-interface";
|
||||
import { SewerageApi } from "../api/sn-service";
|
||||
|
||||
interface ChartDataSet {
|
||||
label: string;
|
||||
data: (number | undefined)[];
|
||||
backgroundColor: string;
|
||||
borderRadius?: number;
|
||||
}
|
||||
// ChartJS 컴포넌트 등록
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
LinearScale,
|
||||
BarElement,
|
||||
Title,
|
||||
Tooltip,
|
||||
Legend
|
||||
);
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
export default function SnOldPpl() {
|
||||
// 차트 데이터
|
||||
const labels = ["한강", "낙동강", "영산강", "금강"];
|
||||
const [oldPpl2025Data, setOldPpl2025Data] = useState<PplLengthContent[]>([]);
|
||||
const [oldPpl2530Data, setOldPpl2530Data] = useState<PplLengthContent[]>([]);
|
||||
const [oldPpl30Data, setOldPpl30Data] = useState<PplLengthContent[]>([]);
|
||||
const [chartData, setChartData] = useState<{
|
||||
labels: string[];
|
||||
datasets: ChartDataSet[];
|
||||
}>({ labels: [], datasets: [] });
|
||||
|
||||
const datasets = [
|
||||
{
|
||||
label: '20-25년',
|
||||
data: [3859, 1066, 1929, 2828],
|
||||
backgroundColor: '#3B82F6', // 파란색
|
||||
borderRadius: 4,
|
||||
},
|
||||
{
|
||||
label: '25년-30년',
|
||||
data: [3153, 1420, 1204, 2877],
|
||||
backgroundColor: '#EF4444', // 빨간색
|
||||
borderRadius: 4,
|
||||
},
|
||||
{
|
||||
label: '30년이상',
|
||||
data: [9404, 1835, 1821, 8115],
|
||||
backgroundColor: '#F87171', // 연한 빨간색/분홍색
|
||||
borderRadius: 4,
|
||||
},
|
||||
];
|
||||
useEffect(() => {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const ranges = {
|
||||
"20-25년": { from: `${currentYear - 25 + 1}0101`, to: `${currentYear - 20}1231`, setter: setOldPpl2025Data },
|
||||
"25-30년": { from: `${currentYear - 30 + 1}0101`, to: `${currentYear - 25}1231`, setter: setOldPpl2530Data },
|
||||
"30년이상": { from: "19010101", to: `${currentYear - 30}1231`, setter: setOldPpl30Data },
|
||||
};
|
||||
|
||||
Object.entries(ranges).forEach(([label, range]) => {
|
||||
SewerageApi.getPplLength(range.from, range.to)
|
||||
.then(range.setter)
|
||||
.catch((e) => console.error(`${label} 데이터 조회 실패`, e));
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const allData = [...oldPpl2025Data, ...oldPpl2530Data, ...oldPpl30Data];
|
||||
if (allData.length === 0) return;
|
||||
|
||||
const allZones = Array.from(new Set(allData.map(d => d.zoneNm)));
|
||||
|
||||
const createDataset = (label: string, data: PplLengthContent[], color: string) => {
|
||||
const dataMap = new Map(data.map(item => [item.zoneNm, item.sumPrlgVl]));
|
||||
console.log(dataMap);
|
||||
return {
|
||||
label,
|
||||
data: allZones.map(zone => dataMap.get(zone) ?? 0),
|
||||
backgroundColor: color,
|
||||
borderRadius: 4,
|
||||
};
|
||||
};
|
||||
|
||||
const newDatasets: ChartDataSet[] = [];
|
||||
if (oldPpl2025Data.length > 0) {
|
||||
newDatasets.push(createDataset('20-25년', oldPpl2025Data, '#3B82F6'));
|
||||
}
|
||||
if (oldPpl2530Data.length > 0) {
|
||||
newDatasets.push(createDataset('25-30년', oldPpl2530Data, '#EF4444'));
|
||||
}
|
||||
if (oldPpl30Data.length > 0) {
|
||||
newDatasets.push(createDataset('30년이상', oldPpl30Data, '#F87171'));
|
||||
}
|
||||
|
||||
setChartData({
|
||||
labels: allZones,
|
||||
datasets: newDatasets,
|
||||
});
|
||||
|
||||
}, [oldPpl2025Data, oldPpl2530Data, oldPpl30Data]);
|
||||
|
||||
// 각 지역별 총 길이 계산
|
||||
const totalLengths = [44251, 33786, 11888, 14878];
|
||||
const totalLengths = chartData.labels.map((label, i) => {
|
||||
return chartData.datasets.reduce((sum, dataset) => {
|
||||
const value = dataset.data[i];
|
||||
return sum + (typeof value === 'number' ? value : 0);
|
||||
}, 0);
|
||||
});
|
||||
|
||||
// 차트 옵션 설정
|
||||
const options: ChartOptions<"bar"> = {
|
||||
@@ -104,30 +145,25 @@ export default function SnOldPpl() {
|
||||
const ctx = chart.ctx;
|
||||
|
||||
// 각 데이터셋의 마지막 데이터셋의 인덱스
|
||||
const lastDatasetIndex = datasets.length - 1;
|
||||
console.log(lastDatasetIndex);
|
||||
if (chartData.datasets.length === 0) return;
|
||||
// const lastMeta = chart.getDatasetMeta(chartData.datasets.length - 1);
|
||||
// 텍스트 스타일 설정
|
||||
ctx.font = '11px Arial';
|
||||
ctx.fillStyle = '#333';
|
||||
ctx.textAlign = 'center';
|
||||
|
||||
// 각 지역별로 총 길이 표시
|
||||
labels.forEach((_, i) => {
|
||||
chartData.labels.forEach((_, i) => {
|
||||
const xPos = chart.scales.x ? chart.scales.x.getPixelForTick(i) : 0;
|
||||
const yPos = chart.chartArea.top; // 차트 영역 최상단 위에 표시
|
||||
|
||||
// 막대 위에 총 길이 텍스트 표시
|
||||
if (totalLengths[i]) ctx.fillText(`총 ${totalLengths[i].toLocaleString()}km`, xPos, yPos);
|
||||
if (totalLengths[i]) ctx.fillText(`총 ${totalLengths[i]?.toLocaleString()}km`, xPos, yPos);
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const data = {
|
||||
labels,
|
||||
datasets,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="card card-border">
|
||||
<div className="card-header text-foreground">
|
||||
@@ -136,7 +172,7 @@ export default function SnOldPpl() {
|
||||
</div>
|
||||
<div className="h-70 p-4 flex items-center justify-center">
|
||||
<div style={{ width: "100%", maxWidth: 400, height: 131 }}>
|
||||
<Bar options={options} data={data} />
|
||||
<Bar options={options} data={chartData} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
/**
|
||||
* 오수관로 및 우수관로 현황
|
||||
*/
|
||||
import React from "react";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import {
|
||||
Chart as ChartJS,
|
||||
CategoryScale,
|
||||
@@ -15,37 +15,123 @@ import {
|
||||
} from "chart.js";
|
||||
import { Bar } from "react-chartjs-2";
|
||||
import { Button } from "@common/components";
|
||||
import type { PplLengthContent } from "../model/sn-interface";
|
||||
import { SewerageApi } from "../api/sn-service";
|
||||
|
||||
interface ChartDataSet {
|
||||
label: string;
|
||||
data: (number | undefined)[];
|
||||
backgroundColor: string;
|
||||
borderColor?: string;
|
||||
}
|
||||
|
||||
ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
|
||||
|
||||
export default function SnPplStatus() {
|
||||
const labels = ["한강", "낙동강", "영산강", "금강"];
|
||||
const [sewageData, setSewageData] = useState<PplLengthContent[]>([]);
|
||||
const [stormwaterData, setStormwaterData] = useState<PplLengthContent[]>([]);
|
||||
const [totalData, setTotalData] = useState<PplLengthContent[]>([]);
|
||||
const [chartData, setChartData] = useState<{
|
||||
labels: string[];
|
||||
datasets: ChartDataSet[];
|
||||
}>({ labels: [], datasets: [] });
|
||||
|
||||
// 데이터: [기타, 오수관로, 우수관로]
|
||||
const datasets = [
|
||||
{
|
||||
label: "기타",
|
||||
data: [15481, 6959, 1938, 1967],
|
||||
backgroundColor: "rgba(255, 178, 150, 0.9)", // 연한 주황(파스텔톤)
|
||||
borderColor: "rgba(255,178,150,1)",
|
||||
},
|
||||
{
|
||||
useEffect(() => {
|
||||
const ymdFrom = "19010101";
|
||||
const ymdTo = "20241231";
|
||||
|
||||
SewerageApi.getPplUseLength(ymdFrom, ymdTo, "SBA004")
|
||||
.then((apiResult) => {
|
||||
// getPplUseLength의 결과(apiResult)를
|
||||
// setSewageData 함수의 인자로 넣어서 호출합니다.
|
||||
// console.log(apiResult);
|
||||
setSewageData(apiResult);
|
||||
})
|
||||
.catch((e) => console.error("오수관 데이터 조회 실패", e));
|
||||
|
||||
SewerageApi.getPplUseLength(ymdFrom, ymdTo, "SBA003")
|
||||
.then(setStormwaterData)
|
||||
.catch((e) => console.error("우수관 데이터 조회 실패", e));
|
||||
|
||||
SewerageApi.getPplLength(ymdFrom, ymdTo)
|
||||
.then((apiResult) => {
|
||||
// getPplLength 결과(apiResult)를
|
||||
// setTotalData 함수의 인자로 넣어서 호출합니다.
|
||||
// console.log(apiResult);
|
||||
setTotalData(apiResult);
|
||||
})
|
||||
.catch((e) => console.error("전체 관로 데이터 조회 실패", e));
|
||||
}, []);
|
||||
|
||||
// 데이터가 변경될 때마다 차트를 다시 계산
|
||||
useEffect(() => {
|
||||
// 오수관 또는 우수관 데이터가 없으면 차트를 그리지 않음
|
||||
if (sewageData.length === 0 && stormwaterData.length === 0) return;
|
||||
|
||||
const allZones = new Set([
|
||||
...sewageData.map(d => d.zoneNm),
|
||||
...stormwaterData.map(d => d.zoneNm),
|
||||
...totalData.map(d => d.zoneNm)
|
||||
]);
|
||||
const labels = Array.from(allZones);
|
||||
|
||||
const dataByZone: { [key: string]: { total: number; sewage: number; stormwater: number } } = {};
|
||||
labels.forEach(zone => {dataByZone[zone] = { total: 0, sewage: 0, stormwater: 0 }; });
|
||||
|
||||
totalData.forEach(item => {
|
||||
if (!dataByZone[item.zoneNm]) {
|
||||
dataByZone[item.zoneNm] = { total: 0, sewage: 0, stormwater: 0 };
|
||||
}
|
||||
dataByZone[item.zoneNm]!.total = item.sumPrlgVl;
|
||||
});
|
||||
sewageData.forEach(item => {
|
||||
if (!dataByZone[item.zoneNm]) {
|
||||
dataByZone[item.zoneNm] = { total: 0, sewage: 0, stormwater: 0 };
|
||||
}
|
||||
dataByZone[item.zoneNm]!.sewage = item.sumPrlgVl;
|
||||
});
|
||||
stormwaterData.forEach(item => {
|
||||
if (!dataByZone[item.zoneNm]) {
|
||||
dataByZone[item.zoneNm] = { total: 0, sewage: 0, stormwater: 0 };
|
||||
}
|
||||
dataByZone[item.zoneNm]!.stormwater = item.sumPrlgVl;
|
||||
});
|
||||
|
||||
const datasets: ChartDataSet[] = [];
|
||||
|
||||
// 오수관로 데이터셋
|
||||
datasets.push({
|
||||
label: "오수관로",
|
||||
data: [15878, 16336, 5670, 8073],
|
||||
backgroundColor: "rgba(180, 240, 190, 0.95)", // 연한 초록(파스텔톤)
|
||||
borderColor: "rgba(180,240,190,1)",
|
||||
},
|
||||
{
|
||||
data: labels.map(zone => dataByZone[zone]?.sewage),
|
||||
backgroundColor: "rgba(180, 240, 190, 0.95)",
|
||||
});
|
||||
|
||||
// 우수관로 데이터셋
|
||||
datasets.push({
|
||||
label: "우수관로",
|
||||
data: [12892, 10491, 4280, 4838],
|
||||
backgroundColor: "rgba(170, 210, 245, 0.95)", // 연한 파랑(파스텔톤)
|
||||
borderColor: "rgba(170,210,245,1)",
|
||||
},
|
||||
];
|
||||
data: labels.map(zone => dataByZone[zone]?.stormwater),
|
||||
backgroundColor: "rgba(170, 210, 245, 0.95)",
|
||||
});
|
||||
|
||||
// 전체 데이터가 도착했다면 '기타' 데이터셋을 계산하고 추가
|
||||
if (totalData.length > 0) {
|
||||
datasets.unshift({ // '기타'를 맨 아래에 쌓기 위해 unshift 사용
|
||||
label: "기타",
|
||||
data: labels.map(zone => {
|
||||
const zoneData = dataByZone[zone] ?? { total: 0, sewage: 0, stormwater: 0 };
|
||||
return zoneData.total - zoneData.sewage - zoneData.stormwater;
|
||||
}),
|
||||
backgroundColor: "rgba(255, 178, 150, 0.9)",
|
||||
});
|
||||
}
|
||||
|
||||
setChartData({ labels, datasets });
|
||||
|
||||
}, [sewageData, stormwaterData, totalData]);
|
||||
|
||||
// 총합(라벨당) 계산
|
||||
const totals = labels.map((_, i) =>
|
||||
datasets.reduce((s, ds) => s + (ds.data[i] as number), 0),
|
||||
const totals = chartData.labels.map((_, i) =>
|
||||
chartData.datasets.reduce((s, ds) => s + (ds.data[i] as number), 0)
|
||||
);
|
||||
|
||||
const options: ChartOptions<"bar"> = {
|
||||
@@ -78,7 +164,7 @@ export default function SnPplStatus() {
|
||||
onComplete: function (animation) {
|
||||
const chart = animation.chart;
|
||||
const ctx = chart.ctx;
|
||||
const lastMeta = chart.getDatasetMeta(datasets.length - 1);
|
||||
const lastMeta = chart.getDatasetMeta(chartData.datasets.length - 1);
|
||||
ctx.save();
|
||||
ctx.fillStyle = "#111";
|
||||
ctx.font = "gray bold 12px Arial";
|
||||
@@ -98,7 +184,7 @@ export default function SnPplStatus() {
|
||||
},
|
||||
};
|
||||
|
||||
const data = { labels, datasets };
|
||||
//const data = { labels, datasets };
|
||||
|
||||
return (
|
||||
<div className="card card-border">
|
||||
@@ -108,7 +194,7 @@ export default function SnPplStatus() {
|
||||
</div>
|
||||
<div className="h-60 p-4">
|
||||
<div style={{ width: "100%", maxWidth: 400, height: 150 }}>
|
||||
<Bar options={options} data={data} />
|
||||
<Bar options={options} data={chartData} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -70,6 +70,8 @@ export const ENDPOINT = {
|
||||
POST_EVALUATION: "/board-service/api/v1/pstEvl",
|
||||
},
|
||||
SEWERAGE_SERVICE: {
|
||||
PPL_LENGTH: "/sewerage-service/api/v1/ms/pplLength",
|
||||
PPL_USE_LENGTH: "/sewerage-service/api/v1/ms/pplUseLength",
|
||||
PPL_COUNTS: "/sewerage-service/api/v1/ms/pplCounts",
|
||||
OLD_PPL_COUNTS: "/sewerage-service/api/v1/ms/oldPplCounts",
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user