From c950652f34e7792a292d0a3c651f82b1e77235e3 Mon Sep 17 00:00:00 2001 From: rsone001 Date: Mon, 20 Apr 2026 10:39:25 +0900 Subject: [PATCH 1/5] =?UTF-8?q?[st]=20fix:=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=A1=B0=ED=9A=8C,=20SearchForm,=20DataGrid=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 --- .../components/button/ReviewOpinionButton.tsx | 2 +- apps/st/src/components/module/DataGrid.tsx | 7 +- apps/st/src/components/module/SearchForm.tsx | 28 +- .../statistics/input/SewagePipeInp.tsx | 267 ++++++++++------ .../input/SeweragePenetrationRateInp.tsx | 30 +- .../statistics/management/LogHistory.tsx | 297 +++++++++++++++--- .../statistics/review/ReturnReasonDialog.tsx | 99 ++++++ .../statistics/review/ReviewOpinionDialog.tsx | 168 ++++++++++ .../review/SewagePipeMaintainRvw.tsx | 89 ++++-- .../statistics/review/SewagePipeRvw.tsx | 90 ++++-- .../review/SeweragePenetrationRateRvw.tsx | 175 ++++++++++- .../statistics/review/StatsInputStatusRvw.tsx | 147 +++++++-- .../statistics/total/SewagePipeTot.tsx | 88 ++++-- .../total/SeweragePenetrationRateTot.tsx | 2 +- .../total/SewerageServiceLevelTot.tsx | 121 ++++++- apps/st/src/components/type/DataGrid.type.ts | 2 + .../st/src/components/type/SearchForm.type.ts | 5 +- .../column/management/LogHistoryColumn.ts | 16 +- .../ComplaintTotColumn.ts | 77 +++++ .../ResidentFriendlyFactTotColumn.ts | 81 +++++ .../api/statistics-management-service.ts | 14 +- .../statistics/api/statistics-service.ts | 51 ++- .../api/statistics-total-service.ts | 21 ++ .../statistics-input-review-interface.ts | 16 + .../statistics/model/statistics-interface.ts | 81 ++++- ...s-sewerage-penetration-review-interface.ts | 7 +- ...tatistics-service-level-total-interface.ts | 61 ++++ apps/st/src/shared/store/user-store.ts | 2 + 28 files changed, 1742 insertions(+), 302 deletions(-) create mode 100644 apps/st/src/components/statistics/review/ReturnReasonDialog.tsx create mode 100644 apps/st/src/components/statistics/review/ReviewOpinionDialog.tsx create mode 100644 apps/st/src/components/util/column/total/sewerageServiceLevel/ComplaintTotColumn.ts create mode 100644 apps/st/src/components/util/column/total/sewerageServiceLevel/ResidentFriendlyFactTotColumn.ts create mode 100644 apps/st/src/entities/statistics/model/statistics-total/statistics-service-level-total-interface.ts diff --git a/apps/st/src/components/button/ReviewOpinionButton.tsx b/apps/st/src/components/button/ReviewOpinionButton.tsx index 50df8fcb4..fbcdbd754 100644 --- a/apps/st/src/components/button/ReviewOpinionButton.tsx +++ b/apps/st/src/components/button/ReviewOpinionButton.tsx @@ -1,7 +1,7 @@ import { Button } from "@common/components"; interface ReviewOpinionButtonProps { - reviewOpinionFunc: () => void; + reviewOpinionFunc: () => void | Promise; } const ReviewOpinionButton = ({ reviewOpinionFunc }: ReviewOpinionButtonProps) => { diff --git a/apps/st/src/components/module/DataGrid.tsx b/apps/st/src/components/module/DataGrid.tsx index d85693a53..9471e473a 100644 --- a/apps/st/src/components/module/DataGrid.tsx +++ b/apps/st/src/components/module/DataGrid.tsx @@ -311,6 +311,7 @@ function buildFieldsFromColumns( const DataGrid = ({ columnInfo, rowStartNum, + excelExportFileSuffix, isExcelImport = true, isExcelExport = true, fixedColumns, @@ -470,10 +471,14 @@ const DataGrid = ({ gridView.setColumnLayout?.(normalizedLayout); } + const exportBaseName = excelExportFileSuffix + ? `${menuName}_${excelExportFileSuffix}` + : menuName; + gridView.exportGrid({ type: "excel", target: "local", - fileName: `${menuName}.xlsx`, + fileName: `${exportBaseName}.xlsx`, footer: "hidden", allColumns: true, // 숨김(visible: false) 컬럼도 엑셀에 포함 done: () => { diff --git a/apps/st/src/components/module/SearchForm.tsx b/apps/st/src/components/module/SearchForm.tsx index 44492a1dd..2284be541 100644 --- a/apps/st/src/components/module/SearchForm.tsx +++ b/apps/st/src/components/module/SearchForm.tsx @@ -16,28 +16,36 @@ import { } from "@common/components"; -import { useAlertStore } from "@/shared/lib"; +import { useAlertStore, useUserStore } 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> => { - return statisticsAPi.getSidoList(); +const getSidoListFetcher = async (key: [string, string]): Promise> => { + return statisticsAPi.getSidoList({ instSeCd: key[1] }); }; -const getSggListFetcher = async (key: [string, string]): Promise> => { - return statisticsAPi.getSggList(key[1]); +const getSggListFetcher = async (key: [string, string, string, string]): Promise> => { + const gugunSkipYn = doList.includes(key[3]) ? "N" : "Y"; + return statisticsAPi.getSggAllList(key[1], { instSeCd: key[2], gugunSkipYn }); }; const SearchForm = (props: SearchFormProps = {}) => { const { setMessage: alert } = useAlertStore((state) => state); + const userInfo = useUserStore((state) => state.userInfo); const mergedProps = { ...defaultSearchFormProps, ...props }; + const instSeCd = userInfo?.instSeCd ?? ""; + const sidoSelectText = + props.sidoSelectText && props.sidoSelectText.trim() !== "" + ? props.sidoSelectText + : "선택"; const currentYear = new Date().getFullYear(); const defaultYear = currentYear - 1; const { data: sidoList } = useSWR>( - "sido-list", + ["sido-list", instSeCd], getSidoListFetcher, { revalidateOnFocus: false, @@ -68,9 +76,9 @@ const SearchForm = (props: SearchFormProps = {}) => { isDetail: false, }); - const sggKey: [string, string] | null = + const sggKey: [string, string, string, string] | null = values.sido && values.sido !== "ALL" - ? ["sgg-list", values.sido] + ? ["sgg-list", values.sido, instSeCd, values.bsnmCd || ""] : null; const { data: sggList } = useSWR>( sggKey, @@ -152,10 +160,10 @@ const SearchForm = (props: SearchFormProps = {}) => { + + + + + 전체 + {sidoList?.data?.sidoList?.map((item) => ( + + {item.sido} + + ))} + + + +
+ + +
+
+ { className="w-[16rem]" />
-
-
- {isLoading &&
-
- + {isLoading && ( +
+
+ +
-
} + )} { ); }; -export default LogHistory; \ No newline at end of file +export default LogHistory; diff --git a/apps/st/src/components/statistics/review/ReturnReasonDialog.tsx b/apps/st/src/components/statistics/review/ReturnReasonDialog.tsx new file mode 100644 index 000000000..b2955bc9a --- /dev/null +++ b/apps/st/src/components/statistics/review/ReturnReasonDialog.tsx @@ -0,0 +1,99 @@ +"use client"; + +import { useEffect, useState } from "react"; + +import { + Button, + Dialog, + DialogClose, + DialogContent, + DialogFooter, + DialogHeader, + DialogTitle, + Label, + Textarea, +} from "@common/components"; + +export interface ReturnReasonDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + onSave?: (reason: string) => void | Promise; +} + +export function ReturnReasonDialog({ + open, + onOpenChange, + onSave, +}: ReturnReasonDialogProps) { + const [reason, setReason] = useState(""); + const [isSaving, setIsSaving] = useState(false); + const [reasonError, setReasonError] = useState(false); + + useEffect(() => { + if (!open) return; + setReason(""); + setReasonError(false); + }, [open]); + + const handleSave = async () => { + if (reason.trim() === "") { + setReasonError(true); + return; + } + + try { + setIsSaving(true); + await onSave?.(reason); + onOpenChange(false); + } finally { + setIsSaving(false); + } + }; + + return ( + + + + 반려 사유 + +
+
+ +