[st] fix: 로그 조회, SearchForm, DataGrid 수정
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { Button } from "@common/components";
|
||||
|
||||
interface ReviewOpinionButtonProps {
|
||||
reviewOpinionFunc: () => void;
|
||||
reviewOpinionFunc: () => void | Promise<void>;
|
||||
}
|
||||
|
||||
const ReviewOpinionButton = ({ reviewOpinionFunc }: ReviewOpinionButtonProps) => {
|
||||
|
||||
@@ -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: () => {
|
||||
|
||||
@@ -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<ApiResponse<SearchCondition>> => {
|
||||
return statisticsAPi.getSidoList();
|
||||
const getSidoListFetcher = async (key: [string, string]): Promise<ApiResponse<SearchCondition>> => {
|
||||
return statisticsAPi.getSidoList({ instSeCd: key[1] });
|
||||
};
|
||||
|
||||
const getSggListFetcher = async (key: [string, string]): Promise<ApiResponse<Sgg[]>> => {
|
||||
return statisticsAPi.getSggList(key[1]);
|
||||
const getSggListFetcher = async (key: [string, string, string, string]): Promise<ApiResponse<Sgg[]>> => {
|
||||
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<ApiResponse<SearchCondition>>(
|
||||
"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<ApiResponse<Sgg[]>>(
|
||||
sggKey,
|
||||
@@ -152,10 +160,10 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
</Label>
|
||||
<Select value={values.sido} onValueChange={handleSidoChange}>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="선택"></SelectValue>
|
||||
<SelectValue placeholder={sidoSelectText}></SelectValue>
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="ALL">선택</SelectItem>
|
||||
<SelectItem value="ALL">{sidoSelectText}</SelectItem>
|
||||
{sidoList?.data?.sidoList?.map((sido) => (
|
||||
<SelectItem key={sido.adstDstrCd} value={sido.sido}>
|
||||
{sido.sido}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
import { useState, useMemo, useRef } from "react";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SplitDataGrid from "@/components/module/SplitDataGrid";
|
||||
import GridTopActionButton from "@/components/module/GridTopActionButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import LastYearDataButton from "@/components/button/LastYearDataButton";
|
||||
import SubmitExceptionButton from "@/components/button/SubmitExceptionButton";
|
||||
|
||||
import { SewagePipeInpColumnInfoList } from "@/components/util/column/input/sewagePipe/SewagePipeInpColumn";
|
||||
import type { ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type";
|
||||
import type { ColumnInfo, ColumnInfoList, ColumnConfig } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { useAdminStore } from "@/store/admin-store";
|
||||
@@ -17,20 +19,13 @@ import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service";
|
||||
import type {
|
||||
SeweragePipeInputAdminAreaConnectionItem,
|
||||
SeweragePipeInputFacilityConnectionItem,
|
||||
SeweragePipeInputFactInstallStatusItem,
|
||||
SeweragePipeInputManholeStatusItem,
|
||||
SeweragePipeInputPipeDiameterStatusItem,
|
||||
SeweragePipeInputPipeTypeStatusItem,
|
||||
SeweragePipeInputRegionalFactStatusItem,
|
||||
SeweragePipeInputRegionalFactStatusSaveRequest,
|
||||
SeweragePipeInputStatusInfo,
|
||||
SeweragePipeRequestParams,
|
||||
} from "@/entities/statistics/model/input/statistics-sewage-pipe-input-interface";
|
||||
import { RegionalFactStatusInpColumn } from "@/components/util/column/input/sewagePipe/RegionalFactStatusInpColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
const applyEnterableFromStatus = (
|
||||
@@ -62,6 +57,23 @@ function buildSewagePipeSearchParam(
|
||||
};
|
||||
}
|
||||
|
||||
/** ST00020006(연결)은 행이 좌·우 그리드 키에만 있음 */
|
||||
function tabRowDataExists(
|
||||
m: Map<string, Record<string, string | number>[]>,
|
||||
tabKey: string,
|
||||
): boolean {
|
||||
if (tabKey === "ST00020006") {
|
||||
const left = m.get("treatmentPlantConnectionInp");
|
||||
const right = m.get("adminAreaConnectionInp");
|
||||
return (
|
||||
(Array.isArray(left) && left.length > 0) ||
|
||||
(Array.isArray(right) && right.length > 0)
|
||||
);
|
||||
}
|
||||
const rows = m.get(tabKey);
|
||||
return Array.isArray(rows) && rows.length > 0;
|
||||
}
|
||||
|
||||
// 하수도 통계 관리 > 02. 하수관로
|
||||
const SewagePipeInp = () => {
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
@@ -128,7 +140,7 @@ const SewagePipeInp = () => {
|
||||
});
|
||||
}, [isEnterable, isAdminInfo]);
|
||||
|
||||
/** mergeIntoExistingMap: true면 지난연도(PRE) 등 — 현재 탭만 갱신, 나머지 탭 캐시 유지 */
|
||||
/** mergeIntoExistingMap: true면 기존 맵에 병합(다른 탭 rowData·검색 후 다른 탭 캐시 유지). false면 맵 전체 교체 */
|
||||
const loadSewagePipeActiveTabData = async (
|
||||
tabKey: string,
|
||||
searchParam: SeweragePipeRequestParams,
|
||||
@@ -290,7 +302,6 @@ const SewagePipeInp = () => {
|
||||
const checkEnterable = periodResponse.data;
|
||||
periodEnterableRef.current = checkEnterable;
|
||||
|
||||
setRowDataMap(new Map());
|
||||
lazyTabLoggedRef.current.clear();
|
||||
|
||||
setIsLoading(true);
|
||||
@@ -301,7 +312,7 @@ const SewagePipeInp = () => {
|
||||
setHasSearched(true);
|
||||
setLastSearchedYear(values.year ?? null);
|
||||
|
||||
await loadSewagePipeActiveTabData(activeTab, searchParam, values, checkEnterable, false, true);
|
||||
await loadSewagePipeActiveTabData(activeTab, searchParam, values, checkEnterable, true, true);
|
||||
} catch {
|
||||
alert('데이터 조회 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
@@ -312,8 +323,7 @@ const SewagePipeInp = () => {
|
||||
const handleActiveTabChange = async (tabKey: string) => {
|
||||
setActiveTab(tabKey);
|
||||
|
||||
const activeTabData = rowDataMap.get(tabKey);
|
||||
const hasActiveTabData = Array.isArray(activeTabData) && activeTabData.length > 0;
|
||||
const hasActiveTabData = tabRowDataExists(rowDataMap, tabKey);
|
||||
|
||||
const canLazyLoad =
|
||||
searchParamRef.current &&
|
||||
@@ -324,86 +334,20 @@ const SewagePipeInp = () => {
|
||||
if (!canLazyLoad) return;
|
||||
|
||||
const searchParam = searchParamRef.current;
|
||||
if (!searchParam) return;
|
||||
const values = lastSearchedValues ?? searchValues;
|
||||
if (!searchParam || !values) return;
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
let response;
|
||||
|
||||
if (tabKey === 'ST00020001') {
|
||||
response = await statisticsInputSewPipeApiService.getRegionalFactStatus(searchParam);
|
||||
} else if (tabKey === 'ST00020002') {
|
||||
response = await statisticsInputSewPipeApiService.getFactInstallStatus(searchParam);
|
||||
} else if (tabKey === 'ST00020003') {
|
||||
response = await statisticsInputSewPipeApiService.getPipeTypeStatus(searchParam);
|
||||
} else if (tabKey === 'ST00020004') {
|
||||
response = await statisticsInputSewPipeApiService.getPipeDiameterStatus(searchParam);
|
||||
} else if (tabKey === 'ST00020005') {
|
||||
response = await statisticsInputSewPipeApiService.getManholeStatus(searchParam);
|
||||
} else if (tabKey === 'ST00020006') {
|
||||
response = await statisticsInputSewPipeApiService.getConnectionStatus(searchParam);
|
||||
}
|
||||
lazyTabLoggedRef.current.add(tabKey);
|
||||
|
||||
if (response?.status === 'success') {
|
||||
setStatusInfo(response.data.statusInfo);
|
||||
setStatsCd(response.data.commonInfo.statsCd);
|
||||
applyEnterableFromStatus(response.data.statusInfo, periodEnterableRef.current, setIsEnterable);
|
||||
setRowDataMap(prev => {
|
||||
const next = new Map(prev);
|
||||
if (tabKey === 'ST00020001') {
|
||||
const commonInfo = response.data.commonInfo;
|
||||
const convertedData = (response.data.regionalFactStatusList ?? []).map((d: SeweragePipeInputRegionalFactStatusItem) => ({
|
||||
ctpvNm: commonInfo.ctpvNm,
|
||||
...d
|
||||
}));
|
||||
next.set(tabKey, toGridRows(convertedData));
|
||||
} else if (tabKey === 'ST00020002') {
|
||||
const commonInfo = response.data.commonInfo;
|
||||
const convertedData = (response.data.factInstallStatusList ?? []).map((d: SeweragePipeInputFactInstallStatusItem) => ({
|
||||
ctpvNm: commonInfo.ctpvNm,
|
||||
...d
|
||||
}));
|
||||
next.set(tabKey, toGridRows(convertedData));
|
||||
} else if (tabKey === 'ST00020003') {
|
||||
const commonInfo = response.data.commonInfo;
|
||||
const convertedData = (response.data.pipeTypeStatusList ?? []).map((d: SeweragePipeInputPipeTypeStatusItem) => ({
|
||||
ctpvNm: commonInfo.ctpvNm,
|
||||
...d
|
||||
}));
|
||||
next.set(tabKey, toGridRows(convertedData));
|
||||
} else if (tabKey === 'ST00020004') {
|
||||
const commonInfo = response.data.commonInfo;
|
||||
const convertedData = (response.data.pipeDiameterStatusList ?? []).map((d: SeweragePipeInputPipeDiameterStatusItem) => ({
|
||||
ctpvNm: commonInfo.ctpvNm,
|
||||
...d
|
||||
}));
|
||||
next.set(tabKey, toGridRows(convertedData));
|
||||
} else if (tabKey === 'ST00020005') {
|
||||
const commonInfo = response.data.commonInfo;
|
||||
const convertedData = (response.data.manholeStatusList ?? []).map((d: SeweragePipeInputManholeStatusItem) => ({
|
||||
ctpvNm: commonInfo.ctpvNm,
|
||||
...d
|
||||
}));
|
||||
next.set(tabKey, toGridRows(convertedData));
|
||||
} else if (tabKey === 'ST00020006') {
|
||||
const commonInfo = response.data.commonInfo;
|
||||
const facilityConvertedData = (response.data.facilityConnectionList ?? []).map((d: SeweragePipeInputFacilityConnectionItem) => ({
|
||||
...d
|
||||
}));
|
||||
const adminConvertedData = (response.data.adminAreaConnectionList ?? []).map((d: SeweragePipeInputAdminAreaConnectionItem) => ({
|
||||
...d,
|
||||
ctpvNm: d.ctpvNm || commonInfo.ctpvNm,
|
||||
}));
|
||||
next.set('treatmentPlantConnectionInp', toGridRows(facilityConvertedData));
|
||||
next.set('adminAreaConnectionInp', toGridRows(adminConvertedData));
|
||||
}
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || '데이터 조회 중 오류가 발생했습니다.');
|
||||
}
|
||||
const ok = await loadSewagePipeActiveTabData(
|
||||
tabKey,
|
||||
searchParam,
|
||||
values,
|
||||
periodEnterableRef.current,
|
||||
true,
|
||||
false,
|
||||
);
|
||||
if (ok) lazyTabLoggedRef.current.add(tabKey);
|
||||
} catch {
|
||||
alert('데이터 조회 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
@@ -543,8 +487,9 @@ const SewagePipeInp = () => {
|
||||
const showSaveInToolbar = (isEnterable || isAdminInfo) && gugunOkOrNotDoList;
|
||||
const showEnterableToolbarButtons = isEnterable && gugunOkOrNotDoList;
|
||||
|
||||
/** TabDataGrid는 rowDataMap 사용 — 행이 하나라도 있을 때만 툴바 카드 의미 있음 */
|
||||
/** rowDataMap — 행이 하나라도 있을 때만 툴바·엑셀 의미 있음 */
|
||||
const hasRowData = Array.from(rowDataMap.values()).some((rows) => rows.length > 0);
|
||||
const excelToolbarEnabled = hasRowData && isEnterable && gugunOkOrNotDoList;
|
||||
|
||||
const showActionToolbarCard =
|
||||
(hasRowData || hasSearched) &&
|
||||
@@ -573,16 +518,132 @@ const SewagePipeInp = () => {
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>}
|
||||
<TabDataGrid
|
||||
columnInfo={columnInfo}
|
||||
rowDataMap={rowDataMap}
|
||||
setRowDataMap={setRowDataMap}
|
||||
activeTab={activeTab}
|
||||
onActiveTabChange={handleActiveTabChange}
|
||||
isExcelImport={hasRowData && isEnterable && gugunOkOrNotDoList}
|
||||
facilitySearchStatsYr={lastSearchedYear}
|
||||
getGridDataRef={getGridDataRef}
|
||||
/>
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{columnInfo.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
|
||||
if (info.isSplitGrid && info.left && info.right) {
|
||||
const leftKey = info.left.tabKey || `${tabKey}_left`;
|
||||
const rightKey = info.right.tabKey || `${tabKey}_right`;
|
||||
const leftRowData = rowDataMap.get(leftKey) ?? [];
|
||||
const rightRowData = rowDataMap.get(rightKey) ?? [];
|
||||
const splitTopButton = info.right.topButton;
|
||||
const showSplitTopButton =
|
||||
Boolean(splitTopButton?.text) &&
|
||||
(splitTopButton?.visible ?? true) &&
|
||||
excelToolbarEnabled;
|
||||
const leftColumnInfo: ColumnInfo = {
|
||||
columns: info.left.columns ?? [],
|
||||
columnLayout: info.left.columnLayout ?? [],
|
||||
};
|
||||
const rightColumnInfo: ColumnInfo = {
|
||||
columns: info.right.columns ?? [],
|
||||
columnLayout: info.right.columnLayout ?? [],
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<div className="flex flex-col gap-[0.6rem]">
|
||||
<div className="flex justify-end gap-[0.6rem]">
|
||||
{showSplitTopButton && (
|
||||
<GridTopActionButton
|
||||
text={splitTopButton?.text ?? ""}
|
||||
onClick={splitTopButton?.onClick}
|
||||
statsYr={lastSearchedYear ?? undefined}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<SplitDataGrid
|
||||
leftColumnInfo={leftColumnInfo}
|
||||
rightColumnInfo={rightColumnInfo}
|
||||
leftFixedColumns={info.left.fixedColumns}
|
||||
rightFixedColumns={info.right.fixedColumns}
|
||||
showHeaderCheckbox={false}
|
||||
enableSort={false}
|
||||
leftRowData={leftRowData}
|
||||
rightRowData={rightRowData}
|
||||
setLeftRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(leftKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function"
|
||||
? updater(cur)
|
||||
: updater;
|
||||
next.set(leftKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
setRightRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(rightKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function"
|
||||
? updater(cur)
|
||||
: updater;
|
||||
next.set(rightKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
);
|
||||
}
|
||||
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={excelToolbarEnabled}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
getGridDataRef={tabKey === "ST00020001" ? getGridDataRef : undefined}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -465,22 +465,22 @@ const SeweragePenetrationRateInp = () => {
|
||||
const response = await statisticsInputSewSupplyRateApi.saveSewerageSupplyRate(requestData);
|
||||
|
||||
if (response.status === 'success') {
|
||||
const imrParam = {
|
||||
user_id: userInfo.userUnqId,
|
||||
sewer_bzmn_cd: searchValues?.bsnmCd ?? '',
|
||||
stats_cd: statsCd,
|
||||
stats_yr: searchValues?.year ?? '',
|
||||
lae_sbmsn_stts_cd: (statusInfo?.laeSbmsnSttsCd === '4' ? '3' : '1'),
|
||||
};
|
||||
// const imrParam = {
|
||||
// user_id: userInfo.userUnqId,
|
||||
// sewer_bzmn_cd: searchValues?.bsnmCd ?? '',
|
||||
// stats_cd: statsCd,
|
||||
// stats_yr: searchValues?.year ?? '',
|
||||
// lae_sbmsn_stts_cd: (statusInfo?.laeSbmsnSttsCd === '4' ? '3' : '1'),
|
||||
// };
|
||||
|
||||
try {
|
||||
await statisticsAPi.callImrService(imrParam);
|
||||
} catch {
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
setIsLoading(false);
|
||||
alert('IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.');
|
||||
return;
|
||||
}
|
||||
// try {
|
||||
// await statisticsAPi.callImrService(imrParam);
|
||||
// } catch {
|
||||
// await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
// setIsLoading(false);
|
||||
// alert('IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.');
|
||||
// return;
|
||||
// }
|
||||
|
||||
await fetchSewerageSupplyRate(getSewerageSupplyRateParams(searchValues ?? {}), true);
|
||||
alert('저장되었습니다.');
|
||||
|
||||
@@ -1,28 +1,57 @@
|
||||
"use client";
|
||||
|
||||
import { useCallback, useState, useEffect } from 'react';
|
||||
import { useCallback, useState, useEffect } from "react";
|
||||
import useSWR from "swr";
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import {
|
||||
Button,
|
||||
InputDatePicker,
|
||||
Label,
|
||||
LoadingWithAnimation,
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
import DataGrid from '@/components/module/DataGrid';
|
||||
import { CodeSelectWithAll } from '@/entities/code';
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import { CodeSelectWithAll } from "@/entities/code";
|
||||
|
||||
import { statisticsAPi } from '@/entities/statistics/api/statistics-service';
|
||||
import type { StatisticsLogRequest, StatisticsLog } from '@/entities/statistics/model/statistics-interface';
|
||||
import { LogHistoryDetailDialog } from '@/components/statistics/management/LogHistoryDetailDialog';
|
||||
import { toGridRows } from '@/components/util/grid/gridFunction';
|
||||
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
|
||||
import type {
|
||||
ApiResponse,
|
||||
SearchCondition,
|
||||
Sgg,
|
||||
StatisticsLog,
|
||||
StatisticsLogRequest,
|
||||
} from "@/entities/statistics/model/statistics-interface";
|
||||
import { LogHistoryDetailDialog } from "@/components/statistics/management/LogHistoryDetailDialog";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
import { LogHistoryColumn } from '@/components/util/column/management/LogHistoryColumn';
|
||||
import { LogHistoryTablePagination } from '@/components/statistics/management/LogHistoryTablePagination';
|
||||
import { LogHistoryColumn } from "@/components/util/column/management/LogHistoryColumn";
|
||||
import { LogHistoryTablePagination } from "@/components/statistics/management/LogHistoryTablePagination";
|
||||
|
||||
const PAGE_SIZE = 100;
|
||||
|
||||
const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
|
||||
return statisticsAPi.getSidoList();
|
||||
};
|
||||
|
||||
const getSggListFetcher = async (
|
||||
key: [string, string],
|
||||
): Promise<ApiResponse<Sgg[]>> => {
|
||||
return statisticsAPi.getSggList(key[1]);
|
||||
};
|
||||
|
||||
const LogHistory = () => {
|
||||
// const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [statsCd, setStatsCd] = useState<string>('');
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [statsCd, setStatsCd] = useState<string>("");
|
||||
const [sido, setSido] = useState<string>("ALL");
|
||||
const [gugun, setGugun] = useState<string>("ALL");
|
||||
const [bsnmCd, setBsnmCd] = useState<string>("");
|
||||
const [statsDataChgComCd, setStatsDataChgComCd] = useState<string>("ALL");
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
@@ -32,44 +61,141 @@ const LogHistory = () => {
|
||||
string,
|
||||
string | number
|
||||
> | null>(null);
|
||||
const [startDate, setStartDate] = useState<Date | undefined>(() =>
|
||||
dayjs().subtract(1, "month").startOf("day").toDate(),
|
||||
);
|
||||
const [endDate, setEndDate] = useState<Date | undefined>(() =>
|
||||
dayjs().endOf("day").toDate(),
|
||||
);
|
||||
|
||||
const loadLogs = useCallback(async (page1Based: number) => {
|
||||
const param: StatisticsLogRequest = {
|
||||
statsCd: (statsCd === '' || statsCd === 'ALL') ? '' : statsCd,
|
||||
page: page1Based,
|
||||
size: PAGE_SIZE,
|
||||
};
|
||||
const { data: sidoList } = useSWR<ApiResponse<SearchCondition>>(
|
||||
"log-history-sido-list",
|
||||
getSidoListFetcher,
|
||||
{
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
revalidateIfStale: false,
|
||||
dedupingInterval: 2000,
|
||||
shouldRetryOnError: false,
|
||||
onSuccess: (data) => {
|
||||
if (data.status !== "success") {
|
||||
alert(`시도 목록을 불러오는데 실패했습니다.
|
||||
새로고침 후 다시 시도해주세요.`);
|
||||
}
|
||||
},
|
||||
onError: () => {
|
||||
alert(`시도 목록을 불러오는데 실패했습니다.
|
||||
새로고침 후 다시 시도해주세요.`);
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await statisticsAPi.getLogList(param);
|
||||
|
||||
if (response.status === 'success') {
|
||||
const lostList = response.data?.statsLogList ?? [];
|
||||
const convertedData: StatisticsLog[] = lostList.map((item: StatisticsLog) => ({
|
||||
...item,
|
||||
dmndFwkNm: item.dmndFwkNm === 'save' ? '저장' : (item.dmndFwkNm === 'submit' ? '제출' : '예외 제출'),
|
||||
}));
|
||||
|
||||
setRowData(toGridRows(convertedData));
|
||||
setTotalPages(response.data?.pageInfo?.totalPages ?? 0);
|
||||
setPageIndex(Math.max(0, page1Based - 1));
|
||||
} else {
|
||||
alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
|
||||
const sggKey: [string, string] | null =
|
||||
sido && sido !== "ALL" ? ["log-history-sgg-list", sido] : null;
|
||||
const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(sggKey, getSggListFetcher, {
|
||||
revalidateOnFocus: false,
|
||||
revalidateOnReconnect: false,
|
||||
revalidateIfStale: false,
|
||||
dedupingInterval: 2000,
|
||||
shouldRetryOnError: false,
|
||||
onSuccess: (data) => {
|
||||
if (data.status !== "success") {
|
||||
alert(`시군구 목록을 불러오는데 실패했습니다.
|
||||
새로고침 후 다시 시도해주세요.`);
|
||||
}
|
||||
} catch {
|
||||
alert('데이터 조회 중 오류가 발생했습니다.');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [statsCd]);
|
||||
},
|
||||
onError: () => {
|
||||
alert(`시군구 목록을 불러오는데 실패했습니다.
|
||||
새로고침 후 다시 시도해주세요.`);
|
||||
},
|
||||
});
|
||||
const sggOptions = sggList?.data ?? [];
|
||||
|
||||
const handleSidoChange = (value: string) => {
|
||||
const selectedBsnm =
|
||||
sidoList?.data.sidoList.find((v) => v.sido === value)?.bsnmCd ?? "";
|
||||
setSido(value);
|
||||
setGugun("ALL");
|
||||
setBsnmCd(selectedBsnm);
|
||||
};
|
||||
|
||||
const handleSggChange = (value: string) => {
|
||||
const listForBsnm = sggList?.data ?? [];
|
||||
const selectedBsnm =
|
||||
value === "ALL"
|
||||
? (sidoList?.data.sidoList.find((v) => v.sido === sido)?.bsnmCd ??
|
||||
"")
|
||||
: (listForBsnm.find((v) => v.gugun === value)?.bsnmCd ?? "");
|
||||
setGugun(value);
|
||||
setBsnmCd(selectedBsnm);
|
||||
};
|
||||
|
||||
const loadLogs = useCallback(
|
||||
async (page1Based: number) => {
|
||||
const param: StatisticsLogRequest = {
|
||||
page: page1Based,
|
||||
size: PAGE_SIZE,
|
||||
startDate: startDate
|
||||
? dayjs(startDate).format("YYYY-MM-DD")
|
||||
: "",
|
||||
endDate: endDate ? dayjs(endDate).format("YYYY-MM-DD") : "",
|
||||
...(!(statsCd === "" || statsCd === "ALL") && { statsCd }),
|
||||
...(sido && sido !== "ALL" && { ctpvNm: sido }),
|
||||
...(gugun && gugun !== "ALL" && { sggNm: gugun }),
|
||||
...(bsnmCd && { sewerBzmnCd: bsnmCd }),
|
||||
...(statsDataChgComCd &&
|
||||
statsDataChgComCd !== "ALL" && {
|
||||
statsDataChgComCd,
|
||||
}),
|
||||
};
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const response = await statisticsAPi.getLogList(param);
|
||||
|
||||
if (response.status === "success") {
|
||||
const lostList = response.data?.statsLogList ?? [];
|
||||
|
||||
setRowData(toGridRows(lostList));
|
||||
setTotalPages(response.data?.pageInfo?.totalPages ?? 0);
|
||||
setPageIndex(Math.max(0, page1Based - 1));
|
||||
} else {
|
||||
alert(response.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
},
|
||||
[
|
||||
statsCd,
|
||||
startDate,
|
||||
endDate,
|
||||
sido,
|
||||
gugun,
|
||||
bsnmCd,
|
||||
statsDataChgComCd,
|
||||
alert,
|
||||
],
|
||||
);
|
||||
|
||||
const handleSearch = async (e: React.FormEvent<HTMLFormElement>) => {
|
||||
e.preventDefault();
|
||||
await loadLogs(1);
|
||||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setStatsCd("");
|
||||
setSido("ALL");
|
||||
setGugun("ALL");
|
||||
setBsnmCd("");
|
||||
setStatsDataChgComCd("ALL");
|
||||
setStartDate(dayjs().subtract(1, "month").startOf("day").toDate());
|
||||
setEndDate(dayjs().endOf("day").toDate());
|
||||
};
|
||||
|
||||
const handleRowClick = (row: Record<string, string | number>) => {
|
||||
setSelectedLogRow(row);
|
||||
setDetailDialogOpen(true);
|
||||
@@ -81,18 +207,54 @@ const LogHistory = () => {
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
loadLogs(1);
|
||||
void loadLogs(1);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<div className="card card-border">
|
||||
<form onSubmit={handleSearch}>
|
||||
<div className="flex items-center gap-8 p-2">
|
||||
<div className="flex flex-wrap items-center gap-8 p-2">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-label">
|
||||
통계항목
|
||||
</Label>
|
||||
<Label className="text-label">시도</Label>
|
||||
<Select value={sido} onValueChange={handleSidoChange}>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="전체" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="ALL">전체</SelectItem>
|
||||
{sidoList?.data?.sidoList?.map((item) => (
|
||||
<SelectItem
|
||||
key={item.adstDstrCd}
|
||||
value={item.sido}
|
||||
>
|
||||
{item.sido}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-label">시군구</Label>
|
||||
<Select value={gugun} onValueChange={handleSggChange}>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
<SelectValue placeholder="전체" />
|
||||
</SelectTrigger>
|
||||
<SelectContent>
|
||||
<SelectItem value="ALL">전체</SelectItem>
|
||||
{sggOptions.map((sgg) => (
|
||||
<SelectItem
|
||||
key={sgg.adstDstrCd}
|
||||
value={sgg.gugun}
|
||||
>
|
||||
{sgg.gugun}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-label">통계항목</Label>
|
||||
<CodeSelectWithAll
|
||||
upCd="STITEM"
|
||||
value={statsCd}
|
||||
@@ -101,8 +263,37 @@ const LogHistory = () => {
|
||||
className="w-[16rem]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 ml-auto">
|
||||
<Button color="green" className="h-[3.2rem] w-[96px]" type="button">
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-label">요청 기능</Label>
|
||||
<CodeSelectWithAll
|
||||
upCd="STATSDATACHG"
|
||||
value={statsDataChgComCd}
|
||||
onValueChange={setStatsDataChgComCd}
|
||||
placeholder="전체"
|
||||
className="w-[16rem]"
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="mx-2 text-label">시작일</Label>
|
||||
<InputDatePicker
|
||||
value={startDate}
|
||||
onChange={setStartDate}
|
||||
className="w-[14rem]"
|
||||
/>
|
||||
<Label className="mx-2 text-label">종료일</Label>
|
||||
<InputDatePicker
|
||||
value={endDate}
|
||||
onChange={setEndDate}
|
||||
className="w-[14rem]"
|
||||
/>
|
||||
</div>
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
<Button
|
||||
color="green"
|
||||
className="h-[3.2rem] w-[96px]"
|
||||
type="button"
|
||||
onClick={handleReset}
|
||||
>
|
||||
초기화
|
||||
</Button>
|
||||
<Button className="h-[3.2rem] w-[96px]" type="submit">
|
||||
@@ -112,11 +303,13 @@ const LogHistory = () => {
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 flex items-center justify-center rounded-lg p-10 [data-theme='black']:bg-transparent">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
</div>}
|
||||
)}
|
||||
<DataGrid
|
||||
columnInfo={LogHistoryColumn}
|
||||
isExcelImport={false}
|
||||
@@ -140,4 +333,4 @@ const LogHistory = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default LogHistory;
|
||||
export default LogHistory;
|
||||
|
||||
@@ -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<void>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<Dialog open={open} onOpenChange={onOpenChange}>
|
||||
<DialogContent className="max-w-[56rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>반려 사유</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 py-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-label">반려 사유</Label>
|
||||
<Textarea
|
||||
value={reason}
|
||||
onChange={(e) => {
|
||||
setReason(e.target.value);
|
||||
if (reasonError) {
|
||||
setReasonError(false);
|
||||
}
|
||||
}}
|
||||
className="min-h-[12rem] resize-y !text-[1.3rem] !leading-6"
|
||||
placeholder="반려 사유를 입력하세요."
|
||||
disabled={isSaving}
|
||||
/>
|
||||
{reasonError && (
|
||||
<p className="text-[1.2rem] text-red-500">
|
||||
반려 사유를 작성해주세요.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button type="button" color="gray" disabled={isSaving}>
|
||||
취소
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
disabled={isSaving}
|
||||
onClick={() => void handleSave()}
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
"use client";
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogClose,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
Label,
|
||||
Textarea,
|
||||
} from "@common/components";
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
|
||||
import type { ReviewOpinionSaveRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
|
||||
export type ReviewOpinionDialogContext = Pick<
|
||||
ReviewOpinionSaveRequest,
|
||||
"statsYr" | "sewerBzmnCd" | "statsCd"
|
||||
>;
|
||||
|
||||
export interface ReviewOpinionDialogProps {
|
||||
open: boolean;
|
||||
onOpenChange: (open: boolean) => void;
|
||||
/** 조회 성공 후의 통계연도·사업자코드·통계코드 (없으면 저장 불가) */
|
||||
context: ReviewOpinionDialogContext | null;
|
||||
/** 다이얼로그 열릴 때 검토의견 초기값 */
|
||||
initialReviewOpinion?: string;
|
||||
/** 다이얼로그 열릴 때 처리내역 초기값 */
|
||||
initialProcessHistory?: string;
|
||||
/** 저장 시 생성된 param 전달 (API 호출 등) */
|
||||
onSave?: (param: ReviewOpinionSaveRequest) => void | Promise<void>;
|
||||
}
|
||||
|
||||
export function ReviewOpinionDialog({
|
||||
open,
|
||||
onOpenChange,
|
||||
context,
|
||||
initialReviewOpinion = "",
|
||||
initialProcessHistory = "",
|
||||
onSave,
|
||||
}: ReviewOpinionDialogProps) {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [reviewOpinion, setReviewOpinion] = useState("");
|
||||
const [processHistory, setProcessHistory] = useState("");
|
||||
const [isSaving, setIsSaving] = useState(false);
|
||||
const [reviewOpinionError, setReviewOpinionError] = useState(false);
|
||||
|
||||
const resetForm = () => {
|
||||
setReviewOpinion("");
|
||||
setProcessHistory("");
|
||||
setReviewOpinionError(false);
|
||||
};
|
||||
|
||||
const handleDialogOpenChange = (nextOpen: boolean) => {
|
||||
if (!nextOpen) resetForm();
|
||||
onOpenChange(nextOpen);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
setReviewOpinion(initialReviewOpinion);
|
||||
setProcessHistory(initialProcessHistory);
|
||||
}, [open, initialReviewOpinion, initialProcessHistory]);
|
||||
|
||||
const handleSave = async () => {
|
||||
if (reviewOpinion.trim() === "") {
|
||||
setReviewOpinionError(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
context == null ||
|
||||
context.statsYr === "" ||
|
||||
context.sewerBzmnCd === ""
|
||||
) {
|
||||
alert("시도·시군구·년도로 조회한 뒤 검토의견을 저장할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
setIsSaving(true);
|
||||
let userInfo: Awaited<ReturnType<typeof getUserInfo>>;
|
||||
try {
|
||||
userInfo = await getUserInfo();
|
||||
} catch {
|
||||
alert("사용자 정보를 불러오지 못했습니다. 다시 시도해주세요.");
|
||||
return;
|
||||
}
|
||||
const param: ReviewOpinionSaveRequest = {
|
||||
statsYr: context.statsYr,
|
||||
sewerBzmnCd: context.sewerBzmnCd,
|
||||
statsCd: context.statsCd,
|
||||
splmntOpnnCn: reviewOpinion,
|
||||
wrterOpnnCn: processHistory,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
};
|
||||
await onSave?.(param);
|
||||
resetForm();
|
||||
onOpenChange(false);
|
||||
} catch {
|
||||
/* 저장 실패 시 부모에서 alert 후 throw — 다이얼로그는 유지 */
|
||||
} finally {
|
||||
setIsSaving(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog open={open} onOpenChange={handleDialogOpenChange}>
|
||||
<DialogContent className="max-w-[56rem]" aria-describedby={undefined}>
|
||||
<DialogHeader>
|
||||
<DialogTitle>검토의견작성</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="flex flex-col gap-4 py-2">
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-label">검토의견</Label>
|
||||
<Textarea
|
||||
value={reviewOpinion}
|
||||
onChange={(e) => {
|
||||
setReviewOpinion(e.target.value);
|
||||
if (reviewOpinionError) {
|
||||
setReviewOpinionError(false);
|
||||
}
|
||||
}}
|
||||
className="min-h-[10rem] resize-y !text-[1.3rem] !leading-6"
|
||||
placeholder="검토의견을 입력하세요."
|
||||
disabled={isSaving}
|
||||
/>
|
||||
{reviewOpinionError && (
|
||||
<p className="text-[1.2rem] text-red-500">
|
||||
검토의견을 작성해주세요.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<Label className="text-label">처리내역</Label>
|
||||
<Textarea
|
||||
value={processHistory}
|
||||
onChange={(e) => setProcessHistory(e.target.value)}
|
||||
className="min-h-[10rem] resize-y !text-[1.3rem] !leading-6"
|
||||
placeholder="처리내역을 입력하세요."
|
||||
disabled={isSaving}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<DialogClose asChild>
|
||||
<Button type="button" color="gray" disabled={isSaving}>
|
||||
취소
|
||||
</Button>
|
||||
</DialogClose>
|
||||
<Button
|
||||
type="button"
|
||||
color="primary"
|
||||
disabled={isSaving}
|
||||
onClick={() => void handleSave()}
|
||||
>
|
||||
저장
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
||||
@@ -1,17 +1,17 @@
|
||||
import { useMemo, useRef,useState } from "react";
|
||||
import { useMemo, useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type { SewerPipeMtcRequestParams } from "@/entities/statistics/model/statistics-sewage-pipe-mtc-review-interface";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import type { ColumnConfig,ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeMaintainColumnInfoList } from "@/components/util/column/review/SewagePipeMaintainRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
@@ -79,8 +79,8 @@ const SewagePipeMaintainRvw = () => {
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
if (!values.sido || values.sido === 'ALL') {
|
||||
alert('시도를 선택해주세요.');
|
||||
if (!values.sido || values.sido === "ALL") {
|
||||
alert("시도를 선택해주세요.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -91,26 +91,27 @@ const SewagePipeMaintainRvw = () => {
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === 'ALL' ? '' : values.gugun
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
};
|
||||
|
||||
searchParamRef.current = searchParam;
|
||||
searchDoneRef.current = true;
|
||||
lazyTabLoggedRef.current.clear();
|
||||
setRowDataMap(new Map());
|
||||
|
||||
const response = await fetchSewagePipeMaintainReviewTab(activeTab, searchParam);
|
||||
|
||||
if (response?.status === 'success' && response.data) {
|
||||
if (response?.status === "success" && response.data) {
|
||||
const listData = getListDataByTab(activeTab, response.data);
|
||||
setRowDataMap(
|
||||
new Map([[activeTab, toGridRows(listData)]]),
|
||||
);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, toGridRows(listData));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert('데이터 조회 중 오류가 발생했습니다.');
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
@@ -177,14 +178,60 @@ const SewagePipeMaintainRvw = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TabDataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
rowDataMap={rowDataMap}
|
||||
setRowDataMap={setRowDataMap}
|
||||
activeTab={activeTab}
|
||||
onActiveTabChange={handleActiveTabChange}
|
||||
/>
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{columnInfo.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { useMemo, useRef,useState } from "react";
|
||||
import { useRef, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type { SewagePipeReviewRequestParams } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import type { ColumnConfig,ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnInfo } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeColumnInfoListRvw } from "@/components/util/column/review/SewagePipeRvwColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
@@ -48,17 +48,6 @@ const SewagePipeRvw = () => {
|
||||
const searchDoneRef = useRef(false);
|
||||
const lazyTabLoggedRef = useRef<Set<string>>(new Set());
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
return SewagePipeColumnInfoListRvw.map((tab: ColumnInfoList) => ({
|
||||
...tab,
|
||||
columns: (tab.columns ?? []).map((col: ColumnConfig) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
styleName: col.styleName ?? ("rg-cell-readonly" as const),
|
||||
})),
|
||||
}));
|
||||
}, []);
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab(firstTabKey);
|
||||
@@ -86,14 +75,15 @@ const SewagePipeRvw = () => {
|
||||
searchParamRef.current = searchParam;
|
||||
searchDoneRef.current = true;
|
||||
lazyTabLoggedRef.current.clear();
|
||||
setRowDataMap(new Map());
|
||||
|
||||
const response = await fetchSewagePipeReviewTab(activeTab, searchParam);
|
||||
|
||||
if (response?.status === "success" && response.data) {
|
||||
setRowDataMap(
|
||||
new Map([[activeTab, toGridRows(response.data as object[])]]),
|
||||
);
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(activeTab, toGridRows(response.data as object[]));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response?.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
@@ -163,14 +153,60 @@ const SewagePipeRvw = () => {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<TabDataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
rowDataMap={rowDataMap}
|
||||
setRowDataMap={setRowDataMap}
|
||||
activeTab={activeTab}
|
||||
onActiveTabChange={handleActiveTabChange}
|
||||
/>
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(v) => void handleActiveTabChange(v)}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{SewagePipeColumnInfoListRvw.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{SewagePipeColumnInfoListRvw.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,13 +5,22 @@ import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import SubmitButton from "@/components/button/SumitButton";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
|
||||
|
||||
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
||||
import { SeweragePenetrationRateInfoList } from "@/components/util/column/review/SeweragePenetrationRateRvwColumn";
|
||||
import type { SewerageSupplyRateReviewItem, statsStatusInfo } from "@/entities/statistics/model/statistics-sewerage-penetration-review-interface";
|
||||
import type { ReviewOpinionSaveRequest } from "@/entities/statistics/model/statistics-interface";
|
||||
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import {
|
||||
ReviewOpinionDialog,
|
||||
type ReviewOpinionDialogContext,
|
||||
} from "@/components/statistics/review/ReviewOpinionDialog";
|
||||
|
||||
// 하수도 통계 검토 01.하수도 보급률
|
||||
const SeweragePenetrationRateRvw = () => {
|
||||
@@ -19,6 +28,14 @@ const SeweragePenetrationRateRvw = () => {
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [statsStatusInfo, setStatsStatusInfo] = useState<statsStatusInfo>({});
|
||||
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
|
||||
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
|
||||
const [reviewOpinionContext, setReviewOpinionContext] =
|
||||
useState<ReviewOpinionDialogContext | null>(null);
|
||||
const [latestReviewOpinion, setLatestReviewOpinion] = useState("");
|
||||
const [latestProcessHistory, setLatestProcessHistory] = useState("");
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
const { confirm } = useConfirm();
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
const { columns, columnLayout, fixedColumns, rowStartNum } = SeweragePenetrationRateInfoList;
|
||||
@@ -35,14 +52,14 @@ const SeweragePenetrationRateRvw = () => {
|
||||
}, []);
|
||||
|
||||
const { showReviewOpinionButton, showReturnButton, showSubmitButton } = useMemo(() => {
|
||||
const lae = statsStatusInfo.laeSbmsnSttsCd != null ? String(statsStatusInfo.laeSbmsnSttsCd) : "";
|
||||
const pic = statsStatusInfo.statPicSbmsnSttsCd != null ? String(statsStatusInfo.statPicSbmsnSttsCd) : "";
|
||||
const laeIs3 = lae === "3";
|
||||
const reviewSubmitVisible = laeIs3 && (pic === "0" || pic === "1");
|
||||
const submit = statsStatusInfo.submitStatusCd != null ? String(statsStatusInfo.submitStatusCd) : "";
|
||||
const prvn = statsStatusInfo.prvnStatusCd != null ? String(statsStatusInfo.prvnStatusCd) : "";
|
||||
const submitIs3 = submit === "3";
|
||||
const reviewSubmitVisible = submitIs3 && (prvn === "0" || prvn === "1");
|
||||
return {
|
||||
showReviewOpinionButton: reviewSubmitVisible,
|
||||
showSubmitButton: reviewSubmitVisible,
|
||||
showReturnButton: laeIs3 && pic !== "3",
|
||||
showReturnButton: submitIs3 && prvn !== "3",
|
||||
};
|
||||
}, [statsStatusInfo]);
|
||||
|
||||
@@ -52,6 +69,10 @@ const SeweragePenetrationRateRvw = () => {
|
||||
const handleReset = () => {
|
||||
setRowData([]);
|
||||
setStatsStatusInfo({});
|
||||
setReviewOpinionContext(null);
|
||||
setLatestReviewOpinion("");
|
||||
setLatestProcessHistory("");
|
||||
setLastSearchValues(null);
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
@@ -60,6 +81,7 @@ const SeweragePenetrationRateRvw = () => {
|
||||
alert('시도를 선택해주세요.');
|
||||
return;
|
||||
}
|
||||
setLastSearchValues(values);
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
@@ -80,9 +102,23 @@ const SeweragePenetrationRateRvw = () => {
|
||||
}));
|
||||
|
||||
setStatsStatusInfo(response.data.statsStatusInfo);
|
||||
setReviewOpinionContext({
|
||||
statsYr: values.year ?? "",
|
||||
sewerBzmnCd: values.bsnmCd ?? "",
|
||||
statsCd: response.data.statsStatusInfo.statsCd ?? "",
|
||||
});
|
||||
setLatestReviewOpinion(
|
||||
response.data.statsStatusInfo.splmntOpnnCn ?? "",
|
||||
);
|
||||
setLatestProcessHistory(
|
||||
response.data.statsStatusInfo.wrterOpnnCn ?? "",
|
||||
);
|
||||
setRowData(toGridRows(convertedData));
|
||||
} else {
|
||||
setRowData([]);
|
||||
setReviewOpinionContext(null);
|
||||
setLatestReviewOpinion("");
|
||||
setLatestProcessHistory("");
|
||||
alert(response.message || "데이터가 없습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -93,11 +129,120 @@ const SeweragePenetrationRateRvw = () => {
|
||||
}
|
||||
};
|
||||
|
||||
const submitFunc = () => console.log('submit');
|
||||
const submitFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 제출할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const reviewOpinionFunc = () => console.log('reviewOpinionFunc');
|
||||
if (!(await confirm("제출 하시겠습니까?"))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const returnFunc = () => console.log('returnFunc');
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
const response = await statisticsAPi.submitReview({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
});
|
||||
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "제출에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
alert(response.message || "제출되었습니다.");
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
} catch {
|
||||
alert("제출 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const reviewOpinionFunc = async () => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 검토의견을 작성할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const detailRes = await statisticsAPi.getReviewOpinionDetail({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
});
|
||||
|
||||
if (detailRes.status !== "success") {
|
||||
alert(detailRes.message || "검토의견 조회에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
const row = detailRes.data?.[0];
|
||||
setLatestReviewOpinion(row?.splmntOpnnCn ?? "");
|
||||
setLatestProcessHistory(row?.wrterOpnnCn ?? "");
|
||||
setReviewOpinionOpen(true);
|
||||
} catch {
|
||||
alert("검토의견 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const saveReviewOpinion = async (param: ReviewOpinionSaveRequest) => {
|
||||
try {
|
||||
const response = await statisticsAPi.saveReviewOpinion(param);
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "저장에 실패했습니다.");
|
||||
return;
|
||||
}
|
||||
alert(response.message || "저장되었습니다.");
|
||||
setStatsStatusInfo((prev) => ({
|
||||
...prev,
|
||||
splmntOpnnCn: param.splmntOpnnCn,
|
||||
wrterOpnnCn: param.wrterOpnnCn,
|
||||
}));
|
||||
setLatestReviewOpinion(param.splmntOpnnCn);
|
||||
setLatestProcessHistory(param.wrterOpnnCn);
|
||||
} catch {
|
||||
alert("저장 중 오류가 발생했습니다.");
|
||||
}
|
||||
};
|
||||
|
||||
const returnFunc = () => setReturnDialogOpen(true);
|
||||
|
||||
const handleSaveReturnReason = async (reason: string) => {
|
||||
if (reviewOpinionContext == null) {
|
||||
alert("조회 후 반려할 수 있습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (await confirm('반려 처리 하시겠습니까?')) {
|
||||
try {
|
||||
const userInfo = await getUserInfo();
|
||||
const response = await statisticsAPi.saveReviewReturn({
|
||||
statsYr: reviewOpinionContext.statsYr,
|
||||
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
|
||||
statsCd: reviewOpinionContext.statsCd,
|
||||
rjctOpnnCn: reason,
|
||||
mdfrId: userInfo.userUnqId,
|
||||
});
|
||||
|
||||
if (response.status !== "success") {
|
||||
alert(response.message || "반려 처리 중 오류가 발생했습니다.");
|
||||
return;
|
||||
}
|
||||
|
||||
alert(response.message || "반려 처리 되었습니다.");
|
||||
if (lastSearchValues != null) {
|
||||
await handleSearch(lastSearchValues);
|
||||
}
|
||||
|
||||
} catch {
|
||||
alert("반려 처리 중 오류가 발생했습니다.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
@@ -131,6 +276,20 @@ const SeweragePenetrationRateRvw = () => {
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
/>
|
||||
|
||||
<ReviewOpinionDialog
|
||||
open={reviewOpinionOpen}
|
||||
onOpenChange={setReviewOpinionOpen}
|
||||
context={reviewOpinionContext}
|
||||
initialReviewOpinion={latestReviewOpinion}
|
||||
initialProcessHistory={latestProcessHistory}
|
||||
onSave={saveReviewOpinion}
|
||||
/>
|
||||
<ReturnReasonDialog
|
||||
open={returnDialogOpen}
|
||||
onOpenChange={setReturnDialogOpen}
|
||||
onSave={handleSaveReturnReason}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,23 +1,54 @@
|
||||
import { useState, useMemo } from "react";
|
||||
import { createColumnHelper, getCoreRowModel, useReactTable } from "@tanstack/react-table";
|
||||
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import SaveButton from "@/components/button/SaveButton";
|
||||
import ApproveButton from "@/components/button/ApproveButton";
|
||||
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
|
||||
import ReturnButton from "@/components/button/ReturnButton";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
LoadingWithAnimation,
|
||||
DataTable
|
||||
} from "@common/components";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { StatsInputStatusInfoList } from "@/components/util/column/review/StatsInputStatusRvwColumn";
|
||||
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
|
||||
import type { ReviewHistoryResponse } from "@/entities/statistics/model/statistics-input-review-interface";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
|
||||
type ReviewHistoryRow = ReviewHistoryResponse["data"][number];
|
||||
|
||||
const checkPrvnStatusCd = (code: string) => {
|
||||
return (code === '0' || code === '1') ? '미작성' : (code === '2' ? '제출완료' : (code === '3' ? '승인완료' : '반려'));
|
||||
};
|
||||
|
||||
const checkSubmitStatusCd = (code: string) => {
|
||||
return code === '0' ? '미작성' : (code === '1' ? '작성중' : (code === '2' ? '제출완료' : (code === '3' ? '승인완료' : '보완요청')));
|
||||
};
|
||||
|
||||
// 하수도 통계 검토 00. 통계 입력 현황
|
||||
const StatsInputStatusRvw = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
|
||||
const [historyRowData, setHistoryRowData] = useState<ReviewHistoryRow[]>([]);
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [searchedYear, setSearchedYear] = useState<string>("");
|
||||
const [reviewHistoryOpen, setReviewHistoryOpen] = useState<boolean>(false);
|
||||
const [historySearchInfo, setHistorySearchInfo] = useState<{
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
tabNm: string;
|
||||
}>({
|
||||
ctpvNm: '',
|
||||
sggNm: '',
|
||||
tabNm: '',
|
||||
});
|
||||
|
||||
const columnInfo = useMemo(() => {
|
||||
const { columns, columnLayout } = StatsInputStatusInfoList;
|
||||
@@ -30,8 +61,30 @@ const StatsInputStatusRvw = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const reviewHistoryColumns = useMemo(() => {
|
||||
const col = createColumnHelper<ReviewHistoryRow>();
|
||||
return [
|
||||
col.accessor("logDt", {
|
||||
header: "등록일",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
col.accessor("historyContent", {
|
||||
header: "내용",
|
||||
cell: (info) => info.getValue(),
|
||||
}),
|
||||
];
|
||||
}, []);
|
||||
|
||||
const historyTable = useReactTable({
|
||||
data: historyRowData,
|
||||
columns: reviewHistoryColumns,
|
||||
getCoreRowModel: getCoreRowModel(),
|
||||
getRowId: (_row, index) => String(index),
|
||||
});
|
||||
|
||||
const handleReset = () => {
|
||||
setRowData([]);
|
||||
setSearchedYear("");
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
@@ -42,24 +95,29 @@ const StatsInputStatusRvw = () => {
|
||||
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
setSearchedYear(values.year ?? "");
|
||||
|
||||
const response = await statisticsManagementApi.getStatsInputStatusList({
|
||||
ctpvNm: values.sido,
|
||||
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
|
||||
statsYr: values.year,
|
||||
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
|
||||
statsYr: values.year,
|
||||
sewerBzmnCd: values.bsnmCd
|
||||
});
|
||||
|
||||
if (response.status === 'success' && response.data) {
|
||||
const rawList = response.data.sewerageInputList ?? [];
|
||||
const convertedData = rawList.map((r) => ({
|
||||
...r,
|
||||
prvnStatusNm: checkPrvnStatusCd(r.prvnStatusCd || '0'),
|
||||
submitStatusNm: checkSubmitStatusCd(r.submitStatusCd || '0')
|
||||
}));
|
||||
|
||||
setRowData(toGridRows(rawList));
|
||||
setRowData(toGridRows(convertedData));
|
||||
} else {
|
||||
setRowData([]);
|
||||
alert(response.message || "조회된 데이터가 없습니다.");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("입력현황 조회 중 에러 발생:", error);
|
||||
} catch {
|
||||
alert('데이터 조회 중 오류가 발생했습니다.');
|
||||
setRowData([]);
|
||||
} finally {
|
||||
@@ -68,20 +126,40 @@ const StatsInputStatusRvw = () => {
|
||||
};
|
||||
|
||||
const approveFunc = () => console.log('approveFunc');
|
||||
const saveFunc = () => console.log('submit');
|
||||
const reviewOpinionFunc = () => console.log('reviewOpinionFunc');
|
||||
const returnFunc = () => console.log('returnFunc');
|
||||
|
||||
const handleRowClick = async (row: Record<string, string | number>) => {
|
||||
setHistorySearchInfo({
|
||||
ctpvNm: String(row.ctpvNm ?? ""),
|
||||
sggNm: String(row.sggNm ?? ""),
|
||||
tabNm: String(row.tabNm ?? ""),
|
||||
});
|
||||
const params = {
|
||||
statsYr: searchedYear,
|
||||
sewerBzmnCd: String(row.sewerBzmnCd ?? ""),
|
||||
statsCd: String(row.statsCd ?? ""),
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await statisticsManagementApi.getReviewHistory(params);
|
||||
|
||||
if (response.status === 'success') {
|
||||
setHistoryRowData(response.data);
|
||||
setReviewHistoryOpen(true);
|
||||
} else {
|
||||
alert(response.message || '검토 이력 조회 중 오류가 발생했습니다.');
|
||||
}
|
||||
} catch {
|
||||
alert('검토 이력 조회 중 오류가 발생했습니다.');
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
|
||||
|
||||
|
||||
<div className="card !py-0">
|
||||
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
|
||||
<ReviewOpinionButton reviewOpinionFunc={reviewOpinionFunc} />
|
||||
<ApproveButton approveFunc={approveFunc} />
|
||||
<ReturnButton returnFunc={returnFunc} />
|
||||
<SaveButton saveFunc={saveFunc} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -96,12 +174,43 @@ const StatsInputStatusRvw = () => {
|
||||
<DataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
showHeaderCheckbox={true}
|
||||
rowStartNum={2}
|
||||
showHeaderCheckbox={true}
|
||||
rowStartNum={2}
|
||||
fixedColumns={3}
|
||||
rowData={rowData}
|
||||
setRowData={setRowData}
|
||||
onRowClick={handleRowClick}
|
||||
/>
|
||||
<Dialog open={reviewHistoryOpen} onOpenChange={setReviewHistoryOpen}>
|
||||
<DialogContent
|
||||
className="flex max-h-[85vh] max-w-[48rem] flex-col gap-4"
|
||||
aria-describedby={undefined}
|
||||
>
|
||||
<DialogHeader className="shrink-0">
|
||||
<DialogTitle>
|
||||
{(historySearchInfo.sggNm ? `${historySearchInfo.ctpvNm} ${historySearchInfo.sggNm} - ${searchedYear}년 ${historySearchInfo.tabNm} 검토이력` : `${historySearchInfo.ctpvNm} - ${searchedYear}년 ${historySearchInfo.tabNm} 검토이력`)}
|
||||
</DialogTitle>
|
||||
</DialogHeader>
|
||||
{historyRowData.length === 0 ?
|
||||
<div className="text-2xl h-40 flex items-center justify-center">
|
||||
검토 이력이 없습니다.
|
||||
</div>
|
||||
:
|
||||
<div className="min-h-0 flex-1 overflow-y-auto overflow-x-auto">
|
||||
<DataTable table={historyTable} />
|
||||
</div>
|
||||
}
|
||||
<DialogFooter className="shrink-0">
|
||||
<Button
|
||||
type="button"
|
||||
color="gray"
|
||||
onClick={() => setReviewHistoryOpen(false)}
|
||||
>
|
||||
닫기
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -2,20 +2,22 @@ import { useMemo, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from "@common/components";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import type { ColumnConfig, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import DataGrid from "@/components/module/DataGrid";
|
||||
import type { ColumnConfig, ColumnInfo, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { SewagePipeColumnInfoListTot } from "@/components/util/column/total/SewagePipeTotColumn";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
const firstTabKey = SewagePipeColumnInfoListTot[0]?.tabKey ?? "ST00020001";
|
||||
|
||||
const SewagePipeTot = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState("ST00020001");
|
||||
const [activeTab, setActiveTab] = useState(firstTabKey);
|
||||
|
||||
const columnInfo = useMemo(
|
||||
() =>
|
||||
@@ -32,11 +34,11 @@ const SewagePipeTot = () => {
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab("ST00020001");
|
||||
setActiveTab(firstTabKey);
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
setActiveTab("ST00020001");
|
||||
setActiveTab(firstTabKey);
|
||||
setRowDataMap(new Map());
|
||||
|
||||
try {
|
||||
@@ -76,20 +78,66 @@ const SewagePipeTot = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<TabDataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
rowDataMap={rowDataMap}
|
||||
setRowDataMap={setRowDataMap}
|
||||
activeTab={activeTab}
|
||||
onActiveTabChange={setActiveTab}
|
||||
cellStyleCallback={({ row }) => {
|
||||
const rowType = String(row.rowType ?? "");
|
||||
if (rowType === "DETAIL") return undefined;
|
||||
if (rowType === "BIZ_SUM") return "rg-cell-rowtype-biz-sum";
|
||||
return "rg-cell-rowtype-other";
|
||||
}}
|
||||
/>
|
||||
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={setActiveTab}
|
||||
className="w-full"
|
||||
variant="default"
|
||||
>
|
||||
<TabsList>
|
||||
{columnInfo.map((info) => (
|
||||
<TabsTrigger key={info.tabKey} value={info.tabKey}>
|
||||
{info.tabLabel || info.tabKey}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
{columnInfo.map((info) => {
|
||||
const tabKey = info.tabKey;
|
||||
if (!info.columns || !info.columnLayout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const rowData = rowDataMap.get(tabKey) ?? [];
|
||||
const gridColumnInfo: ColumnInfo = {
|
||||
columns: info.columns,
|
||||
columnLayout: info.columnLayout,
|
||||
};
|
||||
|
||||
return (
|
||||
<TabsContent key={tabKey} value={tabKey}>
|
||||
<DataGrid
|
||||
columnInfo={gridColumnInfo}
|
||||
rowStartNum={info.rowStartNum}
|
||||
excelExportFileSuffix={info.tabLabel || tabKey}
|
||||
rowData={rowData}
|
||||
setRowData={(updater) => {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
const cur = next.get(tabKey) ?? [];
|
||||
const n =
|
||||
typeof updater === "function" ? updater(cur) : updater;
|
||||
next.set(tabKey, n);
|
||||
return next;
|
||||
});
|
||||
}}
|
||||
isExcelImport={false}
|
||||
isExcelExport
|
||||
showHeaderCheckbox={false}
|
||||
fixedColumns={info.fixedColumns}
|
||||
enableSort={false}
|
||||
cellStyleCallback={({ row }) => {
|
||||
const rowType = String(row.rowType ?? "");
|
||||
if (rowType === "DETAIL") return undefined;
|
||||
if (rowType === "BIZ_SUM") return "rg-cell-rowtype-biz-sum";
|
||||
return "rg-cell-rowtype-other";
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
);
|
||||
})}
|
||||
</Tabs>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ const SeweragePenetrationRateTot = () => {
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm isDetail={true} onSubmit={handleSearch} />
|
||||
<SearchForm isDetail={true} onSubmit={handleSearch} sidoSelectText="전체" />
|
||||
{isLoading && <div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
|
||||
@@ -1,10 +1,123 @@
|
||||
import SearchForm from "../../module/SearchForm";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
|
||||
import { useAlertStore } from "@/shared/lib";
|
||||
import { statisticsTotalStatusApi } from "@/entities/statistics/api/statistics-total-service";
|
||||
import { toGridRows } from "@/components/util/grid/gridFunction";
|
||||
|
||||
import SearchForm from "@/components/module/SearchForm";
|
||||
import TabDataGrid from "@/components/module/TabDataGrid";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import type { ColumnConfig, ColumnInfoList } from "@/components/type/DataGrid.type";
|
||||
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
||||
import { ComplaintInpColumn } from "@/components/util/column/total/sewerageServiceLevel/ComplaintTotColumn";
|
||||
import { ResidentFriendlyFactTotColumn } from "@/components/util/column/total/sewerageServiceLevel/ResidentFriendlyFactTotColumn";
|
||||
|
||||
const SewerageServiceLevelInpTabList = [
|
||||
ComplaintInpColumn,
|
||||
ResidentFriendlyFactTotColumn,
|
||||
];
|
||||
|
||||
const SewerageServiceLevelTot = () => {
|
||||
const { setMessage: alert } = useAlertStore((state) => state);
|
||||
const [rowDataMap, setRowDataMap] = useState<Map<string, Record<string, string | number>[]>>(new Map());
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const [activeTab, setActiveTab] = useState(ComplaintInpColumn.tabKey);
|
||||
const [lastSearchValues, setLastSearchValues] = useState<SearchFormValues | null>(null);
|
||||
|
||||
const columnInfo = useMemo(
|
||||
() =>
|
||||
SewerageServiceLevelInpTabList.map((tab: ColumnInfoList) => ({
|
||||
...tab,
|
||||
columns: (tab.columns ?? []).map((col: ColumnConfig) => ({
|
||||
...col,
|
||||
editable: false as const,
|
||||
styleName: col.styleName ?? ("rg-cell-readonly" as const),
|
||||
})),
|
||||
})),
|
||||
[],
|
||||
);
|
||||
|
||||
const handleReset = () => {
|
||||
setRowDataMap(new Map());
|
||||
setActiveTab(ComplaintInpColumn.tabKey);
|
||||
setLastSearchValues(null);
|
||||
};
|
||||
|
||||
const fetchByTab = async (tabKey: string, values: SearchFormValues) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
|
||||
const param = {
|
||||
ctpvNm: values.sido === "ALL" ? "" : values.sido,
|
||||
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
||||
sewerBzmnCd: values.bsnmCd,
|
||||
statsYr: values.year,
|
||||
rowDataYn: values.isDetail,
|
||||
};
|
||||
|
||||
let response;
|
||||
if (tabKey === ResidentFriendlyFactTotColumn.tabKey) {
|
||||
response =
|
||||
await statisticsTotalStatusApi.getSewerageServiceLevelResidentFriendlyFact(param);
|
||||
} else {
|
||||
response =
|
||||
await statisticsTotalStatusApi.getSewerageServiceLevelComplaint(param);
|
||||
}
|
||||
|
||||
if (response.status === "success" && response.data) {
|
||||
setRowDataMap((prev) => {
|
||||
const next = new Map(prev);
|
||||
next.set(tabKey, toGridRows(response.data as object[]));
|
||||
return next;
|
||||
});
|
||||
} else {
|
||||
alert(response.message || "데이터 조회 중 오류가 발생했습니다.");
|
||||
}
|
||||
} catch {
|
||||
alert("데이터 조회 중 오류가 발생했습니다.");
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSearch = async (values: SearchFormValues) => {
|
||||
setLastSearchValues(values);
|
||||
setRowDataMap(new Map());
|
||||
await fetchByTab(activeTab, values);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (lastSearchValues == null) return;
|
||||
if (rowDataMap.has(activeTab)) return;
|
||||
void fetchByTab(activeTab, lastSearchValues);
|
||||
}, [activeTab]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<SearchForm />
|
||||
</>
|
||||
<div className="relative">
|
||||
<SearchForm isDetail={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 z-50 [data-theme='black']:bg-transparent rounded-lg p-10 flex justify-center items-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<TabDataGrid
|
||||
columnInfo={columnInfo}
|
||||
isExcelImport={false}
|
||||
rowDataMap={rowDataMap}
|
||||
setRowDataMap={setRowDataMap}
|
||||
activeTab={activeTab}
|
||||
onActiveTabChange={setActiveTab}
|
||||
cellStyleCallback={({ row }) => {
|
||||
const rowType = String(row.rowType ?? "");
|
||||
if (rowType === "DETAIL") return undefined;
|
||||
if (rowType === "BIZ_SUM") return "rg-cell-rowtype-biz-sum";
|
||||
return "rg-cell-rowtype-other";
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -234,6 +234,8 @@ export type CellTooltipCallback = (params: CellTooltipCallbackParams) => string
|
||||
export type DataGridProps = {
|
||||
columnInfo: ColumnInfo;
|
||||
rowStartNum?: number; // 헤더 행 개수 (1부터 시작, 예: 2면 1행과 2행까지 헤더로 간주)
|
||||
/** 있으면보내기 파일명: `{메뉴명}_{suffix}.xlsx` (TabDataGrid의 탭 라벨 접미사와 동일) */
|
||||
excelExportFileSuffix?: string;
|
||||
isExcelImport?: boolean; // 엑셀 가져오기 유무
|
||||
isExcelExport?: boolean;
|
||||
onRowsChange?: (rows: Record<string, string | number>[]) => void;
|
||||
|
||||
@@ -10,6 +10,7 @@ export type SearchFormValues = {
|
||||
|
||||
export type SearchFormProps = {
|
||||
sido?: boolean;
|
||||
sidoSelectText?: string;
|
||||
gugun?: boolean;
|
||||
year?: boolean;
|
||||
order?: boolean;
|
||||
@@ -22,7 +23,9 @@ export type SearchFormProps = {
|
||||
defaultValues?: SearchFormValues;
|
||||
};
|
||||
|
||||
export const defaultSearchFormProps: Required<Omit<SearchFormProps, 'onSubmit' | 'onReset' | 'onValuesChange' | 'defaultValues'>> = {
|
||||
export const defaultSearchFormProps: Required<
|
||||
Omit<SearchFormProps, "onSubmit" | "onReset" | "onValuesChange" | "defaultValues" | "sidoSelectText">
|
||||
> = {
|
||||
sido: true,
|
||||
gugun: true,
|
||||
year: true,
|
||||
|
||||
@@ -2,20 +2,26 @@ import { ValueType } from "realgrid";
|
||||
|
||||
export const LogHistoryColumn = {
|
||||
columns: [
|
||||
{ name: 'statsLogSn', fieldName: 'statsLogSn', header: { text: '일련번호' }, editable: false, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'ctpvNm', fieldName: 'ctpvNm', header: { text: '시도' }, editable: false },
|
||||
{ name: 'sggNm', fieldName: 'sggNm', header: { text: '시군구' }, editable: false },
|
||||
{ name: 'statsYr', fieldName: 'statsYr', header: { text: '연도' }, editable: false },
|
||||
{ name: 'statsLogSn', fieldName: 'statsLogSn', header: { text: '일련번호' }, editable: false, visible: 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: 'statsDataChgComNm', fieldName: 'statsDataChgComNm', header: { text: '요청 기능명' }, editable: false },
|
||||
{ name: 'rgtrId', fieldName: 'rgtrId', header: { text: '등록자' }, editable: false, visible: false },
|
||||
{ name: 'regDt', fieldName: 'regDt', header: { text: '등록일' }, editable: false },
|
||||
],
|
||||
|
||||
columnLayout: [
|
||||
'statsLogSn',
|
||||
'ctpvNm',
|
||||
'sggNm',
|
||||
'statsYr',
|
||||
'menuNm',
|
||||
'tblNm',
|
||||
'dmndFwkNm',
|
||||
'statsDataChgComNm',
|
||||
'rgtrId',
|
||||
'regDt'
|
||||
'regDt',
|
||||
'statsLogSn',
|
||||
]
|
||||
};
|
||||
@@ -0,0 +1,77 @@
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
// 하수도 통계 관리 > 13. 하수도 서비스 수준 > tab 1: 민원
|
||||
export const ComplaintInpColumn = {
|
||||
columns: [
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" } },
|
||||
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" } },
|
||||
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역명" } },
|
||||
{
|
||||
name: "swtrmtPpltnCnt",
|
||||
fieldName: "swtrmtPpltnCnt",
|
||||
header: { text: "하수처리장민원건수(건)" },
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0",
|
||||
},
|
||||
{
|
||||
name: "whol",
|
||||
fieldName: "whol",
|
||||
header: { text: "합계" },
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0",
|
||||
},
|
||||
{ name: "odorCnt", fieldName: "odorCnt", header: { text: "악취" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "swagePplCnt", fieldName: "swagePplCnt", header: { text: "하수관거" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "dschgWqCnt", fieldName: "dschgWqCnt", header: { text: "방류수수질" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "ldsCnt", fieldName: "ldsCnt", header: { text: "퇴적토" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "plltnAcdntCnt", fieldName: "plltnAcdntCnt", header: { text: "오염사고" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "sewerCrgAmt", fieldName: "sewerCrgAmt", header: { text: "하수도요금" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "etcCnt", fieldName: "etcCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{
|
||||
name: "crtrPpltnCvlcptEvntcsCnt",
|
||||
fieldName: "crtrPpltnCvlcptEvntcsCnt",
|
||||
header: { text: "인구10만명당 민원건수(건)" },
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0.00",
|
||||
},
|
||||
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
|
||||
{ name: "statsYr", fieldName: "statsYr", header: { text: "통계연도" }, visible: false },
|
||||
{ name: "admdstCd", fieldName: "admdstCd", header: { text: "행정코드" }, visible: false },
|
||||
{ name: "rowType", fieldName: "rowType", header: { text: "행타입" }, visible: false },
|
||||
].map((col) => ({ ...col, editable: false })),
|
||||
|
||||
columnLayout: [
|
||||
{
|
||||
name: "groupA",
|
||||
hideChildHeaders: false,
|
||||
header: { text: "행정구역" },
|
||||
items: ["ctpvNm", "sggNm", "admdstNm"],
|
||||
},
|
||||
{
|
||||
name: "groupB",
|
||||
hideChildHeaders: false,
|
||||
header: { text: "민원건수(건)" },
|
||||
items: [
|
||||
"whol",
|
||||
"odorCnt",
|
||||
"swagePplCnt",
|
||||
"dschgWqCnt",
|
||||
"ldsCnt",
|
||||
"plltnAcdntCnt",
|
||||
"sewerCrgAmt",
|
||||
"etcCnt",
|
||||
],
|
||||
},
|
||||
"swtrmtPpltnCnt",
|
||||
"crtrPpltnCvlcptEvntcsCnt",
|
||||
"rmrk",
|
||||
"statsYr",
|
||||
"admdstCd",
|
||||
"rowType",
|
||||
],
|
||||
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 1,
|
||||
tabKey: "ST00130001",
|
||||
tabLabel: "민원",
|
||||
};
|
||||
+81
@@ -0,0 +1,81 @@
|
||||
import { ValueType } from "realgrid";
|
||||
|
||||
// 하수도 통계 관리 > 13. 하수도 서비스 수준 > tab 2: 주민친화시설
|
||||
export const ResidentFriendlyFactTotColumn = {
|
||||
columns: [
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" } },
|
||||
{ name: "sggNm", fieldName: "sggNm", header: { text: "시군구" } },
|
||||
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역명" } },
|
||||
{ name: "swtrmtFcltNm", fieldName: "swtrmtFcltNm", header: { text: "처리장명" } },
|
||||
{
|
||||
name: "fcltCpct",
|
||||
fieldName: "fcltCpct",
|
||||
header: { text: "시설용량(m³/일)" },
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0",
|
||||
},
|
||||
{
|
||||
name: "tmplntArea",
|
||||
fieldName: "tmplntArea",
|
||||
header: { text: "처리장면적(㎡)" },
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0.00",
|
||||
},
|
||||
{
|
||||
name: "whol",
|
||||
fieldName: "whol",
|
||||
header: { text: "총계" },
|
||||
valueType: ValueType.NUMBER,
|
||||
dataFormat: "#,##0",
|
||||
},
|
||||
{ name: "brkFcltArea", fieldName: "brkFcltArea", header: { text: "휴게시설" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "childFrshpSpacArea", fieldName: "childFrshpSpacArea", header: { text: "어린이놀이공간" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "bdySportsFcltArea", fieldName: "bdySportsFcltArea", header: { text: "체력단련시설" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "sportsFcltArea", fieldName: "sportsFcltArea", header: { text: "운동시설" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "pkltArea", fieldName: "pkltArea", header: { text: "주차장" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "eduMultiFcltArea", fieldName: "eduMultiFcltArea", header: { text: "교육 및 문화시설" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "wtlndMakeArea", fieldName: "wtlndMakeArea", header: { text: "습지조성(생태공원)" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "etcCnt", fieldName: "etcCnt", header: { text: "기타" }, valueType: ValueType.NUMBER, dataFormat: "#,##0" },
|
||||
{ name: "rmrk", fieldName: "rmrk", header: { text: "비고" } },
|
||||
{ name: "statsYr", fieldName: "statsYr", header: { text: "통계연도" }, visible: false },
|
||||
{ name: "admdstCd", fieldName: "admdstCd", header: { text: "행정코드" }, visible: false },
|
||||
{ name: "rowType", fieldName: "rowType", header: { text: "행타입" }, visible: false },
|
||||
].map((col) => ({ ...col, editable: false })),
|
||||
|
||||
columnLayout: [
|
||||
{
|
||||
name: "groupA",
|
||||
hideChildHeaders: false,
|
||||
header: { text: "행정구역" },
|
||||
items: ["ctpvNm", "sggNm", "admdstNm"],
|
||||
},
|
||||
"swtrmtFcltNm",
|
||||
"fcltCpct",
|
||||
"tmplntArea",
|
||||
{
|
||||
name: "groupB",
|
||||
hideChildHeaders: false,
|
||||
header: { text: "주민친화시설 면적(㎡)" },
|
||||
items: [
|
||||
"whol",
|
||||
"brkFcltArea",
|
||||
"childFrshpSpacArea",
|
||||
"bdySportsFcltArea",
|
||||
"sportsFcltArea",
|
||||
"pkltArea",
|
||||
"eduMultiFcltArea",
|
||||
"wtlndMakeArea",
|
||||
"etcCnt",
|
||||
],
|
||||
},
|
||||
"rmrk",
|
||||
"statsYr",
|
||||
"admdstCd",
|
||||
"rowType",
|
||||
],
|
||||
|
||||
rowStartNum: 2,
|
||||
fixedColumns: 3,
|
||||
tabKey: "ST00130002",
|
||||
tabLabel: "주민친화시설",
|
||||
};
|
||||
@@ -2,8 +2,10 @@ import { ENDPOINT } from "@/shared/config";
|
||||
import { stClient } from "@/shared/api/client";
|
||||
|
||||
|
||||
import {
|
||||
type StatsInputStatusReviewApiResponse
|
||||
import type {
|
||||
StatsInputStatusReviewApiResponse,
|
||||
ReviewHistoryRequest,
|
||||
ReviewHistoryResponse,
|
||||
} from "../model/statistics-input-review-interface";
|
||||
import {
|
||||
type SewerageSupplyRateReviewRequestParams,
|
||||
@@ -190,6 +192,14 @@ export class StatisticsManagementApiService {
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getReviewHistory(params: ReviewHistoryRequest): Promise<ReviewHistoryResponse> {
|
||||
const response = await stClient.get<ReviewHistoryResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/history`,
|
||||
{ params }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
// 통합 인스턴스 수출
|
||||
|
||||
@@ -14,28 +14,37 @@ import {
|
||||
type StatisticsLogRequest,
|
||||
type StatisticsLogResponse,
|
||||
type StatisticsLogDetailResponse,
|
||||
type ReviewOpinionSaveRequest,
|
||||
type ReviewOpinionSaveResponse,
|
||||
type getReviewOpinionDetailRequest,
|
||||
type getReviewOpinionDetailResponse,
|
||||
type ReviewReturnSaveRequest,
|
||||
type ReviewReturnSaveResponse,
|
||||
type ReviewSubmitRequest,
|
||||
type ReviewSubmitResponse,
|
||||
} from "../model/statistics-interface";
|
||||
|
||||
export class StatisticsAPiService implements StatisticsService {
|
||||
public async getSidoList(): Promise<ApiResponse<SearchCondition>> {
|
||||
public async getSidoList(params?: { instSeCd?: string }): Promise<ApiResponse<SearchCondition>> {
|
||||
const response = await stClient.get<ApiResponse<SearchCondition>>(
|
||||
`${ENDPOINT.ST_SERVICE.COMMON}/search-condition`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getSggList(sidoName: string): Promise<ApiResponse<Sgg[]>> {
|
||||
public async getSggList(sidoName: string, params?: { instSeCd?: string; gugunSkipYn?: "Y" | "N" }): Promise<ApiResponse<Sgg[]>> {
|
||||
const response = await stClient.get<ApiResponse<Sgg[]>>(
|
||||
`${ENDPOINT.ST_SERVICE.COMMON}/sgg`,
|
||||
{ params: { sidoName } },
|
||||
{ params: { sidoName, ...params } },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getSggAllList(sidoName: string): Promise<ApiResponse<Sgg[]>> {
|
||||
public async getSggAllList(sidoName: string, params?: { instSeCd?: string; gugunSkipYn?: "Y" | "N" }): Promise<ApiResponse<Sgg[]>> {
|
||||
const response = await stClient.get<ApiResponse<Sgg[]>>(
|
||||
`${ENDPOINT.ST_SERVICE.COMMON}/sgg-all`,
|
||||
{ params: { sidoName } },
|
||||
{ params: { sidoName, ...params } },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
@@ -80,6 +89,38 @@ export class StatisticsAPiService implements StatisticsService {
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async saveReviewOpinion(param: ReviewOpinionSaveRequest): Promise<ReviewOpinionSaveResponse> {
|
||||
const response = await stClient.post<ReviewOpinionSaveResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/opinion-save`,
|
||||
param
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getReviewOpinionDetail(param: getReviewOpinionDetailRequest): Promise<getReviewOpinionDetailResponse> {
|
||||
const response = await stClient.get<getReviewOpinionDetailResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/opinion-select`,
|
||||
{ params: param },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async saveReviewReturn(param: ReviewReturnSaveRequest): Promise<ReviewReturnSaveResponse> {
|
||||
const response = await stClient.post<ReviewReturnSaveResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/return`,
|
||||
param
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async submitReview(param: ReviewSubmitRequest): Promise<ReviewSubmitResponse> {
|
||||
const response = await stClient.post<ReviewSubmitResponse>(
|
||||
`${ENDPOINT.ST_SERVICE.REVIEW}/submit`,
|
||||
param
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const statisticsAPi = new StatisticsAPiService();
|
||||
@@ -3,6 +3,11 @@ import { stClient } from "@/shared/api/client";
|
||||
|
||||
import type { SewerageSupplyRateRequest, SewerageSupplyRateResponse } from "../model/statistics-total/statistics-sewage-supply-rate-total-interface";
|
||||
import type { SewagePipeTotalRequest, SewagePipeTotalResponse } from "../model/statistics-total/statistics-sewage-pipe-total-interface";
|
||||
import type {
|
||||
SewerageServiceLevelComplaintResponse,
|
||||
SewerageServiceLevelRequest,
|
||||
SewerageServiceLevelResidentFriendlyFactResponse
|
||||
} from "../model/statistics-total/statistics-service-level-total-interface";
|
||||
|
||||
export class StatisticsTotalApiService {
|
||||
public async getSewerageSupplyRateList(params: SewerageSupplyRateRequest): Promise<SewerageSupplyRateResponse> {
|
||||
@@ -20,6 +25,22 @@ export class StatisticsTotalApiService {
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getSewerageServiceLevelComplaint(params: SewerageServiceLevelRequest): Promise<SewerageServiceLevelComplaintResponse> {
|
||||
const response = await stClient.get<SewerageServiceLevelComplaintResponse> (
|
||||
`${ENDPOINT.ST_SERVICE.TOTAL}/sewage-complaint`,
|
||||
{ params: params }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
public async getSewerageServiceLevelResidentFriendlyFact(params: SewerageServiceLevelRequest): Promise<SewerageServiceLevelResidentFriendlyFactResponse> {
|
||||
const response = await stClient.get<SewerageServiceLevelResidentFriendlyFactResponse> (
|
||||
`${ENDPOINT.ST_SERVICE.TOTAL}/resident-friendly-facility`,
|
||||
{ params: params }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
export const statisticsTotalStatusApi = new StatisticsTotalApiService();
|
||||
@@ -27,6 +27,21 @@ export type StatsInputStatusReviewApiResponse = ApiResponse<StatsInputStatusRevi
|
||||
};
|
||||
|
||||
|
||||
export interface ReviewHistoryRequest {
|
||||
statsCd: string;
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
}
|
||||
|
||||
export interface ReviewHistoryResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data: {
|
||||
logDt: string;
|
||||
historyContent: string;
|
||||
}[]
|
||||
}
|
||||
|
||||
export interface StatsInputStatusReviewService {
|
||||
getStatsInputStatusList(params: {
|
||||
yr?: string;
|
||||
@@ -34,4 +49,5 @@ export interface StatsInputStatusReviewService {
|
||||
sido?: string;
|
||||
gugun?: string;
|
||||
}): Promise<StatsInputStatusReviewApiResponse>;
|
||||
getReviewHistory(param: ReviewHistoryRequest): Promise<ReviewHistoryResponse>;
|
||||
}
|
||||
@@ -71,7 +71,7 @@ export interface StatisticsLog {
|
||||
menuNm?: string;
|
||||
tabNm?: string;
|
||||
tblNm?: string;
|
||||
dmndFwkNm?: string;
|
||||
statsDataChgComCd?: string;
|
||||
rgtrId?: string;
|
||||
regDt?: string;
|
||||
}
|
||||
@@ -80,6 +80,15 @@ export interface StatisticsLogRequest {
|
||||
page: number;
|
||||
size: number;
|
||||
statsCd?: string;
|
||||
startDate?: string;
|
||||
endDate?: string;
|
||||
/** 시도명 */
|
||||
ctpvNm?: string;
|
||||
/** 시군구명 */
|
||||
sggNm?: string;
|
||||
sewerBzmnCd?: string;
|
||||
/** 요청 기능(통계 데이터 변경 공통코드) */
|
||||
statsDataChgComCd?: string;
|
||||
}
|
||||
|
||||
export interface StatisticsLogResponse {
|
||||
@@ -114,13 +123,77 @@ export interface StatisticsLogDetailResponse {
|
||||
data?: StatisticsLogDetailRow[];
|
||||
}
|
||||
|
||||
export interface getReviewOpinionDetailRequest {
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
}
|
||||
|
||||
export interface ReviewOpinionDetailItem {
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
splmntOpnnCn: string | null;
|
||||
wrterOpnnCn: string | null;
|
||||
}
|
||||
|
||||
export interface getReviewOpinionDetailResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data?: ReviewOpinionDetailItem[];
|
||||
}
|
||||
|
||||
/** 검토의견 저장 API body */
|
||||
export interface ReviewOpinionSaveRequest {
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
splmntOpnnCn: string;
|
||||
wrterOpnnCn: string;
|
||||
mdfrId: string;
|
||||
}
|
||||
|
||||
export interface ReviewOpinionSaveResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface ReviewReturnSaveRequest {
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
rjctOpnnCn: string;
|
||||
mdfrId: string;
|
||||
}
|
||||
|
||||
export interface ReviewReturnSaveResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface ReviewSubmitRequest {
|
||||
statsYr: string;
|
||||
sewerBzmnCd: string;
|
||||
statsCd: string;
|
||||
mdfrId: string;
|
||||
}
|
||||
|
||||
export interface ReviewSubmitResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface StatisticsService {
|
||||
getSidoList(): Promise<ApiResponse<SearchCondition>>;
|
||||
getSggList(sidoName: string): Promise<ApiResponse<Sgg[]>>;
|
||||
getSggAllList(sidoName: string): Promise<ApiResponse<Sgg[]>>;
|
||||
getSidoList(params?: { instSeCd?: string }): Promise<ApiResponse<SearchCondition>>;
|
||||
getSggList(sidoName: string, params?: { instSeCd?: string; gugunSkipYn?: "Y" | "N" }): Promise<ApiResponse<Sgg[]>>;
|
||||
getSggAllList(sidoName: string, params?: { instSeCd?: string; gugunSkipYn?: "Y" | "N" }): Promise<ApiResponse<Sgg[]>>;
|
||||
cancelSubmit(param: cancelSubmitRequest): Promise<cancelSubmitResponse>;
|
||||
callImrService(param: ImrRequest): Promise<null>;
|
||||
submitException(param: SubmitExceptionRequest): Promise<SubmitExceptionResponse>;
|
||||
getLogList(param: StatisticsLogRequest): Promise<StatisticsLogResponse>;
|
||||
getLogDetail(param: string): Promise<StatisticsLogDetailResponse>;
|
||||
saveReviewOpinion(param: ReviewOpinionSaveRequest): Promise<ReviewOpinionSaveResponse>;
|
||||
getReviewOpinionDetail(param: getReviewOpinionDetailRequest): Promise<getReviewOpinionDetailResponse>;
|
||||
saveReviewReturn(param: ReviewReturnSaveRequest): Promise<ReviewReturnSaveResponse>;
|
||||
submitReview(param :ReviewSubmitRequest): Promise<ReviewSubmitResponse>;
|
||||
}
|
||||
+4
-3
@@ -55,10 +55,11 @@ export interface SewerageSupplyRateReviewRequestParams {
|
||||
|
||||
export interface statsStatusInfo {
|
||||
statsCd?: string;
|
||||
laeSbmsnSttsCd?: string;
|
||||
statPicSbmsnSttsCd?: string;
|
||||
submitStatusCd?: string;
|
||||
prvnStatusCd?: string;
|
||||
rjctOpnnCn?: string;
|
||||
splmntOpnnCn?: string
|
||||
splmntOpnnCn?: string;
|
||||
wrterOpnnCn?: string;
|
||||
}
|
||||
|
||||
export interface SewerageSupplyRateReviewResponseData {
|
||||
|
||||
+61
@@ -0,0 +1,61 @@
|
||||
export interface SewerageServiceLevelRequest {
|
||||
ctpvNm?: string;
|
||||
sggNm?: string;
|
||||
statsYr?: string;
|
||||
sewerBzmnCd?: string;
|
||||
rowDataYn?: boolean;
|
||||
}
|
||||
|
||||
export interface SewerageServiceLevelComplaintTotalItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string | null;
|
||||
admdstCd: string;
|
||||
admdstNm: string;
|
||||
rowType: string;
|
||||
swtrmtPpltnCnt: number;
|
||||
whol: number;
|
||||
odorCnt: number;
|
||||
swagePplCnt: number;
|
||||
dschgWqCnt: number;
|
||||
ldsCnt: number;
|
||||
plltnAcdntCnt: number;
|
||||
sewerCrgAmt: number;
|
||||
etcCnt: number;
|
||||
crtrPpltnCvlcptEvntcsCnt: number;
|
||||
rmrk: string | null;
|
||||
}
|
||||
|
||||
export interface SewerageServiceLevelComplaintResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data: SewerageServiceLevelComplaintTotalItem[];
|
||||
}
|
||||
|
||||
export interface SewerageServiceLevelResidentFriendlyFactTotalItem {
|
||||
statsYr: string;
|
||||
ctpvNm: string;
|
||||
sggNm: string;
|
||||
admdstCd: string;
|
||||
admdstNm: string;
|
||||
rowType: string;
|
||||
swtrmtFcltNm: string;
|
||||
fcltCpct: number;
|
||||
tmplntArea: number;
|
||||
whol: number;
|
||||
brkFcltArea: number;
|
||||
childFrshpSpacArea: number;
|
||||
bdySportsFcltArea: number;
|
||||
sportsFcltArea: number;
|
||||
pkltArea: number;
|
||||
eduMultiFcltArea: number;
|
||||
wtlndMakeArea: number;
|
||||
etcCnt: number;
|
||||
rmrk: string;
|
||||
}
|
||||
|
||||
export interface SewerageServiceLevelResidentFriendlyFactResponse {
|
||||
status: string;
|
||||
message?: string;
|
||||
data: SewerageServiceLevelResidentFriendlyFactTotalItem[];
|
||||
}
|
||||
@@ -23,6 +23,8 @@ export interface IUserInfo {
|
||||
addr: string;
|
||||
daddr: string;
|
||||
laeSeCd: string;
|
||||
/** 기관 구분 코드 — 통계 공통 조회 등 query `instSeCd`로 전달 */
|
||||
instSeCd?: string;
|
||||
ogdpSeCd: string;
|
||||
aprvYn: string;
|
||||
autzrId: string;
|
||||
|
||||
Reference in New Issue
Block a user