From d08df3ff3560621c24bd0d713090fc754780de02 Mon Sep 17 00:00:00 2001 From: bicochan Date: Thu, 5 Feb 2026 15:14:54 +0900 Subject: [PATCH] =?UTF-8?q?[st]=20feat:=20=EA=B2=80=EC=A6=9D=20=EC=9D=B4?= =?UTF-8?q?=EB=A0=A5=20=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EB=AA=A9=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/package.json | 3 +- .../ManagementVerificationRule.tsx | 711 +++++++++--------- .../model/verification-history-column.ts | 120 +++ .../model/verification-history-interface.ts | 27 + .../model/verification-history-mock.ts | 67 ++ pnpm-lock.yaml | 52 +- 6 files changed, 606 insertions(+), 374 deletions(-) create mode 100644 apps/st/src/entities/statistics/model/verification-history-column.ts create mode 100644 apps/st/src/entities/statistics/model/verification-history-interface.ts create mode 100644 apps/st/src/entities/statistics/model/verification-history-mock.ts diff --git a/apps/st/package.json b/apps/st/package.json index a501dde6a..cd951af0d 100644 --- a/apps/st/package.json +++ b/apps/st/package.json @@ -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", diff --git a/apps/st/src/components/statistics/verification/ManagementVerificationRule.tsx b/apps/st/src/components/statistics/verification/ManagementVerificationRule.tsx index b0c35ce1a..1d5dfd169 100644 --- a/apps/st/src/components/statistics/verification/ManagementVerificationRule.tsx +++ b/apps/st/src/components/statistics/verification/ManagementVerificationRule.tsx @@ -1,6 +1,7 @@ "use client"; import { useState } from "react"; +import { DataGrid } from "@rsone/components"; import SearchForm from "@/components/module/SearchForm"; import { Button, @@ -13,6 +14,8 @@ import { 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; @@ -37,6 +40,11 @@ interface VerificationFormValues { } const ManagementVerificationRule = () => { + const [verificationHistoryRowData, setVerificationHistoryRowData] = + useState[]>( + verificationHistoryMockData, + ); + const [formValues, setFormValues] = useState({ mu: "0", percentile: "0", @@ -90,379 +98,384 @@ const ManagementVerificationRule = () => { }; return ( - <> - +
+
+ {/* 통계 검증 섹션 */} +
+

+ 통계 검증 +

-
-
- {/* 통계 검증 섹션 */} -
-

- 통계 검증 -

+
+ {/* 왼쪽: 검증 변수 */} +
+

+ 검증 변수 +

-
- {/* 왼쪽: 검증 변수 */} -
-

- 검증 변수 -

+
+
+ + + handleInputChange( + "mu", + e.target.value, + ) + } + className="flex-1" + /> +
-
-
- - - handleInputChange( - "mu", - e.target.value, - ) - } - className="flex-1" - /> -
+
+ + + handleInputChange( + "percentile", + e.target.value, + ) + } + className="flex-1" + /> +
-
- - - handleInputChange( - "percentile", - e.target.value, - ) - } - className="flex-1" - /> -
+
+ + + handleInputChange( + "a", + e.target.value, + ) + } + className="flex-1" + /> +
-
- - - handleInputChange( - "a", - e.target.value, - ) - } - className="flex-1" - /> -
+
+ + + handleInputChange( + "c", + e.target.value, + ) + } + className="flex-1" + /> +
-
- - - handleInputChange( - "c", - e.target.value, - ) - } - className="flex-1" - /> -
- -
- - -
+
+ +
+
- {/* 오른쪽: 검증 대상 */} -
-

- 검증 대상 -

+ {/* 오른쪽: 검증 대상 */} +
+

+ 검증 대상 +

