From 42d6010efaf62fbc3b138ce719b3bd4f2632e04e Mon Sep 17 00:00:00 2001 From: rsone001 Date: Wed, 4 Feb 2026 15:21:38 +0900 Subject: [PATCH 1/2] =?UTF-8?q?st:=20refactor:=20=EC=8B=9C=EB=8F=84=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=80=EC=83=89=EC=97=B0=EB=8F=84=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=88=98=EC=A0=95=20feat:=20=ED=95=98=EC=88=98?= =?UTF-8?q?=EB=8F=84=EB=B3=B4=EA=B8=89=EB=A5=A0=20=EC=A0=84=EB=85=84?= =?UTF-8?q?=EB=8F=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EA=B0=80=EC=A0=B8?= =?UTF-8?q?=EC=98=A4=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?=EB=B0=8F=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EC=8B=9C=20=EC=9E=85=EB=A0=A5=20=EA=B8=B0=ED=95=9C=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=B6=B0=20=EA=B7=B8=EB=A6=AC=EB=93=9C=20=EC=85=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20or=20=EC=88=98=EC=A0=95=20=EB=B6=88?= =?UTF-8?q?=EA=B0=80=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/module/SearchForm.tsx | 20 +-- .../input/SeweragePenetrationRateInp.tsx | 125 ++++++++++++------ .../st/src/components/type/SearchForm.type.ts | 3 +- .../api/statistics-input-service.ts | 9 ++ .../statistics/api/statistics-service.ts | 8 +- .../model/statistics-input-interface.ts | 13 +- apps/st/src/shared/config/api.ts | 1 + 7 files changed, 123 insertions(+), 56 deletions(-) diff --git a/apps/st/src/components/module/SearchForm.tsx b/apps/st/src/components/module/SearchForm.tsx index 530b3579b..bd15477f8 100644 --- a/apps/st/src/components/module/SearchForm.tsx +++ b/apps/st/src/components/module/SearchForm.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import useSWR from 'swr'; import { @@ -18,9 +18,9 @@ from "@common/components"; 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, Sido, Sgg } from "@/entities/statistics/model/statistics-interface"; +import type { ApiResponse, SearchCondition, Sgg } from "@/entities/statistics/model/statistics-interface"; -const getSidoListFetcher = async (): Promise> => { +const getSidoListFetcher = async (): Promise> => { return statisticsAPi.getSidoList(); }; @@ -36,8 +36,6 @@ const SearchForm = (props: SearchFormProps = {}) => { const defaultYear = currentYear - 1; const startYear = currentYear - 10; - const yearOptions = Array.from({ length: currentYear - startYear + 1 }, (_, i) => currentYear - i); - const [values, setValues] = useState({ sido: props.defaultValues?.sido || "", gugun: props.defaultValues?.gugun || '', @@ -47,7 +45,11 @@ const SearchForm = (props: SearchFormProps = {}) => { bsnmCd: props.defaultValues?.bsnmCd || '', }); - const { data: sidoList } = useSWR>( + useEffect(() => { + props.onValuesChange?.(values); + }, [values]); + + const { data: sidoList } = useSWR>( "sido-list", getSidoListFetcher, { @@ -111,7 +113,7 @@ const SearchForm = (props: SearchFormProps = {}) => { }; const handleSidoChange = (value: string) => { - const selectedBsnm = sidoList?.data.find((v) => v.sido === value)?.bsnmCd || ''; + const selectedBsnm = sidoList?.data.sidoList.find((v) => v.sido === value)?.bsnmCd || ''; setValues((prev) => ({ ...prev, @@ -136,7 +138,7 @@ const SearchForm = (props: SearchFormProps = {}) => { 선택 - {sidoList?.data?.map((sido) => ( + {sidoList?.data?.sidoList?.map((sido) => ( {sido.sido} @@ -175,7 +177,7 @@ const SearchForm = (props: SearchFormProps = {}) => { - {yearOptions.map((year) => ( + {sidoList?.data?.yearList.map((year) => ( {year}년 diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index b0069a82a..24a88436c 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useState, useMemo } from "react"; import SearchForm from "@/components/module/SearchForm"; // import { DataGrid } from "@rsone/components"; @@ -12,47 +12,63 @@ import { LoadingWithAnimation } from "@common/components"; import { useAlertStore } from "@/shared/lib"; import { statisticsInputAPi } from "@/entities/statistics/api/statistics-input-service"; import type { SearchFormValues } from "@/components/type/SearchForm.type"; -import type { SewerageSupplyRateItem } from "@/entities/statistics/model/statistics-input-interface"; +import type { + SewerageSupplyRateItem, + SewerageSupplyRateRequestParams, +} from "@/entities/statistics/model/statistics-input-interface"; import { SeweragePenetrationRateInpColumn } from "@/components/util/column/input/SeweragePenetrationRateInpColumn"; import { toGridRows } from "@/components/util/grid/gridFunction"; +function getSewerageSupplyRateParams( + values: SearchFormValues, + overrides?: { yr?: string; demoYr?: string; mode?: string } +): SewerageSupplyRateRequestParams { + return { + sido: values.sido, + gugun: values.gugun === 'ALL' ? '' : values.gugun, + bsnmCd: values.bsnmCd, + yr: overrides?.yr ?? values.year, + demoYr: overrides?.demoYr ?? values.year, + ...(overrides?.mode && { mode: overrides.mode }), + }; +} + // 하수도 통계 관리 > 01. 하수도 보급률 const SeweragePenetrationRateInp = () => { const { setMessage: alert } = useAlertStore((state) => state); const [rowData, setRowData] = useState[]>([]); const [isLoading, setIsLoading] = useState(false); + const [searchValues, setSearchValues] = useState(null); + const [isEnterable, setIsEnterable] = useState(true); + + const columnInfo = useMemo(() => { + const { columns, columnLayout } = SeweragePenetrationRateInpColumn; + return { + columnLayout, + columns: isEnterable + ? columns + : columns.map((col) => ({ ...col, editable: false as const })), + }; + }, [isEnterable]); const handleReset = () => { setRowData([]); }; - const handleSearch = async (values: SearchFormValues) => { + const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams) => { try { - if (values.sido !== '' && values.sido !== 'ALL') { - setIsLoading(true); - - const response = await statisticsInputAPi.getSewerageSupplyRateList({ - sido: values.sido, - gugun: values.gugun === 'ALL' ? '' : values.gugun, - bsnmCd: values.bsnmCd, - yr: values.year, - demoYr: values.year, - }); - - if (response.status === 'success' && response.data) { - const convertedData: SewerageSupplyRateItem[] = response.data.sewerageSupplyRateList.map((d) => ({ - ...d, - speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr, - })); - - setRowData(toGridRows(convertedData)); - } else { - alert(response.message); - } + setIsLoading(true); + const response = await statisticsInputAPi.getSewerageSupplyRateList(params); + if (response.status === 'success' && response.data) { + const convertedData: SewerageSupplyRateItem[] = response.data.sewerageSupplyRateList.map((d) => ({ + ...d, + speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr, + })); + setRowData(toGridRows(convertedData)); } else { - alert('시도를 선택해주세요.'); + alert(response.message); } - } catch (error) { + } catch { alert('데이터 조회 중 오류가 발생했습니다.'); setRowData([]); } finally { @@ -60,12 +76,40 @@ const SeweragePenetrationRateInp = () => { } }; - const SubmitExceptionFunc = () => { - // 예외 제출 로직 + const handleSearch = async (values: SearchFormValues) => { + if (values.sido === '' || values.sido === 'ALL') { + alert('시도를 선택해주세요.'); + return; + } + + try { + const response = await statisticsInputAPi.checkInputPeriod(searchValues?.year || ''); + + if (response.status === 'success') { + setIsEnterable(response.data); + + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values)); + } else { + alert(response.message); + } + } catch { + alert('데이터 조회 중 오류가 발생했습니다.'); + } }; - const getLastYearDataFunc = () => { - // 지난연도 데이터 가져오기 + const SubmitExceptionFunc = () => { + // 예외 제출 로직 + }; + + const getLastYearDataFunc = async (values: SearchFormValues) => { + if (values.sido === '' || values.sido === 'ALL') { + alert('시도를 선택해주세요.'); + return; + } + const prevYear = (Number(values.year) - 1).toString(); + await fetchSewerageSupplyRate( + getSewerageSupplyRateParams(values, { yr: prevYear, demoYr: prevYear, mode: 'PRE' }) + ); }; const submitFunc = () => { @@ -78,22 +122,25 @@ const SeweragePenetrationRateInp = () => { return (
- -
-
- - - - + + {rowData.length > 0 && +
+
+ + getLastYearDataFunc(searchValues ?? {})} /> + + +
-
+ } {isLoading &&
} void; onReset?: () => void; + onValuesChange?: (values: SearchFormValues) => void; defaultValues?: SearchFormValues; }; -export const defaultSearchFormProps: Required> = { +export const defaultSearchFormProps: Required> = { sido: true, gugun: true, year: true, 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 ca5d4e102..32cae5723 100644 --- a/apps/st/src/entities/statistics/api/statistics-input-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-input-service.ts @@ -5,6 +5,7 @@ import { type StatisticsInputService, type SewerageSupplyRateRequestParams, type SewerageSupplyRateApiResponse, + type CheckInputPeriodResponse, } from "../model/statistics-input-interface"; export class StatisticsInputAPiService implements StatisticsInputService { @@ -18,6 +19,14 @@ export class StatisticsInputAPiService implements StatisticsInputService { return response.data; } + + public async checkInputPeriod(params: string): Promise { + const response = await axios.get ( + `${ENDPOINT.ST_SERVICE.INPUTCOMMON}/check-input-period?year=${params}`, + ); + + return response.data; + } } export const statisticsInputAPi = new StatisticsInputAPiService(); \ No newline at end of file diff --git a/apps/st/src/entities/statistics/api/statistics-service.ts b/apps/st/src/entities/statistics/api/statistics-service.ts index dfec7d694..8455e68c5 100644 --- a/apps/st/src/entities/statistics/api/statistics-service.ts +++ b/apps/st/src/entities/statistics/api/statistics-service.ts @@ -3,16 +3,16 @@ import { ENDPOINT } from "@/shared/config"; import { type StatisticsService, - type Sido, + type SearchCondition, type ApiResponse, type Sgg } from "../model/statistics-interface"; export class StatisticsAPiService implements StatisticsService { - public async getSidoList(): Promise> { - const response = await axios.get>( - `${ENDPOINT.ST_SERVICE.COMMON}/sido`, + public async getSidoList(): Promise> { + const response = await axios.get>( + `${ENDPOINT.ST_SERVICE.COMMON}/search-condition`, ); return response.data; 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 111216424..6a76d567e 100644 --- a/apps/st/src/entities/statistics/model/statistics-input-interface.ts +++ b/apps/st/src/entities/statistics/model/statistics-input-interface.ts @@ -1,6 +1,6 @@ import type { ApiResponse } from "./statistics-interface"; -// 하수도 보급률 API 응답의 상태 정보 +// 하수도 보급률 데이터 항목 export interface SewerageSupplyRateStatusInfo { locgStatus: string; locgDt: string; @@ -13,7 +13,6 @@ export interface SewerageSupplyRateStatusInfo { returnOpinionSm: string | null; } -// 하수도 보급률 데이터 항목 export interface SewerageSupplyRateItem { sido: string; gugun: string; @@ -103,7 +102,7 @@ export interface SewerageSupplyRateResponseData { export type SewerageSupplyRateApiResponse = ApiResponse & { message: string; -}; +} export interface SewerageSupplyRateRequestParams { sido?: string; @@ -111,6 +110,14 @@ export interface SewerageSupplyRateRequestParams { bsnmCd?: string; yr?: string; demoYr?: string; + mode?: string; +} + +// 하수도 통계 입력 기간 조회 +export interface CheckInputPeriodResponse { + status: string; + message: string; + data: boolean; } export interface StatisticsInputService { diff --git a/apps/st/src/shared/config/api.ts b/apps/st/src/shared/config/api.ts index 1fd2505c3..cd1527192 100644 --- a/apps/st/src/shared/config/api.ts +++ b/apps/st/src/shared/config/api.ts @@ -67,6 +67,7 @@ export const ENDPOINT = { ST_SERVICE: { COMMON: "/st-management-service/api/v1/common", INPUT: "/st-input-service/api/v1/input", + INPUTCOMMON: "/st-input-service/api/v1/common", } }; From eca74c1f2ec48d7c80be3bcb8002ffd69702ff2d Mon Sep 17 00:00:00 2001 From: rsone001 Date: Wed, 4 Feb 2026 17:54:02 +0900 Subject: [PATCH 2/2] =?UTF-8?q?st:=20refactor:=20=EC=97=B0=EB=8F=84=20&=20?= =?UTF-8?q?=EC=A0=84=EB=85=84=EB=8F=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20&=20=EA=B7=B8=EB=A6=AC?= =?UTF-8?q?=EB=93=9C=20=EB=82=B4=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20editable?= =?UTF-8?q?=20=ED=8C=90=EB=B3=84=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/st/src/components/module/SearchForm.tsx | 29 ++++++++--------- .../input/SeweragePenetrationRateInp.tsx | 32 +++++++++++++------ 2 files changed, 37 insertions(+), 24 deletions(-) diff --git a/apps/st/src/components/module/SearchForm.tsx b/apps/st/src/components/module/SearchForm.tsx index bd15477f8..0b50ecf89 100644 --- a/apps/st/src/components/module/SearchForm.tsx +++ b/apps/st/src/components/module/SearchForm.tsx @@ -31,23 +31,9 @@ const getSggListFetcher = async (key: [string, string]): Promise { const { setMessage: alert } = useAlertStore((state) => state); const mergedProps = { ...defaultSearchFormProps, ...props }; - + const currentYear = new Date().getFullYear(); const defaultYear = currentYear - 1; - const startYear = currentYear - 10; - - const [values, setValues] = useState({ - sido: props.defaultValues?.sido || "", - gugun: props.defaultValues?.gugun || '', - year: props.defaultValues?.year || String(defaultYear), - order: props.defaultValues?.order || '', - fctName: props.defaultValues?.fctName || '', - bsnmCd: props.defaultValues?.bsnmCd || '', - }); - - useEffect(() => { - props.onValuesChange?.(values); - }, [values]); const { data: sidoList } = useSWR>( "sido-list", @@ -71,6 +57,15 @@ const SearchForm = (props: SearchFormProps = {}) => { } ); + const [values, setValues] = useState({ + sido: props.defaultValues?.sido || '', + gugun: props.defaultValues?.gugun || '', + year: props.defaultValues?.year || defaultYear.toString(), + order: props.defaultValues?.order || '', + fctName: props.defaultValues?.fctName || '', + bsnmCd: props.defaultValues?.bsnmCd || '', + }); + const { data: sggList } = useSWR>( values.sido && values.sido !== "ALL" ? ["sgg-list", values.sido] : null, getSggListFetcher, @@ -123,6 +118,10 @@ const SearchForm = (props: SearchFormProps = {}) => { })); }; + useEffect(() => { + props.onValuesChange?.(values); + }, [values]); + return (
diff --git a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx index 24a88436c..7644ab1e3 100644 --- a/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx +++ b/apps/st/src/components/statistics/input/SeweragePenetrationRateInp.tsx @@ -40,7 +40,10 @@ const SeweragePenetrationRateInp = () => { const [isLoading, setIsLoading] = useState(false); const [searchValues, setSearchValues] = useState(null); const [isEnterable, setIsEnterable] = useState(true); + const [lastSearchedYear, setLastSearchedYear] = useState(null); + + // 최상위 관리자일땐 모든 상황에서 edit 가능하게 수정 필요. const columnInfo = useMemo(() => { const { columns, columnLayout } = SeweragePenetrationRateInpColumn; return { @@ -53,6 +56,7 @@ const SeweragePenetrationRateInp = () => { const handleReset = () => { setRowData([]); + setLastSearchedYear(null); }; const fetchSewerageSupplyRate = async (params: SewerageSupplyRateRequestParams) => { @@ -64,6 +68,7 @@ const SeweragePenetrationRateInp = () => { ...d, speclCntrplnAr: d.speclCntrplnAr === '' || d.speclCntrplnAr === null ? '해당없음' : d.speclCntrplnAr, })); + setRowData(toGridRows(convertedData)); } else { alert(response.message); @@ -83,10 +88,11 @@ const SeweragePenetrationRateInp = () => { } try { - const response = await statisticsInputAPi.checkInputPeriod(searchValues?.year || ''); + const response = await statisticsInputAPi.checkInputPeriod(values.year || ''); if (response.status === 'success') { setIsEnterable(response.data); + setLastSearchedYear(values.year || null); await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values)); } else { @@ -106,10 +112,14 @@ const SeweragePenetrationRateInp = () => { alert('시도를 선택해주세요.'); return; } - const prevYear = (Number(values.year) - 1).toString(); - await fetchSewerageSupplyRate( - getSewerageSupplyRateParams(values, { yr: prevYear, demoYr: prevYear, mode: 'PRE' }) - ); + + // 실제 조회된 연도가 없으면 실행 불가 + if (!lastSearchedYear) { + alert('먼저 데이터를 조회해주세요.'); + return; + } + + await fetchSewerageSupplyRate(getSewerageSupplyRateParams(values, { mode: 'PRE' })); }; const submitFunc = () => { @@ -126,10 +136,13 @@ const SeweragePenetrationRateInp = () => { {rowData.length > 0 &&
- - getLastYearDataFunc(searchValues ?? {})} /> - - + {isEnterable && } + {isEnterable && getLastYearDataFunc(searchValues ?? {})} />} + {isEnterable && } + { + // 최고 관리자일땐 저장 가능하게 + + }
} @@ -141,6 +154,7 @@ const SeweragePenetrationRateInp = () => { 0) && isEnterable} rowStartNum={3} fixedColumns={1} rowData={rowData}