336 lines
13 KiB
TypeScript
336 lines
13 KiB
TypeScript
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
|
|
import { useConfirm } from "@frontend-opensource/use-react-hooks";
|
|
import { RealGridReact } from "realgrid-react";
|
|
import useSWR, { useSWRConfig } from "swr";
|
|
|
|
import { useActiveTabRegAuthorityAllowed, useAlertStore } from "@/shared/lib";
|
|
import { statisticsInputSewSupplyRateApi } from "@/entities/statistics/api/input/statistics-input-sew-supply-rate-service";
|
|
import { StatisticsInputStatusApi } from "@/entities/statistics/api/input/statistics-input-status-service";
|
|
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
|
|
import {
|
|
isInputPeriodCurrent,
|
|
type CheckInputPeriodItem,
|
|
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
|
|
import type { StatsInputStatusApproveItem } from "@/entities/statistics/model/statistics-interface";
|
|
import type {
|
|
StatsInputStatusListApiResponse,
|
|
StatsInputStatusListRequestParams,
|
|
} from "@/entities/statistics/model/input/statistics-input-status-interface";
|
|
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
|
|
|
|
import { LoadingWithAnimation } from "@common/components";
|
|
import ApproveButton from "@/components/button/ApproveButton";
|
|
import DataGrid from "@/components/module/DataGrid";
|
|
import type { DataCellClickCallbackParams } from "@/components/type/DataGrid.type";
|
|
import {
|
|
ExceptionProcessingHistoryDialog,
|
|
type ExceptionProcessingHistoryQueryContext,
|
|
} from "@/components/module/ExceptionProcessingHistoryDialog";
|
|
import SearchForm from "@/components/module/SearchForm";
|
|
import type { SearchFormValues } from "@/components/type/SearchForm.type";
|
|
import { doList } from "@/components/util/column/input/DoList";
|
|
import { StatsInputStatusInpColumn } from "@/components/util/column/input/StatsInputStatusInpColumn";
|
|
import { toGridRows } from "@/components/util/grid/gridFunction";
|
|
import { useTabStore } from "@/store/tab-store";
|
|
|
|
const getStatsInputStatusFetcher = async (
|
|
key: [string, StatsInputStatusListRequestParams],
|
|
): Promise<StatsInputStatusListApiResponse> => {
|
|
return StatisticsInputStatusApi.getStatisticsInputStatusList(key[1]);
|
|
};
|
|
|
|
// 하수도 통계 관리 > 00. 통계 입력 현황
|
|
const StatsInputStatusInp = () => {
|
|
|
|
const { setMessage: alert } = useAlertStore((state) => state);
|
|
const { confirm } = useConfirm();
|
|
const activeTabId = useTabStore((state) => state.activeTabId);
|
|
const [rowData, setRowData] = useState<Record<string, string | number>[]>(
|
|
[],
|
|
);
|
|
const [searchParams, setSearchParams] =
|
|
useState<StatsInputStatusListRequestParams | null>(null);
|
|
const [lastSearchedValues, setLastSearchedValues] =
|
|
useState<SearchFormValues | null>(null);
|
|
const [hasCompletedSearch, setHasCompletedSearch] = useState(false);
|
|
const periodCheckListRef = useRef<CheckInputPeriodItem[]>([]);
|
|
const inputPeriodCurrentRef = useRef(false);
|
|
const gridRef = useRef<RealGridReact | null>(null);
|
|
const [exceptionHistoryOpen, setExceptionHistoryOpen] = useState(false);
|
|
const [exceptionHistoryContext, setExceptionHistoryContext] = useState<
|
|
ExceptionProcessingHistoryQueryContext | null
|
|
>(null);
|
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
|
const regAuthorityToolbarAllowed = useActiveTabRegAuthorityAllowed();
|
|
|
|
const swrKey = searchParams
|
|
? (["stats-input-status", searchParams] as const)
|
|
: null;
|
|
const { mutate } = useSWRConfig();
|
|
const { isLoading } = useSWR<StatsInputStatusListApiResponse>(
|
|
swrKey,
|
|
getStatsInputStatusFetcher,
|
|
{
|
|
revalidateOnFocus: true,
|
|
onSuccess: (data) => {
|
|
if (data.status === "success" && data.data) {
|
|
setRowData(
|
|
toGridRows(data.data).map((r) => ({
|
|
...r,
|
|
exceptionSubmitHistory: "예외제출내역",
|
|
})),
|
|
);
|
|
setHasCompletedSearch(true);
|
|
} else {
|
|
setHasCompletedSearch(false);
|
|
setRowData([]);
|
|
alert(data.message ?? "조회에 실패했습니다.");
|
|
}
|
|
},
|
|
onError: () => {
|
|
alert("데이터 조회 중 오류가 발생했습니다.");
|
|
setHasCompletedSearch(false);
|
|
setRowData([]);
|
|
},
|
|
},
|
|
);
|
|
|
|
useEffect(() => {
|
|
if (activeTabId === "StatsInputStatusInp" && swrKey) {
|
|
mutate(swrKey);
|
|
}
|
|
}, [activeTabId]);
|
|
|
|
const handleSearch = async (values: SearchFormValues) => {
|
|
if (values.sido === "" || values.sido === "ALL") {
|
|
alert("시도를 선택해주세요.");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await statisticsInputSewSupplyRateApi.checkInputPeriod(values.year || "");
|
|
|
|
if (response.status !== "success") {
|
|
alert(response.message ?? "입력기간 확인에 실패했습니다.");
|
|
return;
|
|
}
|
|
|
|
const periods = Array.isArray(response.data) ? response.data : [];
|
|
periodCheckListRef.current = periods;
|
|
const checkEnterable = isInputPeriodCurrent(periods);
|
|
inputPeriodCurrentRef.current = checkEnterable;
|
|
|
|
const params: StatsInputStatusListRequestParams = {
|
|
ctpvNm: values.sido,
|
|
sewerBzmnCd: values.bsnmCd,
|
|
sggNm: values.gugun === "ALL" ? "" : values.gugun,
|
|
statsYr: values.year ?? "",
|
|
};
|
|
setHasCompletedSearch(false);
|
|
setLastSearchedValues(values);
|
|
setSearchParams(params);
|
|
mutate(["stats-input-status", params]);
|
|
} catch {
|
|
alert("데이터 조회 중 오류가 발생했습니다.");
|
|
}
|
|
};
|
|
|
|
const handleReset = () => {
|
|
setSearchParams(null);
|
|
setRowData([]);
|
|
setLastSearchedValues(null);
|
|
setHasCompletedSearch(false);
|
|
periodCheckListRef.current = [];
|
|
inputPeriodCurrentRef.current = false;
|
|
setExceptionHistoryOpen(false);
|
|
setExceptionHistoryContext(null);
|
|
setIsApproveLoading(false);
|
|
};
|
|
|
|
const approveFunc = async () => {
|
|
if (rowData.length === 0) {
|
|
alert("조회된 통계 항목이 없습니다.");
|
|
return;
|
|
}
|
|
if (!lastSearchedValues?.year?.trim() || !lastSearchedValues?.bsnmCd?.trim()) {
|
|
alert("먼저 조회를 진행해주세요.");
|
|
return;
|
|
}
|
|
|
|
const hasInvalidStatus = rowData.some((row) =>
|
|
["0", "1", "4"].includes(String(row.laeSbmsnSttsCd ?? "")),
|
|
);
|
|
|
|
if (hasInvalidStatus) {
|
|
alert("미작성이나 미제출, 혹은 보완요청(반려) 상태인 항목이 있습니다.");
|
|
return;
|
|
}
|
|
|
|
if (!(await confirm("조회된 통계 항목을 승인하시겠습니까?"))) {
|
|
return;
|
|
}
|
|
|
|
setIsApproveLoading(true);
|
|
try {
|
|
const userInfo = await getUserInfo();
|
|
const mdfrId = (userInfo.userUnqId ?? "").trim();
|
|
if (!mdfrId) {
|
|
alert("수정자 ID를 확인할 수 없습니다.");
|
|
return;
|
|
}
|
|
|
|
const statsYr = lastSearchedValues.year.trim();
|
|
const sewerBzmnCdDefault = lastSearchedValues.bsnmCd.trim();
|
|
|
|
const approveItems: StatsInputStatusApproveItem[] = rowData
|
|
.map((row) => {
|
|
const statsCd = String(row.statsCd ?? "").trim();
|
|
const sewerBzmnCd = String(row.sewerBzmnCd ?? sewerBzmnCdDefault).trim();
|
|
return { statsYr, sewerBzmnCd, statsCd, mdfrId };
|
|
})
|
|
.filter((item) => item.statsCd.length > 0);
|
|
|
|
if (approveItems.length === 0) {
|
|
alert("통계 코드(statsCd)가 있는 행이 없습니다.");
|
|
return;
|
|
}
|
|
|
|
const response = await statisticsApi.approveStatsInputStatus({ list: approveItems }, "INPUT");
|
|
if (response.resultCode.toLocaleLowerCase() === "success") {
|
|
if (searchParams) {
|
|
await mutate(["stats-input-status", searchParams]);
|
|
}
|
|
alert(response.message ?? "승인되었습니다.");
|
|
} else {
|
|
alert(response.message ?? "승인에 실패했습니다.");
|
|
}
|
|
} catch {
|
|
alert("승인 처리 중 오류가 발생했습니다.");
|
|
} finally {
|
|
setIsApproveLoading(false);
|
|
}
|
|
};
|
|
|
|
const openExceptionHistoryForRow = useCallback(
|
|
(row: Record<string, string | number>) => {
|
|
if (!regAuthorityToolbarAllowed) {
|
|
alert("권한이 없습니다.");
|
|
return;
|
|
}
|
|
const ctx = lastSearchedValues;
|
|
if (!ctx?.year || !ctx?.sido || !ctx?.bsnmCd) {
|
|
alert("먼저 조회를 진행해주세요.");
|
|
return;
|
|
}
|
|
const statsCd = String(row.statsCd ?? "").trim();
|
|
if (!statsCd) {
|
|
alert("통계 코드가 없어 예외처리 내역을 조회할 수 없습니다.");
|
|
return;
|
|
}
|
|
setExceptionHistoryContext({
|
|
statsYr: ctx.year ?? "",
|
|
sewerBzmnCd: ctx.bsnmCd ?? "",
|
|
ctpvNm: ctx.sido ?? "",
|
|
sggNm: ctx.gugun === "ALL" ? "" : (ctx.gugun ?? ""),
|
|
statsCd,
|
|
});
|
|
setExceptionHistoryOpen(true);
|
|
},
|
|
[alert, lastSearchedValues, regAuthorityToolbarAllowed],
|
|
);
|
|
|
|
const handleExceptionSubmitHistoryCellClick = useCallback(
|
|
({ fieldName, row }: DataCellClickCallbackParams) => {
|
|
if (fieldName !== "exceptionSubmitHistory") return;
|
|
openExceptionHistoryForRow(row);
|
|
},
|
|
[openExceptionHistoryForRow],
|
|
);
|
|
|
|
const handleExceptionHistoryRejectSuccess = useCallback(async () => {
|
|
if (!searchParams) return;
|
|
await mutate(["stats-input-status", searchParams]);
|
|
}, [mutate, searchParams]);
|
|
|
|
const gugunOkOrNotDoList = doList.includes(lastSearchedValues?.bsnmCd || "")
|
|
? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== "ALL")
|
|
: true;
|
|
|
|
const allRowsLaeSbmsnApproved = useMemo(
|
|
() =>
|
|
rowData.length > 0 &&
|
|
rowData.every((row) => String(row.laeSbmsnSttsCd ?? "") === "3"),
|
|
[rowData],
|
|
);
|
|
|
|
const showActionToolbarCard =
|
|
hasCompletedSearch &&
|
|
rowData.length > 0 &&
|
|
gugunOkOrNotDoList &&
|
|
inputPeriodCurrentRef.current &&
|
|
!allRowsLaeSbmsnApproved &&
|
|
regAuthorityToolbarAllowed;
|
|
|
|
return (
|
|
<>
|
|
<div className="relative">
|
|
<SearchForm onSubmit={handleSearch} onReset={handleReset} />
|
|
{showActionToolbarCard && (
|
|
<div className="card !py-0">
|
|
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
|
|
<ApproveButton approveFunc={approveFunc} />
|
|
</div>
|
|
</div>
|
|
)}
|
|
{(isLoading || isApproveLoading) && (
|
|
<div className="[data-theme='black']:bg-transparent absolute inset-0 z-50 flex items-center justify-center rounded-lg p-10">
|
|
<div className="relative size-[5rem]">
|
|
<LoadingWithAnimation color="primary" />
|
|
</div>
|
|
</div>
|
|
)}
|
|
<DataGrid
|
|
columnInfo={StatsInputStatusInpColumn}
|
|
rowStartNum={2}
|
|
fixedColumns={3}
|
|
rowData={rowData}
|
|
setRowData={setRowData}
|
|
isExcelImport={false}
|
|
showHeaderCheckbox={false}
|
|
getGridRef={gridRef}
|
|
onDataCellClick={handleExceptionSubmitHistoryCellClick}
|
|
cellStyleCallback={({ row, fieldName }) => {
|
|
if (fieldName !== "laeSbmsnSttsNm") {
|
|
return undefined;
|
|
}
|
|
|
|
const statusCd = String(row.laeSbmsnSttsCd ?? "");
|
|
if (statusCd === "0" || statusCd === "1") {
|
|
return "rg-cell-stats-status-pending";
|
|
}
|
|
if (statusCd === "2") {
|
|
return "rg-cell-stats-status-submitted";
|
|
}
|
|
if (statusCd === "3") {
|
|
return "rg-cell-stats-status-approved";
|
|
}
|
|
if (statusCd === "4") {
|
|
return "rg-cell-stats-status-rejected";
|
|
}
|
|
return undefined;
|
|
}}
|
|
/>
|
|
</div>
|
|
<ExceptionProcessingHistoryDialog
|
|
open={exceptionHistoryOpen}
|
|
onOpenChange={setExceptionHistoryOpen}
|
|
queryContext={exceptionHistoryContext}
|
|
onRejectSuccess={handleExceptionHistoryRejectSuccess}
|
|
/>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default StatsInputStatusInp;
|