diff --git a/apps/st/src/components/css/DataGrid.css b/apps/st/src/components/css/DataGrid.css
index e69de29bb..8b1378917 100644
--- a/apps/st/src/components/css/DataGrid.css
+++ b/apps/st/src/components/css/DataGrid.css
@@ -0,0 +1 @@
+
diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx
index 4e423571d..0d0d7ca02 100644
--- a/apps/st/src/components/module/DataGrid.tsx
+++ b/apps/st/src/components/module/DataGrid.tsx
@@ -1,9 +1,9 @@
-import { useRef, useEffect } from "react";
+import { useRef, useEffect, useLayoutEffect } from "react";
import { ValueType } from "realgrid";
import RealGrid, { GridFitStyle, type DisplayOptions } from "realgrid";
import { RealGridReact, RGDataColumn } from "realgrid-react";
-import type { DataGridProps, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback, ColumnLayoutItem } from "../type/DataGrid.type";
+import type { DataGridProps, RealGridColumnInfo, GridViewWithSetColumns, FooterItemWithCallback, ColumnLayoutItem, CellStyleCallback } from "../type/DataGrid.type";
import { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks";
import JSZip from "jszip";
import * as XLSX from "xlsx";
@@ -289,12 +289,33 @@ function buildFieldsFromColumns(columns: { fieldName: string; valueType?: ValueT
});
}
-const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport = true, fixedColumns, showHeaderCheckbox = false, rowData, setRowData, getGridDataRef }: DataGridProps) => {
+const DataGrid = ({
+ columnInfo,
+ rowStartNum,
+ isExcelImport = true,
+ isExcelExport = true,
+ fixedColumns,
+ showHeaderCheckbox = false,
+ rowData,
+ setRowData,
+ getGridDataRef,
+ cellStyleCallback,
+}: DataGridProps) => {
const { columns, columnLayout } = columnInfo;
const gridRef = useRef(null);
const fileInputRef = useRef(null);
const sortDirRef = useRef>({});
const originalRowsRef = useRef[]>>({});
+ const rowDataRef = useRef[]>(rowData);
+ const cellStyleCallbackRef = useRef(cellStyleCallback);
+ rowDataRef.current = rowData;
+ cellStyleCallbackRef.current = cellStyleCallback;
+
+ useLayoutEffect(() => {
+ if (!cellStyleCallback || !gridRef.current?.gridView) return;
+ gridRef.current.gridView.refresh();
+ }, [cellStyleCallback, rowData]);
+
const { setMessage: alert } = useAlertStore((state) => state);
const activeTabId = useTabStore((state) => state.activeTabId);
const menuList = useTabStore((state) => state.menuList);
@@ -801,6 +822,7 @@ const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport
layout={columnLayout}
rows={rowData}
onInitialized={(grid) => {
+ if (!grid) return;
(grid as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef);
const dataProvider = gridRef.current?.dataProvider as unknown as
| { setFields?(f: unknown[]): void; setRows?(r: Record[]): void; getJsonRow?(r: number): Record }
@@ -811,32 +833,61 @@ const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport
if (dataProvider?.setRows && rowData?.length) {
dataProvider.setRows(rowData);
}
+ if (!cellStyleCallbackRef.current) return;
+ const gridView = gridRef.current?.gridView ?? grid;
+ if (!gridView?.columnByName) return;
+ setTimeout(() => {
+ const gv = gridRef.current?.gridView;
+ if (!gv?.columnByName) return;
+ columns.forEach((column) => {
+ const baseStyleName = column.styleName ?? (column.editable === false ? 'rg-cell-readonly' : undefined);
+ const gridCol = gv.columnByName(column.name) as { styleCallback?: (g: unknown, dataCell: { itemIndex?: number; index?: { itemIndex?: number }; dataField?: string; fieldName?: string }) => string } | null;
+ if (gridCol) {
+ gridCol.styleCallback = (_g, dataCell) => {
+ const itemIndex = dataCell?.index?.itemIndex ?? dataCell?.itemIndex ?? -1;
+ const fieldName = dataCell?.dataField ?? dataCell?.fieldName ?? column.fieldName;
+ const data = rowDataRef.current;
+ if (itemIndex < 0 || itemIndex >= data.length) return baseStyleName ?? '';
+ const row = data[itemIndex] as Record;
+ const extra = cellStyleCallbackRef.current?.({ rowIndex: itemIndex, fieldName, row });
+ if (!extra) return baseStyleName ?? '';
+ return baseStyleName ? `${extra} ${baseStyleName}` : extra;
+ };
+ }
+ });
+ gv.refresh();
+ }, 0);
}}
>
- {columns.map((column) => (
- {
+ const baseStyleName =
+ column.styleName != null
+ ? column.styleName
: column.editable === false
- ? { styleName: 'rg-cell-readonly' as const }
- : {})}
- {...(column.visible === false && { visible: false })}
- footer={column.footer && !Array.isArray(column.footer) ? {
- text: column.footer.text,
- expression: column.footer.expression,
- numberFormat: column.footer.numberFormat,
- } : undefined}
- mergeRule={column.mergeCells ? { criteria: 'value' } : undefined}
- sortable={!column.sortdisable}
- />
- ))}
+ ? ('rg-cell-readonly' as const)
+ : undefined;
+
+ return (
+
+ );
+ })}
diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx
index 9333b92cb..2c1b15f5e 100644
--- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx
+++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx
@@ -17,6 +17,8 @@ import type {
SewerageSupplyRateItem,
SewerageSupplyRateRequestParams,
SewerageSupplyRateSaveItem,
+ SewerageSupplyRateVerificationResult,
+ SewerageSupplyRateSaveRequest
} from "@/entities/statistics/model/statistics-input-interface";
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
import { toGridRows } from "@/components/util/grid/gridFunction";
@@ -91,7 +93,13 @@ const SeweragePenetrationRateInp = () => {
speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr,
piplSupprt: commonInfo.piplSupprt,
}));
- setRowData(toGridRows(convertedData));
+ setRowData(
+ convertedData.map((d) => {
+ const row = toGridRows([d])[0] as Record & { verificationResult?: SewerageSupplyRateVerificationResult };
+ if (d.verificationResult) row.verificationResult = d.verificationResult;
+ return row;
+ })
+ );
setFooterFromApi(
commonInfo
@@ -193,13 +201,9 @@ const SeweragePenetrationRateInp = () => {
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
-
- // SaveItem 타입에 정의된 필드만 추출
+
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
- bsnmCd: searchValues?.bsnmCd ?? '',
- sido: d.sido as string,
gugun: (d.gugun as string) || null,
- yr: searchValues?.year ?? '',
adstCd: d.adstCd as string,
adstDong: d.adstDong as string,
bstrm: d.bstrm as string,
@@ -226,10 +230,17 @@ const SeweragePenetrationRateInp = () => {
pblswrgPopltnSuprt: formatNumber(d.pblswrgPopltnSuprt, 'pblswrgPopltnSuprt'),
adtrtPopltnSuprt: formatNumber(d.adtrtPopltnSuprt, 'adtrtPopltnSuprt'),
swrgInstlrt: formatNumber(d.swrgInstlrt, 'swrgInstlrt'),
- userId: userInfo.userName,
}));
- const response = await statisticsInputAPi.saveSewerageSupplyRate(convertedData);
+ const requestData: SewerageSupplyRateSaveRequest = {
+ bsnmCd: searchValues?.bsnmCd ?? '',
+ sido: searchValues?.sido ?? '',
+ yr: searchValues?.year ?? '',
+ userId: userInfo.userName,
+ saveList: convertedData,
+ }
+
+ const response = await statisticsInputAPi.saveSewerageSupplyRate(requestData);
console.log(convertedData)
console.log(response);
} else {
@@ -273,6 +284,14 @@ const SeweragePenetrationRateInp = () => {
rowData={rowData}
setRowData={setRowData}
getGridDataRef={getGridDataRef}
+ cellStyleCallback={({ row, fieldName }) => {
+ const verification = (row as any).verificationResult as SewerageSupplyRateVerificationResult | undefined;
+ if (!verification) return;
+ const errors = verification[fieldName];
+ if (Array.isArray(errors) && errors.length > 0) {
+ return 'rg-cell-verify-error';
+ }
+ }}
/>
diff --git a/apps/st/src/components/type/DataGrid.type.ts b/apps/st/src/components/type/DataGrid.type.ts
index 8079148d6..634512d58 100644
--- a/apps/st/src/components/type/DataGrid.type.ts
+++ b/apps/st/src/components/type/DataGrid.type.ts
@@ -140,6 +140,15 @@ export type ColumnInfoList = {
right?: ColumnInfoList; // SplitDataGrid용 오른쪽 그리드 정보
}
+// 개별 셀 스타일을 동적으로 결정하기 위한 콜백 (예: 검증 오류 셀 강조)
+export type CellStyleCallbackParams = {
+ rowIndex: number;
+ fieldName: string;
+ row: Record;
+};
+
+export type CellStyleCallback = (params: CellStyleCallbackParams) => string | undefined;
+
export type DataGridProps = {
columnInfo: ColumnInfo;
rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주)
@@ -152,6 +161,8 @@ export type DataGridProps = {
setRowData: React.Dispatch[]>>;
/** 저장 등 시점에 그리드 현재 데이터를 가져올 수 있도록 할당 (편집 반영된 값) */
getGridDataRef?: { current: (() => Record[]) | null };
+ /** 셀 단위로 styleName을 동적으로 결정 (예: 검증 오류 셀) */
+ cellStyleCallback?: CellStyleCallback;
};
export type TabDataGridProps = {
diff --git a/apps/st/src/entities/statistics/api/statistics-input-service.ts b/apps/st/src/entities/statistics/api/statistics-input-service.ts
index bf758e0a8..f8fdc0850 100644
--- a/apps/st/src/entities/statistics/api/statistics-input-service.ts
+++ b/apps/st/src/entities/statistics/api/statistics-input-service.ts
@@ -6,7 +6,7 @@ import {
type SewerageSupplyRateRequestParams,
type SewerageSupplyRateApiResponse,
type CheckInputPeriodResponse,
- type SewerageSupplyRateSaveItem,
+ type SewerageSupplyRateSaveRequest,
type SewerageSupplyRateSaveApiResponse,
} from "../model/statistics-input-interface";
@@ -30,7 +30,7 @@ export class StatisticsInputAPiService implements StatisticsInputService {
return response.data;
}
- public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveItem[]): Promise {
+ public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise {
const response = await axios.post (
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`,
params
diff --git a/apps/st/src/entities/statistics/model/statistics-input-interface.ts b/apps/st/src/entities/statistics/model/statistics-input-interface.ts
index 4f36f49b3..3fd0ba0c8 100644
--- a/apps/st/src/entities/statistics/model/statistics-input-interface.ts
+++ b/apps/st/src/entities/statistics/model/statistics-input-interface.ts
@@ -138,10 +138,7 @@ export interface SewerageSupplyRateRequestParams {
/** 하수도 보급률 저장 API 요청 body (한 행) */
export interface SewerageSupplyRateSaveItem {
- bsnmCd: string;
- sido: string;
gugun: string | null;
- yr: string;
adstCd: string;
adstDong: string;
bstrm: string;
@@ -168,7 +165,14 @@ export interface SewerageSupplyRateSaveItem {
pblswrgPopltnSuprt: number;
adtrtPopltnSuprt: number;
swrgInstlrt: number;
+}
+
+export interface SewerageSupplyRateSaveRequest {
+ yr: string;
+ sido: string;
+ bsnmCd: string;
userId: string;
+ saveList: SewerageSupplyRateSaveItem[];
}
export interface SewerageSupplyRateSaveData {
@@ -190,5 +194,5 @@ export interface CheckInputPeriodResponse {
export interface StatisticsInputService {
getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise;
checkInputPeriod(params: string): Promise;
- saveSewerageSupplyRate(params: SewerageSupplyRateSaveItem[]): Promise;
+ saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise;
}
\ No newline at end of file
diff --git a/packages/rsone/components/grid/css/DataGrid.css b/packages/rsone/components/grid/css/DataGrid.css
index 4469b8490..6aa237533 100644
--- a/packages/rsone/components/grid/css/DataGrid.css
+++ b/packages/rsone/components/grid/css/DataGrid.css
@@ -110,4 +110,9 @@
/* 소계, 합계, 율(%) cell */
.rg-cell-total {
background-color: #e3eaf2 !important;
+}
+
+.rg-cell-verify-error {
+ background-color: #ffe6e6 !important;
+ color: #c00 !important;
}
\ No newline at end of file