st: fix: 하수도보급률 저장, 일반 검증 결과 체크
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { useRef, useEffect } from "react";
|
import { useRef, useEffect, useLayoutEffect } from "react";
|
||||||
import { ValueType } from "realgrid";
|
import { ValueType } from "realgrid";
|
||||||
|
|
||||||
import RealGrid, { GridFitStyle, type DisplayOptions } from "realgrid";
|
import RealGrid, { GridFitStyle, type DisplayOptions } from "realgrid";
|
||||||
import { RealGridReact, RGDataColumn } from "realgrid-react";
|
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 { getComputedValueCallback } from "@/components/util/grid/computedFieldCallbacks";
|
||||||
import JSZip from "jszip";
|
import JSZip from "jszip";
|
||||||
import * as XLSX from "xlsx";
|
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 { columns, columnLayout } = columnInfo;
|
||||||
const gridRef = useRef<RealGridReact>(null);
|
const gridRef = useRef<RealGridReact>(null);
|
||||||
const fileInputRef = useRef<HTMLInputElement>(null);
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
||||||
const sortDirRef = useRef<Record<string, "asc" | "desc" | "none">>({});
|
const sortDirRef = useRef<Record<string, "asc" | "desc" | "none">>({});
|
||||||
const originalRowsRef = useRef<Record<string, Record<string, string | number>[]>>({});
|
const originalRowsRef = useRef<Record<string, Record<string, string | number>[]>>({});
|
||||||
|
const rowDataRef = useRef<Record<string, string | number>[]>(rowData);
|
||||||
|
const cellStyleCallbackRef = useRef<CellStyleCallback | undefined>(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 { setMessage: alert } = useAlertStore((state) => state);
|
||||||
const activeTabId = useTabStore((state) => state.activeTabId);
|
const activeTabId = useTabStore((state) => state.activeTabId);
|
||||||
const menuList = useTabStore((state) => state.menuList);
|
const menuList = useTabStore((state) => state.menuList);
|
||||||
@@ -801,6 +822,7 @@ const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport
|
|||||||
layout={columnLayout}
|
layout={columnLayout}
|
||||||
rows={rowData}
|
rows={rowData}
|
||||||
onInitialized={(grid) => {
|
onInitialized={(grid) => {
|
||||||
|
if (!grid) return;
|
||||||
(grid as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef);
|
(grid as { onSorting?: (g: unknown, f: unknown[], d: unknown[]) => boolean }).onSorting = createOnSortingHandler(columns, setRowData, sortDirRef, originalRowsRef);
|
||||||
const dataProvider = gridRef.current?.dataProvider as unknown as
|
const dataProvider = gridRef.current?.dataProvider as unknown as
|
||||||
| { setFields?(f: unknown[]): void; setRows?(r: Record<string, string | number>[]): void; getJsonRow?(r: number): Record<string, unknown> }
|
| { setFields?(f: unknown[]): void; setRows?(r: Record<string, string | number>[]): void; getJsonRow?(r: number): Record<string, unknown> }
|
||||||
@@ -811,32 +833,61 @@ const DataGrid = ({ columnInfo, rowStartNum, isExcelImport = true, isExcelExport
|
|||||||
if (dataProvider?.setRows && rowData?.length) {
|
if (dataProvider?.setRows && rowData?.length) {
|
||||||
dataProvider.setRows(rowData);
|
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<string, string | number>;
|
||||||
|
const extra = cellStyleCallbackRef.current?.({ rowIndex: itemIndex, fieldName, row });
|
||||||
|
if (!extra) return baseStyleName ?? '';
|
||||||
|
return baseStyleName ? `${extra} ${baseStyleName}` : extra;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
gv.refresh();
|
||||||
|
}, 0);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{columns.map((column) => (
|
{columns.map((column) => {
|
||||||
<RGDataColumn
|
const baseStyleName =
|
||||||
key={column.name}
|
column.styleName != null
|
||||||
name={column.name}
|
? column.styleName
|
||||||
fieldName={column.fieldName}
|
|
||||||
valueType={column.valueType ? column.valueType : ValueType.TEXT}
|
|
||||||
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.000' })}
|
|
||||||
header={column.header}
|
|
||||||
editable={column.editable}
|
|
||||||
{...(column.styleName != null
|
|
||||||
? { styleName: column.styleName }
|
|
||||||
: column.editable === false
|
: column.editable === false
|
||||||
? { styleName: 'rg-cell-readonly' as const }
|
? ('rg-cell-readonly' as const)
|
||||||
: {})}
|
: undefined;
|
||||||
{...(column.visible === false && { visible: false })}
|
|
||||||
footer={column.footer && !Array.isArray(column.footer) ? {
|
return (
|
||||||
text: column.footer.text,
|
<RGDataColumn
|
||||||
expression: column.footer.expression,
|
key={column.name}
|
||||||
numberFormat: column.footer.numberFormat,
|
name={column.name}
|
||||||
} : undefined}
|
fieldName={column.fieldName}
|
||||||
mergeRule={column.mergeCells ? { criteria: 'value' } : undefined}
|
valueType={column.valueType ? column.valueType : ValueType.TEXT}
|
||||||
sortable={!column.sortdisable}
|
{...(column.valueType === ValueType.NUMBER && { numberFormat: column.dataFormat ? column.dataFormat : '#,##0.000' })}
|
||||||
/>
|
header={column.header}
|
||||||
))}
|
editable={column.editable}
|
||||||
|
{...(baseStyleName != null && { styleName: baseStyleName })}
|
||||||
|
{...(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}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
})}
|
||||||
</RealGridReact>
|
</RealGridReact>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ import type {
|
|||||||
SewerageSupplyRateItem,
|
SewerageSupplyRateItem,
|
||||||
SewerageSupplyRateRequestParams,
|
SewerageSupplyRateRequestParams,
|
||||||
SewerageSupplyRateSaveItem,
|
SewerageSupplyRateSaveItem,
|
||||||
|
SewerageSupplyRateVerificationResult,
|
||||||
|
SewerageSupplyRateSaveRequest
|
||||||
} from "@/entities/statistics/model/statistics-input-interface";
|
} from "@/entities/statistics/model/statistics-input-interface";
|
||||||
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
|
import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn";
|
||||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||||
@@ -91,7 +93,13 @@ const SeweragePenetrationRateInp = () => {
|
|||||||
speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr,
|
speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr,
|
||||||
piplSupprt: commonInfo.piplSupprt,
|
piplSupprt: commonInfo.piplSupprt,
|
||||||
}));
|
}));
|
||||||
setRowData(toGridRows(convertedData));
|
setRowData(
|
||||||
|
convertedData.map((d) => {
|
||||||
|
const row = toGridRows([d])[0] as Record<string, string | number> & { verificationResult?: SewerageSupplyRateVerificationResult };
|
||||||
|
if (d.verificationResult) row.verificationResult = d.verificationResult;
|
||||||
|
return row;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
setFooterFromApi(
|
setFooterFromApi(
|
||||||
commonInfo
|
commonInfo
|
||||||
@@ -193,13 +201,9 @@ const SeweragePenetrationRateInp = () => {
|
|||||||
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
|
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
|
||||||
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
|
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
|
||||||
};
|
};
|
||||||
|
|
||||||
// SaveItem 타입에 정의된 필드만 추출
|
|
||||||
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
|
const convertedData: SewerageSupplyRateSaveItem[] = currentData.map((d) => ({
|
||||||
bsnmCd: searchValues?.bsnmCd ?? '',
|
|
||||||
sido: d.sido as string,
|
|
||||||
gugun: (d.gugun as string) || null,
|
gugun: (d.gugun as string) || null,
|
||||||
yr: searchValues?.year ?? '',
|
|
||||||
adstCd: d.adstCd as string,
|
adstCd: d.adstCd as string,
|
||||||
adstDong: d.adstDong as string,
|
adstDong: d.adstDong as string,
|
||||||
bstrm: d.bstrm as string,
|
bstrm: d.bstrm as string,
|
||||||
@@ -226,10 +230,17 @@ const SeweragePenetrationRateInp = () => {
|
|||||||
pblswrgPopltnSuprt: formatNumber(d.pblswrgPopltnSuprt, 'pblswrgPopltnSuprt'),
|
pblswrgPopltnSuprt: formatNumber(d.pblswrgPopltnSuprt, 'pblswrgPopltnSuprt'),
|
||||||
adtrtPopltnSuprt: formatNumber(d.adtrtPopltnSuprt, 'adtrtPopltnSuprt'),
|
adtrtPopltnSuprt: formatNumber(d.adtrtPopltnSuprt, 'adtrtPopltnSuprt'),
|
||||||
swrgInstlrt: formatNumber(d.swrgInstlrt, 'swrgInstlrt'),
|
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(convertedData)
|
||||||
console.log(response);
|
console.log(response);
|
||||||
} else {
|
} else {
|
||||||
@@ -273,6 +284,14 @@ const SeweragePenetrationRateInp = () => {
|
|||||||
rowData={rowData}
|
rowData={rowData}
|
||||||
setRowData={setRowData}
|
setRowData={setRowData}
|
||||||
getGridDataRef={getGridDataRef}
|
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';
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<ConfirmDialog isOpen={false} />
|
<ConfirmDialog isOpen={false} />
|
||||||
|
|||||||
@@ -140,6 +140,15 @@ export type ColumnInfoList = {
|
|||||||
right?: ColumnInfoList; // SplitDataGrid용 오른쪽 그리드 정보
|
right?: ColumnInfoList; // SplitDataGrid용 오른쪽 그리드 정보
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 개별 셀 스타일을 동적으로 결정하기 위한 콜백 (예: 검증 오류 셀 강조)
|
||||||
|
export type CellStyleCallbackParams = {
|
||||||
|
rowIndex: number;
|
||||||
|
fieldName: string;
|
||||||
|
row: Record<string, string | number>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CellStyleCallback = (params: CellStyleCallbackParams) => string | undefined;
|
||||||
|
|
||||||
export type DataGridProps = {
|
export type DataGridProps = {
|
||||||
columnInfo: ColumnInfo;
|
columnInfo: ColumnInfo;
|
||||||
rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주)
|
rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주)
|
||||||
@@ -152,6 +161,8 @@ export type DataGridProps = {
|
|||||||
setRowData: React.Dispatch<React.SetStateAction<Record<string, string | number>[]>>;
|
setRowData: React.Dispatch<React.SetStateAction<Record<string, string | number>[]>>;
|
||||||
/** 저장 등 시점에 그리드 현재 데이터를 가져올 수 있도록 할당 (편집 반영된 값) */
|
/** 저장 등 시점에 그리드 현재 데이터를 가져올 수 있도록 할당 (편집 반영된 값) */
|
||||||
getGridDataRef?: { current: (() => Record<string, string | number>[]) | null };
|
getGridDataRef?: { current: (() => Record<string, string | number>[]) | null };
|
||||||
|
/** 셀 단위로 styleName을 동적으로 결정 (예: 검증 오류 셀) */
|
||||||
|
cellStyleCallback?: CellStyleCallback;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TabDataGridProps = {
|
export type TabDataGridProps = {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
type SewerageSupplyRateRequestParams,
|
type SewerageSupplyRateRequestParams,
|
||||||
type SewerageSupplyRateApiResponse,
|
type SewerageSupplyRateApiResponse,
|
||||||
type CheckInputPeriodResponse,
|
type CheckInputPeriodResponse,
|
||||||
type SewerageSupplyRateSaveItem,
|
type SewerageSupplyRateSaveRequest,
|
||||||
type SewerageSupplyRateSaveApiResponse,
|
type SewerageSupplyRateSaveApiResponse,
|
||||||
} from "../model/statistics-input-interface";
|
} from "../model/statistics-input-interface";
|
||||||
|
|
||||||
@@ -30,7 +30,7 @@ export class StatisticsInputAPiService implements StatisticsInputService {
|
|||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveItem[]): Promise<SewerageSupplyRateSaveApiResponse> {
|
public async saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse> {
|
||||||
const response = await axios.post<SewerageSupplyRateSaveApiResponse> (
|
const response = await axios.post<SewerageSupplyRateSaveApiResponse> (
|
||||||
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`,
|
`${ENDPOINT.ST_SERVICE.INPUT}/sewerage-supply-rate`,
|
||||||
params
|
params
|
||||||
|
|||||||
@@ -138,10 +138,7 @@ export interface SewerageSupplyRateRequestParams {
|
|||||||
|
|
||||||
/** 하수도 보급률 저장 API 요청 body (한 행) */
|
/** 하수도 보급률 저장 API 요청 body (한 행) */
|
||||||
export interface SewerageSupplyRateSaveItem {
|
export interface SewerageSupplyRateSaveItem {
|
||||||
bsnmCd: string;
|
|
||||||
sido: string;
|
|
||||||
gugun: string | null;
|
gugun: string | null;
|
||||||
yr: string;
|
|
||||||
adstCd: string;
|
adstCd: string;
|
||||||
adstDong: string;
|
adstDong: string;
|
||||||
bstrm: string;
|
bstrm: string;
|
||||||
@@ -168,7 +165,14 @@ export interface SewerageSupplyRateSaveItem {
|
|||||||
pblswrgPopltnSuprt: number;
|
pblswrgPopltnSuprt: number;
|
||||||
adtrtPopltnSuprt: number;
|
adtrtPopltnSuprt: number;
|
||||||
swrgInstlrt: number;
|
swrgInstlrt: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SewerageSupplyRateSaveRequest {
|
||||||
|
yr: string;
|
||||||
|
sido: string;
|
||||||
|
bsnmCd: string;
|
||||||
userId: string;
|
userId: string;
|
||||||
|
saveList: SewerageSupplyRateSaveItem[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SewerageSupplyRateSaveData {
|
export interface SewerageSupplyRateSaveData {
|
||||||
@@ -190,5 +194,5 @@ export interface CheckInputPeriodResponse {
|
|||||||
export interface StatisticsInputService {
|
export interface StatisticsInputService {
|
||||||
getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise<SewerageSupplyRateApiResponse>;
|
getSewerageSupplyRateList(params: SewerageSupplyRateRequestParams): Promise<SewerageSupplyRateApiResponse>;
|
||||||
checkInputPeriod(params: string): Promise<CheckInputPeriodResponse>;
|
checkInputPeriod(params: string): Promise<CheckInputPeriodResponse>;
|
||||||
saveSewerageSupplyRate(params: SewerageSupplyRateSaveItem[]): Promise<SewerageSupplyRateSaveApiResponse>;
|
saveSewerageSupplyRate(params: SewerageSupplyRateSaveRequest): Promise<SewerageSupplyRateSaveApiResponse>;
|
||||||
}
|
}
|
||||||
@@ -110,4 +110,9 @@
|
|||||||
/* 소계, 합계, 율(%) cell */
|
/* 소계, 합계, 율(%) cell */
|
||||||
.rg-cell-total {
|
.rg-cell-total {
|
||||||
background-color: #e3eaf2 !important;
|
background-color: #e3eaf2 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rg-cell-verify-error {
|
||||||
|
background-color: #ffe6e6 !important;
|
||||||
|
color: #c00 !important;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user