Merge remote-tracking branch 'origin/iipbs/st/dev' into feature/review-skeleton-sein
This commit is contained in:
+2
-1
@@ -38,4 +38,5 @@ yarn-error.log*
|
|||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
|
|
||||||
.env*
|
.env*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
*.md
|
||||||
+2
-1
@@ -1,3 +1,4 @@
|
|||||||
{
|
{
|
||||||
"plugins": ["prettier-plugin-tailwindcss"]
|
"plugins": ["prettier-plugin-tailwindcss"],
|
||||||
|
"tabWidth": 4
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
"@common/assets": "workspace:*",
|
"@common/assets": "workspace:*",
|
||||||
"@common/components": "workspace:*",
|
"@common/components": "workspace:*",
|
||||||
"@common/config": "workspace:*",
|
"@common/config": "workspace:*",
|
||||||
"@rsone/components": "workspace:*",
|
"@faker-js/faker": "^9.9.0",
|
||||||
"@fontsource-variable/noto-sans-kr": "^5.2.5",
|
"@fontsource-variable/noto-sans-kr": "^5.2.5",
|
||||||
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
||||||
"@fullcalendar/core": "^6.1.17",
|
"@fullcalendar/core": "^6.1.17",
|
||||||
@@ -26,6 +26,7 @@
|
|||||||
"@radix-ui/react-label": "^2.1.2",
|
"@radix-ui/react-label": "^2.1.2",
|
||||||
"@radix-ui/react-slot": "^1.1.2",
|
"@radix-ui/react-slot": "^1.1.2",
|
||||||
"@radix-ui/react-tooltip": "^1.1.8",
|
"@radix-ui/react-tooltip": "^1.1.8",
|
||||||
|
"@rsone/components": "workspace:*",
|
||||||
"@tanstack/react-query": "^5.67.1",
|
"@tanstack/react-query": "^5.67.1",
|
||||||
"@tanstack/react-query-devtools": "^5.75.5",
|
"@tanstack/react-query-devtools": "^5.75.5",
|
||||||
"@tanstack/react-table": "^8.21.2",
|
"@tanstack/react-table": "^8.21.2",
|
||||||
|
|||||||
@@ -155,16 +155,16 @@ function updateHeaderSortIndicator(
|
|||||||
function createOnSortingHandler(
|
function createOnSortingHandler(
|
||||||
columns: { fieldName?: string; name: string; mergeCells?: boolean; sortdisable?: boolean; valueType?: ValueType }[],
|
columns: { fieldName?: string; name: string; mergeCells?: boolean; sortdisable?: boolean; valueType?: ValueType }[],
|
||||||
setRowData: React.Dispatch<React.SetStateAction<Record<string, string | number>[]>>,
|
setRowData: React.Dispatch<React.SetStateAction<Record<string, string | number>[]>>,
|
||||||
sortDirRef: React.MutableRefObject<Record<string, "asc" | "desc" | "none">>,
|
sortDirRef: { current: Record<string, "asc" | "desc" | "none"> },
|
||||||
originalRowsRef: React.MutableRefObject<Record<string, Record<string, string | number>[]>>
|
originalRowsRef: { current: Record<string, Record<string, string | number>[]> }
|
||||||
) {
|
) {
|
||||||
const fieldNames = [...new Set(columns.map((c) => c.fieldName).filter((x): x is string => typeof x === "string"))];
|
const fieldNames = [...new Set(columns.map((c) => c.fieldName).filter((x): x is string => typeof x === "string"))];
|
||||||
// mergeCells: true인 컬럼 찾기 (시군구 등) - 그룹 필드로 사용
|
// mergeCells: true인 컬럼 찾기 (시군구 등) - 그룹 필드로 사용
|
||||||
// 시군구를 우선으로 찾고, 없으면 첫 번째 mergeCells 컬럼 사용
|
// 시군구를 우선으로 찾고, 없으면 첫 번째 mergeCells 컬럼 사용
|
||||||
const mergeCellCols = columns.filter((c) => c.mergeCells === true && c.fieldName);
|
const mergeCellCols = columns.filter((c) => c.mergeCells === true && c.fieldName);
|
||||||
const mergeCellCol = mergeCellCols.find((c) => c.fieldName === '시군구') || mergeCellCols[0];
|
const mergeCellCol = mergeCellCols.find((c) => c.fieldName === 'gugun') || mergeCellCols[0];
|
||||||
const groupField = mergeCellCol?.fieldName || null;
|
const groupField = mergeCellCol?.fieldName || null;
|
||||||
const orderByField = '구분'; // 고정값 또는 설정 가능하게 할 수 있음
|
const orderByField = 'rgnNm'; // 고정값 또는 설정 가능하게 할 수 있음
|
||||||
const hasMergeCells = mergeCellCols.length > 0;
|
const hasMergeCells = mergeCellCols.length > 0;
|
||||||
|
|
||||||
return function onSorting(g: unknown, fields: unknown[], _directions: unknown[]) {
|
return function onSorting(g: unknown, fields: unknown[], _directions: unknown[]) {
|
||||||
|
|||||||
@@ -118,6 +118,18 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSggChange = (value: string) => {
|
||||||
|
const selectedBsnm = value === 'ALL'
|
||||||
|
? (sidoList?.data.sidoList.find((v) => v.sido === values.sido)?.bsnmCd || '')
|
||||||
|
: (sggList?.data?.find((v) => v.gugun === value)?.bsnmCd || '');
|
||||||
|
|
||||||
|
setValues((prev) => ({
|
||||||
|
...prev,
|
||||||
|
gugun: value,
|
||||||
|
bsnmCd: selectedBsnm,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
props.onValuesChange?.(values);
|
props.onValuesChange?.(values);
|
||||||
}, [values]);
|
}, [values]);
|
||||||
@@ -151,7 +163,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
|||||||
<Label className="text-label">
|
<Label className="text-label">
|
||||||
시군구
|
시군구
|
||||||
</Label>
|
</Label>
|
||||||
<Select value={values.gugun} onValueChange={(value) => setValues({ ...values, gugun: value })}>
|
<Select value={values.gugun} onValueChange={handleSggChange}>
|
||||||
<SelectTrigger className="w-[12rem]">
|
<SelectTrigger className="w-[12rem]">
|
||||||
<SelectValue placeholder="전체"></SelectValue>
|
<SelectValue placeholder="전체"></SelectValue>
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { useState } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import { DataGrid } from "@rsone/components";
|
// import { DataGrid } from "@rsone/components";
|
||||||
|
import DataGrid from "@/components/module/DataGrid";
|
||||||
import SearchForm from "@/components/module/SearchForm";
|
import SearchForm from "@/components/module/SearchForm";
|
||||||
import SaveButton from "@/components/button/SaveButton";
|
import SaveButton from "@/components/button/SaveButton";
|
||||||
import ApproveButton from "@/components/button/ApproveButton";
|
import ApproveButton from "@/components/button/ApproveButton";
|
||||||
@@ -12,12 +13,27 @@ import { sewerageSupplyRateReviewApi } from "@/entities/statistics/api/statistic
|
|||||||
import type { SewerageSupplyRateReviewItem } from "@/entities/statistics/model/statistics-management-interface";
|
import type { SewerageSupplyRateReviewItem } from "@/entities/statistics/model/statistics-management-interface";
|
||||||
import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn";
|
import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn";
|
||||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||||
|
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||||
|
|
||||||
// 하수도 통계 검토 01. 하수도 보급률
|
// 하수도 통계 검토 01. 하수도 보급률
|
||||||
const SeweragePenetrationRateRvw = () => {
|
const SeweragePenetrationRateRvw = () => {
|
||||||
const { setMessage: alert } = useAlertStore((state) => state);
|
const { setMessage: alert } = useAlertStore((state) => state);
|
||||||
|
|
||||||
const [rowData, setRowData] = useState<any[]>([]);
|
const columnInfo = useMemo(() => {
|
||||||
|
const { columns, columnLayout, fixedColumns, rowStartNum } = SeweragePenetrationRateInfoList;
|
||||||
|
return {
|
||||||
|
columnLayout,
|
||||||
|
fixedColumns,
|
||||||
|
rowStartNum,
|
||||||
|
columns: columns.map((col) => ({
|
||||||
|
...col,
|
||||||
|
editable: false as const,
|
||||||
|
styleName: col.styleName ?? ("rg-cell-readonly" as const),
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const [rowData, setRowData] = useState<any[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
|
|
||||||
const handleReset = () => {
|
const handleReset = () => {
|
||||||
@@ -29,10 +45,10 @@ const [rowData, setRowData] = useState<any[]>([]);
|
|||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
|
||||||
const response = await sewerageSupplyRateReviewApi.getSewerageSupplyRateReviewList({
|
const response = await sewerageSupplyRateReviewApi.getSewerageSupplyRateReviewList({
|
||||||
sido: "경기도",
|
sido: values.sido,
|
||||||
gugun: "과천시",
|
gugun: values.gugun === 'ALL' ? '' : values.gugun,
|
||||||
yr: 2024,
|
bsnmCd: values.bsnmCd,
|
||||||
bsnmCd: "4129000000"
|
yr: values.year,
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log("네트워크 응답 전체:", response);
|
console.log("네트워크 응답 전체:", response);
|
||||||
@@ -42,14 +58,13 @@ const [rowData, setRowData] = useState<any[]>([]);
|
|||||||
|
|
||||||
console.log("받아온 리스트:", rawList);
|
console.log("받아온 리스트:", rawList);
|
||||||
|
|
||||||
|
|
||||||
const convertedData: SewerageSupplyRateReviewItem[] = rawList.map((item) => ({
|
const convertedData: SewerageSupplyRateReviewItem[] = rawList.map((item) => ({
|
||||||
...item,
|
...item,
|
||||||
|
|
||||||
speclCntrplnAr: !item.speclCntrplnAr ? '해당없음' : item.speclCntrplnAr,
|
speclCntrplnAr: !item.speclCntrplnAr ? '해당없음' : item.speclCntrplnAr,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setRowData(convertedData);
|
setRowData(toGridRows(convertedData));
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
setRowData([]);
|
setRowData([]);
|
||||||
@@ -80,12 +95,12 @@ const [rowData, setRowData] = useState<any[]>([]);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<DataGrid
|
<DataGrid
|
||||||
columnInfo={SeweragePenetrationRateInfoList}
|
columnInfo={columnInfo}
|
||||||
isExcelImport={false}
|
isExcelImport={true}
|
||||||
fixedColumns={SeweragePenetrationRateInfoList.fixedColumns}
|
fixedColumns={columnInfo.fixedColumns}
|
||||||
rowStartNum={SeweragePenetrationRateInfoList.rowStartNum}
|
rowStartNum={columnInfo.rowStartNum}
|
||||||
rowData={rowData}
|
rowData={rowData}
|
||||||
setRowData={setRowData}
|
setRowData={setRowData}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,11 +1,482 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useState } from "react";
|
||||||
|
import { DataGrid } from "@rsone/components";
|
||||||
import SearchForm from "@/components/module/SearchForm";
|
import SearchForm from "@/components/module/SearchForm";
|
||||||
|
import {
|
||||||
|
Button,
|
||||||
|
Input,
|
||||||
|
Label,
|
||||||
|
Select,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectValue,
|
||||||
|
Checkbox,
|
||||||
|
} from "@common/components";
|
||||||
|
import { verificationHistoryColumnInfo } from "@/entities/statistics/model/verification-history-column";
|
||||||
|
import { verificationHistoryMockData } from "@/entities/statistics/model/verification-history-mock";
|
||||||
|
|
||||||
|
interface VerificationFormValues {
|
||||||
|
mu: string;
|
||||||
|
percentile: string;
|
||||||
|
a: string;
|
||||||
|
c: string;
|
||||||
|
verificationYear: string;
|
||||||
|
checkboxOptions: {
|
||||||
|
sidoDistribution: boolean;
|
||||||
|
countyDistribution: boolean;
|
||||||
|
countyDistributionUserManagement: boolean;
|
||||||
|
lowerDistribution: boolean;
|
||||||
|
sidoServiceAvailability: boolean;
|
||||||
|
individualService: boolean;
|
||||||
|
legalServiceUsage: boolean;
|
||||||
|
excessUse: boolean;
|
||||||
|
lowerOrgStatistics: boolean;
|
||||||
|
studentStatistics: boolean;
|
||||||
|
developmentStatistics: boolean;
|
||||||
|
lowerManagementEnterprise: boolean;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const ManagementVerificationRule = () => {
|
const ManagementVerificationRule = () => {
|
||||||
|
const [verificationHistoryRowData, setVerificationHistoryRowData] =
|
||||||
|
useState<Record<string, string | number>[]>(
|
||||||
|
verificationHistoryMockData,
|
||||||
|
);
|
||||||
|
|
||||||
|
const [formValues, setFormValues] = useState<VerificationFormValues>({
|
||||||
|
mu: "0",
|
||||||
|
percentile: "0",
|
||||||
|
a: "0",
|
||||||
|
c: "0",
|
||||||
|
verificationYear: "2025",
|
||||||
|
checkboxOptions: {
|
||||||
|
sidoDistribution: false,
|
||||||
|
countyDistribution: false,
|
||||||
|
countyDistributionUserManagement: false,
|
||||||
|
lowerDistribution: false,
|
||||||
|
sidoServiceAvailability: false,
|
||||||
|
individualService: false,
|
||||||
|
legalServiceUsage: false,
|
||||||
|
excessUse: false,
|
||||||
|
lowerOrgStatistics: false,
|
||||||
|
studentStatistics: false,
|
||||||
|
developmentStatistics: false,
|
||||||
|
lowerManagementEnterprise: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleCheckboxChange = (
|
||||||
|
key: keyof VerificationFormValues["checkboxOptions"],
|
||||||
|
) => {
|
||||||
|
setFormValues((prev) => ({
|
||||||
|
...prev,
|
||||||
|
checkboxOptions: {
|
||||||
|
...prev.checkboxOptions,
|
||||||
|
[key]: !prev.checkboxOptions[key],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleInputChange = (
|
||||||
|
key: keyof Omit<VerificationFormValues, "checkboxOptions">,
|
||||||
|
value: string,
|
||||||
|
) => {
|
||||||
|
setFormValues((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[key]: value,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
console.log("검증 실행:", formValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleReset = () => {
|
||||||
|
console.log("초기화");
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="card card-border mt-4">
|
||||||
<SearchForm />
|
<div className="p-2">
|
||||||
</>
|
{/* 통계 검증 섹션 */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h3 className="px-2 text-[1.5rem] font-semibold">
|
||||||
|
통계 검증
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div className="flex gap-2">
|
||||||
|
{/* 왼쪽: 검증 변수 */}
|
||||||
|
<div className="card card-border flex-[0.5] rounded p-4">
|
||||||
|
<h4 className="mb-3 rounded bg-gray-100 p-2 text-[1.4rem] font-medium">
|
||||||
|
검증 변수
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div className="space-y-3">
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Label className="w-32 text-label">μ</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={formValues.mu}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(
|
||||||
|
"mu",
|
||||||
|
e.target.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Label className="w-32 text-label">
|
||||||
|
Percentile
|
||||||
|
</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={formValues.percentile}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(
|
||||||
|
"percentile",
|
||||||
|
e.target.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Label className="w-32 text-label">A</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={formValues.a}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(
|
||||||
|
"a",
|
||||||
|
e.target.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Label className="w-32 text-label">C</Label>
|
||||||
|
<Input
|
||||||
|
type="number"
|
||||||
|
value={formValues.c}
|
||||||
|
onChange={(e) =>
|
||||||
|
handleInputChange(
|
||||||
|
"c",
|
||||||
|
e.target.value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-4">
|
||||||
|
<Label className="w-32 text-label">
|
||||||
|
검증 년도
|
||||||
|
</Label>
|
||||||
|
<Select
|
||||||
|
value={formValues.verificationYear}
|
||||||
|
onValueChange={(value) =>
|
||||||
|
handleInputChange(
|
||||||
|
"verificationYear",
|
||||||
|
value,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="flex-1">
|
||||||
|
<SelectValue />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="2025">
|
||||||
|
2025년
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 오른쪽: 검증 대상 */}
|
||||||
|
<div className="card card-border flex-1 rounded p-4">
|
||||||
|
<h4 className="mb-3 rounded bg-gray-100 p-2 text-[1.4rem] font-medium">
|
||||||
|
검증 대상
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div className="space-y-4 p-2">
|
||||||
|
<div className="grid grid-cols-4 gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.sidoDistribution
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"sidoDistribution",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
하수도 보급률
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.countyDistribution
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"countyDistribution",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
하수관로
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.countyDistributionUserManagement
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"countyDistributionUserManagement",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
하수관로 유지관리
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.lowerDistribution
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"lowerDistribution",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
하수저류시설 및 빗물펌프장
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-4 gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.sidoServiceAvailability
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"sidoServiceAvailability",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
펌프장
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.individualService
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"individualService",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
공공하수처리시설
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.sidoServiceAvailability
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"sidoServiceAvailability",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
분뇨처리시설
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.individualService
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"individualService",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
개인하수처리시설
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-4 gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.lowerOrgStatistics
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"lowerOrgStatistics",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
하수도재정
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.studentStatistics
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"studentStatistics",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
물 재이용
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.lowerOrgStatistics
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"lowerOrgStatistics",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
하수 및 분뇨찌꺼기(슬러지) 처리
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.studentStatistics
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"studentStatistics",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
하수 및 분뇨관련산업
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-4 gap-4">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Checkbox
|
||||||
|
checked={
|
||||||
|
formValues.checkboxOptions
|
||||||
|
.lowerManagementEnterprise
|
||||||
|
}
|
||||||
|
onCheckedChange={() =>
|
||||||
|
handleCheckboxChange(
|
||||||
|
"lowerManagementEnterprise",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
<Label className="cursor-pointer text-label">
|
||||||
|
하수도 서비스 수준
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 버튼 */}
|
||||||
|
<div className="mt-6 flex justify-center gap-4 pt-4">
|
||||||
|
<Button
|
||||||
|
color="green"
|
||||||
|
className="h-[3.6rem] w-[120px]"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
검증 실행
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="gray"
|
||||||
|
className="h-[3.6rem] w-[120px]"
|
||||||
|
onClick={handleReset}
|
||||||
|
>
|
||||||
|
초기화
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 검증 이력 섹션 */}
|
||||||
|
<div className="mt-8 border-t pt-6">
|
||||||
|
<h3 className="mb-4 px-2 text-[1.5rem] font-semibold">
|
||||||
|
검증 이력
|
||||||
|
</h3>
|
||||||
|
<DataGrid
|
||||||
|
columnInfo={verificationHistoryColumnInfo}
|
||||||
|
rowData={verificationHistoryRowData}
|
||||||
|
setRowData={setVerificationHistoryRowData}
|
||||||
|
isExcelImport={false}
|
||||||
|
isExcelExport={false}
|
||||||
|
showHeaderCheckbox={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default ManagementVerificationRule;
|
export default ManagementVerificationRule;
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const SeweragePenetrationRateInpColumn = {
|
|||||||
|
|
||||||
// 수계 및 권역
|
// 수계 및 권역
|
||||||
{ name: 'rvbsn', fieldName: 'rvbsn', header: { text: '수계' }, editable: false },
|
{ name: 'rvbsn', fieldName: 'rvbsn', header: { text: '수계' }, editable: false },
|
||||||
{ name: 'bstrm', fieldName: 'bstrm', header: { text: '지류' }, editable: false },
|
{ name: 'bstrm', fieldName: 'bstrm', header: { text: '지류' } },
|
||||||
|
|
||||||
// 수계영향권별분류
|
// 수계영향권별분류
|
||||||
{ name: 'mdbsn', fieldName: 'mdbsn', header: { text: '세부단위유역' }, editable: false },
|
{ name: 'mdbsn', fieldName: 'mdbsn', header: { text: '세부단위유역' }, editable: false },
|
||||||
|
|||||||
@@ -48,8 +48,8 @@ export const getFooterAvgSummary = (grid: GridView, column: DataColumn) => {
|
|||||||
return Math.floor(avg * 10) / 10;
|
return Math.floor(avg * 10) / 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
// "구분" 필드의 고유값들을 찾는 함수 (첫 번째, 두 번째)
|
// 구분 필드의 고유값들을 찾는 함수 (첫 번째, 두 번째). 기본 필드명 'rgnNm'
|
||||||
const getDivisionValues = (grid: GridView, divisionFieldName: string = '구분'): string[] => {
|
const getDivisionValues = (grid: GridView, divisionFieldName: string = 'rgnNm'): string[] => {
|
||||||
const dataSource = grid.getDataSource();
|
const dataSource = grid.getDataSource();
|
||||||
const rowCount = dataSource.getRowCount();
|
const rowCount = dataSource.getRowCount();
|
||||||
const uniqueValues = new Set<string>();
|
const uniqueValues = new Set<string>();
|
||||||
@@ -65,13 +65,13 @@ const getDivisionValues = (grid: GridView, divisionFieldName: string = '구분')
|
|||||||
return Array.from(uniqueValues);
|
return Array.from(uniqueValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
// "구분" 필드 값에 따라 필터링하여 합계 계산하는 함수
|
// 구분 필드 값에 따라 필터링하여 합계 계산하는 함수. 기본 필드명 'rgnNm'
|
||||||
// divisionIndex: 0 = 첫 번째 고유값, 1 = 두 번째 고유값
|
// divisionIndex: 0 = 첫 번째 고유값, 1 = 두 번째 고유값
|
||||||
export const getFooterSummaryByDivision = (
|
export const getFooterSummaryByDivision = (
|
||||||
grid: GridView,
|
grid: GridView,
|
||||||
column: DataColumn,
|
column: DataColumn,
|
||||||
divisionIndex: number,
|
divisionIndex: number,
|
||||||
divisionFieldName: string = '구분'
|
divisionFieldName: string = 'rgnNm'
|
||||||
) => {
|
) => {
|
||||||
const dataSource = grid.getDataSource();
|
const dataSource = grid.getDataSource();
|
||||||
const rowCount = dataSource.getRowCount();
|
const rowCount = dataSource.getRowCount();
|
||||||
@@ -107,13 +107,13 @@ export const getFooterSummaryByDivision = (
|
|||||||
return sum;
|
return sum;
|
||||||
};
|
};
|
||||||
|
|
||||||
// "구분" 필드 값에 따라 필터링하여 평균 계산하는 함수
|
// 구분 필드 값에 따라 필터링하여 평균 계산하는 함수. 기본 필드명 'rgnNm'
|
||||||
// divisionIndex: 0 = 첫 번째 고유값, 1 = 두 번째 고유값
|
// divisionIndex: 0 = 첫 번째 고유값, 1 = 두 번째 고유값
|
||||||
export const getFooterAvgSummaryByDivision = (
|
export const getFooterAvgSummaryByDivision = (
|
||||||
grid: GridView,
|
grid: GridView,
|
||||||
column: DataColumn,
|
column: DataColumn,
|
||||||
divisionIndex: number,
|
divisionIndex: number,
|
||||||
divisionFieldName: string = '구분'
|
divisionFieldName: string = 'rgnNm'
|
||||||
) => {
|
) => {
|
||||||
const dataSource = grid.getDataSource();
|
const dataSource = grid.getDataSource();
|
||||||
const rowCount = dataSource.getRowCount();
|
const rowCount = dataSource.getRowCount();
|
||||||
@@ -173,67 +173,51 @@ const createAvgFooter = (format: string) => ({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// "구분" 값에 따라 필터링하는 Footer 배열 생성 함수 (2줄)
|
// 구분 값에 따라 필터링하는 Footer 배열 생성 함수 (2줄). 기본 필드명 'rgnNm'
|
||||||
const createSumFooterArrayByDivision = (format: string) => [
|
const createSumFooterArrayByDivision = (format: string, divisionFieldName: string = 'rgnNm') => [
|
||||||
{
|
{
|
||||||
numberFormat: format,
|
numberFormat: format,
|
||||||
valueCallback: (
|
valueCallback: (
|
||||||
grid: GridView,
|
grid: GridView,
|
||||||
column: DataColumn,
|
column: DataColumn,
|
||||||
footerIndex: number,
|
footerIndex: number,
|
||||||
columnFooter: unknown,
|
columnFooter: unknown,
|
||||||
value: number | string | undefined
|
value: number | string | undefined
|
||||||
) => {
|
) => getFooterSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
|
||||||
// footerIndex를 divisionIndex로 사용
|
|
||||||
return getFooterSummaryByDivision(grid, column, footerIndex);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
numberFormat: format,
|
numberFormat: format,
|
||||||
valueCallback: (
|
valueCallback: (
|
||||||
grid: GridView,
|
grid: GridView,
|
||||||
column: DataColumn,
|
column: DataColumn,
|
||||||
footerIndex: number,
|
footerIndex: number,
|
||||||
columnFooter: unknown,
|
columnFooter: unknown,
|
||||||
value: number | string | undefined
|
value: number | string | undefined
|
||||||
) => {
|
) => getFooterSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
|
||||||
// footerIndex를 divisionIndex로 사용
|
|
||||||
return getFooterSummaryByDivision(grid, column, footerIndex);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// "구분" 값에 따라 필터링하는 평균 Footer 배열 생성 함수 (2줄)
|
// 구분 값에 따라 필터링하는 평균 Footer 배열 생성 함수 (2줄). 기본 필드명 'rgnNm'
|
||||||
const createAvgFooterArrayByDivision = (format: string) => [
|
const createAvgFooterArrayByDivision = (format: string, divisionFieldName: string = 'rgnNm') => [
|
||||||
{
|
{
|
||||||
numberFormat: format,
|
numberFormat: format,
|
||||||
valueCallback: (
|
valueCallback: (
|
||||||
grid: GridView,
|
grid: GridView,
|
||||||
column: DataColumn,
|
column: DataColumn,
|
||||||
footerIndex: number,
|
footerIndex: number,
|
||||||
columnFooter: unknown,
|
columnFooter: unknown,
|
||||||
value: number | string | undefined
|
value: number | string | undefined
|
||||||
) => {
|
) => getFooterAvgSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
|
||||||
// footerIndex를 divisionIndex로 사용
|
|
||||||
return getFooterAvgSummaryByDivision(grid, column, footerIndex);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
numberFormat: format,
|
numberFormat: format,
|
||||||
valueCallback: (
|
valueCallback: (
|
||||||
grid: GridView,
|
grid: GridView,
|
||||||
column: DataColumn,
|
column: DataColumn,
|
||||||
footerIndex: number,
|
footerIndex: number,
|
||||||
columnFooter: unknown,
|
columnFooter: unknown,
|
||||||
value: number | string | undefined
|
value: number | string | undefined
|
||||||
) => {
|
) => getFooterAvgSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
|
||||||
// footerIndex를 divisionIndex로 사용
|
|
||||||
return getFooterAvgSummaryByDivision(grid, column, footerIndex);
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import type { SewerageSupplyRateItem } from "@/entities/statistics/model/statistics-input-interface";
|
/**
|
||||||
|
* 객체 배열을 그리드 row 형식으로 변환 (null/undefined → "").
|
||||||
export function toGridRows(items: SewerageSupplyRateItem[]): Record<string, string | number>[] {
|
* 어떤 엔티티 타입이든 공통 사용 가능.
|
||||||
|
*/
|
||||||
|
export function toGridRows<T extends object>(
|
||||||
|
items: T[]
|
||||||
|
): Record<string, string | number>[] {
|
||||||
return items.map((item) => {
|
return items.map((item) => {
|
||||||
const row: Record<string, string | number> = {};
|
const row: Record<string, string | number> = {};
|
||||||
for (const [key, value] of Object.entries(item)) {
|
for (const [key, value] of Object.entries(item)) {
|
||||||
row[key] = value === null || value === undefined ? "" : value;
|
row[key] =
|
||||||
|
value === null || value === undefined ? "" : (value as string | number);
|
||||||
}
|
}
|
||||||
return row;
|
return row;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -4,6 +4,11 @@ export interface Sido {
|
|||||||
bsnmCd: string;
|
bsnmCd: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface SearchCondition {
|
||||||
|
yearList: string[];
|
||||||
|
sidoList: Sido[];
|
||||||
|
}
|
||||||
|
|
||||||
export interface Sgg {
|
export interface Sgg {
|
||||||
adstDstrCd: string;
|
adstDstrCd: string;
|
||||||
gugun: string;
|
gugun: string;
|
||||||
@@ -17,6 +22,6 @@ export interface ApiResponse<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface StatisticsService {
|
export interface StatisticsService {
|
||||||
getSidoList(): Promise<ApiResponse<Sido[]>>;
|
getSidoList(): Promise<ApiResponse<SearchCondition>>;
|
||||||
getSggList(sidoName: string): Promise<ApiResponse<Sgg[]>>;
|
getSggList(sidoName: string): Promise<ApiResponse<Sgg[]>>;
|
||||||
}
|
}
|
||||||
@@ -83,7 +83,7 @@ export type SewerageSupplyRateReviewApiResponse = ApiResponse<SewerageSupplyRate
|
|||||||
export interface SewerageSupplyRateReviewRequestParams {
|
export interface SewerageSupplyRateReviewRequestParams {
|
||||||
sido?: string;
|
sido?: string;
|
||||||
gugun?: string;
|
gugun?: string;
|
||||||
yr?: number;
|
yr?: string;
|
||||||
bsnmCd?: string;
|
bsnmCd?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,120 @@
|
|||||||
|
import { ValueType } from "realgrid";
|
||||||
|
import type { ColumnInfo } from "@rsone/components";
|
||||||
|
|
||||||
|
/** 검증 이력 그리드 컬럼 설정 */
|
||||||
|
export const verificationHistoryColumnInfo: ColumnInfo = {
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: "executedAt",
|
||||||
|
fieldName: "executedAt",
|
||||||
|
header: { text: "검증 실행 일시" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "progressStatus",
|
||||||
|
fieldName: "progressStatus",
|
||||||
|
header: { text: "진행 상태" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "endedAt",
|
||||||
|
fieldName: "endedAt",
|
||||||
|
header: { text: "종료 일시" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "verificationYear",
|
||||||
|
fieldName: "verificationYear",
|
||||||
|
header: { text: "검증년도" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "mu",
|
||||||
|
fieldName: "mu",
|
||||||
|
header: { text: "μ" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
valueType: ValueType.NUMBER,
|
||||||
|
dataFormat: "#,##0.###",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "percentile",
|
||||||
|
fieldName: "percentile",
|
||||||
|
header: { text: "Percentile" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
valueType: ValueType.NUMBER,
|
||||||
|
dataFormat: "#,##0.###",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "a",
|
||||||
|
fieldName: "a",
|
||||||
|
header: { text: "A" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
valueType: ValueType.NUMBER,
|
||||||
|
dataFormat: "#,##0.###",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "c",
|
||||||
|
fieldName: "c",
|
||||||
|
header: { text: "C" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
valueType: ValueType.NUMBER,
|
||||||
|
dataFormat: "#,##0.###",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "target",
|
||||||
|
fieldName: "target",
|
||||||
|
header: { text: "검증 대상" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "normalCount",
|
||||||
|
fieldName: "normalCount",
|
||||||
|
header: { text: "정상건수" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
valueType: ValueType.NUMBER,
|
||||||
|
dataFormat: "#,##0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "excludedCount",
|
||||||
|
fieldName: "excludedCount",
|
||||||
|
header: { text: "제외건수" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
valueType: ValueType.NUMBER,
|
||||||
|
dataFormat: "#,##0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "abnormalCount",
|
||||||
|
fieldName: "abnormalCount",
|
||||||
|
header: { text: "이상건수" },
|
||||||
|
editable: false,
|
||||||
|
sortdisable: false,
|
||||||
|
valueType: ValueType.NUMBER,
|
||||||
|
dataFormat: "#,##0",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
columnLayout: [
|
||||||
|
"executedAt",
|
||||||
|
"progressStatus",
|
||||||
|
"endedAt",
|
||||||
|
"verificationYear",
|
||||||
|
"mu",
|
||||||
|
"percentile",
|
||||||
|
"a",
|
||||||
|
"c",
|
||||||
|
"target",
|
||||||
|
"normalCount",
|
||||||
|
"excludedCount",
|
||||||
|
"abnormalCount",
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/** 검증 이력 한 건 (그리드 행 데이터) */
|
||||||
|
export interface VerificationHistoryItem {
|
||||||
|
/** 검증 실행 일시 */
|
||||||
|
executedAt: string;
|
||||||
|
/** 진행 상태 */
|
||||||
|
progressStatus: string;
|
||||||
|
/** 종료 일시 */
|
||||||
|
endedAt: string;
|
||||||
|
/** 검증년도 */
|
||||||
|
verificationYear: string;
|
||||||
|
/** μ */
|
||||||
|
mu: number;
|
||||||
|
/** Percentile */
|
||||||
|
percentile: number;
|
||||||
|
/** A */
|
||||||
|
a: number;
|
||||||
|
/** C */
|
||||||
|
c: number;
|
||||||
|
/** 검증 대상 */
|
||||||
|
target: string;
|
||||||
|
/** 정상건수 */
|
||||||
|
normalCount: number;
|
||||||
|
/** 제외건수 */
|
||||||
|
excludedCount: number;
|
||||||
|
/** 이상건수 */
|
||||||
|
abnormalCount: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
import { fakerKO as faker } from "@faker-js/faker";
|
||||||
|
import type { VerificationHistoryItem } from "./verification-history-interface";
|
||||||
|
|
||||||
|
const VERIFICATION_TARGETS = [
|
||||||
|
"하수도 보급률",
|
||||||
|
"하수관로",
|
||||||
|
"하수관로 유지관리",
|
||||||
|
"하수저류시설 및 빗물펌프장",
|
||||||
|
"펌프장",
|
||||||
|
"공공하수처리시설",
|
||||||
|
"분뇨처리시설",
|
||||||
|
"개인하수처리시설",
|
||||||
|
"하수도재정",
|
||||||
|
"물 재이용",
|
||||||
|
"하수도 서비스 수준",
|
||||||
|
];
|
||||||
|
|
||||||
|
const PROGRESS_STATUSES = ["대기", "진행중", "완료", "실패"];
|
||||||
|
|
||||||
|
function formatDateTime(date: Date): string {
|
||||||
|
const y = date.getFullYear();
|
||||||
|
const m = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const d = String(date.getDate()).padStart(2, "0");
|
||||||
|
const h = String(date.getHours()).padStart(2, "0");
|
||||||
|
const min = String(date.getMinutes()).padStart(2, "0");
|
||||||
|
return `${y}-${m}-${d} ${h}:${min}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 검증 이력 목데이터 생성 (그리드 행 형식) */
|
||||||
|
export function createVerificationHistoryMock(
|
||||||
|
count = 10,
|
||||||
|
): Record<string, string | number>[] {
|
||||||
|
faker.seed(42);
|
||||||
|
return Array.from({ length: count }, () => {
|
||||||
|
const executedAt = faker.date.recent({ days: 90 });
|
||||||
|
const durationMinutes = faker.number.int({ min: 1, max: 120 });
|
||||||
|
const endedAt = new Date(
|
||||||
|
executedAt.getTime() + durationMinutes * 60 * 1000,
|
||||||
|
);
|
||||||
|
const status = faker.helpers.arrayElement(PROGRESS_STATUSES);
|
||||||
|
const normalCount = faker.number.int({ min: 0, max: 500 });
|
||||||
|
const excludedCount = faker.number.int({ min: 0, max: 50 });
|
||||||
|
const abnormalCount = faker.number.int({ min: 0, max: 30 });
|
||||||
|
const item: VerificationHistoryItem = {
|
||||||
|
executedAt: formatDateTime(executedAt),
|
||||||
|
progressStatus: status,
|
||||||
|
endedAt: formatDateTime(endedAt),
|
||||||
|
verificationYear: String(executedAt.getFullYear()),
|
||||||
|
mu: faker.number.float({ min: 0, max: 1, fractionDigits: 3 }),
|
||||||
|
percentile: faker.number.float({
|
||||||
|
min: 0,
|
||||||
|
max: 100,
|
||||||
|
fractionDigits: 2,
|
||||||
|
}),
|
||||||
|
a: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }),
|
||||||
|
c: faker.number.float({ min: 0, max: 10, fractionDigits: 3 }),
|
||||||
|
target: faker.helpers.arrayElement(VERIFICATION_TARGETS),
|
||||||
|
normalCount,
|
||||||
|
excludedCount,
|
||||||
|
abnormalCount,
|
||||||
|
};
|
||||||
|
return { ...item } as Record<string, string | number>;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 검증 이력 기본 목데이터 (10건) */
|
||||||
|
export const verificationHistoryMockData = createVerificationHistoryMock(10);
|
||||||
Generated
+28
-24
@@ -170,10 +170,10 @@ importers:
|
|||||||
version: 5.28.5
|
version: 5.28.5
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9.0.0
|
specifier: ^9.0.0
|
||||||
version: 9.39.2(jiti@1.21.7)
|
version: 9.39.2(jiti@2.6.1)
|
||||||
eslint-config-next:
|
eslint-config-next:
|
||||||
specifier: 15.4.0
|
specifier: 15.4.0
|
||||||
version: 15.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
version: 15.4.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.4.49
|
specifier: ^8.4.49
|
||||||
version: 8.5.6
|
version: 8.5.6
|
||||||
@@ -897,6 +897,9 @@ importers:
|
|||||||
'@common/config':
|
'@common/config':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../packages/common/config
|
version: link:../../packages/common/config
|
||||||
|
'@faker-js/faker':
|
||||||
|
specifier: ^9.9.0
|
||||||
|
version: 9.9.0
|
||||||
'@fontsource-variable/noto-sans-kr':
|
'@fontsource-variable/noto-sans-kr':
|
||||||
specifier: ^5.2.5
|
specifier: ^5.2.5
|
||||||
version: 5.2.9
|
version: 5.2.9
|
||||||
@@ -1035,10 +1038,10 @@ importers:
|
|||||||
version: 5.28.5
|
version: 5.28.5
|
||||||
eslint:
|
eslint:
|
||||||
specifier: ^9
|
specifier: ^9
|
||||||
version: 9.39.2(jiti@2.6.1)
|
version: 9.39.2(jiti@1.21.7)
|
||||||
eslint-config-next:
|
eslint-config-next:
|
||||||
specifier: 15.1.7
|
specifier: 15.1.7
|
||||||
version: 15.1.7(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)
|
version: 15.1.7(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||||
postcss:
|
postcss:
|
||||||
specifier: ^8
|
specifier: ^8
|
||||||
version: 8.5.6
|
version: 8.5.6
|
||||||
@@ -5036,6 +5039,7 @@ packages:
|
|||||||
|
|
||||||
glob@10.5.0:
|
glob@10.5.0:
|
||||||
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
|
||||||
|
deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
globals@14.0.0:
|
globals@14.0.0:
|
||||||
@@ -10316,6 +10320,26 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
source-map: 0.7.6
|
source-map: 0.7.6
|
||||||
|
|
||||||
|
eslint-config-next@15.1.7(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3):
|
||||||
|
dependencies:
|
||||||
|
'@next/eslint-plugin-next': 15.1.7
|
||||||
|
'@rushstack/eslint-patch': 1.15.0
|
||||||
|
'@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||||
|
'@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
||||||
|
eslint: 9.39.2(jiti@1.21.7)
|
||||||
|
eslint-import-resolver-node: 0.3.9
|
||||||
|
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7))
|
||||||
|
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7))
|
||||||
|
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7))
|
||||||
|
eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7))
|
||||||
|
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@1.21.7))
|
||||||
|
optionalDependencies:
|
||||||
|
typescript: 5.9.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- eslint-import-resolver-webpack
|
||||||
|
- eslint-plugin-import-x
|
||||||
|
- supports-color
|
||||||
|
|
||||||
eslint-config-next@15.1.7(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
eslint-config-next@15.1.7(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/eslint-plugin-next': 15.1.7
|
'@next/eslint-plugin-next': 15.1.7
|
||||||
@@ -10336,26 +10360,6 @@ snapshots:
|
|||||||
- eslint-plugin-import-x
|
- eslint-plugin-import-x
|
||||||
- supports-color
|
- supports-color
|
||||||
|
|
||||||
eslint-config-next@15.4.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3):
|
|
||||||
dependencies:
|
|
||||||
'@next/eslint-plugin-next': 15.4.0
|
|
||||||
'@rushstack/eslint-patch': 1.15.0
|
|
||||||
'@typescript-eslint/eslint-plugin': 8.49.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
|
||||||
'@typescript-eslint/parser': 8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3)
|
|
||||||
eslint: 9.39.2(jiti@1.21.7)
|
|
||||||
eslint-import-resolver-node: 0.3.9
|
|
||||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@1.21.7))
|
|
||||||
eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.49.0(eslint@9.39.2(jiti@1.21.7))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@1.21.7))
|
|
||||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@1.21.7))
|
|
||||||
eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@1.21.7))
|
|
||||||
eslint-plugin-react-hooks: 5.2.0(eslint@9.39.2(jiti@1.21.7))
|
|
||||||
optionalDependencies:
|
|
||||||
typescript: 5.9.3
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- eslint-import-resolver-webpack
|
|
||||||
- eslint-plugin-import-x
|
|
||||||
- supports-color
|
|
||||||
|
|
||||||
eslint-config-next@15.4.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
eslint-config-next@15.4.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3):
|
||||||
dependencies:
|
dependencies:
|
||||||
'@next/eslint-plugin-next': 15.4.0
|
'@next/eslint-plugin-next': 15.4.0
|
||||||
|
|||||||
Reference in New Issue
Block a user