) => {
+ if (!searchedYear) {
+ alert("먼저 조회를 진행해주세요.");
+ return;
}
- } catch {
- alert('검토 이력 조회 중 오류가 발생했습니다.');
- }
- };
+ setHistorySearchInfo({
+ ctpvNm: String(row.ctpvNm ?? ""),
+ sggNm: String(row.sggNm ?? ""),
+ tabNm: String(row.tabNm ?? ""),
+ });
+ const params = {
+ statsYr: searchedYear,
+ sewerBzmnCd: String(row.sewerBzmnCd ?? ""),
+ statsCd: String(row.statsCd ?? ""),
+ };
+
+ try {
+ const response = await statisticsManagementApi.getReviewHistory(params);
+
+ if (response.status === "success") {
+ setHistoryRowData(response.data);
+ setReviewHistoryOpen(true);
+ } else {
+ alert(response.message || "검토 이력 조회 중 오류가 발생했습니다.");
+ }
+ } catch {
+ alert("검토 이력 조회 중 오류가 발생했습니다.");
+ }
+ },
+ [alert, searchedYear],
+ );
+
+ const handleReviewHistoryCellClick = useCallback(
+ ({ fieldName, row }: DataCellClickCallbackParams) => {
+ if (fieldName !== "reviewHistory") return;
+ void openReviewHistoryForRow(row);
+ },
+ [openReviewHistoryForRow],
+ );
return (
@@ -193,12 +293,12 @@ const StatsInputStatusRvw = () => {
{
if (fieldName === "submitStatusNm") {
const statusCd = String(row.submitStatusCd ?? "");
diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts
index c6c3e0171..3dea0d65a 100644
--- a/apps/st/src/components/type/DataGrid.type.ts
+++ b/apps/st/src/components/type/DataGrid.type.ts
@@ -279,6 +279,16 @@ export type CellEditedCallback = (
params: CellEditedCallbackParams,
) => void;
+/** 데이터 셀 클릭 시 컬럼(필드)까지 전달 — 특정 컬럼만 반응할 때 사용 */
+export type DataCellClickCallbackParams = {
+ row: Record;
+ fieldName: string;
+ itemIndex: number;
+};
+export type DataCellClickCallback = (
+ params: DataCellClickCallbackParams,
+) => void;
+
export type DataGridProps = {
columnInfo: ColumnInfo;
rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주)
@@ -306,6 +316,8 @@ export type DataGridProps = {
/** 외부에서 gridRef에 직접 접근하기 위한 ref (setRows/addRows/setValue 직접 호출용) */
getGridRef?: React.MutableRefObject;
onRowClick?: (rowData: Record) => void; // 행 클릭 이벤트
+ /** 데이터 셀 클릭 시 행 + 클릭한 필드명 전달 (`onRowClick`과 함께 쓸 수 있음) */
+ onDataCellClick?: DataCellClickCallback;
/** CheckBar 행 체크 변경 */
onItemChecked?: (itemIndex: number, checked: boolean) => void;
/** CheckBar 헤더 전체 선택/해제 */
diff --git a/apps/st/src/components/util/column/input/StatsInputStatusInpColumn.ts b/apps/st/src/components/util/column/input/StatsInputStatusInpColumn.ts
index 5ecd59ffa..4cc2b6e65 100644
--- a/apps/st/src/components/util/column/input/StatsInputStatusInpColumn.ts
+++ b/apps/st/src/components/util/column/input/StatsInputStatusInpColumn.ts
@@ -10,6 +10,14 @@ export const StatsInputStatusInpColumn = {
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false },
{ name: 'statsCd', fieldName: 'statsCd', header: { text: '메뉴코드' }, visible: false, editable: false },
{ name: 'menuTabName', fieldName: 'menuTabName', header: { text: '구분' }, editable: false, width: 220 },
+ {
+ name: 'exceptionSubmitHistory',
+ fieldName: 'exceptionSubmitHistory',
+ header: { text: '예외제출내역' },
+ editable: false,
+ width: 118,
+ styleName: 'rg-cell-outline-action-btn',
+ },
{ name: 'laeSbmsnSttsNm', fieldName: 'laeSbmsnSttsNm', header: { text: '진행상황' }, editable: false },
{ name: 'laePicNm', fieldName: 'laePicNm', header: { text: '작성자' }, editable: false },
{ name: 'laePicTelNo', fieldName: 'laePicTelNo', header: { text: '작성자연락처' }, editable: false },
@@ -27,6 +35,7 @@ export const StatsInputStatusInpColumn = {
'sggNm',
'statsCd',
'menuTabName',
+ 'exceptionSubmitHistory',
{
name: 'groupA',
hideChildHeaders: false,
diff --git a/apps/st/src/components/util/column/management/LogHistoryColumn.ts b/apps/st/src/components/util/column/management/LogHistoryColumn.ts
index adaad69ea..cef11bcc4 100644
--- a/apps/st/src/components/util/column/management/LogHistoryColumn.ts
+++ b/apps/st/src/components/util/column/management/LogHistoryColumn.ts
@@ -6,7 +6,7 @@ export const LogHistoryColumn = {
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false },
{ name: 'statsYr', fieldName: 'statsYr', header: { text: '연도' }, editable: false },
{ name: 'statsLogSn', fieldName: 'statsLogSn', header: { text: '일련번호' }, editable: false, visible: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
- { name: 'menuNm', fieldName: 'menuNm', header: { text: '메뉴명' }, editable: false },
+ { name: 'menuNm', fieldName: 'menuNm', header: { text: '통계항목' }, editable: false },
{ name: 'tblNm', fieldName: 'tblNm', header: { text: '테이블명' }, visible: false, editable: false },
{ name: 'statsDataChgComNm', fieldName: 'statsDataChgComNm', header: { text: '요청 기능명' }, editable: false },
{ name: 'rgtrId', fieldName: 'rgtrId', header: { text: '등록자' }, editable: false, visible: false },
diff --git a/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts b/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts
index 997072a65..bb6f6d984 100644
--- a/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts
+++ b/apps/st/src/components/util/column/review/StatsInputStatusRvwColumn.ts
@@ -9,6 +9,13 @@ const StatsInputStatusInfoColumnRaw = {
{ name: 'sewerBzmnCd', fieldName: 'sewerBzmnCd', header: { text: '사업자코드' }, visible: false },
{ name: 'statsCd', fieldName: 'statsCd', header: { text: '메뉴코드' }, visible: false },
{ name: 'tabNm', fieldName: 'tabNm', header: { text: '구분' }, width: 220 },
+ {
+ name: 'reviewHistory',
+ fieldName: 'reviewHistory',
+ header: { text: '검토이력' },
+ width: 118,
+ styleName: 'rg-cell-outline-action-btn',
+ },
{ name: 'chargerNm', fieldName: 'chargerNm', header: { text: '제출자' } },
{ name: 'chargerCttpc', fieldName: 'chargerCttpc', header: { text: '연락처' } },
@@ -22,6 +29,7 @@ const StatsInputStatusInfoColumnRaw = {
'ctpvNm',
'sggNm',
'tabNm',
+ 'reviewHistory',
'chargerNm',
'chargerCttpc',
'submitStatusNm',
diff --git a/apps/st/src/components/util/column/total/SewagePipeTotColumn.ts b/apps/st/src/components/util/column/total/SewagePipeTotColumn.ts
index cfb46b13f..a576c5caa 100644
--- a/apps/st/src/components/util/column/total/SewagePipeTotColumn.ts
+++ b/apps/st/src/components/util/column/total/SewagePipeTotColumn.ts
@@ -7,7 +7,7 @@ import { ValueType } from "realgrid";
*/
/** ST00020003 관종별: 입력 `SeweragePipeInputPipeTypeStatusItem`·합계 `SewagePipePipeTypeFacilityStatusItem`과 동일 (yy1=1996년 이전 … yy30) */
-const YY_PIPE_TYPE_FACILITY_MAX = 30;
+// const YY_PIPE_TYPE_FACILITY_MAX = 30;
const rawSewagePipeTotList = [
{
diff --git a/apps/st/src/entities/statistics/api/statistics-service.ts b/apps/st/src/entities/statistics/api/statistics-service.ts
index dc96d9fb6..0fdfbf292 100644
--- a/apps/st/src/entities/statistics/api/statistics-service.ts
+++ b/apps/st/src/entities/statistics/api/statistics-service.ts
@@ -12,6 +12,7 @@ import {
type ImrStatusResponse,
type ReviewerInfoRequest,
type ReviewerInfoResponse,
+ type ReviewInputSaveRequest,
type ReviewOpinionSaveRequest,
type ReviewOpinionSaveResponse,
type ReviewReturnSaveRequest,
@@ -33,6 +34,9 @@ import {
type StatsInputPeriodListRequest,
type StatsInputPeriodSaveApiResponse,
type StatsInputPeriodSaveRequest,
+ type StatsInputStatusApproveBatchRequest,
+ type StatsInputStatusApproveKind,
+ type StatsInputStatusApproveResponse,
type SubmitExceptionRequest,
type SubmitExceptionResponse,
type SubmitVerifyExceptionRequest,
@@ -214,6 +218,56 @@ export class StatisticsApiService implements StatisticsService {
return response.data;
}
+ /** 입력 반려 — JSON body는 `ReviewInputSaveRequest`(type 제외) 항목의 배열, `type`은 쿼리 */
+ public async saveInputReturn(
+ items: ReviewInputSaveRequest[],
+ ): Promise {
+ if (items.length === 0) {
+ throw new Error("saveInputReturn: 항목이 비어 있습니다.");
+ }
+ const [first] = items;
+ if (!first) {
+ throw new Error("saveInputReturn: 항목이 비어 있습니다.");
+ }
+ const type = first.type;
+ if (!items.every((i) => i.type === type)) {
+ throw new Error("saveInputReturn: 모든 항목의 type이 같아야 합니다.");
+ }
+ const body = items.map(({ type: _t, rjctOpnnCn, ...rest }) => {
+ const row: Record = {
+ statsYr: rest.statsYr,
+ sewerBzmnCd: rest.sewerBzmnCd,
+ statsCd: rest.statsCd,
+ mdfrId: rest.mdfrId,
+ };
+ if (rest.vrfcSn != null && String(rest.vrfcSn).trim() !== "") {
+ row.vrfcSn = String(rest.vrfcSn);
+ }
+ const trimmed = rjctOpnnCn?.trim();
+ if (trimmed) row.rjctOpnnCn = trimmed;
+ return row;
+ });
+ const response = await stClient.post(
+ `${ENDPOINT.ST_SERVICE.STATS}/reject`,
+ body,
+ { params: { type } },
+ );
+ return response.data;
+ }
+
+ /** 통계 입력 현황 일괄 승인 — 쿼리 `type`(기본 `INPUT`), body는 `{ list }` */
+ public async approveStatsInputStatus(
+ param: StatsInputStatusApproveBatchRequest,
+ type: StatsInputStatusApproveKind = "INPUT",
+ ): Promise {
+ const response = await stClient.post(
+ `${ENDPOINT.ST_SERVICE.STATS}/approve`,
+ param.list,
+ { params: { type } },
+ );
+ return response.data;
+ }
+
public async submitReview(
param: ReviewSubmitRequest,
): Promise {
diff --git a/apps/st/src/entities/statistics/index.ts b/apps/st/src/entities/statistics/index.ts
index 159b0eb76..c8045b1f3 100644
--- a/apps/st/src/entities/statistics/index.ts
+++ b/apps/st/src/entities/statistics/index.ts
@@ -74,6 +74,12 @@ export {
type ImrLogResponse,
} from "./model/imr/imr-interface";
export { imrVerificationHistoryColumns } from "./model/imr/imr-verification-history-columns";
+export {
+ type StatsInputStatusApproveBatchRequest,
+ type StatsInputStatusApproveItem,
+ type StatsInputStatusApproveKind,
+ type StatsInputStatusApproveResponse,
+} from "./model/statistics-interface";
export { type StatsInputStatusList } from "./model/input/statistics-input-status-interface";
export { userListColumns } from "./model/notice-talk/notice-talk-sending-columns";
export {
diff --git a/apps/st/src/entities/statistics/model/statistics-interface.ts b/apps/st/src/entities/statistics/model/statistics-interface.ts
index d31bb787e..cd8caaf77 100644
--- a/apps/st/src/entities/statistics/model/statistics-interface.ts
+++ b/apps/st/src/entities/statistics/model/statistics-interface.ts
@@ -340,6 +340,7 @@ export interface ReviewReturnSaveRequest {
statsCd: string;
rjctOpnnCn: string;
mdfrId: string;
+ vrfcSn?: string;
}
export interface ReviewReturnSaveResponse {
@@ -349,6 +350,17 @@ export interface ReviewReturnSaveResponse {
updatedStts: string;
}
+export interface ReviewInputSaveRequest {
+ type: "INPUT";
+ statsYr: string;
+ sewerBzmnCd: string;
+ statsCd: string;
+ /** 미입력·빈 문자열이면 요청 body에 포함하지 않음 */
+ rjctOpnnCn?: string;
+ mdfrId: string;
+ vrfcSn?: string;
+}
+
export interface ReviewSubmitRequest {
statsYr: string;
sewerBzmnCd: string;
@@ -363,6 +375,28 @@ export interface ReviewSubmitResponse {
message?: string;
}
+/** 통계 일괄 승인 `/stats/approve` 쿼리 파라미터 `type` */
+export type StatsInputStatusApproveKind = "INPUT" | "REVIEW";
+
+/** 통계 입력 현황 승인 요청 항목 (행 단위) */
+export interface StatsInputStatusApproveItem {
+ statsYr: string;
+ sewerBzmnCd: string;
+ statsCd: string;
+ mdfrId: string;
+}
+
+/** 통계 입력 현황 일괄 승인 API 요청 본문 */
+export interface StatsInputStatusApproveBatchRequest {
+ list: StatsInputStatusApproveItem[];
+}
+
+/** 통계 입력 현황 일괄 승인 API 응답 */
+export interface StatsInputStatusApproveResponse {
+ status: string;
+ message?: string;
+}
+
export interface SubmitVerifyExceptionItem {
vrfcSn: number;
prcsOpnnExpln: string;
@@ -416,6 +450,11 @@ export interface StatisticsService {
saveReviewOpinion(param: ReviewOpinionSaveRequest): Promise;
getReviewOpinionDetail(param: getReviewOpinionDetailRequest): Promise;
saveReviewReturn(param: ReviewReturnSaveRequest): Promise;
+ saveInputReturn(items: ReviewInputSaveRequest[]): Promise;
+ approveStatsInputStatus(
+ param: StatsInputStatusApproveBatchRequest,
+ type?: StatsInputStatusApproveKind,
+ ): Promise;
submitReview(param: ReviewSubmitRequest): Promise;
getVerificationResultList(param: VerificationResultListRequest): Promise;
getImrStatus(param: string): Promise;
From 25320d43c4b2f7acda34fc35b3d84e44aca4bd81 Mon Sep 17 00:00:00 2001
From: bicochan
Date: Thu, 14 May 2026 14:03:52 +0900
Subject: [PATCH 13/13] =?UTF-8?q?[st]=20chore:=20=ED=86=B5=EA=B3=84=20?=
=?UTF-8?q?=EA=B2=80=EC=A6=9D=20=EB=A6=AC=ED=8F=AC=ED=8A=B8=20=EC=88=98?=
=?UTF-8?q?=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../StatisticsVerificationReport.tsx | 26 ++++++++++++-------
.../model/general-verification-interface.ts | 1 +
.../statistics-verification-report-columns.ts | 18 ++++++++++---
3 files changed, 33 insertions(+), 12 deletions(-)
diff --git a/apps/st/src/components/statistics/verification/StatisticsVerificationReport.tsx b/apps/st/src/components/statistics/verification/StatisticsVerificationReport.tsx
index 6cd57b4f1..36274ddcd 100644
--- a/apps/st/src/components/statistics/verification/StatisticsVerificationReport.tsx
+++ b/apps/st/src/components/statistics/verification/StatisticsVerificationReport.tsx
@@ -4,11 +4,13 @@ import type { Dispatch, SetStateAction } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
+import { useQuery } from "@tanstack/react-query";
import dayjs from "dayjs";
import type { RealGridReact } from "realgrid-react";
+import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper, useUserStore } from "@/shared/lib";
-import { CodeSelect } from "@/entities/code";
+import { CodeApi, CodeSelect } from "@/entities/code";
import {
type ApproveRequest,
type RejectRequest,
@@ -138,12 +140,6 @@ const DEFAULT_SUB_FILTERS: StatisticsVerificationSubFilterState = {
pbcpAprvYn: "N",
};
-const STATUS_OPTIONS = [
- { label: "수정완료", value: "1" },
- { label: "예외통과", value: "2" },
- { label: "반려", value: "3" },
-];
-
const APPROVAL_STATUS_OPTIONS = [
{ label: "승인", value: "Y" },
{ label: "미승인", value: "N" },
@@ -195,6 +191,18 @@ function StatisticsVerificationReport() {
[filters, subFilters],
);
+ const { data: statusOptions } = useQuery({
+ queryKey: [ENDPOINT.CMS_SERVICE.CODE, "STINPTEXCT"],
+ queryFn: () => CodeApi.getCode("STINPTEXCT"),
+ select: (data) =>
+ data
+ .filter(({ lwpoCd }) => !["0", "4"].includes(lwpoCd))
+ .map(({ lwpoCdNm: label, lwpoCd: value }) => ({
+ label,
+ value,
+ })),
+ });
+
const statisticsVerificationReportQuery = useStatsVrfcRptData(filters);
const statisticsVerificationResultQuery =
useStatsVrfcRsltData(detailParams);
@@ -594,7 +602,7 @@ function StatisticsVerificationReport() {
label="상태"
value={subFilters.prcsYnList}
onChange={handleSubFilterChange("prcsYnList")}
- options={STATUS_OPTIONS}
+ options={statusOptions ?? []}
/>
0 ? list.join(",") : undefined);
}}
>
-
+
{(values: string[]) => (
<>
diff --git a/apps/st/src/entities/statistics/model/general-verification-interface.ts b/apps/st/src/entities/statistics/model/general-verification-interface.ts
index c4ef996d4..a3fcd66c0 100644
--- a/apps/st/src/entities/statistics/model/general-verification-interface.ts
+++ b/apps/st/src/entities/statistics/model/general-verification-interface.ts
@@ -230,6 +230,7 @@ export interface VerifyResultItem {
nowVlCn?: string;
compVlCn?: string;
prcsYn?: string;
+ prcsYnNm?: string;
prcsOpnnExpln?: string;
prcrId?: string;
rjctOpnnCn?: string;
diff --git a/apps/st/src/entities/statistics/model/statistics-verification-report/statistics-verification-report-columns.ts b/apps/st/src/entities/statistics/model/statistics-verification-report/statistics-verification-report-columns.ts
index 1ebf0337c..969d3c6b3 100644
--- a/apps/st/src/entities/statistics/model/statistics-verification-report/statistics-verification-report-columns.ts
+++ b/apps/st/src/entities/statistics/model/statistics-verification-report/statistics-verification-report-columns.ts
@@ -9,12 +9,14 @@ export const statisticsVerificationReportColumnInfo: ColumnInfo = {
fieldName: "ctpvNm",
header: { text: "시도" },
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "sggNm",
fieldName: "sggNm",
header: { text: "시군구" },
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "menuTabName",
@@ -22,6 +24,7 @@ export const statisticsVerificationReportColumnInfo: ColumnInfo = {
header: { text: "구분" },
width: 150,
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "locgStatusNm",
@@ -29,12 +32,14 @@ export const statisticsVerificationReportColumnInfo: ColumnInfo = {
header: { text: "진행상태" },
width: 120,
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "laePicNm",
fieldName: "laePicNm",
header: { text: "작성자" },
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "laePicTelNo",
@@ -42,36 +47,42 @@ export const statisticsVerificationReportColumnInfo: ColumnInfo = {
header: { text: "작성자연락처" },
width: 130,
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "laeKbrdrDeptNm",
fieldName: "laeKbrdrDeptNm",
header: { text: "부서" },
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "laeKbrdrJbgdNm",
fieldName: "laeKbrdrJbgdNm",
header: { text: "직급" },
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "laeIdfrNm",
fieldName: "laeIdfrNm",
header: { text: "승인자" },
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "laeIdfrDeptNm",
fieldName: "laeIdfrDeptNm",
header: { text: "부서" },
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "laeIdfrJbgdNm",
fieldName: "laeIdfrJbgdNm",
header: { text: "직급" },
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "rjctOpnnCn",
@@ -79,6 +90,7 @@ export const statisticsVerificationReportColumnInfo: ColumnInfo = {
header: { text: "반려사유" },
width: 220,
styleName: READ_ONLY_STYLE,
+ editable: false,
},
{
name: "sewerBzmnCd",
@@ -176,8 +188,8 @@ export const statisticsVerificationResultColumnInfo: ColumnInfo = {
editable: false,
},
{
- name: "prcsYn",
- fieldName: "prcsYn",
+ name: "prcsYnNm",
+ fieldName: "prcsYnNm",
header: { text: "상태" },
width: 90,
styleName: READ_ONLY_STYLE,
@@ -223,7 +235,7 @@ export const statisticsVerificationResultColumnInfo: ColumnInfo = {
"rsnExpln",
"nowVlCn",
"compVlCn",
- "prcsYn",
+ "prcsYnNm",
"prcsOpnnExpln",
"rjctOpnnCn",
"pbcpAprvYn",