Merge remote-tracking branch 'origin/iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 3m54s

This commit is contained in:
2026-05-13 15:05:26 +09:00
6 changed files with 91 additions and 61 deletions
@@ -1115,7 +1115,7 @@ const PublicSewageFacilityInp = () => {
if (!verification) return;
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
const hasImr = errors.some((e) => e.vrfcCd === "IMR");
const hasImr = errors.some((e) => e.vrfcCd === "IMR" || e.vrfcCd === "HB");
return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error";
}
}}
@@ -1127,10 +1127,11 @@ const PublicSewageFacilityInp = () => {
return errors
.map((e) => {
const prefix =
e.vrfcCd === "IMR" ? "IMR검증 - " : "일반검증 - ";
e.vrfcCd === "IMR" ? "IMR검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "HB검증 - ");
const comp = e.compVlCn ?? "";
const now = e.nowVlCn ?? "";
return `${prefix}${e.rsnExpln}(검증 기준값: ${comp} / 현재 입력값: ${now})`;
return `${prefix}${e.rsnExpln}
(검증 기준값: ${comp} / 현재 입력값: ${now})`;
})
.filter(Boolean)
.join("\n");
@@ -1978,7 +1978,7 @@ const SewagePipeInp = () => {
if (!verification) return;
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
const hasImr = errors.some((e) => e.vrfcCd === "IMR");
const hasImr = errors.some((e) => e.vrfcCd === "IMR" || e.vrfcCd === "HB");
return hasImr ? "rg-cell-verify-error-imr" : "rg-cell-verify-error";
}
}}
@@ -1990,10 +1990,11 @@ const SewagePipeInp = () => {
return errors
.map((e) => {
const prefix =
e.vrfcCd === "IMR" ? "IMR검증 - " : "일반검증 - ";
e.vrfcCd === "IMR" ? "IMR검증 - " : (e.vrfcCd === "NV" ? "일반검증 - " : "HB검증 - ");
const comp = e.compVlCn ?? "";
const now = e.nowVlCn ?? "";
return `${prefix}${e.rsnExpln}(검증 기준값: ${comp} / 현재 입력값: ${now})`;
return `${prefix}${e.rsnExpln}
(검증 기준값: ${comp} / 현재 입력값: ${now})`;
})
.filter(Boolean)
.join("\n");
@@ -766,7 +766,7 @@ const SeweragePenetrationRateInp = () => {
const errors = verification[fieldName];
if (Array.isArray(errors) && errors.length > 0) {
// IMR 검증은 노란색으로 구분
const hasImr = errors.some((e) => e.vrfcCd === 'IMR');
const hasImr = errors.some((e) => e.vrfcCd === 'IMR' || e.vrfcCd === 'HB');
return hasImr ? 'rg-cell-verify-error-imr' : 'rg-cell-verify-error';
}
}}
@@ -777,8 +777,9 @@ const SeweragePenetrationRateInp = () => {
if (Array.isArray(errors) && errors.length > 0) {
return errors
.map((e) => {
const prefix = e.vrfcCd === 'IMR' ? 'IMR검증 - ' : '일반검증 - ';
return `${prefix}${e.rsnExpln}(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
const prefix = e.vrfcCd === 'IMR' ? 'IMR검증 - ' : (e.vrfcCd === 'NM' ? '일반검증 - ' : 'HB검증 -');
return `${prefix}${e.rsnExpln}
(검증 기준값: ${e.compVlCn} / 현재 입력값: ${e.nowVlCn})`;
})
.filter(Boolean)
.join('\n');
@@ -4,13 +4,17 @@ import { RealGridReact } from "realgrid-react";
import useSWR, { useSWRConfig } from "swr";
import { 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 {
isInputPeriodCurrent,
type CheckInputPeriodItem,
} from "@/entities/statistics/model/input/statistics-sewage-supply-rate-input-interface";
import type { StatsInputStatusListRequestParams } from "@/entities/statistics/model/input/statistics-input-status-interface";
import type { StatsInputStatusListApiResponse } from "@/entities/statistics/model/input/statistics-input-status-interface";
import { LoadingWithAnimation } from "@common/components";
import ApproveButton from "@/components/button/ApproveButton";
import RejectButton from "@/components/button/RejectButton";
import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import type { SearchFormValues } from "@/components/type/SearchForm.type";
@@ -37,6 +41,8 @@ const StatsInputStatusInp = () => {
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 swrKey = searchParams
@@ -72,21 +78,38 @@ const StatsInputStatusInp = () => {
}
}, [activeTabId]);
const handleSearch = (values: SearchFormValues) => {
const handleSearch = async (values: SearchFormValues) => {
if (values.sido === "" || values.sido === "ALL") {
alert("시도를 선택해주세요.");
return;
}
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]);
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 = () => {
@@ -94,10 +117,8 @@ const StatsInputStatusInp = () => {
setRowData([]);
setLastSearchedValues(null);
setHasCompletedSearch(false);
};
const rejectFunc = () => {
// 보완 요청 로직
periodCheckListRef.current = [];
inputPeriodCurrentRef.current = false;
};
const approveFunc = () => {
@@ -125,7 +146,7 @@ const StatsInputStatusInp = () => {
? Boolean(lastSearchedValues?.gugun && lastSearchedValues.gugun !== "ALL")
: true;
const showActionToolbarCard =
hasCompletedSearch && rowData.length > 0 && gugunOkOrNotDoList;
hasCompletedSearch && rowData.length > 0 && gugunOkOrNotDoList && inputPeriodCurrentRef.current;
return (
<>
@@ -134,7 +155,6 @@ const StatsInputStatusInp = () => {
{showActionToolbarCard && (
<div className="card !py-0">
<div className="m-[0.6rem] flex items-center justify-end gap-[0.6rem]">
<RejectButton rejectFunc={rejectFunc} />
<ApproveButton approveFunc={approveFunc} />
</div>
</div>
@@ -1,20 +1,22 @@
import { useMemo, useState } from "react";
import { LoadingWithAnimation } from "@common/components";
import { getUserInfo } from "../../../../../../packages/common/components/src/api";
import { useConfirm } from "@frontend-opensource/use-react-hooks";
import { useAlertStore } from "@/shared/lib";
import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import { statisticsManagementApi } from "@/entities/statistics/api/statistics-management-service";
import type { PublicSewageFacilityReviewItem } from "@/entities/statistics/model/statistics-public-sewage-facility-review-interface";
import type { StatsStatusInfo } from "@/entities/statistics/model/statistics-sewage-pipe-review-interface";
import type { ReviewOpinionSaveRequest } from "@/entities/statistics/model/statistics-interface";
import type {
ReviewOpinionSaveRequest,
ReviewerInfoRequest,
} from "@/entities/statistics/model/statistics-interface";
import DataGrid from "@/components/module/DataGrid";
import SearchForm from "@/components/module/SearchForm";
import SubmitButton from "@/components/button/SumitButton";
import ReviewOpinionButton from "@/components/button/ReviewOpinionButton";
import ReturnButton from "@/components/button/ReturnButton";
import { ReturnReasonDialog } from "@/components/statistics/review/ReturnReasonDialog";
import { ReviewSubmitDialog } from "@/components/statistics/review/ReviewSubmitDialog";
import {
ReviewOpinionDialog,
type ReviewOpinionDialogContext,
@@ -27,12 +29,13 @@ import { PublicSewageFacilityInfoList } from "@/components/util/column/review/Pu
// 하수도 통계 관리 06.공공하수처리시설
const PublicSewageFacilityRvw = () => {
const { setMessage: alert } = useAlertStore((state) => state);
const { confirm } = useConfirm();
const userInfo = useUserStore((state) => state.userInfo);
const [rowData, setRowData] = useState<Record<string, string | number>[]>([]);
const [isLoading, setIsLoading] = useState<boolean>(false);
const [statsStatusInfo, setStatsStatusInfo] = useState<StatsStatusInfo>({});
const [reviewOpinionOpen, setReviewOpinionOpen] = useState(false);
const [returnDialogOpen, setReturnDialogOpen] = useState(false);
const [submitDialogOpen, setSubmitDialogOpen] = useState(false);
const [reviewOpinionContext, setReviewOpinionContext] =
useState<ReviewOpinionDialogContext | null>(null);
const [latestReviewOpinion, setLatestReviewOpinion] = useState("");
@@ -67,6 +70,24 @@ const PublicSewageFacilityRvw = () => {
const showActionBar =
showReviewOpinionButton || showReturnButton || showSubmitButton;
const submitDialogContext = useMemo<ReviewerInfoRequest | null>(() => {
if (reviewOpinionContext == null) {
return null;
}
const instSeCd = userInfo?.instSeCd ?? "";
if (!instSeCd) {
return null;
}
return {
statsYr: reviewOpinionContext.statsYr,
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
statsCd: reviewOpinionContext.statsCd,
instSeCd,
};
}, [reviewOpinionContext, userInfo?.instSeCd]);
const handleReset = () => {
setRowData([]);
setStatsStatusInfo({});
@@ -74,6 +95,7 @@ const PublicSewageFacilityRvw = () => {
setLatestReviewOpinion("");
setLatestProcessHistory("");
setLastSearchValues(null);
setSubmitDialogOpen(false);
};
const handleSearch = async (values: SearchFormValues) => {
@@ -134,37 +156,13 @@ const PublicSewageFacilityRvw = () => {
}
};
const submitFunc = async () => {
if (reviewOpinionContext == null) {
const submitFunc = () => {
if (submitDialogContext == null) {
alert("조회 후 제출할 수 있습니다.");
return;
}
if (!(await confirm("제출 하시겠습니까?"))) {
return;
}
try {
const userInfo = await getUserInfo();
const response = await statisticsApi.submitReview({
statsYr: reviewOpinionContext.statsYr,
sewerBzmnCd: reviewOpinionContext.sewerBzmnCd,
statsCd: reviewOpinionContext.statsCd,
mdfrId: userInfo.userUnqId,
});
if (response.status !== "success") {
alert(response.message || "제출에 실패했습니다.");
return;
}
alert(response.message || "제출되었습니다.");
if (lastSearchValues != null) {
await handleSearch(lastSearchValues);
}
} catch {
alert("제출 중 오류가 발생했습니다.");
}
setSubmitDialogOpen(true);
};
const reviewOpinionFunc = async () => {
@@ -261,6 +259,16 @@ const PublicSewageFacilityRvw = () => {
initialProcessHistory={latestProcessHistory}
onSave={saveReviewOpinion}
/>
<ReviewSubmitDialog
open={submitDialogOpen}
onOpenChange={setSubmitDialogOpen}
context={submitDialogContext}
onSubmitted={async () => {
if (lastSearchValues != null) {
await handleSearch(lastSearchValues);
}
}}
/>
<ReturnReasonDialog
open={returnDialogOpen}
onOpenChange={setReturnDialogOpen}
@@ -23,7 +23,6 @@
border-radius: 0.5rem !important;
/* box-shadow: 0 0 10px -1px rgba(0, 0, 0, 0.06); */
}
/* 헤더 스타일 */
.rg-header,
.rg-head,
@@ -115,4 +114,4 @@
.rg-cell-verify-error {
background-color: #ffe6e6 !important;
color: #c00 !important;
}
}