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
|
||||
|
||||
.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/components": "workspace:*",
|
||||
"@common/config": "workspace:*",
|
||||
"@rsone/components": "workspace:*",
|
||||
"@faker-js/faker": "^9.9.0",
|
||||
"@fontsource-variable/noto-sans-kr": "^5.2.5",
|
||||
"@frontend-opensource/use-react-hooks": "^1.1.4",
|
||||
"@fullcalendar/core": "^6.1.17",
|
||||
@@ -26,6 +26,7 @@
|
||||
"@radix-ui/react-label": "^2.1.2",
|
||||
"@radix-ui/react-slot": "^1.1.2",
|
||||
"@radix-ui/react-tooltip": "^1.1.8",
|
||||
"@rsone/components": "workspace:*",
|
||||
"@tanstack/react-query": "^5.67.1",
|
||||
"@tanstack/react-query-devtools": "^5.75.5",
|
||||
"@tanstack/react-table": "^8.21.2",
|
||||
|
||||
@@ -155,16 +155,16 @@ function updateHeaderSortIndicator(
|
||||
function createOnSortingHandler(
|
||||
columns: { fieldName?: string; name: string; mergeCells?: boolean; sortdisable?: boolean; valueType?: ValueType }[],
|
||||
setRowData: React.Dispatch<React.SetStateAction<Record<string, string | number>[]>>,
|
||||
sortDirRef: React.MutableRefObject<Record<string, "asc" | "desc" | "none">>,
|
||||
originalRowsRef: React.MutableRefObject<Record<string, Record<string, string | number>[]>>
|
||||
sortDirRef: { current: Record<string, "asc" | "desc" | "none"> },
|
||||
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"))];
|
||||
// mergeCells: true인 컬럼 찾기 (시군구 등) - 그룹 필드로 사용
|
||||
// 시군구를 우선으로 찾고, 없으면 첫 번째 mergeCells 컬럼 사용
|
||||
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 orderByField = '구분'; // 고정값 또는 설정 가능하게 할 수 있음
|
||||
const orderByField = 'rgnNm'; // 고정값 또는 설정 가능하게 할 수 있음
|
||||
const hasMergeCells = mergeCellCols.length > 0;
|
||||
|
||||
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(() => {
|
||||
props.onValuesChange?.(values);
|
||||
}, [values]);
|
||||
@@ -151,7 +163,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
<Label className="text-label">
|
||||
시군구
|
||||
</Label>
|
||||
<Select value={values.gugun} onValueChange={(value) => setValues({ ...values, gugun: value })}>
|
||||
<Select value={values.gugun} onValueChange={handleSggChange}>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="전체"></SelectValue>
|
||||
</SelectTrigger>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useState } from "react";
|
||||
import { DataGrid } from "@rsone/components";
|
||||
import { useState, useMemo } from "react";
|
||||
// import { DataGrid } from "@rsone/components";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
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 { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
// 하수도 통계 검토 01. 하수도 보급률
|
||||
const SeweragePenetrationRateRvw = () => {
|
||||
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 handleReset = () => {
|
||||
@@ -29,10 +45,10 @@ const [rowData, setRowData] = useState<any[]>([]);
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await sewerageSupplyRateReviewApi.getSewerageSupplyRateReviewList({
|
||||
sido: "경기도",
|
||||
gugun: "과천시",
|
||||
yr: 2024,
|
||||
bsnmCd: "4129000000"
|
||||
sido: values.sido,
|
||||
gugun: values.gugun === 'ALL' ? '' : values.gugun,
|
||||
bsnmCd: values.bsnmCd,
|
||||
yr: values.year,
|
||||
});
|
||||
|
||||
console.log("네트워크 응답 전체:", response);
|
||||
@@ -42,14 +58,13 @@ const [rowData, setRowData] = useState<any[]>([]);
|
||||
|
||||
console.log("받아온 리스트:", rawList);
|
||||
|
||||
|
||||
const convertedData: SewerageSupplyRateReviewItem[] = rawList.map((item) => ({
|
||||
...item,
|
||||
|
||||
speclCntrplnAr: !item.speclCntrplnAr ? '해당없음' : item.speclCntrplnAr,
|
||||
}));
|
||||
|
||||
setRowData(convertedData);
|
||||
setRowData(toGridRows(convertedData));
|
||||
|
||||
} else {
|
||||
setRowData([]);
|
||||
@@ -80,12 +95,12 @@ const [rowData, setRowData] = useState<any[]>([]);
|
||||
</div>
|
||||
</div>
|
||||
<DataGrid
|
||||
columnInfo={SeweragePenetrationRateInfoList}
|
||||
isExcelImport={false}
|
||||
fixedColumns={SeweragePenetrationRateInfoList.fixedColumns}
|
||||
rowStartNum={SeweragePenetrationRateInfoList.rowStartNum}
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={true}
|
||||
fixedColumns={columnInfo.fixedColumns}
|
||||
rowStartNum={columnInfo.rowStartNum}
|
||||
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 {
|
||||
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 [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 (
|
||||
<>
|
||||
<SearchForm />
|
||||
</>
|
||||
<div className="card card-border mt-4">
|
||||
<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: 'bstrm', fieldName: 'bstrm', header: { text: '지류' }, editable: false },
|
||||
{ name: 'bstrm', fieldName: 'bstrm', header: { text: '지류' } },
|
||||
|
||||
// 수계영향권별분류
|
||||
{ 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;
|
||||
}
|
||||
|
||||
// "구분" 필드의 고유값들을 찾는 함수 (첫 번째, 두 번째)
|
||||
const getDivisionValues = (grid: GridView, divisionFieldName: string = '구분'): string[] => {
|
||||
// 구분 필드의 고유값들을 찾는 함수 (첫 번째, 두 번째). 기본 필드명 'rgnNm'
|
||||
const getDivisionValues = (grid: GridView, divisionFieldName: string = 'rgnNm'): string[] => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const rowCount = dataSource.getRowCount();
|
||||
const uniqueValues = new Set<string>();
|
||||
@@ -65,13 +65,13 @@ const getDivisionValues = (grid: GridView, divisionFieldName: string = '구분')
|
||||
return Array.from(uniqueValues);
|
||||
};
|
||||
|
||||
// "구분" 필드 값에 따라 필터링하여 합계 계산하는 함수
|
||||
// 구분 필드 값에 따라 필터링하여 합계 계산하는 함수. 기본 필드명 'rgnNm'
|
||||
// divisionIndex: 0 = 첫 번째 고유값, 1 = 두 번째 고유값
|
||||
export const getFooterSummaryByDivision = (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
divisionIndex: number,
|
||||
divisionFieldName: string = '구분'
|
||||
divisionFieldName: string = 'rgnNm'
|
||||
) => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const rowCount = dataSource.getRowCount();
|
||||
@@ -107,13 +107,13 @@ export const getFooterSummaryByDivision = (
|
||||
return sum;
|
||||
};
|
||||
|
||||
// "구분" 필드 값에 따라 필터링하여 평균 계산하는 함수
|
||||
// 구분 필드 값에 따라 필터링하여 평균 계산하는 함수. 기본 필드명 'rgnNm'
|
||||
// divisionIndex: 0 = 첫 번째 고유값, 1 = 두 번째 고유값
|
||||
export const getFooterAvgSummaryByDivision = (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
divisionIndex: number,
|
||||
divisionFieldName: string = '구분'
|
||||
divisionFieldName: string = 'rgnNm'
|
||||
) => {
|
||||
const dataSource = grid.getDataSource();
|
||||
const rowCount = dataSource.getRowCount();
|
||||
@@ -173,67 +173,51 @@ const createAvgFooter = (format: string) => ({
|
||||
},
|
||||
});
|
||||
|
||||
// "구분" 값에 따라 필터링하는 Footer 배열 생성 함수 (2줄)
|
||||
const createSumFooterArrayByDivision = (format: string) => [
|
||||
// 구분 값에 따라 필터링하는 Footer 배열 생성 함수 (2줄). 기본 필드명 'rgnNm'
|
||||
const createSumFooterArrayByDivision = (format: string, divisionFieldName: string = 'rgnNm') => [
|
||||
{
|
||||
numberFormat: format,
|
||||
valueCallback: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
value: number | string | undefined
|
||||
) => {
|
||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
||||
// footerIndex를 divisionIndex로 사용
|
||||
return getFooterSummaryByDivision(grid, column, footerIndex);
|
||||
},
|
||||
) => getFooterSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||
},
|
||||
{
|
||||
numberFormat: format,
|
||||
valueCallback: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
value: number | string | undefined
|
||||
) => {
|
||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
||||
// footerIndex를 divisionIndex로 사용
|
||||
return getFooterSummaryByDivision(grid, column, footerIndex);
|
||||
},
|
||||
) => getFooterSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||
},
|
||||
];
|
||||
|
||||
// "구분" 값에 따라 필터링하는 평균 Footer 배열 생성 함수 (2줄)
|
||||
const createAvgFooterArrayByDivision = (format: string) => [
|
||||
// 구분 값에 따라 필터링하는 평균 Footer 배열 생성 함수 (2줄). 기본 필드명 'rgnNm'
|
||||
const createAvgFooterArrayByDivision = (format: string, divisionFieldName: string = 'rgnNm') => [
|
||||
{
|
||||
numberFormat: format,
|
||||
valueCallback: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
value: number | string | undefined
|
||||
) => {
|
||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
||||
// footerIndex를 divisionIndex로 사용
|
||||
return getFooterAvgSummaryByDivision(grid, column, footerIndex);
|
||||
},
|
||||
) => getFooterAvgSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||
},
|
||||
{
|
||||
numberFormat: format,
|
||||
valueCallback: (
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
grid: GridView,
|
||||
column: DataColumn,
|
||||
footerIndex: number,
|
||||
columnFooter: unknown,
|
||||
value: number | string | undefined
|
||||
) => {
|
||||
// footerIndex: 0 = 첫 번째 footer 행 (첫 번째 "구분" 값), 1 = 두 번째 footer 행 (두 번째 "구분" 값)
|
||||
// footerIndex를 divisionIndex로 사용
|
||||
return getFooterAvgSummaryByDivision(grid, column, footerIndex);
|
||||
},
|
||||
) => getFooterAvgSummaryByDivision(grid, column, footerIndex, divisionFieldName),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import type { SewerageSupplyRateItem } from "@/entities/statistics/model/statistics-input-interface";
|
||||
|
||||
export function toGridRows(items: SewerageSupplyRateItem[]): Record<string, string | number>[] {
|
||||
/**
|
||||
* 객체 배열을 그리드 row 형식으로 변환 (null/undefined → "").
|
||||
* 어떤 엔티티 타입이든 공통 사용 가능.
|
||||
*/
|
||||
export function toGridRows<T extends object>(
|
||||
items: T[]
|
||||
): Record<string, string | number>[] {
|
||||
return items.map((item) => {
|
||||
const row: Record<string, string | number> = {};
|
||||
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;
|
||||
});
|
||||
|
||||
@@ -4,6 +4,11 @@ export interface Sido {
|
||||
bsnmCd: string;
|
||||
}
|
||||
|
||||
export interface SearchCondition {
|
||||
yearList: string[];
|
||||
sidoList: Sido[];
|
||||
}
|
||||
|
||||
export interface Sgg {
|
||||
adstDstrCd: string;
|
||||
gugun: string;
|
||||
@@ -17,6 +22,6 @@ export interface ApiResponse<T> {
|
||||
}
|
||||
|
||||
export interface StatisticsService {
|
||||
getSidoList(): Promise<ApiResponse<Sido[]>>;
|
||||
getSidoList(): Promise<ApiResponse<SearchCondition>>;
|
||||
getSggList(sidoName: string): Promise<ApiResponse<Sgg[]>>;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ export type SewerageSupplyRateReviewApiResponse = ApiResponse<SewerageSupplyRate
|
||||
export interface SewerageSupplyRateReviewRequestParams {
|
||||
sido?: string;
|
||||
gugun?: string;
|
||||
yr?: number;
|
||||
yr?: 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);
|
||||
Reference in New Issue
Block a user