Merge branch 'iipbs/st/dev'

This commit is contained in:
2026-05-08 15:36:04 +09:00
34 changed files with 2073 additions and 553 deletions
+3
View File
@@ -32,6 +32,9 @@
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-total {
@apply !bg-[#e3eaf2];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-selected-row {
@apply !bg-[#dbeafe];
}
.rg-grid .rg-body .rg-table .rg-data-row .rg-data-cell.rg-cell-rowtype-biz-sum {
@apply !bg-[#ffe9cf];
}
@@ -1,6 +1,7 @@
import CompactDataGrid from "./CompactDataGrid";
import type { SplitDataGridProps } from "../type/DataGrid.type";
import CompactDataGrid from "./CompactDataGrid";
const SplitDataGrid = ({
leftColumnInfo,
rightColumnInfo,
@@ -11,6 +12,8 @@ const SplitDataGrid = ({
rightRowData,
setLeftRowData,
setRightRowData,
leftOnRowClick,
leftCellStyleCallback,
enableSort = false,
}: SplitDataGridProps) => {
return (
@@ -18,10 +21,12 @@ const SplitDataGrid = ({
<CompactDataGrid
columnInfo={leftColumnInfo}
fixedColumns={leftFixedColumns}
showHeaderCheckbox={showHeaderCheckbox}
showHeaderCheckbox={false}
enableSort={enableSort}
rowData={leftRowData}
setRowData={setLeftRowData}
onRowClick={leftOnRowClick}
cellStyleCallback={leftCellStyleCallback}
/>
<CompactDataGrid
columnInfo={rightColumnInfo}
@@ -1,33 +1,338 @@
import { useState } from 'react';
import { useRef, useState } from 'react';
import SearchForm from '@/components/module/SearchForm';
import DataGrid from '@/components/module/DataGrid';
import SplitDataGrid from '@/components/module/SplitDataGrid';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@common/components';
import SubmitButton from '@/components/button/SumitButton';
import { useConfirm } from '@frontend-opensource/use-react-hooks';
import { useAlertStore } from '@/shared/lib';
import { statisticsInputSewSupplyRateApi } from '@/entities/statistics/api/input/statistics-input-sew-supply-rate-service';
import { statisticsInputPublicSewageFacilityApiService } from '@/entities/statistics/api/input/statistics-public-sewage-facility-input-service';
import { statisticsApi } from '@/entities/statistics/api/statistics-service';
import type {
PublicSewageFacilityRequestParams,
PublicSewageFacilitySaveItem,
PublicSewageFacilitySaveRequest,
PublicSewageMappingTargetsRequestParams,
} from '@/entities/statistics/model/input/statistics-public-sewage-facility-input-interface';
import {
type CheckInputPeriodItem,
getVerificationPeriodActionBlockedMessage,
isInputPeriodCurrent,
} from '@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface';
import type { StatisticsInputSubmitInfoFormData } from '@/entities/statistics/model/statistics-input-submit-info-schema';
import type { cancelSubmitRequest, ImrStatusResponse, SewerageStatusInfo } from '@/entities/statistics/model/statistics-interface';
import { LoadingWithAnimation, Tabs, TabsContent, TabsList, TabsTrigger } from '@common/components';
import { getUserInfo } from '../../../../../../packages/common/components/src/api';
import SubmitDailog from './SubmitDailog';
import CancelSubmitButton from '@/components/button/CancelSubmitButton';
import NotApplicableCheckButton from '@/components/button/NotApplicableCheckButton';
import SaveButton from '@/components/button/SaveButton';
import OperateDataButton from '@/components/button/OperateDataButton';
import SubmitButton from '@/components/button/SumitButton';
import UpdateOperateDataButton from '@/components/button/UpdateOperateDataButton';
import type { ColumnInfo, ColumnInfoList } from '@/components/type/DataGrid.type';
import DataGrid from '@/components/module/DataGrid';
import SearchForm from '@/components/module/SearchForm';
import SplitDataGrid from '@/components/module/SplitDataGrid';
import type { CellStyleCallbackParams, ColumnInfo, ColumnInfoList } from '@/components/type/DataGrid.type';
import type { SearchFormValues } from '@/components/type/SearchForm.type';
import { doList } from '@/components/util/column/input/DoList';
import { PublicSewageFactInpColumnList } from '@/components/util/column/input/PublicSewageFactInpColumn/PublicSewageFactInpColumn';
import { toGridRows } from '@/components/util/grid/gridFunction';
import { useAdminStore } from '@/store/admin-store';
type TabToolbarSnapshot = {
showSaveInToolbar: boolean;
showEnterableToolbarButtons: boolean;
showActionToolbarCard: boolean;
};
function buildGugunOkForToolbar(values: SearchFormValues | null): boolean {
return doList.includes(values?.bsnmCd || '')
? Boolean(values?.gugun && values.gugun !== 'ALL')
: true;
}
const computeTabEnterable = (
statusInfo: SewerageStatusInfo | null | undefined,
periodEnterable: boolean,
): boolean => {
if (statusInfo?.laeSbmsnSttsCd) {
if (['2', '3'].includes(statusInfo.laeSbmsnSttsCd)) {
return false;
}
return periodEnterable;
}
return periodEnterable;
};
function computeTabToolbarSnapshot(
statusInfo: SewerageStatusInfo | null,
hasRowData: boolean,
treatAsSearched: boolean,
searchContext: SearchFormValues | null,
periodEnterable: boolean,
isAdminInfo: boolean,
): TabToolbarSnapshot {
const gugunOk = buildGugunOkForToolbar(searchContext);
const enterable = computeTabEnterable(statusInfo, periodEnterable);
const showSaveInToolbar = (enterable || isAdminInfo) && gugunOk;
const showEnterableToolbarButtons = enterable && gugunOk;
const showActionToolbarCard =
(hasRowData || treatAsSearched) &&
(showSaveInToolbar ||
(statusInfo?.laeSbmsnSttsCd === '2' &&
(isAdminInfo || (enterable && gugunOk))));
return { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard };
}
const sleep = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms));
async function waitForImrJobCompleted(
jobId: string,
getStatus: (id: string) => Promise<ImrStatusResponse>,
options?: {
pollIntervalMs?: number;
maxAttempts?: number;
},
): Promise<void> {
const pollIntervalMs = options?.pollIntervalMs ?? 2000;
const maxAttempts = options?.maxAttempts ?? 150;
for (let i = 0; i < maxAttempts; i++) {
const res = await getStatus(jobId);
if (res.status === 'COMPLETED' && Number(res.job_prog) === 100) return;
if (res.status === 'FAILED' || res.status === 'ERROR' || res.status === 'CANCELLED') {
throw new Error(res.error_msg || `IMR 검증이 ${res.status} 상태로 종료되었습니다.`);
}
await sleep(pollIntervalMs);
}
throw new Error('IMR 검증 완료 대기 시간이 초과되었습니다.');
}
// 하수도 통계 관리 > 06. 공공하수처리시설
const PublicSewageFacilityInp = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm();
const { isAdminInfo } = useAdminStore();
const [isLoading, setIsLoading] = useState<boolean>(false);
const [hasSearched, setHasSearched] = useState(false);
const [isNotApplicable, setIsNotApplicable] = useState(false);
const [showIsNotApplicable, setShowIsNotApplicable] = useState(false);
const [searchValues, setSearchValues] = useState<SearchFormValues | null>(null);
const [lastSearchedValues, setLastSearchedValues] = useState<SearchFormValues | null>(null);
const [tabStatusInfoMap, setTabStatusInfoMap] = useState<Map<string, SewerageStatusInfo | null>>(new Map());
const [tabStatsCdMap, setTabStatsCdMap] = useState<Map<string, string>>(new Map());
const [toolbarSnapshotByTab, setToolbarSnapshotByTab] = useState<Map<string, TabToolbarSnapshot>>(() => new Map());
const [inputPeriodEnterable, setInputPeriodEnterable] = useState(true);
const [periodCheckList, setPeriodCheckList] = useState<CheckInputPeriodItem[]>([]);
const [showDailog, setShowDailog] = useState<boolean>(false);
const [selectedMainFacilitySn, setSelectedMainFacilitySn] = useState<string | null>(null);
const periodEnterableRef = useRef(true);
const searchDoneRef = useRef(false);
const submitFunc = () => {
console.log('submit');
// 제출 로직
void (async () => {
if (activeTab !== 'ST00060001') {
alert('ST00060002 제출 API 연동 전입니다.');
return;
}
if (!lastSearchedValues || !searchParamRef.current) {
alert('먼저 조회를 진행해주세요.');
return;
}
const currentData = rowDataMap.get('ST00060001') ?? [];
if (!currentData || currentData.length === 0) {
alert('제출할 데이터가 없습니다.');
return;
}
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
if (verificationBlocked) {
alert(verificationBlocked);
return;
}
if (await confirm('제출 하시겠습니까?')) {
setShowDailog(true);
}
})();
};
const buildFacilitySaveList = (currentData: Record<string, string | number>[]): PublicSewageFacilitySaveItem[] => {
const toNumber = (v: unknown): number => {
if (v === null || v === undefined) return 0;
if (v === '') return 0;
const num = typeof v === 'string' ? parseFloat(v.replace(/,/g, '')) : (v as number);
return Number.isFinite(num) ? num : 0;
};
const toNullableString = (v: unknown): string | null => {
if (v === null || v === undefined) return null;
const s = typeof v === 'string' ? v : String(v);
return s.trim() === '' ? null : s;
};
return currentData.map((d) => ({
admdstCd: String(d.admdstCd ?? ''),
admdstNm: String(d.admdstNm ?? ''),
swtrmtFcltCd: String(d.swtrmtFcltCd ?? ''),
swtrmtFcltNm: String(d.swtrmtFcltNm ?? ''),
fcltSn: String(d.fcltSn ?? ''),
fcltPstnCn: d.fcltPstnCn === '' ? null : toNullableString(d.fcltPstnCn),
inflwSwagePhysQnt: toNumber(d.inflwSwagePhysQnt),
inflwSwageBoilQnt: toNumber(d.inflwSwageBoilQnt),
inflwSwageAltdQnt: toNumber(d.inflwSwageAltdQnt),
prcsPhysCpct: toNumber(d.prcsPhysCpct),
prcsBoilCpct: toNumber(d.prcsBoilCpct),
prcsAltdCpct: toNumber(d.prcsAltdCpct),
prxyCtrtCn: d.prxyCtrtCn === '' ? null : toNullableString(d.prxyCtrtCn),
sftyAcdntEvntcsCnt: toNumber(d.sftyAcdntEvntcsCnt),
rmrk: toNullableString(d.rmrk),
}));
};
const getNoApplicableForRequest = (): boolean => {
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const searchContextForToolbar = lastSearchedValues ?? searchValues;
const gugunOk = buildGugunOkForToolbar(searchContextForToolbar);
const enterable = computeTabEnterable(activeTabStatus, inputPeriodEnterable);
const showEnterableToolbarButtons = enterable && gugunOk;
const noApplicableVisible = showEnterableToolbarButtons && showIsNotApplicable;
return noApplicableVisible ? Boolean(isNotApplicable) : false;
};
const executeSubmit = async (submitInfo: StatisticsInputSubmitInfoFormData) => {
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
if (verificationBlocked) {
alert(verificationBlocked);
return;
}
if (activeTab !== 'ST00060001') {
alert('ST00060002 제출 API 연동 전입니다.');
return;
}
if (!lastSearchedValues || !searchParamRef.current) {
alert('먼저 조회를 진행해주세요.');
return;
}
const currentData = rowDataMap.get('ST00060001') ?? [];
if (!currentData || currentData.length === 0) {
alert('제출할 데이터가 없습니다.');
return;
}
const userInfo = await getUserInfo();
const submitList = buildFacilitySaveList(currentData);
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const statsCdForTab = tabStatsCdMap.get(activeTab) ?? activeTab;
const requestData = {
statsYr: lastSearchedValues.year ?? '',
ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '',
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
userId: userInfo.userUnqId,
noApplicable: getNoApplicableForRequest(),
laePicNm: submitInfo.laePicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
laePicTelNo: submitInfo.laePicTelNo,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
submitList,
};
try {
setIsLoading(true);
const response = await statisticsInputPublicSewageFacilityApiService.submitPublicSewageFacility(requestData);
if (response.status === 'success') {
if (response.data?.finalStatus === '5') {
const imrParam = {
user_id: userInfo.userUnqId,
sewer_bzmn_cd: lastSearchedValues.bsnmCd ?? '',
stats_cd: statsCdForTab,
stats_yr: lastSearchedValues.year ?? '',
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === '4' ? '4' : '2',
gen_verify_stts_cd: response.data?.nvpassed ? '1' : '2',
};
try {
const imrResponse = await statisticsApi.callImrService(imrParam);
if (imrResponse.job_id) {
await waitForImrJobCompleted(
imrResponse.job_id,
(id) => statisticsApi.getImrStatus(id),
);
}
alert('제출되었습니다.');
} catch {
alert('제출은 완료되었으나 IMR 검증 처리 중 오류가 발생했습니다.');
}
} else {
alert(response.message || '제출 처리 결과를 확인해주세요.');
}
await loadPublicSewageFacilityActiveTabData(
'ST00060001',
searchParamRef.current,
lastSearchedValues,
);
} else {
alert(response.message || '데이터 제출 중 오류가 발생했습니다.');
}
} finally {
setIsLoading(false);
}
};
const saveFunc = () => {
console.log('save');
// 저장 로직
};
void (async () => {
if (activeTab !== 'ST00060001') {
alert('ST00060002 저장 API 연동 전입니다.');
return;
}
const getOperateDataFunc = () => {
console.log('get operate data');
// 운영 데이터 가져오기
if (!lastSearchedValues || !searchParamRef.current) {
alert('먼저 조회를 진행해주세요.');
return;
}
const currentData = rowDataMap.get('ST00060001') ?? [];
if (!currentData || currentData.length === 0) {
alert('저장할 데이터가 없습니다.');
return;
}
if (!(await confirm("저장 하시겠습니까?"))) return;
const userInfo = await getUserInfo();
const saveList = buildFacilitySaveList(currentData);
const param: PublicSewageFacilitySaveRequest = {
statsYr: lastSearchedValues.year ?? '',
ctpvNm: lastSearchedValues.sido ?? '',
sggNm: lastSearchedValues.gugun === 'ALL' ? '' : lastSearchedValues.gugun ?? '',
sewerBzmnCd: lastSearchedValues.bsnmCd ?? '',
userId: userInfo.userUnqId,
superAdmin: isAdminInfo ? 'superAdmin' : '',
noApplicable: getNoApplicableForRequest(),
saveList,
};
try {
setIsLoading(true);
const response = await statisticsInputPublicSewageFacilityApiService.savePublicSewageFacility(param);
if (response.status === 'success') {
alert('저장되었습니다.');
await loadPublicSewageFacilityActiveTabData(
'ST00060001',
searchParamRef.current,
lastSearchedValues,
);
} else {
alert(response.message || '데이터 저장 중 오류가 발생했습니다.');
}
} finally {
setIsLoading(false);
}
})();
};
const updateOperateDataButtonFunc = () => {
@@ -39,6 +344,276 @@ const PublicSewageFacilityInp = () => {
const [activeTab, setActiveTab] = useState<string>(
PublicSewageFactInpColumnList[0]?.tabKey ?? 'ST00060001',
);
const activeTabRef = useRef<string>(PublicSewageFactInpColumnList[0]?.tabKey ?? 'ST00060001');
const firstTabKey = PublicSewageFactInpColumnList[0]?.tabKey ?? 'ST00060001';
const searchParamRef = useRef<PublicSewageFacilityRequestParams | null>(null);
const commitToolbarSnapshotForLoadedTab = (
tabKey: string,
statusInfo: SewerageStatusInfo | null,
hasRowData: boolean,
searchContext: SearchFormValues | null,
) => {
setToolbarSnapshotByTab((prev) => {
const next = new Map(prev);
next.set(
tabKey,
computeTabToolbarSnapshot(
statusInfo,
hasRowData,
searchDoneRef.current,
searchContext,
periodEnterableRef.current,
isAdminInfo,
),
);
return next;
});
};
const buildPublicSewageFacilityParams = (values: SearchFormValues): PublicSewageFacilityRequestParams => ({
ctpvNm: values.sido,
sggNm: values.gugun === 'ALL' ? '' : values.gugun,
sewerBzmnCd: values.bsnmCd,
fcltNm: values.fctName,
statsYr: values.year,
});
const tabRowDataExists = (tabKey: string): boolean => {
if (tabKey === 'ST00060002') {
const left = rowDataMap.get('MainFacility');
const right = rowDataMap.get('MainFacilityTarget');
return (left?.length ?? 0) > 0 || (right?.length ?? 0) > 0;
}
return (rowDataMap.get(tabKey)?.length ?? 0) > 0;
};
const loadPublicSewageFacilityActiveTabData = async (
tabKey: string,
param: PublicSewageFacilityRequestParams,
searchContext: SearchFormValues | null,
): Promise<boolean> => {
try {
if (tabKey === 'ST00060001') {
const response = await statisticsInputPublicSewageFacilityApiService.getPublicSewageFacility(param);
if (response.status !== 'success') {
alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
return false;
}
setSelectedMainFacilitySn(null);
const statusInfo = response.data.statusInfo ?? null;
const statsCd = response.data.commonInfo.statsCd || tabKey;
const commonInfo = response.data.commonInfo;
const list = response.data.publicSewageFacilityList ?? [];
const convertedData = list.map((item) => ({
ctpvNm: commonInfo.ctpvNm,
...item,
rmrk: item.rmrk ?? null,
}));
if (list.length === 0) {
setShowIsNotApplicable(true);
} else {
setShowIsNotApplicable(false);
}
const gridRows = toGridRows(convertedData);
setTabStatusInfoMap((prev) => {
const next = new Map(prev);
next.set(tabKey, statusInfo);
return next;
});
setTabStatsCdMap((prev) => {
const next = new Map(prev);
next.set(tabKey, statsCd);
return next;
});
setRowDataMap((prev) => {
const next = new Map(prev);
next.set('ST00060001', gridRows);
return next;
});
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, gridRows.length > 0, searchContext);
return true;
}
if (tabKey === 'ST00060002') {
const response = await statisticsInputPublicSewageFacilityApiService.getPublicSewageMainFacility(param);
if (response.status !== 'success') {
alert(response.message || '데이터 조회 중 오류가 발생했습니다.');
return false;
}
setSelectedMainFacilitySn(null);
const statusInfo = response.data.statusInfo ?? null;
const statsCd = response.data.commonInfo.statsCd || tabKey;
const list = response.data.mainFacilityList ?? [];
const leftRows = list.map((item) => ({
swtrmtFcltNm: item.swtrmtFcltNm,
fcltPstnCn: item.fcltPstnCn,
prxyBzentyNmCn: item.prxyBzentyNmCn,
cnsgnCstVl: item.cnsgnCstVl,
prxyCtrtCn: item.prxyCtrtCn,
wholNope: item.wholNope,
isMainYn: item.isMainYn,
admdstCd: item.admdstCd,
fcltSn: item.fcltSn,
swtrmtFcltCd: item.swtrmtFcltCd,
sggNm: item.sggNm ?? '',
admdstNm: item.admdstNm,
}));
const leftGridRows = toGridRows(leftRows);
setTabStatusInfoMap((prev) => {
const next = new Map(prev);
next.set(tabKey, statusInfo);
return next;
});
setTabStatsCdMap((prev) => {
const next = new Map(prev);
next.set(tabKey, statsCd);
return next;
});
setRowDataMap((prev) => {
const next = new Map(prev);
next.set('MainFacility', leftGridRows);
next.set('MainFacilityTarget', []);
return next;
});
commitToolbarSnapshotForLoadedTab(tabKey, statusInfo, leftGridRows.length > 0, searchContext);
return true;
}
return true;
} catch {
alert('데이터 조회 중 오류가 발생했습니다.');
return false;
}
};
const handleSearch = async (values: SearchFormValues) => {
if (!values.sido || values.sido === 'ALL') {
alert('시도를 선택해주세요.');
return;
}
try {
const periodResponse = await statisticsInputSewSupplyRateApi.checkInputPeriod(values.year || '');
if (periodResponse.status !== 'success') {
alert(periodResponse.message);
return;
}
const periods = Array.isArray(periodResponse.data) ? periodResponse.data : [];
setPeriodCheckList(periods);
const periodOk = isInputPeriodCurrent(periods);
periodEnterableRef.current = periodOk;
setInputPeriodEnterable(periodOk);
const yearChangedFromLastSearch =
lastSearchedValues != null &&
(lastSearchedValues.year ?? '') !== (values.year ?? '');
// 조회 시 이전 선택 로우 강조 초기화
setSelectedMainFacilitySn(null);
if (yearChangedFromLastSearch) {
setRowDataMap(new Map());
setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map());
setToolbarSnapshotByTab(new Map());
activeTabRef.current = firstTabKey;
setActiveTab(firstTabKey);
}
setLastSearchedValues(values);
searchDoneRef.current = true;
setHasSearched(true);
setIsLoading(true);
const param = buildPublicSewageFacilityParams(values);
searchParamRef.current = param;
const tabKeyToLoad = yearChangedFromLastSearch ? firstTabKey : activeTabRef.current;
await loadPublicSewageFacilityActiveTabData(tabKeyToLoad, param, values);
} catch {
alert('데이터 조회 중 오류가 발생했습니다.');
} finally {
setIsLoading(false);
}
};
const handleActiveTabChange = async (tabKey: string) => {
activeTabRef.current = tabKey;
setActiveTab(tabKey);
if (!hasSearched || tabRowDataExists(tabKey) || !searchParamRef.current) return;
try {
setIsLoading(true);
await loadPublicSewageFacilityActiveTabData(tabKey, searchParamRef.current, lastSearchedValues);
} finally {
setIsLoading(false);
}
};
const handleReset = () => {
setIsLoading(false);
setHasSearched(false);
setIsNotApplicable(false);
setShowIsNotApplicable(false);
setRowDataMap(new Map());
setTabStatusInfoMap(new Map());
setTabStatsCdMap(new Map());
setToolbarSnapshotByTab(new Map());
setInputPeriodEnterable(true);
periodEnterableRef.current = true;
setPeriodCheckList([]);
searchParamRef.current = null;
searchDoneRef.current = false;
activeTabRef.current = firstTabKey;
setActiveTab(firstTabKey);
setLastSearchedValues(null);
setSelectedMainFacilitySn(null);
};
const cnacelSubmitFunc = async () => {
try {
if (await confirm('제출된 내용을 취소하시겠습니까?')) {
const verificationBlocked = getVerificationPeriodActionBlockedMessage(periodCheckList);
if (verificationBlocked) {
alert(verificationBlocked);
return;
}
const userInfo = await getUserInfo();
const statsCdForCancel = tabStatsCdMap.get(activeTab) ?? activeTab;
const requestData: cancelSubmitRequest = {
sewerBzmnCd: lastSearchedValues?.bsnmCd ?? '',
ctpvNm: lastSearchedValues?.sido ?? '',
sggNm: lastSearchedValues?.gugun ?? '',
statsYr: lastSearchedValues?.year ?? '',
mdfrId: userInfo.userUnqId,
statsCd: statsCdForCancel,
};
const response = await statisticsApi.cancelSubmit(requestData);
if (response.status === 'success') {
if (searchParamRef.current) {
await loadPublicSewageFacilityActiveTabData(
activeTab,
searchParamRef.current,
lastSearchedValues,
);
}
alert('제출 취소되었습니다.');
} else {
alert(response.message ?? '제출 취소 중 오류가 발생했습니다.');
}
}
} catch {
alert('제출 취소 중 오류가 발생했습니다.');
}
};
const getGridColumnInfo = (
tab: ColumnInfoList,
@@ -47,25 +622,109 @@ const PublicSewageFacilityInp = () => {
columnLayout: tab.columnLayout ?? [],
});
const handleMainFacilityRowClick = async (rowData: Record<string, string | number>) => {
if (!searchParamRef.current) {
alert('먼저 조회를 진행해주세요.');
return;
}
const fcltSn = String(rowData.fcltSn ?? '');
if (!fcltSn) {
alert('시설 일련번호가 없습니다.');
return;
}
setSelectedMainFacilitySn(fcltSn);
const param: PublicSewageMappingTargetsRequestParams = {
sewerBzmnCd: searchParamRef.current.sewerBzmnCd ?? '',
ctpvNm: searchParamRef.current.ctpvNm ?? '',
sggNm: searchParamRef.current.sggNm ?? '',
statsYr: searchParamRef.current.statsYr ?? '',
fcltSn,
};
try {
setIsLoading(true);
const response = await statisticsInputPublicSewageFacilityApiService.getMappingTargets(param);
if (response.status !== 'success') {
alert(response.message || '매핑 대상 조회 중 오류가 발생했습니다.');
return;
}
const mappedTargetRows = (response.data ?? []).map((item) => ({
...item,
__checked: item.mappedYn === 'Y' ? 'Y' : '',
}));
setRowDataMap((prev) => {
const next = new Map(prev);
next.set('MainFacilityTarget', toGridRows(mappedTargetRows));
return next;
});
} catch {
alert('매핑 대상 조회 중 오류가 발생했습니다.');
} finally {
setIsLoading(false);
}
};
const leftMainFacilityCellStyleCallback = ({ row }: CellStyleCallbackParams) => {
if (!selectedMainFacilitySn) return;
return String(row.fcltSn ?? '') === selectedMainFacilitySn ? 'rg-cell-selected-row' : undefined;
};
const searchContextForToolbar = lastSearchedValues ?? searchValues;
const activeTabStatus = tabStatusInfoMap.get(activeTab) ?? null;
const activeTabToolbar =
toolbarSnapshotByTab.get(activeTab) ??
computeTabToolbarSnapshot(
activeTabStatus,
tabRowDataExists(activeTab),
hasSearched,
searchContextForToolbar,
inputPeriodEnterable,
isAdminInfo,
);
const { showSaveInToolbar, showEnterableToolbarButtons, showActionToolbarCard } = activeTabToolbar;
const isMainFacilityTab = activeTab === 'ST00060002';
return (
<>
<SearchForm fctName={true} />
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] m-[0.6rem]">
<UpdateOperateDataButton updateOperateDataButtonFunc={updateOperateDataButtonFunc} />
<OperateDataButton getOperateDataFunc={getOperateDataFunc} />
<SubmitButton submitFunc={submitFunc} />
<SaveButton saveFunc={saveFunc} />
<div className="relative">
<SearchForm
fctName={true}
onSubmit={handleSearch}
onReset={handleReset}
onValuesChange={setSearchValues}
/>
{showActionToolbarCard && (
<div className="card !py-0">
<div className="flex justify-end items-center gap-[0.6rem] mx-[0.6rem]">
{showEnterableToolbarButtons && !isMainFacilityTab && (
<>
{showIsNotApplicable && <NotApplicableCheckButton isCheck={isNotApplicable} setIsCheck={setIsNotApplicable} />}
<UpdateOperateDataButton updateOperateDataButtonFunc={updateOperateDataButtonFunc} />
<SubmitButton submitFunc={submitFunc} />
</>
)}
{!isMainFacilityTab && activeTabStatus?.laeSbmsnSttsCd === '2' && (
<CancelSubmitButton cnacelSubmitFunc={cnacelSubmitFunc} />
)}
{showSaveInToolbar && <SaveButton saveFunc={saveFunc} />}
</div>
</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" />
</div>
</div>
</div>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
{PublicSewageFactInpColumnList.map((tab) => (
<TabsTrigger key={tab.tabKey} value={tab.tabKey}>
{tab.tabLabel || tab.tabKey}
</TabsTrigger>
))}
</TabsList>
}
<Tabs value={activeTab} onValueChange={(v) => void handleActiveTabChange(v)}>
<TabsList>
{PublicSewageFactInpColumnList.map((tab) => (
<TabsTrigger key={tab.tabKey} value={tab.tabKey}>
{tab.tabLabel || tab.tabKey}
</TabsTrigger>
))}
</TabsList>
{PublicSewageFactInpColumnList.map((tab) => {
if (tab.isSplitGrid && tab.left && tab.right) {
@@ -78,10 +737,22 @@ const PublicSewageFacilityInp = () => {
rightColumnInfo={getGridColumnInfo(tab.right)}
leftFixedColumns={tab.left.fixedColumns}
rightFixedColumns={tab.right.fixedColumns}
showHeaderCheckbox={false}
showHeaderCheckbox={true}
enableSort={false}
leftRowData={rowDataMap.get(leftKey) ?? []}
rightRowData={rowDataMap.get(rightKey) ?? []}
leftOnRowClick={
tab.tabKey === 'ST00060002'
? (row) => {
void handleMainFacilityRowClick(row);
}
: undefined
}
leftCellStyleCallback={
tab.tabKey === 'ST00060002'
? leftMainFacilityCellStyleCallback
: undefined
}
setLeftRowData={(updater) => {
setRowDataMap((prev) => {
const next = new Map(prev);
@@ -118,6 +789,7 @@ const PublicSewageFacilityInp = () => {
columnInfo={gridColumnInfo}
rowStartNum={tab.rowStartNum}
fixedColumns={tab.fixedColumns}
statusInfo={activeTabStatus}
rowData={rowDataMap.get(tab.tabKey) ?? []}
setRowData={(updater) => {
setRowDataMap((prev) => {
@@ -137,7 +809,16 @@ const PublicSewageFacilityInp = () => {
</TabsContent>
);
})}
</Tabs>
</Tabs>
</div>
<SubmitDailog
open={showDailog}
onOpenChange={setShowDailog}
statusInfo={activeTabStatus}
onConfirm={executeSubmit}
submitMode="normal"
isLoading={isLoading}
/>
</>
);
};
@@ -28,13 +28,20 @@ import { doList } from "@/components/util/column/input/DoList";
import { statisticsInputSewPipeApiService } from "@/entities/statistics/api/input/statistics-sewage-pipe-input-service";
import type {
SeweragePipeInputFactInstallStatusSaveRequest,
SeweragePipeInputFactInstallStatusSubmitRequest,
SeweragePipeInputManholeStatusItem,
SeweragePipeInputManholeStatusSaveYearlyStatItem,
SeweragePipeInputManholeStatusSaveRequest,
SeweragePipeInputManholeStatusSubmitRequest,
SeweragePipeInputPipeDiameterStatusSaveRequest,
SeweragePipeInputPipeDiameterStatusSubmitRequest,
SeweragePipeInputPipeTypeStatusItem,
SeweragePipeInputPipeTypeStatusSaveRequest,
SeweragePipeInputPipeTypeStatusSubmitRequest,
SeweragePipeInputRegionalFactStatusSaveRequest,
SeweragePipeInputRegionalFactStatusSaveItem,
SeweragePipeInputRegionalFactStatusSubmitRequest,
SeweragePipeInputSubmitResponse,
SeweragePipeInputVerificationResult,
SeweragePipeRequestParams,
} from "@/entities/statistics/model/input/statistics-sewage-pipe-input-interface";
@@ -903,6 +910,22 @@ const SewagePipeInp = () => {
const userInfo = await getUserInfo();
const statsCdForTab = tabStatsCdMap.get(tabKey) ?? statsCd;
const statusForTab = tabStatusInfoMap.get(tabKey) ?? null;
const submitBase = {
statsYr: values.year ?? "",
ctpvNm: values.sido ?? "",
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
laePicNm: submitInfo.laePicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
laePicTelNo: submitInfo.laePicTelNo,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
};
let response: SeweragePipeInputSubmitResponse | null = null;
switch (tabKey) {
case REGIONAL_FACT_TAB_KEY: {
@@ -914,97 +937,294 @@ const SewagePipeInp = () => {
const submitList = buildRegionalFactSubmitListFromGrid(currentData);
const requestData = {
statsYr: values.year ?? "",
ctpvNm: values.sido ?? "",
sggNm: values.gugun === "ALL" ? "" : (values.gugun ?? ""),
sewerBzmnCd: values.bsnmCd ?? "",
userId: userInfo.userUnqId,
const requestData: SeweragePipeInputRegionalFactStatusSubmitRequest = {
...submitBase,
submitList,
laePicNm: submitInfo.laePicNm,
laeKbrdrJbgdNm: submitInfo.laeKbrdrJbgdNm,
laeKbrdrDeptNm: submitInfo.laeKbrdrDeptNm,
laePicTelNo: submitInfo.laePicTelNo,
laeIdfrNm: submitInfo.laeIdfrNm,
laeIdfrJbgdNm: submitInfo.laeIdfrJbgdNm,
laeIdfrDeptNm: submitInfo.laeIdfrDeptNm,
laeIdfrTelNo: submitInfo.laeIdfrTelNo,
};
setGlobalProgressLabel("일반 검증 진행 중...");
const response =
response =
await statisticsInputSewPipeApiService.submitRegionalFactStatus(requestData);
if (response.status !== "success") {
alert(response.message || "데이터 제출 중 오류가 발생했습니다.");
break;
}
case "ST00020002": {
const currentData = getFactInstallGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert("제출할 데이터가 없습니다.");
return;
}
const finalStatus = response.data?.finalStatus;
const nvpassed = response.data?.nvpassed;
if (finalStatus === "5") {
setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중...");
const imrParam = {
user_id: userInfo.userUnqId,
sewer_bzmn_cd: values.bsnmCd ?? "",
stats_cd: statsCdForTab,
stats_yr: values.year ?? "",
lae_sbmsn_stts_cd: statusForTab?.laeSbmsnSttsCd === "4" ? "4" : "2",
gen_verify_stts_cd: nvpassed ? "1" : "2",
};
try {
const imrResponse = await statisticsApi.callImrService(imrParam);
if (imrResponse.job_id) {
await waitForImrJobCompleted(
imrResponse.job_id,
(id) => statisticsApi.getImrStatus(id),
{
onProgress: (jobProgress) => {
setGlobalProgress(50 + Math.round(jobProgress / 2));
},
},
);
}
setGlobalProgress(100);
await loadSewagePipeActiveTabData(
tabKey,
buildSewagePipeSearchParam(values),
values,
true,
false,
);
setVerificationResultDialogOpen(true);
} catch (e) {
await loadSewagePipeActiveTabData(
tabKey,
buildSewagePipeSearchParam(values),
values,
true,
false,
);
const msg =
e instanceof Error && e.message
? e.message
: "IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.";
alert(msg);
}
} else {
setGlobalProgress(100);
await loadSewagePipeActiveTabData(
tabKey,
buildSewagePipeSearchParam(values),
values,
true,
false,
);
alert("제출되었습니다.");
const columnFormatMap = new Map<string, string>();
FactInstallStatusInpColumn.columns.forEach((col) => {
if (col.fieldName && col.dataFormat) columnFormatMap.set(col.fieldName, col.dataFormat);
});
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === "") return 0;
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const submitList = currentData.map((d) => ({
admdstCd: String(d.admdstCd ?? ""),
sggNm: String(d.sggNm ?? ""),
admdstNm: String(d.admdstNm ?? ""),
fcltSn: String(d.fcltSn ?? ""),
swtrmtFcltCd: String(d.swtrmtFcltCd ?? ""),
swtrmtFcltNm: String(d.swtrmtFcltNm ?? ""),
prcsFcltCpct: formatNumber(d.prcsFcltCpct, "prcsFcltCpct"),
prcsZoneArea: formatNumber(d.prcsZoneArea, "prcsZoneArea"),
cbsysPrcsZoneArea: formatNumber(d.cbsysPrcsZoneArea, "cbsysPrcsZoneArea"),
sprsysPrcsZoneArea: formatNumber(d.sprsysPrcsZoneArea, "sprsysPrcsZoneArea"),
sprsysVartnRt: formatNumber(d.sprsysVartnRt, "sprsysVartnRt"),
mltvEqpmCnt: formatNumber(d.mltvEqpmCnt, "mltvEqpmCnt"),
cbsysMltvEqpmCnt: formatNumber(d.cbsysMltvEqpmCnt, "cbsysMltvEqpmCnt"),
sprsysMltvEqpmCnt: formatNumber(d.sprsysMltvEqpmCnt, "sprsysMltvEqpmCnt"),
sprsysMltvLnkgCnt: formatNumber(d.sprsysMltvLnkgCnt, "sprsysMltvLnkgCnt"),
sprsysMltvYetLnkgCnt: formatNumber(d.sprsysMltvYetLnkgCnt, "sprsysMltvYetLnkgCnt"),
wholFcltPrlg: formatNumber(d.wholFcltPrlg, "wholFcltPrlg"),
wholIcptgAtlfcsPrlg: formatNumber(d.wholIcptgAtlfcsPrlg, "wholIcptgAtlfcsPrlg"),
icptgPrlg: formatNumber(d.icptgPrlg, "icptgPrlg"),
atlfcsPrlg: formatNumber(d.atlfcsPrlg, "atlfcsPrlg"),
cbsysStofcbrCnt: formatNumber(d.cbsysStofcbrCnt, "cbsysStofcbrCnt"),
sprsysStofcbrCnt: formatNumber(d.sprsysStofcbrCnt, "sprsysStofcbrCnt"),
sprsysExlnOtflCnt: formatNumber(d.sprsysExlnOtflCnt, "sprsysExlnOtflCnt"),
wholBrchlnPrlg: formatNumber(d.wholBrchlnPrlg, "wholBrchlnPrlg"),
cbsysBrchlnPrlg: formatNumber(d.cbsysBrchlnPrlg, "cbsysBrchlnPrlg"),
exlnBrchlnPrlg: formatNumber(d.exlnBrchlnPrlg, "exlnBrchlnPrlg"),
drtwBrchlnPrlg: formatNumber(d.drtwBrchlnPrlg, "drtwBrchlnPrlg"),
rmrk: String(d.rmrk ?? ""),
}));
const requestData: SeweragePipeInputFactInstallStatusSubmitRequest = {
...submitBase,
submitList,
};
setGlobalProgressLabel("일반 검증 진행 중...");
response = await statisticsInputSewPipeApiService.submitFactInstallStatus(requestData);
break;
}
case "ST00020003": {
const currentData = getPipeTypeGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert("제출할 데이터가 없습니다.");
return;
}
const columnFormatMap = new Map<string, string>();
PipeTypeStatusInpColumn.columns.forEach((col) => {
if (col.fieldName && col.dataFormat) columnFormatMap.set(col.fieldName, col.dataFormat);
});
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === "") return 0;
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const lastDataYear = getPipeTypeLastDataYear();
const submitList = currentData.map((d) => {
const yearlyStatList = [];
for (let year = 1995; year <= lastDataYear; year++) {
const wholKey = `y${year}PrlgWhol`;
const lyngKey = `y${year}LyngPrlg`;
const dmltnKey = `y${year}DmltnPrlg`;
yearlyStatList.push({
lyngYr: String(year),
lyngPrlg: formatNumber(d[lyngKey] as string | number | undefined, lyngKey),
dmltnPrlg: formatNumber(d[dmltnKey] as string | number | undefined, dmltnKey),
prlgWhol: formatNumber(d[wholKey] as string | number | undefined, wholKey),
});
}
return {
admdstCd: String(d.admdstCd ?? ""),
admdstNm: String(d.admdstNm ?? ""),
pipeSeCd: String(d.pipeSeCd ?? ""),
yearlyStatList,
rmrk: String(d.rmrk ?? ""),
lyngPrlgWhol: formatNumber(d.lyngPrlg, "lyngPrlg"),
dmltnPrlgWhol: formatNumber(d.dmltnPrlg, "dmltnPrlg"),
pipeByPrstWhol: formatNumber(d.pipeByPrstWhol, "pipeByPrstWhol"),
};
});
const requestData: SeweragePipeInputPipeTypeStatusSubmitRequest = {
...submitBase,
submitList,
};
setGlobalProgressLabel("일반 검증 진행 중...");
response = await statisticsInputSewPipeApiService.submitPipeTypeStatus(requestData);
break;
}
case "ST00020004": {
const currentData = getPipeDiameterGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert("제출할 데이터가 없습니다.");
return;
}
const columnFormatMap = new Map<string, string>();
PipeDiameterStatusInpColumn.columns.forEach((col) => {
if (col.fieldName && col.dataFormat) columnFormatMap.set(col.fieldName, col.dataFormat);
});
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === "") return 0;
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const submitList = currentData.map((d) => ({
pipeSeCd: String(d.pipeSeCd ?? ""),
step1PdmtPrlg: formatNumber(d.step1PdmtPrlg, "step1PdmtPrlg"),
step2PdmtPrlg: formatNumber(d.step2PdmtPrlg, "step2PdmtPrlg"),
step3PdmtPrlg: formatNumber(d.step3PdmtPrlg, "step3PdmtPrlg"),
step4PdmtPrlg: formatNumber(d.step4PdmtPrlg, "step4PdmtPrlg"),
step5PdmtPrlg: formatNumber(d.step5PdmtPrlg, "step5PdmtPrlg"),
sqrePdmtPrlg: formatNumber(d.sqrePdmtPrlg, "sqrePdmtPrlg"),
etcPdmtPrlg: formatNumber(d.etcPdmtPrlg, "etcPdmtPrlg"),
rmrk: String(d.rmrk ?? ""),
pdmtByTsmWhol: formatNumber(d.pdmtByTsmWhol, "pdmtByTsmWhol"),
admdstCd: String(d.admdstCd ?? ""),
admdstNm: String(d.admdstNm ?? ""),
}));
const requestData: SeweragePipeInputPipeDiameterStatusSubmitRequest = {
...submitBase,
submitList,
};
setGlobalProgressLabel("일반 검증 진행 중...");
response = await statisticsInputSewPipeApiService.submitPipeDiameterStatus(requestData);
break;
}
case "ST00020005": {
const currentData = getManholeGridDataRef.current?.();
if (!currentData || currentData.length === 0) {
alert("제출할 데이터가 없습니다.");
return;
}
const columnFormatMap = new Map<string, string>();
ManholeStatusInpColumn.columns.forEach((col) => {
if (col.fieldName && col.dataFormat) columnFormatMap.set(col.fieldName, col.dataFormat);
});
const formatNumber = (value: string | number | null | undefined, fieldName: string): number => {
if (value === null || value === undefined || value === "") return 0;
const num = typeof value === "string" ? parseFloat(value.replace(/,/g, "")) : value;
if (isNaN(num)) return 0;
const format = columnFormatMap.get(fieldName);
if (!format) return num;
const decimalPlaces = format.match(/\.(\d+)/)?.[1]?.length ?? 0;
return decimalPlaces > 0 ? parseFloat(num.toFixed(decimalPlaces)) : Math.round(num);
};
const minYy = 14;
const maxYy = getManholeLastDataYear() % 100;
const submitList = currentData.map((d) => {
const yearlyStats: SeweragePipeInputManholeStatusSaveYearlyStatItem[] = [];
for (let yy = minYy; yy <= maxYy; yy++) {
const year = 2000 + yy;
const wholKey = `yy${yy}WholMnhlCnt`;
const step1Key = `yy${yy}Step1MnhlCnt`;
const step2Key = `yy${yy}Step2MnhlCnt`;
const step3Key = `yy${yy}Step3MnhlCnt`;
const step4Key = `yy${yy}Step4MnhlCnt`;
const step5Key = `yy${yy}Step5MnhlCnt`;
yearlyStats.push({
manhYr: String(year),
wholMnhlCnt: formatNumber(d[wholKey] as string | number | undefined, wholKey),
step1MnhlCnt: formatNumber(d[step1Key] as string | number | undefined, step1Key),
step2MnhlCnt: formatNumber(d[step2Key] as string | number | undefined, step2Key),
step3MnhlCnt: formatNumber(d[step3Key] as string | number | undefined, step3Key),
step4MnhlCnt: formatNumber(d[step4Key] as string | number | undefined, step4Key),
step5MnhlCnt: formatNumber(d[step5Key] as string | number | undefined, step5Key),
});
}
return {
emphsMngSeCd: String(d.emphsMngSeCd ?? ""),
admdstCd: String(d.admdstCd ?? ""),
admdstNm: String(d.admdstNm ?? ""),
yearlyStats,
rmrk: String(d.rmrk ?? ""),
};
});
const requestData: SeweragePipeInputManholeStatusSubmitRequest = {
...submitBase,
submitList,
};
setGlobalProgressLabel("일반 검증 진행 중...");
response = await statisticsInputSewPipeApiService.submitManholeStatus(requestData);
break;
}
default:
alert("해당 탭 제출 API는 준비 중입니다.");
return;
}
if (!response || response.status !== "success") {
alert(response?.message || "데이터 제출 중 오류가 발생했습니다.");
return;
}
const finalStatus = response.data?.finalStatus;
const nvpassed = response.data?.nvpassed;
if (finalStatus === "5") {
setGlobalProgress(50);
setGlobalProgressLabel("IMR 검증 진행 중...");
const imrParam = {
user_id: userInfo.userUnqId,
sewer_bzmn_cd: values.bsnmCd ?? "",
stats_cd: statsCdForTab,
stats_yr: values.year ?? "",
lae_sbmsn_stts_cd: statusForTab?.laeSbmsnSttsCd === "4" ? "4" : "2",
gen_verify_stts_cd: nvpassed ? "1" : "2",
};
try {
const imrResponse = await statisticsApi.callImrService(imrParam);
if (imrResponse.job_id) {
await waitForImrJobCompleted(
imrResponse.job_id,
(id) => statisticsApi.getImrStatus(id),
{
onProgress: (jobProgress) => {
setGlobalProgress(50 + Math.round(jobProgress / 2));
},
},
);
}
setGlobalProgress(100);
await loadSewagePipeActiveTabData(
tabKey,
buildSewagePipeSearchParam(values),
values,
true,
false,
);
setVerificationResultDialogOpen(true);
} catch (e) {
await loadSewagePipeActiveTabData(
tabKey,
buildSewagePipeSearchParam(values),
values,
true,
false,
);
const msg =
e instanceof Error && e.message
? e.message
: "IMR 검증 중 오류가 발생했습니다.\n관리자에게 문의하세요.";
alert(msg);
}
} else {
setGlobalProgress(100);
await loadSewagePipeActiveTabData(
tabKey,
buildSewagePipeSearchParam(values),
values,
true,
false,
);
alert("제출되었습니다.");
}
} catch {
alert("데이터 제출 중 오류가 발생했습니다.");
@@ -1537,7 +1757,7 @@ const SewagePipeInp = () => {
/>
{showEnterableToolbarButtons && (
<>
<LastYearDataButton getLastYearDataFunc={() => void getLastYearDataFunc()} />
{activeTab !== 'ST00020002' && <LastYearDataButton getLastYearDataFunc={() => void getLastYearDataFunc()} />}
<SubmitButton submitFunc={() => void submitFunc(false)} />
</>
)}
@@ -295,9 +295,9 @@ const SeweragePenetrationRateInp = () => {
statsCd: statsCd,
};
// if (isAdminInfo) {
// params.superAdmin = 'superAdmin';
// }
if (isAdminInfo) {
params.superAdmin = 'superAdmin';
}
try {
setIsLoading(true);
@@ -3,9 +3,7 @@
import { useMemo, useState } from "react";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useQuery } from "@tanstack/react-query";
import {
type ColumnDef,
getCoreRowModel,
type RowSelectionState,
useReactTable,
@@ -14,17 +12,21 @@ import {
import { client } from "@/shared/api/client";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore } from "@/shared/lib";
import type { PageableList } from "@/shared/model";
import {
type AdstZoneResponseDto,
type BusinessSendRequest,
type BusinessSendResponse,
type SelectedRecipient,
type SewerStUserItem,
useNtftalkTemplateList,
useNtftalkTemplateVariables,
userListColumns,
useSewerStUserList,
useStAnlsSidoCombo,
useStAnlsSigunguCombo,
} from "@/entities/statistics";
import {
Button,
Checkbox,
DataTable,
Dialog,
DialogClose,
@@ -49,35 +51,6 @@ import {
TableRow,
} from "@common/components";
interface NtftalkTemplate {
templateCode: string;
templateName: string;
templateContent: string;
}
interface TemplateVariablesResponse {
templateCode: string;
variables: string[];
count: number;
}
interface BusinessSendRequest {
templateCode: string;
callback?: string;
fixedRecipientNum?: string;
recipients: Record<string, string>[];
}
interface BusinessSendResponse {
success: boolean;
sentCount: number;
message: string;
}
interface SelectedRecipient extends SewerStUserItem {
variableOverrides: Record<string, string>;
}
type CommonVariableKey =
| "userNm"
| "sido"
@@ -94,14 +67,6 @@ const GUBUN_OPTIONS = [
{ value: "IDFR", label: "승인자" },
{ value: "MCTPV", label: "검토자" },
] as const;
const DEFAULT_TEMPLATE_PARAMS = {
sort: "createdAt,desc",
senderKey: "",
inspectionStatus: "APR",
templateMessageType: "BA",
page: 0,
size: 10,
};
function getDefaultStatsYear(): string {
return String(new Date().getFullYear() - 1);
@@ -161,38 +126,11 @@ function NtitlkSndng() {
const [userListRowSelection, setUserListRowSelection] =
useState<RowSelectionState>({});
const { data: templatePageData } = useQuery({
queryKey: [
ENDPOINT.NTFTALK_SERVICE.TEMP,
"ntitlk-sndng",
DEFAULT_TEMPLATE_PARAMS,
],
queryFn: async () => {
const response = await client.get<PageableList<NtftalkTemplate>>(
ENDPOINT.NTFTALK_SERVICE.TEMP,
{ params: DEFAULT_TEMPLATE_PARAMS },
);
return response.data;
},
});
const { data: templatePageData } = useNtftalkTemplateList();
const templates = templatePageData?.content ?? [];
const { data: templateVariablesData } = useQuery({
queryKey: [
ENDPOINT.NTFTALK_SERVICE.ATA_SEND,
"template-variables",
selectedTemplateCode,
],
queryFn: async () => {
const response = await client.get<TemplateVariablesResponse>(
`${ENDPOINT.NTFTALK_SERVICE.ATA_SEND}/template-variables/${selectedTemplateCode}`,
);
return response.data;
},
enabled: Boolean(selectedTemplateCode),
});
const { data: templateVariablesData } =
useNtftalkTemplateVariables(selectedTemplateCode);
const variableNames = templateVariablesData?.variables ?? [];
const { data: sidoData } = useStAnlsSidoCombo();
@@ -203,80 +141,6 @@ function NtitlkSndng() {
const { data: userListResponse, isLoading: isUserListLoading } =
useSewerStUserList(appliedParams);
const userList = (userListResponse as unknown as SewerStUserItem[]) ?? [];
const userListColumns = useMemo<ColumnDef<SewerStUserItem>[]>(
() => [
{
id: "select",
header: ({ table }) => (
<div
className="flex justify-center"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
role="presentation"
>
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() &&
"indeterminate")
}
onCheckedChange={(value) =>
table.toggleAllPageRowsSelected(!!value)
}
aria-label="전체 선택"
/>
</div>
),
cell: ({ row }) => (
<div
className="flex justify-center"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
role="presentation"
>
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) =>
row.toggleSelected(!!value)
}
aria-label="행 선택"
/>
</div>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "userNm",
header: "이름",
},
{
accessorKey: "sido",
header: "시도",
},
{
accessorKey: "gugun",
header: "시군구",
},
{
accessorKey: "gubunNm",
header: "구분",
},
{
accessorKey: "deptNm",
header: "소속",
},
{
accessorKey: "jbgdNm",
header: "직급",
},
{
accessorKey: "telNo",
header: "전화번호",
},
],
[],
);
const userListTable = useReactTable({
data: userList,
columns: userListColumns,
@@ -11,16 +11,21 @@ import type { SearchFormValues } from "@/components/type/SearchForm.type";
import { SewagePipeColumnInfoListTot } from "@/components/util/column/total/SewagePipeTotColumn";
import { toGridRows } from "@/components/util/grid/gridFunction";
import type { SewagePipeTotalRequest } from "@/entities/statistics/model/statistics-total/statistics-sewage-pipe-total-interface";
import CellColorInfo from "./CellColorInfo";
const firstTabKey = SewagePipeColumnInfoListTot[0]?.tabKey ?? "ST00020001";
function buildSewagePipeTotSearchParam(values: SearchFormValues): SewagePipeTotalRequest {
const isSummaryEnabled = values.isSummary !== 'ALL';
const summaryYearCnt = isSummaryEnabled ? Number(values.isSummary) : undefined;
return {
ctpvNm: values.sido === "ALL" ? "" : values.sido,
sggNm: values.gugun === "ALL" ? "" : values.gugun,
sewerBzmnCd: values.bsnmCd,
statsYr: values.year,
rowDataYn: values.isDetail,
...(isSummaryEnabled ? { summaryYearCnt } : {}),
};
}
@@ -164,7 +169,7 @@ const SewagePipeTot = () => {
return (
<div className="relative">
<SearchForm isDetail={true} onSubmit={handleSearch} onReset={handleReset} sidoSelectText="전체" />
<SearchForm isDetail={true} isSummary={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]">
@@ -172,6 +177,7 @@ const SewagePipeTot = () => {
</div>
</div>
)}
<CellColorInfo />
<div className="flex flex-col gap-[0.6rem] mx-[0.6rem]">
<Tabs
value={activeTab}
@@ -221,10 +227,11 @@ const SewagePipeTot = () => {
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";
const rowType = String(row.rowType ?? '');
if (rowType === 'DETAIL') return undefined;
if (rowType === 'BIZ_SUM') return 'rg-cell-rowtype-biz-sum';
if (rowType === 'SUMMARY') return 'rg-cell-rowtype-summary';
return 'rg-cell-rowtype-other';
}}
/>
</TabsContent>
@@ -20,18 +20,18 @@ import { ENDPOINT } from "@/shared/config";
import { useAlertStore } from "@/shared/lib";
import type { ApiErrorResponse } from "@/shared/model";
import { CodeSelect } from "@/entities/code";
import type {
AnalysisRequestPayload,
AnalysisResponseDto,
StAnlsMstResponse,
StAnlsMstSearchParams,
StAnlsOutlierListResponse,
import {
analysisApi,
type AnalysisRequestPayload,
type AnalysisResponseDto,
type StAnlsMstResponse,
type StAnlsMstSearchParams,
type StAnlsOutlierListResponse,
statisticsAnalysisMasterService,
verificationHistoryColumns,
type VerificationTargetCodeResponse,
verificationTargetManagementApi,
} from "@/entities/statistics";
import { verificationHistoryColumns } from "@/entities/statistics";
import { analysisApi } from "@/entities/statistics/api/analysis-api";
import { stAnlsMstApi } from "@/entities/statistics/api/statistics-analysis-master-api";
import { verificationTargetManagementApi } from "@/entities/statistics/api/verification-target-management-service";
import type { VerificationTargetCodeResponse } from "@/entities/statistics/model/verification-target-management-interface";
import {
Button,
Checkbox,
@@ -102,7 +102,7 @@ function toNumber(value: string): number {
return Number.isNaN(parsed) ? 0 : parsed;
}
const HbStatisticsVerification = () => {
export default function HbStatisticsVerification() {
const { setMessage: alert } = useAlertStore((state) => state);
const queryClient = useQueryClient();
@@ -207,7 +207,9 @@ const HbStatisticsVerification = () => {
params.verifyDtEnd = dayjs(to).format("YYYY-MM-DD");
}
return stAnlsMstApi.getAnalysisHistoryList(params);
return statisticsAnalysisMasterService.getAnalysisHistoryList(
params,
);
},
refetchInterval: (query) => {
const data = query.state.data;
@@ -248,9 +250,10 @@ const HbStatisticsVerification = () => {
],
queryFn: async () => {
if (!outlierDialog.anlsSn) return [];
const response = await stAnlsMstApi.getOutlierList(
outlierDialog.anlsSn,
);
const response =
await statisticsAnalysisMasterService.getOutlierList(
outlierDialog.anlsSn,
);
return response;
},
enabled: outlierDialog.open && !!outlierDialog.anlsSn,
@@ -771,6 +774,4 @@ const HbStatisticsVerification = () => {
</Dialog>
</>
);
};
export default HbStatisticsVerification;
}
@@ -24,25 +24,21 @@ import dayjs from "dayjs";
import { ENDPOINT } from "@/shared/config";
import { CodeSelect } from "@/entities/code";
import type {
AdstZoneResponseDto,
StAnlsMstResponse,
StAnlsMstSearchParams,
StAnlsRsltResponse,
} from "@/entities/statistics";
import {
type AdstZoneResponseDto,
IMR_DETAIL_VISIBLE_COLUMNS_BY_STATS_CD,
IMR_DETAIL_VISIBLE_COLUMNS_DEFAULT,
imrApi,
imrDetailGridColumnInfo,
imrVerificationHistoryColumns,
} from "@/entities/statistics";
import { imrApi } from "@/entities/statistics/api/imr-api";
import {
type StAnlsMstResponse,
type StAnlsMstSearchParams,
stAnlsRsltApi,
type StAnlsRsltResponse,
statisticsAnalysisMasterService,
useStAnlsSidoCombo,
useStAnlsSigunguCombo,
} from "@/entities/statistics/api/queries";
import { stAnlsMstApi } from "@/entities/statistics/api/statistics-analysis-master-api";
import { stAnlsRsltApi } from "@/entities/statistics/api/statistics-analysis-result-api";
} from "@/entities/statistics";
import {
Button,
DataTable,
@@ -144,7 +140,7 @@ const ImrVerificationHistory = () => {
historyPagination,
],
queryFn: () =>
stAnlsMstApi.getImrAnalysisMasterList({
statisticsAnalysisMasterService.getImrAnalysisMasterList({
...appliedSearchParams,
page: historyPagination.pageIndex,
size: historyPagination.pageSize,
@@ -1,6 +1,18 @@
import React, { useState } from "react";
import type { StAnlsDtlResponse } from "@/entities/statistics";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useQueryClient } from "@tanstack/react-query";
import type { AxiosError } from "axios";
import { ENDPOINT } from "@/shared/config";
import { useAlertStore, useMutationWrapper } from "@/shared/lib";
import type { ApiErrorResponse } from "@/shared/model";
import type {
StAnlsDtlConfirmRequest,
StAnlsDtlConfirmResponse,
StAnlsDtlResponse,
} from "@/entities/statistics";
import { statisticsAnalysisDetailService } from "@/entities/statistics";
import { Button } from "@common/components";
import Indicators from "./Indicators";
@@ -38,6 +50,47 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
vrfcTrgtNm: string;
} | null>(null);
const queryClient = useQueryClient();
const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm();
const confirmMutation = useMutationWrapper<
StAnlsDtlConfirmResponse,
AxiosError<ApiErrorResponse>,
StAnlsDtlConfirmRequest
>({
mutationFn: (payload) =>
statisticsAnalysisDetailService.confirmDetail(payload),
onSuccess: async () => {
setCheckedSns([]);
await Promise.all([
queryClient.invalidateQueries({
queryKey: [
ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL,
"list",
anlsSn,
],
}),
queryClient.invalidateQueries({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST],
}),
]);
},
});
const handleConfirmClick = async () => {
if (checkedSns.length === 0) return;
if (!(await confirm("검증확정 처리하시겠습니까?"))) return;
const vrfcTrgtSnList = checkedSns
.map((value) => Number(value))
.filter((value) => !Number.isNaN(value));
confirmMutation.mutate({ anlsSn, vrfcTrgtSnList });
};
const handleTargetSelect = (row: StAnlsDtlResponse) => {
setSelectedVrfcTrgtSn(String(row.vrfcTrgtSn));
setSelectedTargetInfo({
@@ -70,7 +123,14 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
검증 상세
</h2>
</div>
<Button disabled={checkedSns.length === 0}>검증확정</Button>
<Button
disabled={
checkedSns.length === 0 || confirmMutation.isPending
}
onClick={handleConfirmClick}
>
검증확정
</Button>
</div>
<div className="flex flex-col gap-4">
@@ -6,7 +6,7 @@ import { getCoreRowModel, useReactTable } from "@tanstack/react-table";
import { ENDPOINT } from "@/shared/config";
import type { StAnlsDtlResponse } from "@/entities/statistics";
import { getVerificationListColumns } from "@/entities/statistics";
import { stAnlsDtlApi } from "@/entities/statistics/api/statistics-analysis-detail-api";
import { statisticsAnalysisDetailService } from "@/entities/statistics/api/statistics-analysis-detail-service";
import { DataTable } from "@common/components";
interface VerificationListProps {
@@ -27,7 +27,11 @@ const VerificationList: React.FC<VerificationListProps> = ({
const { data: detailData, isLoading } = useQuery({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL, "list", anlsSn],
queryFn: () =>
stAnlsDtlApi.getDetailList({ anlsSn, page: 0, size: 100 }),
statisticsAnalysisDetailService.getDetailList({
anlsSn,
page: 0,
size: 100,
}),
enabled: !!anlsSn,
});
@@ -1,6 +1,11 @@
import { useEffect, useRef, useState } from 'react';
import { useConfirm } from '@frontend-opensource/use-react-hooks';
import { useAlertStore } from '@/shared/lib';
import { NoData } from '@/shared/ui/no-data';
import { generalverificationApiService } from '@/entities/statistics/api/general-verification-service';
import type { VerifyDetailData, VerifyDetailResponseDto } from '@/entities/statistics/model/general-verification-interface';
import {
Button,
Table,
@@ -9,17 +14,13 @@ import {
TableHead,
TableRow,
} from "@common/components";
import FormulaComponents from './FormulaComponents';
import { LoadingWithAnimation } from '@common/components';
import FomulaDialog from './FomulaDialog';
import { useAlertStore } from '@/shared/lib';
import { useConfirm } from '@frontend-opensource/use-react-hooks';
import { generalverificationApiService } from '@/entities/statistics/api/general-verification-service';
import type { VerifyDetailData, VerifyDetailResponseDto } from '@/entities/statistics/model/general-verification-interface';
import { getUserInfo } from '../../../../../../../packages/common/components/src/api';
import FomulaDialog from './FomulaDialog';
import FormulaComponents from './FormulaComponents';
interface DetailComponentProps {
selectedVrfcRow: number | null;
}
@@ -107,19 +108,16 @@ const DetailComponent = ({ selectedVrfcRow }: DetailComponentProps) => {
}
}
// 여러 항목일 때 마지막이 단항 연산자면: 이전 항목들 연산자는 무조건 null
// 여러 항목일 때 마지막이 단항 연산자면:
// 이전 항목들의 연산자는 '?'일 때만 등록 허용 (자동 null 보정하지 않음)
if (rows.length > 1 && lastIsUnary) {
for (let i = 0; i < rows.length - 1; i++) {
const prev = rows[i];
if (prev) prev.cmprOpert = null;
}
setFormulaList((prev) => {
if (prev.length <= 1) return prev;
const next = prev.map((item, idx) =>
idx < prev.length - 1 && item != null ? { ...item, cmprOpert: null } : item,
const invalidUnaryPrevIdx = rows.slice(0, -1).findIndex((r) => r.cmprOpert !== '?');
if (invalidUnaryPrevIdx !== -1) {
alert(
`마지막 항목이 단항 연산자(is_null, !is_null, floor)일 때는 이전 항목의 연산자를 '?'로 선택해야 합니다.\n(순번: ${invalidUnaryPrevIdx + 1})`,
);
return next;
});
return;
}
}
// 마지막 항목의 연산자는 (단항 연산자 케이스 제외) 무조건 null 처리
@@ -365,6 +365,10 @@ export type SplitDataGridProps = {
setRightRowData: React.Dispatch<
React.SetStateAction<Record<string, string | number>[]>
>;
/** 좌측 분할 그리드 행 클릭 이벤트 */
leftOnRowClick?: (rowData: Record<string, string | number>) => void;
/** 좌측 분할 그리드 셀 스타일 콜백 */
leftCellStyleCallback?: CellStyleCallback;
/** 전체 정렬 기능 on/off (기본값: false) */
enableSort?: boolean;
};
@@ -7,6 +7,7 @@ export const MainFacilityColumn = {
{ name: 'prxyBzentyNmCn', fieldName: 'prxyBzentyNmCn', header: { text: '위탁업체명' }, editable: false },
{ name: 'cnsgnCstVl', fieldName: 'cnsgnCstVl', header: { text: '위탁비용(백만원/년)' }, editable: false },
{ name: 'prxyCtrtCn', fieldName: 'prxyCtrtCn', header: { text: '위탁계약기간' }, editable: false },
{ name: 'wholNope', fieldName: 'wholNope', header: { text: '직원수(명)' }, editable: false },
{ name: 'isMainYn', fieldName: 'isMainYn', header: { text: '중심처리시설여부' }, editable: false },
{ name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, editable: false, styleName: 'rg-cell-readonly' },
@@ -21,6 +22,7 @@ export const MainFacilityColumn = {
'prxyBzentyNmCn',
'cnsgnCstVl',
'prxyCtrtCn',
'wholNope',
'isMainYn',
'admdstCd',
'fcltSn',
@@ -1,6 +1,7 @@
import { footerInt, footerDecimal1, footerDecimal3 } from "@/components/util/grid/footerSummary";
import { ValueType } from "realgrid";
import { footerDecimal1, footerDecimal3,footerInt } from "@/components/util/grid/footerSummary";
/**
* 하수도 통계 관리 > 06. 공공하수처리시설
*/
@@ -110,8 +111,9 @@ export const PublicSewageFactColumn = {
{ name: 'intgPrcsFcltCn', fieldName: 'intgPrcsFcltCn', header: { text: '중심처리시설' }, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'wholNope', fieldName: 'wholNope', header: { text: '직원총수(명)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
{ name: 'sftyAcdntEvntcsCnt', fieldName: 'sftyAcdntEvntcsCnt', header: { text: '안전사고건수' }, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
{ name: 'rmk', fieldName: '비고', header: { text: '비고' } },
{ name: 'rmrk', fieldName: 'rmrk', header: { text: '비고' } },
{ name: 'admdstCd', fieldName: 'admdstCd', header: { text: '행정구역코드' }, visible: false, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, visible: false, editable: false, styleName: 'rg-cell-readonly' },
{ name: 'fcltSn', fieldName: 'fcltSn', header: { text: '처리시설일련번호' }, visible: false, editable: false, valueType: ValueType.TEXT, styleName: 'rg-cell-readonly' },
{ name: 'swtrmtFcltCd', fieldName: 'swtrmtFcltCd', header: { text: '하수처리시설코드' }, visible: false, editable: false, styleName: 'rg-cell-readonly' },
],
@@ -208,7 +210,7 @@ export const PublicSewageFactColumn = {
'intgPrcsFcltCn',
'wholNope',
'sftyAcdntEvntcsCnt',
'rmk',
'rmrk',
'admdstCd',
'fcltSn',
'swtrmtFcltCd',
@@ -0,0 +1,65 @@
import { stClient } from "@/shared/api/client";
import { ENDPOINT } from "@/shared/config";
import type {
PublicSewageFacilityRequestParams,
PublicSewageFacilityResponse,
PublicSewageFacilitySaveRequest,
PublicSewageFacilitySaveResponse,
PublicSewageFacilitySubmitRequest,
PublicSewageFacilitySubmitResponse,
PublicSewageMainFacilityResponse,
PublicSewageMappingTargetsRequestParams,
PublicSewageMappingTargetsResponse,
StatisticsInputPublicSewageFacilityService,
} from "../../model/input/statistics-public-sewage-facility-input-interface";
export class StatisticsInputPublicSewageFacilityApiService implements StatisticsInputPublicSewageFacilityService {
public async getPublicSewageFacility(param: PublicSewageFacilityRequestParams): Promise<PublicSewageFacilityResponse> {
const response = await stClient.get<PublicSewageFacilityResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/facility`,
{ params: param },
);
return response.data;
}
public async getPublicSewageMainFacility(param: PublicSewageFacilityRequestParams): Promise<PublicSewageMainFacilityResponse> {
const response = await stClient.get<PublicSewageMainFacilityResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/main-facility`,
{ params: param },
);
return response.data;
}
public async getMappingTargets(
param: PublicSewageMappingTargetsRequestParams,
): Promise<PublicSewageMappingTargetsResponse> {
const response = await stClient.get<PublicSewageMappingTargetsResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/main-facility/mapping-targets`,
{ params: param },
);
return response.data;
}
public async savePublicSewageFacility(
param: PublicSewageFacilitySaveRequest,
): Promise<PublicSewageFacilitySaveResponse> {
const response = await stClient.post<PublicSewageFacilitySaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/facility/save`,
param,
);
return response.data;
}
public async submitPublicSewageFacility(
param: PublicSewageFacilitySubmitRequest,
): Promise<PublicSewageFacilitySubmitResponse> {
const response = await stClient.post<PublicSewageFacilitySubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/public-sewage-facility/facility/submit`,
param,
);
return response.data;
}
}
export const statisticsInputPublicSewageFacilityApiService = new StatisticsInputPublicSewageFacilityApiService();
@@ -16,6 +16,8 @@ import type {
SeweragePipeInputRegionalFactStatusSaveResponse,
SeweragePipeInputRegionalFactStatusSubmitRequest,
SeweragePipeInputRegionalFactStatusSubmitResponse,
SeweragePipeInputFactInstallStatusSubmitRequest,
SeweragePipeInputFactInstallStatusSubmitResponse,
SeweragePipeInputRegionalResponse,
SeweragePipeRequestParams,
StatisticsInputSewPipeService,
@@ -23,10 +25,14 @@ import type {
SeweragePipeInputFactInstallStatusSaveResponse,
SeweragePipeInputPipeTypeStatusSaveRequest,
SeweragePipeInputPipeTypeStatusSaveResponse,
SeweragePipeInputPipeTypeStatusSubmitRequest,
SeweragePipeInputPipeDiameterStatusSaveRequest,
SeweragePipeInputPipeDiameterStatusSaveResponse,
SeweragePipeInputPipeDiameterStatusSubmitRequest,
SeweragePipeInputManholeStatusSaveRequest,
SeweragePipeInputManholeStatusSaveResponse,
SeweragePipeInputManholeStatusSubmitRequest,
SeweragePipeInputSubmitResponse,
} from "../../model/input/statistics-sewage-pipe-input-interface";
export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeService {
@@ -126,6 +132,14 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
return response.data;
}
public async submitFactInstallStatus(param: SeweragePipeInputFactInstallStatusSubmitRequest): Promise<SeweragePipeInputFactInstallStatusSubmitResponse> {
const response = await stClient.post<SeweragePipeInputFactInstallStatusSubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/facility-install-status/submit`,
param,
);
return response.data;
}
public async savePipeTypeStatus(param: SeweragePipeInputPipeTypeStatusSaveRequest): Promise<SeweragePipeInputPipeTypeStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputPipeTypeStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-type-status/save`,
@@ -134,6 +148,14 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
return response.data;
}
public async submitPipeTypeStatus(param: SeweragePipeInputPipeTypeStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse> {
const response = await stClient.post<SeweragePipeInputSubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-type-status/submit`,
param,
);
return response.data;
}
public async savePipeDiameterStatus(param: SeweragePipeInputPipeDiameterStatusSaveRequest): Promise<SeweragePipeInputPipeDiameterStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputPipeDiameterStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-diameter-status/save`,
@@ -142,6 +164,14 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
return response.data;
}
public async submitPipeDiameterStatus(param: SeweragePipeInputPipeDiameterStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse> {
const response = await stClient.post<SeweragePipeInputSubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/pipe-diameter-status/submit`,
param,
);
return response.data;
}
public async saveManholeStatus(param: SeweragePipeInputManholeStatusSaveRequest): Promise<SeweragePipeInputManholeStatusSaveResponse> {
const response = await stClient.post<SeweragePipeInputManholeStatusSaveResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/manhole-status/save`,
@@ -149,6 +179,14 @@ export class StatisticsInputSewPipeApiService implements StatisticsInputSewPipeS
);
return response.data;
}
public async submitManholeStatus(param: SeweragePipeInputManholeStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse> {
const response = await stClient.post<SeweragePipeInputSubmitResponse>(
`${ENDPOINT.ST_SERVICE.INPUT}/sewage-pipe/manhole-status/submit`,
param,
);
return response.data;
}
};
export const statisticsInputSewPipeApiService = new StatisticsInputSewPipeApiService();
@@ -0,0 +1,43 @@
import { client } from "@/shared/api/client";
import { ENDPOINT } from "@/shared/config";
import type { PageableList } from "@/shared/model";
import type {
NtftalkTemplate,
TemplateVariablesResponse,
} from "../../model/notice-talk/notice-talk-sending-interface";
export const NTFTALK_TEMPLATE_LIST_PARAMS = {
sort: "createdAt,desc",
senderKey: "",
inspectionStatus: "APR",
templateMessageType: "BA",
page: 0,
size: 10,
};
/**
* 알림톡 발송 화면용 Ntftalk API
*/
export class NoticeTalkSendingApiService {
async getTemplatePage(
params: typeof NTFTALK_TEMPLATE_LIST_PARAMS = NTFTALK_TEMPLATE_LIST_PARAMS,
): Promise<PageableList<NtftalkTemplate>> {
const response = await client.get<PageableList<NtftalkTemplate>>(
ENDPOINT.NTFTALK_SERVICE.TEMP,
{ params },
);
return response.data;
}
async getTemplateVariables(
templateCode: string,
): Promise<TemplateVariablesResponse> {
const response = await client.get<TemplateVariablesResponse>(
`${ENDPOINT.NTFTALK_SERVICE.ATA_SEND}/template-variables/${templateCode}`,
);
return response.data;
}
}
export const noticeTalkSendingApi = new NoticeTalkSendingApiService();
@@ -0,0 +1,33 @@
import { useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import {
noticeTalkSendingApi,
NTFTALK_TEMPLATE_LIST_PARAMS,
} from "./notice-talk-sending-service";
export function useNtftalkTemplateList() {
return useQuery({
queryKey: [
ENDPOINT.NTFTALK_SERVICE.TEMP,
"ntitlk-sndng",
NTFTALK_TEMPLATE_LIST_PARAMS,
],
queryFn: () =>
noticeTalkSendingApi.getTemplatePage(NTFTALK_TEMPLATE_LIST_PARAMS),
});
}
export function useNtftalkTemplateVariables(templateCode: string) {
return useQuery({
queryKey: [
ENDPOINT.NTFTALK_SERVICE.ATA_SEND,
"template-variables",
templateCode,
],
queryFn: () =>
noticeTalkSendingApi.getTemplateVariables(templateCode),
enabled: Boolean(templateCode),
});
}
@@ -1,18 +1,18 @@
import { useInfiniteQuery, useQuery } from "@tanstack/react-query";
import { ENDPOINT } from "@/shared/config";
import { generalverificationApiService } from "@/entities/statistics/api/general-verification-service";
import { StatisticsInputStatusApi } from "@/entities/statistics/api/input/statistics-input-status-service";
import type { VerifyResultPageData } from "@/entities/statistics/model/general-verification-interface";
import type { VerifyResultPageData } from "../model/general-verification-interface";
import type { SewerStUserSearchParams } from "../model/sewer-st-user-interface";
import type {
StatisticsVerificationFilterState,
StatisticsVerificationSubFilterState,
} from "@/entities/statistics/model/statistics-verification-report/statistics-verification-report-types";
import type { SewerStUserSearchParams } from "../model/sewer-st-user-interface";
} from "../model/statistics-verification-report/statistics-verification-report-types";
import { StatisticsInputStatusApi } from "./input/statistics-input-status-service";
import { generalverificationApiService } from "./general-verification-service";
import { sewerStUserApi } from "./sewer-st-user-service";
import { stAnlsMstApi } from "./statistics-analysis-master-api";
import { statisticsAnalysisMasterService } from "./statistics-analysis-master-service";
import { stAnlsRsltApi } from "./statistics-analysis-result-api";
const DETAIL_PAGE_SIZE = 100;
@@ -20,14 +20,14 @@ const DETAIL_PAGE_SIZE = 100;
export const useStAnlsSidoCombo = () => {
return useQuery({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, "sido"],
queryFn: () => stAnlsMstApi.getSidoCombo(),
queryFn: () => statisticsAnalysisMasterService.getSidoCombo(),
});
};
export const useStAnlsSigunguCombo = (sidoCd: string) => {
return useQuery({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, "sigungu", sidoCd],
queryFn: () => stAnlsMstApi.getSigunguCombo(sidoCd),
queryFn: () => statisticsAnalysisMasterService.getSigunguCombo(sidoCd),
enabled: Boolean(sidoCd),
});
};
@@ -1,36 +0,0 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
StAnlsDtlListApiResponse,
StAnlsDtlResponse,
StAnlsDtlSearchParams,
} from "../model/statistics-analysis/statistics-analysis-detail-interface";
export class StAnlsDtlApiService {
public async getDetailList(
params: StAnlsDtlSearchParams,
): Promise<StAnlsDtlListApiResponse> {
const response = await client.get<StAnlsDtlListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}`,
{
params,
},
);
return response.data;
}
public async getDetail(
anlsSn: number,
vrfcTrgtSn: number,
): Promise<StAnlsDtlResponse> {
const response = await client.get<StAnlsDtlResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/${anlsSn}/${vrfcTrgtSn}`,
);
return response.data;
}
}
export const stAnlsDtlApi = new StAnlsDtlApiService();
@@ -0,0 +1,51 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
StAnlsDtlConfirmRequest,
StAnlsDtlConfirmResponse,
StAnlsDtlListApiResponse,
StAnlsDtlResponse,
StAnlsDtlSearchParams,
} from "../model/statistics-analysis/statistics-analysis-detail-interface";
async function getDetailList(
params: StAnlsDtlSearchParams,
): Promise<StAnlsDtlListApiResponse> {
const response = await client.get<StAnlsDtlListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}`,
{
params,
},
);
return response.data;
}
async function getDetail(
anlsSn: number,
vrfcTrgtSn: number,
): Promise<StAnlsDtlResponse> {
const response = await client.get<StAnlsDtlResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/${anlsSn}/${vrfcTrgtSn}`,
);
return response.data;
}
async function confirmDetail(
request: StAnlsDtlConfirmRequest,
): Promise<StAnlsDtlConfirmResponse> {
const response = await client.post<StAnlsDtlConfirmResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_DTL}/confirm`,
request,
);
return response.data;
}
export const statisticsAnalysisDetailService = {
getDetailList,
getDetail,
confirmDetail,
};
@@ -1,69 +0,0 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
AdstZoneResponseDto,
StAnlsMstImrListApiResponse,
StAnlsMstListApiResponse,
StAnlsMstSearchParams,
StAnlsOutlierListResponse,
} from "../model/statistics-analysis/statistics-analysis-master-interface";
export class StAnlsMstApiService {
public async getAnalysisHistoryList(
params: StAnlsMstSearchParams,
): Promise<StAnlsMstListApiResponse> {
const response = await client.get<StAnlsMstListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}`,
{
params,
},
);
return response.data;
}
public async getImrAnalysisMasterList(
params: StAnlsMstSearchParams,
): Promise<StAnlsMstImrListApiResponse> {
const response = await client.get<StAnlsMstImrListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/imr`,
{
params,
},
);
return response.data;
}
public async getOutlierList(
anlsSn: number,
): Promise<StAnlsOutlierListResponse[]> {
const response = await client.get<StAnlsOutlierListResponse[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/outlierList/${anlsSn}`,
);
return response.data;
}
public async getSidoCombo(): Promise<AdstZoneResponseDto[]> {
const response = await client.get<AdstZoneResponseDto[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sido`,
);
return response.data;
}
public async getSigunguCombo(
sidoCd: string,
): Promise<AdstZoneResponseDto[]> {
const response = await client.get<AdstZoneResponseDto[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sigungu`,
{ params: { sidoCd } },
);
return response.data;
}
}
export const stAnlsMstApi = new StAnlsMstApiService();
@@ -0,0 +1,71 @@
import { client } from "@/shared/api";
import { ENDPOINT } from "@/shared/config";
import type {
AdstZoneResponseDto,
StAnlsMstImrListApiResponse,
StAnlsMstListApiResponse,
StAnlsMstSearchParams,
StAnlsOutlierListResponse,
} from "../model/statistics-analysis/statistics-analysis-master-interface";
async function getAnalysisHistoryList(
params: StAnlsMstSearchParams,
): Promise<StAnlsMstListApiResponse> {
const response = await client.get<StAnlsMstListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}`,
{
params,
},
);
return response.data;
}
async function getImrAnalysisMasterList(
params: StAnlsMstSearchParams,
): Promise<StAnlsMstImrListApiResponse> {
const response = await client.get<StAnlsMstImrListApiResponse>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/imr`,
{
params,
},
);
return response.data;
}
async function getOutlierList(
anlsSn: number,
): Promise<StAnlsOutlierListResponse[]> {
const response = await client.get<StAnlsOutlierListResponse[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/outlierList/${anlsSn}`,
);
return response.data;
}
async function getSidoCombo(): Promise<AdstZoneResponseDto[]> {
const response = await client.get<AdstZoneResponseDto[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sido`,
);
return response.data;
}
async function getSigunguCombo(sidoCd: string): Promise<AdstZoneResponseDto[]> {
const response = await client.get<AdstZoneResponseDto[]>(
`${ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST}/sigungu`,
{ params: { sidoCd } },
);
return response.data;
}
export const statisticsAnalysisMasterService = {
getAnalysisHistoryList,
getImrAnalysisMasterList,
getOutlierList,
getSidoCombo,
getSigunguCombo,
};
+22
View File
@@ -1,3 +1,4 @@
export { analysisApi } from "./api/analysis-api";
export {
useDashboardEcptReqCount,
useDashboardStatsSubmissionRate,
@@ -8,6 +9,12 @@ export {
export { dashboardApi } from "./api/dashboard/dashboard-service";
export { useScheduleInfo } from "./api/dashboard/schedule-info-queries";
export { scheduleInfoApi } from "./api/dashboard/schedule-info-service";
export { imrApi } from "./api/imr-api";
export { noticeTalkSendingApi } from "./api/notice-talk/notice-talk-sending-service";
export {
useNtftalkTemplateList,
useNtftalkTemplateVariables,
} from "./api/notice-talk/queries";
export {
useSewerStUserList,
useStAnlsSidoCombo,
@@ -16,7 +23,11 @@ export {
useStatsVrfcRsltData,
} from "./api/queries";
export { sewerStUserApi } from "./api/sewer-st-user-service";
export { statisticsAnalysisDetailService } from "./api/statistics-analysis-detail-service";
export { statisticsAnalysisMasterService } from "./api/statistics-analysis-master-service";
export { stAnlsRsltApi } from "./api/statistics-analysis-result-api";
export { statisticsApprovalApi } from "./api/statistics-approval-service";
export { verificationTargetManagementApi } from "./api/verification-target-management-service";
export {
type AnalysisHistoryListApiResponse,
type AnalysisRequestPayload,
@@ -64,12 +75,22 @@ export {
} from "./model/imr/imr-interface";
export { imrVerificationHistoryColumns } from "./model/imr/imr-verification-history-columns";
export { type StatsInputStatusList } from "./model/input/statistics-input-status-interface";
export { userListColumns } from "./model/notice-talk/notice-talk-sending-columns";
export {
type BusinessSendRequest,
type BusinessSendResponse,
type NtftalkTemplate,
type SelectedRecipient,
type TemplateVariablesResponse,
} from "./model/notice-talk/notice-talk-sending-interface";
export {
type SewerStUserItem,
type SewerStUserListResponse,
type SewerStUserSearchParams,
} from "./model/sewer-st-user-interface";
export {
type StAnlsDtlConfirmRequest,
type StAnlsDtlConfirmResponse,
type StAnlsDtlListApiResponse,
type StAnlsDtlResponse,
type StAnlsDtlSearchParams,
@@ -121,3 +142,4 @@ export {
type IMRArticleItem,
type ValidationArticleItem,
} from "./model/verification-article/verification-article-interface";
export { type VerificationTargetCodeResponse } from "./model/verification-target-management-interface";
@@ -0,0 +1,254 @@
import type { SewerageStatusInfo } from "../statistics-interface";
export interface PublicSewageFacilityRequestParams {
ctpvNm?: string;
sggNm?: string;
sewerBzmnCd?: string;
fcltNm?: string;
statsYr?: string;
mode?: string;
}
export interface PublicSewageFacilityVerifyResultItem {
vrfcCd: string;
rsnExpln: string;
nowVlCn?: string;
compVlCn?: string;
}
/** 필드명 -> 검증 결과 배열 */
export type PublicSewageFacilityVerificationResult = Record<
string,
PublicSewageFacilityVerifyResultItem[]
>;
export interface PublicSewageFacilityCommonInfo {
statsYr: string;
ctpvNm: string;
sewerBzmnCd: string;
statsCd: string;
verificationResult?: PublicSewageFacilityVerificationResult | null;
}
export interface PublicSewageFacilityItem {
admdstCd: string;
sggNm: string;
admdstNm: string;
fcltSn: number;
swtrmtFcltCd: string;
swtrmtFcltNm: string;
impdFcltPstnCn: string | null;
prcsFcltCpct: number;
prcsFcltPhysCpct: number;
prcsFcltBoilCpct: number;
prcsFcltAltdCpct: number;
inflwSwageQnt: number;
inflwSwagePhysQnt: number;
inflwSwageBoilQnt: number;
inflwSwageAltdQnt: number;
prcsQnt: number;
prcsPhysCpct: number;
prcsBoilCpct: number;
prcsAltdCpct: number;
inflwBodVl: number;
dschgBodVl: number;
inflwTocQnt: number;
dschgTocQnt: number;
inflwSsQnt: number;
dschgSsQnt: number;
inflwTnVl: number;
dschgTnVl: number;
inflwTpVl: number;
dschgTpVl: number;
inflwGrssClfgVl: number;
dschgGrssClfgVl: number;
prcsRt: number;
prcsLodQnt: number;
prcsMthdExpln: string | null;
wholHmnQnt: number;
wholLvstQnt: number;
wholEfntQnt: number;
wholEtcQnt: number;
telNo: string | null;
cmptnStrtYmd: string | null;
oprtngStrtYmd: string | null;
wholBizVl: number;
eflntDsnfMthdExpln: string | null;
wtsysNm: string | null;
trbtrNm: string | null;
dtlUnitZoneCn: string | null;
rgnSeNm: string | null;
msirbCn: string | null;
goalWqNo: string | null;
engySrlneRt: number;
engyItslfPrdctnQnt: number;
anlEngyWholUseQnt: number;
anlElpwrUseQnt: number;
itslfEngyUseQnt: number;
swtrmtEmsnQnt: number;
operMnbdCn: string | null;
prxyBzentyNmCn: string | null;
cnsgnCstVl: number;
prxyCtrtCn: string | null;
intgPrcsFcltCn: string | null;
wholNope: number;
sftyAcdntEvntcsCnt: number;
rmrk: string | null;
verificationResult?: PublicSewageFacilityVerificationResult | null;
}
export interface PublicSewageFacilityResponseData {
statusInfo: SewerageStatusInfo;
commonInfo: PublicSewageFacilityCommonInfo;
publicSewageFacilityList: PublicSewageFacilityItem[];
}
export interface PublicSewageFacilityResponse {
status: string;
message?: string;
data: PublicSewageFacilityResponseData;
}
/** ST00060002 지자체별 전체시설 목록 항목 */
export interface PublicSewageMainFacilityItem {
admdstCd: string;
sggNm: string | null;
admdstNm: string;
isMainYn: string;
fcltSn: string;
swtrmtFcltCd: string;
swtrmtFcltNm: string;
fcltPstnCn: string | null;
prxyBzentyNmCn: string | null;
cnsgnCstVl: number;
prxyCtrtCn: string | null;
wholNope: number;
}
/** ST00060002 조회 응답 data */
export interface PublicSewageMainFacilityResponseData {
statusInfo: SewerageStatusInfo;
commonInfo: PublicSewageFacilityCommonInfo;
mainFacilityList: PublicSewageMainFacilityItem[];
}
/** ST00060002 조회 API 응답 */
export interface PublicSewageMainFacilityResponse {
status: string;
message?: string;
data: PublicSewageMainFacilityResponseData;
}
/** ST00060002 메인시설 클릭 시 우측 매핑대상 조회 요청 */
export interface PublicSewageMappingTargetsRequestParams {
sewerBzmnCd: string;
ctpvNm: string;
sggNm: string;
statsYr: string;
fcltSn: string;
}
/** ST00060002 메인시설 클릭 시 우측 매핑대상 조회 항목 */
export interface PublicSewageMappingTargetItem {
fcltSn: string;
admdstNm: string;
swtrmtFcltCd: string;
swtrmtFcltNm: string;
fcltPstnCn: string;
mappedYn: string;
mainFcltSn: string;
isMainYn: string;
}
/** ST00060002 메인시설 클릭 시 우측 매핑대상 조회 응답 */
export interface PublicSewageMappingTargetsResponse {
status: string;
message?: string;
data: PublicSewageMappingTargetItem[];
}
/** ST00060001 공공하수처리시설 저장 — saveList 항목 */
export interface PublicSewageFacilitySaveItem {
admdstCd: string;
admdstNm: string;
swtrmtFcltCd: string;
swtrmtFcltNm: string;
fcltSn: string;
fcltPstnCn: string | null;
inflwSwagePhysQnt: number;
inflwSwageBoilQnt: number;
inflwSwageAltdQnt: number;
prcsPhysCpct: number;
prcsBoilCpct: number;
prcsAltdCpct: number;
prxyCtrtCn: string | null;
sftyAcdntEvntcsCnt: number;
rmrk: string | null;
}
/** ST00060001 공공하수처리시설 저장 API 요청 */
export interface PublicSewageFacilitySaveRequest {
statsYr: string;
ctpvNm: string;
sggNm: string;
sewerBzmnCd: string;
userId: string;
superAdmin: string;
/**
* 저장 요청에서 "해당없음" 처리 여부가 함께 전달되는 경우를 고려합니다.
* (백엔드 스펙에 따라 필수 여부는 달라질 수 있어 optional로 둡니다.)
*/
noApplicable?: boolean;
saveList: PublicSewageFacilitySaveItem[];
}
export interface PublicSewageFacilitySaveResponse {
data?: string;
message?: string;
status: string;
}
/** 공공하수처리시설 입력 제출 공통 본문 (`submitList` 제외) */
export interface PublicSewageFacilitySubmitRequestBase {
statsYr: string;
ctpvNm: string;
sggNm: string;
sewerBzmnCd: string;
userId: string;
noApplicable?: boolean;
laePicNm: string;
laePicTelNo: string;
laeKbrdrDeptNm: string;
laeKbrdrJbgdNm: string;
laeIdfrNm: string;
laeIdfrTelNo: string;
laeIdfrDeptNm: string;
laeIdfrJbgdNm: string;
}
/** ST00060001 공공하수처리시설 제출 API 요청 (`submitList` 행은 저장과 동일) */
export interface PublicSewageFacilitySubmitRequest extends PublicSewageFacilitySubmitRequestBase {
submitList: PublicSewageFacilitySaveItem[];
}
export interface PublicSewageFacilitySubmitResponse {
data: {
finalStatus: string;
nvpassed: boolean;
}
message?: string;
status: string;
}
export interface StatisticsInputPublicSewageFacilityService {
getPublicSewageFacility(param: PublicSewageFacilityRequestParams): Promise<PublicSewageFacilityResponse>;
getPublicSewageMainFacility(param: PublicSewageFacilityRequestParams): Promise<PublicSewageMainFacilityResponse>;
getMappingTargets(param: PublicSewageMappingTargetsRequestParams): Promise<PublicSewageMappingTargetsResponse>;
savePublicSewageFacility(param: PublicSewageFacilitySaveRequest): Promise<PublicSewageFacilitySaveResponse>;
submitPublicSewageFacility(param: PublicSewageFacilitySubmitRequest): Promise<PublicSewageFacilitySubmitResponse>;
}
@@ -122,8 +122,8 @@ export interface SeweragePipeInputRegionalFactStatusSaveResponse {
status: string;
}
/** ST00020001 지역별 시설현황 제출 API 요청 (`submitList` 행은 저장과 동일) */
export interface SeweragePipeInputRegionalFactStatusSubmitRequest {
/** 하수관로 입력 제출 공통 본문 (`submitList` 제외) */
export interface SeweragePipeInputSubmitRequestBase {
statsYr: string;
ctpvNm: string;
sggNm: string;
@@ -137,6 +137,10 @@ export interface SeweragePipeInputRegionalFactStatusSubmitRequest {
laeIdfrTelNo: string;
laeIdfrDeptNm: string;
laeIdfrJbgdNm: string;
}
/** ST00020001 지역별 시설현황 제출 API 요청 (`submitList` 행은 저장과 동일) */
export interface SeweragePipeInputRegionalFactStatusSubmitRequest extends SeweragePipeInputSubmitRequestBase {
submitList: SeweragePipeInputRegionalFactStatusSaveItem[];
}
@@ -165,6 +169,30 @@ export interface SeweragePipeInputFactInstallStatusSaveResponse {
status: string;
}
/** ST00020002 시설별 설치현황 제출 API 요청 (`submitList` 행은 Tab2 저장과 동일) */
export interface SeweragePipeInputFactInstallStatusSubmitRequest extends SeweragePipeInputSubmitRequestBase {
submitList: SeweragePipeInputFactInstallStatusSaveItem[];
}
export interface SeweragePipeInputFactInstallStatusSubmitResponse {
data: {
finalStatus: string;
nvpassed: boolean;
}
message?: string;
status: string;
}
/** 하수관로 입력 제출 API 공통 응답 */
export interface SeweragePipeInputSubmitResponse {
data: {
finalStatus: string;
nvpassed: boolean;
}
message?: string;
status: string;
}
/** 시설별 설치 현황 — 목록 항목 */
export interface SeweragePipeInputFactInstallStatusItem {
admdstCd: string;
@@ -283,6 +311,11 @@ export interface SeweragePipeInputPipeTypeStatusSaveResponse {
status: string;
}
/** ST00020003 관종별 현황 제출 API 요청 */
export interface SeweragePipeInputPipeTypeStatusSubmitRequest extends SeweragePipeInputSubmitRequestBase {
submitList: SeweragePipeInputPipeTypeStatusSaveItem[];
}
/** 관경별 현황 — 목록 항목 */
export interface SeweragePipeInputPipeDiameterStatusItem {
admdstCd: string;
@@ -350,6 +383,11 @@ export interface SeweragePipeInputPipeDiameterStatusSaveResponse {
status: string;
}
/** ST00020004 관경별 현황 제출 API 요청 */
export interface SeweragePipeInputPipeDiameterStatusSubmitRequest extends SeweragePipeInputSubmitRequestBase {
submitList: SeweragePipeInputPipeDiameterStatusSaveItem[];
}
/** 맨홀 현황 — 연도별 통계 항목 */
export interface SeweragePipeInputManholeYearlyStatItem {
manhYr: string;
@@ -443,6 +481,11 @@ export interface SeweragePipeInputManholeStatusSaveResponse {
status: string;
}
/** ST00020005 맨홀 현황 제출 API 요청 */
export interface SeweragePipeInputManholeStatusSubmitRequest extends SeweragePipeInputSubmitRequestBase {
submitList: SeweragePipeInputManholeStatusSaveItem[];
}
/** 시설별 현황 관리 - 하수처리장별 연결 현황 목록 항목 */
export interface SeweragePipeInputFacilityConnectionItem {
fcltNm: string;
@@ -571,7 +614,11 @@ export interface StatisticsInputSewPipeService {
saveRegionalFactStatus(param: SeweragePipeInputRegionalFactStatusSaveRequest): Promise<SeweragePipeInputRegionalFactStatusSaveResponse>;
submitRegionalFactStatus(param: SeweragePipeInputRegionalFactStatusSubmitRequest): Promise<SeweragePipeInputRegionalFactStatusSubmitResponse>;
saveFactInstallStatus(param: SeweragePipeInputFactInstallStatusSaveRequest): Promise<SeweragePipeInputFactInstallStatusSaveResponse>;
submitFactInstallStatus(param: SeweragePipeInputFactInstallStatusSubmitRequest): Promise<SeweragePipeInputFactInstallStatusSubmitResponse>;
savePipeTypeStatus(param: SeweragePipeInputPipeTypeStatusSaveRequest): Promise<SeweragePipeInputPipeTypeStatusSaveResponse>;
submitPipeTypeStatus(param: SeweragePipeInputPipeTypeStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse>;
savePipeDiameterStatus(param: SeweragePipeInputPipeDiameterStatusSaveRequest): Promise<SeweragePipeInputPipeDiameterStatusSaveResponse>;
submitPipeDiameterStatus(param: SeweragePipeInputPipeDiameterStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse>;
saveManholeStatus(param: SeweragePipeInputManholeStatusSaveRequest): Promise<SeweragePipeInputManholeStatusSaveResponse>;
submitManholeStatus(param: SeweragePipeInputManholeStatusSubmitRequest): Promise<SeweragePipeInputSubmitResponse>;
}
@@ -0,0 +1,74 @@
import type { ColumnDef } from "@tanstack/react-table";
import { Checkbox } from "@common/components";
import type { SewerStUserItem } from "../sewer-st-user-interface";
export const userListColumns: ColumnDef<SewerStUserItem>[] = [
{
id: "select",
header: ({ table }) => (
<div
className="flex justify-center"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
role="presentation"
>
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && "indeterminate")
}
onCheckedChange={(value) =>
table.toggleAllPageRowsSelected(!!value)
}
aria-label="전체 선택"
/>
</div>
),
cell: ({ row }) => (
<div
className="flex justify-center"
onClick={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
role="presentation"
>
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="행 선택"
/>
</div>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: "userNm",
header: "이름",
},
{
accessorKey: "sido",
header: "시도",
},
{
accessorKey: "gugun",
header: "시군구",
},
{
accessorKey: "gubunNm",
header: "구분",
},
{
accessorKey: "deptNm",
header: "소속",
},
{
accessorKey: "jbgdNm",
header: "직급",
},
{
accessorKey: "telNo",
header: "전화번호",
},
];
@@ -0,0 +1,30 @@
import type { SewerStUserItem } from "../sewer-st-user-interface";
export interface NtftalkTemplate {
templateCode: string;
templateName: string;
templateContent: string;
}
export interface TemplateVariablesResponse {
templateCode: string;
variables: string[];
count: number;
}
export interface BusinessSendRequest {
templateCode: string;
callback?: string;
fixedRecipientNum?: string;
recipients: Record<string, string>[];
}
export interface BusinessSendResponse {
success: boolean;
sentCount: number;
message: string;
}
export interface SelectedRecipient extends SewerStUserItem {
variableOverrides: Record<string, string>;
}
@@ -18,6 +18,8 @@ export interface StAnlsDtlResponse {
nmlDataNocs: number; // 정상 데이터 건수
exclDataNocs: number; // 제외 데이터 건수
otlrDataNocs: number; // 이상치 데이터 건수
cfmtnYn: string; // 확정여부 (Y/N)
cfmtnDt: string | null; // 확정일자 (yyyy-MM-dd HH:mm:ss)
statsCd: string; // 통계 코드
statsNm: string; // 통계 항목 명
vrfcTrgtNm: string; // 검증대상명
@@ -30,3 +32,13 @@ export interface StAnlsDtlSearchParams extends PageableParams {
}
export type StAnlsDtlListApiResponse = PageableList<StAnlsDtlResponse>;
export interface StAnlsDtlConfirmRequest {
anlsSn: number; // 분석 일련번호
vrfcTrgtSnList: number[]; // 검증 대상 일련번호 목록
}
export interface StAnlsDtlConfirmResponse {
anlsSn: number; // 분석 일련번호
processedCount: number; // 처리된 검증 대상 건수
}
@@ -45,13 +45,15 @@ export interface StAnlsMstResponseDto {
/** 검증대상명 */
vrfcTrgtNm: string;
/** 분석진행비율 */
anlsPrgrsRt: number;
anlsPrgrsRt: number | null;
/** 하수도사업자코드 */
sewerBzmnCd: string;
/** 통계코드 */
statsCd: string;
/** 검증방법코드 */
vrfcMthdCd: string;
/** 확정건수/총건수 (예: 3/10) */
cfmtnYnCntTotalCnt: string;
}
/**
@@ -132,7 +134,6 @@ export interface StAnlsOutlierListResponseDto {
outlierDataList: StAnlsOutlierDataResponseDto[];
}
/** 기존 인터페이스 호환을 위한 별칭 (필요 시 유지) */
export type StAnlsMstResponse = StAnlsMstResponseDto;
export type StAnlsMstImrResponse = StAnlsMstImrResponseDto;
export type StAnlsMstSearchParams = StAnlsMstSearchDto;
@@ -77,6 +77,12 @@ export const verificationHistoryColumns = [
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
),
}),
columnHelper.accessor("cfmtnYnCntTotalCnt", {
header: "확정건수/총",
cell: (info) => (
<div className="px-2 py-4">{info.getValue() ?? "-"}</div>
),
}),
columnHelper.accessor("nmlDataNocs", {
header: "정상건수",
cell: (info) => (
@@ -1,6 +1,7 @@
import React from "react";
import type { ColumnDef } from "@tanstack/react-table";
import dayjs from "dayjs";
import { Checkbox } from "@common/components";
@@ -16,94 +17,123 @@ export const getVerificationListColumns = ({
rowData,
checkedSns,
onCheck,
}: GetVerificationListColumnsParams): ColumnDef<StAnlsDtlResponse>[] => [
{
id: "checkbox",
header: () => {
const isAllChecked =
rowData.length > 0 && checkedSns.length === rowData.length;
return (
<div className="flex items-center justify-center">
<Checkbox
checked={isAllChecked}
onCheckedChange={(value: boolean) => {
if (value) {
onCheck(
rowData.map((r) => String(r.vrfcTrgtSn)),
rowData,
}: GetVerificationListColumnsParams): ColumnDef<StAnlsDtlResponse>[] => {
const selectableRows = rowData.filter((r) => r.cfmtnYn !== "Y");
return [
{
id: "checkbox",
header: () => {
const isAllChecked =
selectableRows.length > 0 &&
checkedSns.length === selectableRows.length;
return (
<div className="flex items-center justify-center">
<Checkbox
checked={isAllChecked}
disabled={selectableRows.length === 0}
onCheckedChange={(value: boolean) => {
if (value) {
onCheck(
selectableRows.map((r) =>
String(r.vrfcTrgtSn),
),
selectableRows,
);
} else {
onCheck([], []);
}
}}
aria-label="Select all rows"
/>
</div>
);
},
cell: ({ row }) => {
if (row.original.cfmtnYn === "Y") {
return <div className="flex items-center justify-center" />;
}
const rowId = String(row.original.vrfcTrgtSn);
const isChecked = checkedSns.includes(rowId);
return (
<div className="flex items-center justify-center">
<Checkbox
checked={isChecked}
onCheckedChange={(value: boolean) => {
const newCheckedSns = value
? [...checkedSns, rowId]
: checkedSns.filter((id) => id !== rowId);
const newCheckedRows = selectableRows.filter(
(r) =>
newCheckedSns.includes(
String(r.vrfcTrgtSn),
),
);
} else {
onCheck([], []);
onCheck(newCheckedSns, newCheckedRows);
}}
onClick={(e: React.MouseEvent) =>
e.stopPropagation()
}
}}
aria-label="Select all rows"
/>
</div>
);
aria-label={`Select row ${rowId}`}
/>
</div>
);
},
enableSorting: false,
enableHiding: false,
},
cell: ({ row }) => {
const rowId = String(row.original.vrfcTrgtSn);
const isChecked = checkedSns.includes(rowId);
return (
<div className="flex items-center justify-center">
<Checkbox
checked={isChecked}
onCheckedChange={(value: boolean) => {
const newCheckedSns = value
? [...checkedSns, rowId]
: checkedSns.filter((id) => id !== rowId);
const newCheckedRows = rowData.filter((r) =>
newCheckedSns.includes(String(r.vrfcTrgtSn)),
);
onCheck(newCheckedSns, newCheckedRows);
}}
onClick={(e: React.MouseEvent) => e.stopPropagation()}
aria-label={`Select row ${rowId}`}
/>
</div>
);
{
header: "통계 년도",
accessorKey: "statsYr",
},
enableSorting: false,
enableHiding: false,
},
{
header: "통계 년도",
accessorKey: "statsYr",
},
{
header: "통계 항목",
accessorKey: "statsNm",
},
{
header: "검증 항목",
accessorKey: "vrfcTrgtNm",
},
{
header: "μ",
accessorKey: "ipcrCntVl",
},
{
header: "Percentile",
accessorKey: "pctlCrtrVl",
},
{
header: "A",
accessorKey: "scopAjmtCntVl",
},
{
header: "C",
accessorKey: "thrsCntVl",
},
{
header: "정상건수",
accessorKey: "nmlDataNocs",
},
{
header: "제외건수",
accessorKey: "exclDataNocs",
},
{
header: "이상건수",
accessorKey: "otlrDataNocs",
},
];
{
header: "통계 항목",
accessorKey: "statsNm",
},
{
header: "검증 항목",
accessorKey: "vrfcTrgtNm",
},
{
header: "확정일자",
accessorKey: "cfmtnDt",
cell: ({ row }) => {
return (
<div className="text-center">
{row.original.cfmtnDt
? dayjs(row.original.cfmtnDt).format("YYYY-MM-DD")
: "-"}
</div>
);
},
},
{
header: "μ",
accessorKey: "ipcrCntVl",
},
{
header: "Percentile",
accessorKey: "pctlCrtrVl",
},
{
header: "A",
accessorKey: "scopAjmtCntVl",
},
{
header: "C",
accessorKey: "thrsCntVl",
},
{
header: "정상건수",
accessorKey: "nmlDataNocs",
},
{
header: "제외건수",
accessorKey: "exclDataNocs",
},
{
header: "이상건수",
accessorKey: "otlrDataNocs",
},
];
};
+21 -20
View File
@@ -1,29 +1,30 @@
import { AxiosError } from "axios";
import { useMutation, type UseMutationOptions } from "@tanstack/react-query";
import { AxiosError } from "axios";
import { useAlertStore } from "./use-alert-store";
export const useMutationWrapper = <
TData = unknown,
TError = unknown,
TVariables = void,
TContext = unknown,
TData = unknown,
TError = unknown,
TVariables = void,
TContext = unknown,
>(
mutationOptions: UseMutationOptions<TData, TError, TVariables, TContext>,
mutationOptions: UseMutationOptions<TData, TError, TVariables, TContext>,
) => {
const alert = useAlertStore((state) => state.setMessage);
return useMutation({
...mutationOptions,
onError: (error) => {
let errorMessage = "";
const alert = useAlertStore((state) => state.setMessage);
return useMutation({
...mutationOptions,
onError: (error) => {
let errorMessage = "";
if (error instanceof AxiosError) {
if (error.response?.data?.message)
errorMessage += `${error.response.data.message}\n`;
if (error.response?.data?.errors[0]?.message)
errorMessage += `${error.response.data.errors[0].message}\n`;
}
if (error instanceof AxiosError) {
if (error.response?.data?.message)
errorMessage += `${error.response.data.message}\n`;
if (error.response?.data?.errors[0]?.message)
errorMessage += `${error.response.data.errors[0].message}\n`;
}
alert(`${errorMessage}`);
},
});
alert(`${errorMessage}`);
},
});
};