Merge branch 'iipbs/st/dev' of https://www.onasys.co.kr:3000/ONAANDALL/frontend into iipbs/st/dev

This commit is contained in:
2026-03-06 11:42:58 +09:00
10 changed files with 164 additions and 47 deletions
@@ -951,6 +951,8 @@ const DataGrid = ({
);
}
});
// valueCallback 적용 후 푸터 셀 다시 그리기 (API 합계 등 우선 표시 반영)
currentGridView.refresh?.();
}, 200);
if (getGridDataRef) {
+3 -9
View File
@@ -1,6 +1,6 @@
"use client";
import { useState, useEffect, useRef } from "react";
import { useState, useEffect } from "react";
import useSWR from 'swr';
import {
@@ -19,7 +19,6 @@ import { useAlertStore } from "@/shared/lib";
import { type SearchFormProps, type SearchFormValues, defaultSearchFormProps } from "../type/SearchForm.type";
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
import type { ApiResponse, SearchCondition, Sgg } from "@/entities/statistics/model/statistics-interface";
import { doList } from "../util/column/input/DoList";
const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
return statisticsAPi.getSidoList();
@@ -68,7 +67,6 @@ const SearchForm = (props: SearchFormProps = {}) => {
});
const sggKey: [string, string] | null =
// values.sido && values.sido !== "ALL" && doList.includes(values.bsnmCd ?? "")
values.sido && values.sido !== "ALL"
? ["sgg-list", values.sido]
: null;
@@ -93,9 +91,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
},
}
);
const lastSggListRef = useRef<Sgg[]>([]);
if (sggList?.data) lastSggListRef.current = sggList.data;
const sggOptions = sggList?.data ?? (values.sido && values.sido !== "ALL" ? lastSggListRef.current : []);
const sggOptions = sggList?.data ?? [];
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
@@ -103,7 +99,6 @@ const SearchForm = (props: SearchFormProps = {}) => {
};
const handleReset = () => {
lastSggListRef.current = [];
const resetValues: SearchFormValues = {
sido: '',
gugun: '',
@@ -118,7 +113,6 @@ const SearchForm = (props: SearchFormProps = {}) => {
const handleSidoChange = (value: string) => {
const selectedBsnm = sidoList?.data.sidoList.find((v) => v.sido === value)?.bsnmCd || '';
lastSggListRef.current = []; // 시도 변경 시 이전 시군구 목록 제거(조건 안 맞는 시도로 바꿀 때 옵션 안 남도록)
setValues((prev) => ({
...prev,
sido: value,
@@ -128,7 +122,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
};
const handleSggChange = (value: string) => {
const listForBsnm = sggList?.data ?? lastSggListRef.current;
const listForBsnm = sggList?.data ?? [];
const selectedBsnm = value === 'ALL'
? (sidoList?.data.sidoList.find((v) => v.sido === values.sido)?.bsnmCd || '')
: (listForBsnm.find((v) => v.gugun === value)?.bsnmCd || '');
@@ -64,10 +64,10 @@ const SeweragePenetrationRateInp = () => {
const { isAdminInfo } = useAdminStore();
const [footerFromApi, setFooterFromApi] = useState<{ totalPopl?: number; poplSm?: number; frgnrPoplSm?: number; refArea?: number } | null>(null);
const [footerFromApi, setFooterFromApi] = useState<{ sewerBzmnWholPpltnCnt?: number; sewerBzmnDomplWholPpltnCnt?: number; sewerBzmnFrgnrWholPpltnCnt?: number; sewerBzmnArea?: number } | null>(null);
const getGridDataRef = useRef<(() => Record<string, string | number>[]) | null>(null);
const _footerFieldNames = ['totalPopl', 'poplSm', 'frgnrPoplSm', 'refArea'] as const;
const _footerFieldNames = ['sewerBzmnWholPpltnCnt', 'sewerBzmnDomplWholPpltnCnt', 'sewerBzmnFrgnrWholPpltnCnt', 'sewerBzmnArea'] as const;
const footerWithApiDefault = (fieldName: (typeof _footerFieldNames)[number], numberFormat: string) => ({
expression: 'sum' as const,
numberFormat,
@@ -79,13 +79,14 @@ const SeweragePenetrationRateInp = () => {
const columnInfo = useMemo(() => {
const { columns, columnLayout } = SeweragePenetrationRateInpColumn;
const base = (isEnterable || isAdminInfo) ? columns : columns.map((col) => ({ ...col, editable: false as const }));
return {
columnLayout,
columns: base.map((col) => {
if (col.fieldName === 'totalPopl') return { ...col, footer: footerWithApiDefault('totalPopl', '#,##0') };
if (col.fieldName === 'poplSm') return { ...col, footer: footerWithApiDefault('poplSm', '#,##0') };
if (col.fieldName === 'frgnrPoplSm') return { ...col, footer: footerWithApiDefault('frgnrPoplSm', '#,##0') };
if (col.fieldName === 'refArea') return { ...col, footer: footerWithApiDefault('refArea', '#,##0.000') };
if (col.fieldName === 'pbadmsWholPpltnCnt') return { ...col, footer: footerWithApiDefault('sewerBzmnWholPpltnCnt', '#,##0') };
if (col.fieldName === 'domplWholPpltnCnt') return { ...col, footer: footerWithApiDefault('sewerBzmnDomplWholPpltnCnt', '#,##0') };
if (col.fieldName === 'frgnrWholPpltnCnt') return { ...col, footer: footerWithApiDefault('sewerBzmnFrgnrWholPpltnCnt', '#,##0') };
if (col.fieldName === 'sewerBzmnArea') return { ...col, footer: footerWithApiDefault('sewerBzmnArea', '#,##0.000') };
return col;
}),
};
@@ -124,10 +125,10 @@ const SeweragePenetrationRateInp = () => {
setFooterFromApi(
commonInfo
? {
totalPopl: commonInfo.sewerBzmnWholPpltnCnt,
poplSm: commonInfo.sewerBzmnDomplWholPpltnCnt,
frgnrPoplSm: commonInfo.sewerBzmnFrgnrWholPpltnCnt,
refArea: commonInfo.sewerBzmnArea,
sewerBzmnWholPpltnCnt: commonInfo.sewerBzmnWholPpltnCnt,
sewerBzmnDomplWholPpltnCnt: commonInfo.sewerBzmnDomplWholPpltnCnt,
sewerBzmnFrgnrWholPpltnCnt: commonInfo.sewerBzmnFrgnrWholPpltnCnt,
sewerBzmnArea: commonInfo.sewerBzmnArea,
}
: null
);
@@ -268,7 +269,7 @@ const SeweragePenetrationRateInp = () => {
statsYr: searchValues?.year ?? '',
userId: userInfo.userUnqId,
submitList: convertedData,
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.frgnrPoplSm,
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.sewerBzmnFrgnrWholPpltnCnt,
laePicNm: submitInfo.laePicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
@@ -370,7 +371,7 @@ const SeweragePenetrationRateInp = () => {
statsYr: searchValues?.year ?? '', // 통계 연도
userId: userInfo.userUnqId, // 사용자
saveList: convertedData, // 저장 데이터
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.frgnrPoplSm // 행정참고자료 외국인 합계
sewerBzmnFrgnrWholPpltnCnt: footerFromApi?.sewerBzmnFrgnrWholPpltnCnt // 행정참고자료 외국인 합계
};
if (isAdminInfo) {
@@ -394,7 +395,7 @@ const SeweragePenetrationRateInp = () => {
setIsLoading(false);
}
};
console.log(footerFromApi);
return (
<>
<div className="relative">
@@ -37,7 +37,7 @@ const StatsInputStatusInp = () => {
setIsLoading(true);
const response = await StatisticsInputStatusApi.getStatisticsInputStatusList(searchParam);
console.log(response)
if (response.status === 'success' && response.data) {
setRowData(toGridRows(response.data));
} else {
@@ -1,6 +1,106 @@
"use client";
import { useState } from 'react';
import useSWR from 'swr';
import {
Button,
Input,
Label,
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue
}
from "@common/components";
import DataGrid from '@/components/module/DataGrid';
import { LogHistoryColumn } from '@/components/util/column/management/LogHistoryColumn';
import { useAlertStore } from "@/shared/lib";
import {
LwpoCodeApi,
} from "@/entities/code";
const getStateCdListFetcher = async () => {
return LwpoCodeApi.getLwpoCodeList({
sort: 'regDt,desc',
upCd: 'STITEM',
page: 0,
size: 100,
});
}
const LogHistory = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const [lwpoCd, setLwpoCd] = useState<string>('');
const { data: lwpoCdList } = useSWR(
"lwpo-code-list",
getStateCdListFetcher,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: false,
dedupingInterval: 2000,
shouldRetryOnError: false,
onSuccess: (data) => {
if (!data.content || data.content.length == 0) {
alert(`시도 목록을 불러오는데 실패했습니다.
새로고침 후 다시 시도해주세요.`);
}
},
onError: () => {
alert(`시도 목록을 불러오는데 실패했습니다.
새로고침 후 다시 시도해주세요.`);
},
}
);
return (
<></>
<div className="relative">
<div className="card card-border">
<form>
<div className="flex items-center gap-8 p-2">
<div className="flex items-center gap-2">
<Label className="text-label">
메뉴명
</Label>
<Select value={lwpoCd} onValueChange={setLwpoCd}>
<SelectTrigger className="w-[22rem]">
<SelectValue placeholder="전체"></SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체</SelectItem>
{lwpoCdList?.content?.map((l) => (
<SelectItem key={l.lwpoCd} value={l.lwpoCd}>
{l.lwpoCdNm}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div className="flex items-center gap-2 ml-auto">
<Button color="green" className="h-[3.2rem] w-[96px]" type="button">
초기화
</Button>
<Button className="h-[3.2rem] w-[96px]" type="submit">
조회
</Button>
</div>
</div>
</form>
</div>
<DataGrid
columnInfo={LogHistoryColumn}
// rowStartNum={1}
// fixedColumns={1}
isExcelImport={false}
// showHeaderCheckbox={true}
rowData={[]}
setRowData={() => {}}
/>
</div>
);
};
@@ -24,9 +24,9 @@ export const SeweragePenetrationRateInpColumn = {
{ name: 'spcCtpnRgnCn', fieldName: 'spcCtpnRgnCn', header: { text: '특별대책지역' } },
// 행정참고자료인구
{ name: 'totalPopl', fieldName: 'totalPopl', header: { text: '총인구' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
{ name: 'poplSm', fieldName: 'poplSm', header: { text: '내국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
{ name: 'frgnrPoplSm', fieldName: 'frgnrPoplSm', header: { text: '외국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
{ name: 'pbadmsWholPpltnCnt', fieldName: 'pbadmsWholPpltnCnt', header: { text: '총인구' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
{ name: 'domplWholPpltnCnt', fieldName: 'domplWholPpltnCnt', header: { text: '내국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
{ name: 'frgnrWholPpltnCnt', fieldName: 'frgnrWholPpltnCnt', header: { text: '외국인' }, footer: footerInt, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
// 행정참고자료면적
{ name: 'refArea', fieldName: 'refArea', header: { text: '행정참고자료면적(㎢)' }, footer: footerDecimal3, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0.000', styleName: 'rg-cell-readonly' },
@@ -207,7 +207,7 @@ export const SeweragePenetrationRateInpColumn = {
name: 'groupC',
hideChildHeaders: false,
header: { text: '행정참고자료인구' },
items: ['totalPopl', 'poplSm', 'frgnrPoplSm'],
items: ['pbadmsWholPpltnCnt', 'domplWholPpltnCnt', 'frgnrWholPpltnCnt'],
},
'refArea',
'wholPpltnCnt',
@@ -6,11 +6,11 @@
*/
export const StatsInputStatusInpColumn = {
columns: [
{ name: 'sido', fieldName: 'sido', header: { text: '시도' }, editable: false },
{ name: 'gugun', fieldName: 'gugun', header: { text: '시군구' }, editable: false },
{ name: 'menuCd', fieldName: 'menuCd', header: { text: '메뉴코드' }, visible: false, editable: false },
{ name: 'menuNm', fieldName: 'menuNm', header: { text: '구분' }, editable: false, width: 220 },
{ name: 'laeSbmsnSttsCdNm', fieldName: 'laeSbmsnSttsCdNm', header: { text: '진행상황' }, editable: false },
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, editable: false },
{ 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: 'locgStatusNm', fieldName: 'locgStatusNm', header: { text: '진행상황' }, editable: false },
// { name: 'inputRate', fieldName: 'inputRate', header: { text: '작성률' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'laePicNm', fieldName: 'laePicNm', header: { text: '작성자' }, editable: false },
{ name: 'laePicTelNo', fieldName: 'laePicTelNo', header: { text: '작성자연락처' }, editable: false },
@@ -19,20 +19,20 @@ export const StatsInputStatusInpColumn = {
{ name: 'laeIdfrNm', fieldName: 'laeIdfrNm', header: { text: '승인자' }, editable: false },
{ name: 'laeIdfrDeptNm', fieldName: 'laeIdfrDeptNm', header: { text: '부서' }, editable: false },
{ name: 'laeIdfrJbgdNm', fieldName: 'laeIdfrJbgdNm', header: { text: '직제' }, editable: false },
{ name: 'returnOpinionSm', fieldName: 'returnOpinionSm', header: { text: '반려사유' }, editable: false },
{ name: 'rjctOpnnCn', fieldName: 'rjctOpnnCn', header: { text: '반려사유' }, editable: false },
],
columnLayout: [
'sido',
'gugun',
'menuCd',
'menuNm',
'ctpvNm',
'sggNm',
'statsCd',
'menuTabName',
{
name: 'groupA',
hideChildHeaders: false,
header: { text: '하수도사업자' },
items: [
'laeSbmsnSttsCdNm',
'locgStatusNm',
// 'inputRate',
'laePicNm',
'laePicTelNo',
@@ -41,7 +41,7 @@ export const StatsInputStatusInpColumn = {
'laeIdfrNm',
'laeIdfrDeptNm',
'laeIdfrJbgdNm',
'returnOpinionSm'
'rjctOpnnCn'
],
},
]
@@ -0,0 +1,21 @@
import { ValueType } from "realgrid";
export const LogHistoryColumn = {
columns: [
{ name: 'statsLogSn', fieldName: 'statsLogSn', header: { text: '일련번호' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'menuNm', fieldName: 'menuNm', header: { text: '메뉴명' }, editable: false },
{ name: 'tblNm', fieldName: 'tblNm', header: { text: '테이블명' }, visible: false, editable: false },
{ name: 'dmndFwkNm', fieldName: 'dmndFwkNm', header: { text: '요청 기능명' }, editable: false },
{ name: 'rgtrId', fieldName: 'rgtrId', header: { text: '등록자' }, editable: false },
{ name: 'regDt', fieldName: 'regDt', header: { text: '등록일' }, editable: false },
],
columnLayout: [
'statsLogSn',
'menuNm',
'tblNm',
'dmndFwkNm',
'rgtrId',
'regDt'
]
};
@@ -9,11 +9,11 @@ export interface StatsInputStatusListRequestParams {
}
export interface StatsInputStatusList {
sido?: string;
gugun?: string;
menuCd?: string;
menuNm?: string;
laeSbmsnSttsCdNm?: string;
ctpvNm?: string;
sggNm?: string;
statsCd?: string;
menuTabName?: string;
locgStatusNm?: string;
inputRate?: number;
laePicNm?: string;
laePicTelNo?: string;
@@ -22,7 +22,7 @@ export interface StatsInputStatusList {
laeIdfrNm?: string;
laeIdfrDeptNm?: string;
laeIdfrJbgdNm?: string;
returnOpinionSm?: string;
rjctOpnnCn?: string;
}
export type StatsInputStatusListApiResponse = ApiResponse<StatsInputStatusList[]> & {
@@ -11,7 +11,6 @@ export interface SewerageSupplyRateStatusInfo {
dcsnStatus: string | null;
wrterOpinionSm: string | null;
chckerOpinionSm: string | null;
returnOpinionSm: string | null;
laeIdfrDeptNm: string | null;
laeIdfrJbgdNm: string | null;
laeIdfrNm: string | null;