-
-
-
- - handleCheckboxChange( - "sidoDistribution", - ) - } - /> - -
- -
- - handleCheckboxChange( - "countyDistribution", - ) - } - /> - -
- -
- - handleCheckboxChange( - "countyDistributionUserManagement", - ) - } - /> - -
- -
- - handleCheckboxChange( - "lowerDistribution", - ) - } - /> - -
+
+
+
+ + handleCheckboxChange( + "sidoDistribution", + ) + } + /> +
-
-
- - handleCheckboxChange( - "sidoServiceAvailability", - ) - } - /> - -
- -
- - handleCheckboxChange( - "individualService", - ) - } - /> - -
- -
- - handleCheckboxChange( - "sidoServiceAvailability", - ) - } - /> - -
- -
- - handleCheckboxChange( - "individualService", - ) - } - /> - -
+
+ + handleCheckboxChange( + "countyDistribution", + ) + } + /> +
-
-
- - handleCheckboxChange( - "lowerOrgStatistics", - ) - } - /> - -
- -
- - handleCheckboxChange( - "studentStatistics", - ) - } - /> - -
- -
- - handleCheckboxChange( - "lowerOrgStatistics", - ) - } - /> - -
- -
- - handleCheckboxChange( - "studentStatistics", - ) - } - /> - -
+
+ + handleCheckboxChange( + "countyDistributionUserManagement", + ) + } + /> +
-
-
- - handleCheckboxChange( - "lowerManagementEnterprise", - ) - } - /> - -
+
+ + handleCheckboxChange( + "lowerDistribution", + ) + } + /> + +
+
+ +
+
+ + handleCheckboxChange( + "sidoServiceAvailability", + ) + } + /> + +
+ +
+ + handleCheckboxChange( + "individualService", + ) + } + /> + +
+ +
+ + handleCheckboxChange( + "sidoServiceAvailability", + ) + } + /> + +
+ +
+ + handleCheckboxChange( + "individualService", + ) + } + /> + +
+
+ +
+
+ + handleCheckboxChange( + "lowerOrgStatistics", + ) + } + /> + +
+ +
+ + handleCheckboxChange( + "studentStatistics", + ) + } + /> + +
+ +
+ + handleCheckboxChange( + "lowerOrgStatistics", + ) + } + /> + +
+ +
+ + handleCheckboxChange( + "studentStatistics", + ) + } + /> + +
+
+ +
+
+ + handleCheckboxChange( + "lowerManagementEnterprise", + ) + } + /> +
+
- {/* 버튼 */} -
- - -
+ {/* 버튼 */} +
+ + +
+ + {/* 검증 이력 섹션 */} +
+

+ 검증 이력 +

+
- +
); }; diff --git a/apps/st/src/entities/statistics/model/verification-history-column.ts b/apps/st/src/entities/statistics/model/verification-history-column.ts new file mode 100644 index 000000000..aea9b7f4f --- /dev/null +++ b/apps/st/src/entities/statistics/model/verification-history-column.ts @@ -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", + ], +}; diff --git a/apps/st/src/entities/statistics/model/verification-history-interface.ts b/apps/st/src/entities/statistics/model/verification-history-interface.ts new file mode 100644 index 000000000..869399cba --- /dev/null +++ b/apps/st/src/entities/statistics/model/verification-history-interface.ts @@ -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; +} diff --git a/apps/st/src/entities/statistics/model/verification-history-mock.ts b/apps/st/src/entities/statistics/model/verification-history-mock.ts new file mode 100644 index 000000000..90c152be7 --- /dev/null +++ b/apps/st/src/entities/statistics/model/verification-history-mock.ts @@ -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[] { + 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; + }); +} + +/** 검증 이력 기본 목데이터 (10건) */ +export const verificationHistoryMockData = createVerificationHistoryMock(10); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ab7fd8d4a..2a3d2ab29 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -170,10 +170,10 @@ importers: version: 5.28.5 eslint: specifier: ^9.0.0 - version: 9.39.2(jiti@1.21.7) + version: 9.39.2(jiti@2.6.1) eslint-config-next: 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: specifier: ^8.4.49 version: 8.5.6 @@ -897,6 +897,9 @@ importers: '@common/config': specifier: workspace:* version: link:../../packages/common/config + '@faker-js/faker': + specifier: ^9.9.0 + version: 9.9.0 '@fontsource-variable/noto-sans-kr': specifier: ^5.2.5 version: 5.2.9 @@ -1035,10 +1038,10 @@ importers: version: 5.28.5 eslint: specifier: ^9 - version: 9.39.2(jiti@2.6.1) + version: 9.39.2(jiti@1.21.7) eslint-config-next: 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: specifier: ^8 version: 8.5.6 @@ -5036,6 +5039,7 @@ packages: glob@10.5.0: 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 globals@14.0.0: @@ -10316,6 +10320,26 @@ snapshots: optionalDependencies: 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): dependencies: '@next/eslint-plugin-next': 15.1.7 @@ -10336,26 +10360,6 @@ snapshots: - eslint-plugin-import-x - 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): dependencies: '@next/eslint-plugin-next': 15.4.0