Merge remote-tracking branch 'origin/iipbs/st/dev'
Deploy Frontend / deploy (push) Successful in 5m9s
Deploy Frontend / deploy (push) Successful in 5m9s
This commit is contained in:
@@ -28,7 +28,7 @@ import {
|
||||
SelectValue,
|
||||
} from "@common/components";
|
||||
|
||||
import { doList } from "@/components/util/column/input/DoList";
|
||||
import { doList, doNameList } from "@/components/util/column/input/DoList";
|
||||
|
||||
import {
|
||||
defaultSearchFormProps,
|
||||
@@ -41,12 +41,27 @@ function isDoListBsnm(bsnmCd: string | undefined): boolean {
|
||||
return Boolean(bsnmCd && doList.includes(bsnmCd));
|
||||
}
|
||||
|
||||
function isDoSido(sido?: { bsnmCd?: string; sido?: string }): boolean {
|
||||
if (!sido) return false;
|
||||
if (isDoListBsnm(sido.bsnmCd)) return true;
|
||||
return Boolean(sido.sido && doNameList.includes(sido.sido));
|
||||
}
|
||||
|
||||
function isAllGugunValue(gugun: string | undefined): boolean {
|
||||
const value = (gugun ?? "").trim();
|
||||
return !value || value === "ALL" || value === "전체";
|
||||
}
|
||||
|
||||
function firstConcreteSgg(list: Sgg[] | undefined): Sgg | undefined {
|
||||
return (list ?? []).find((sgg) => !isAllGugunValue(sgg.gugun));
|
||||
}
|
||||
|
||||
const getSggListFetcher = async (
|
||||
key: [string, string, string, "Y" | "N"],
|
||||
): Promise<ApiResponse<Sgg[]>> => {
|
||||
const [, sidoName, instSeCd, gugunSkipYn] = key;
|
||||
const [kind, sidoName, instSeCd, gugunSkipYn] = key;
|
||||
const params = { instSeCd, gugunSkipYn };
|
||||
if (gugunSkipYn === "N") {
|
||||
if (kind === "sgg-all") {
|
||||
return statisticsApi.getSggAllList(sidoName, params);
|
||||
}
|
||||
return statisticsApi.getSggList(sidoName, params);
|
||||
@@ -69,7 +84,7 @@ function buildSearchFormValues(
|
||||
);
|
||||
const gugun =
|
||||
d.gugun ||
|
||||
(sidoItem && !isDoListBsnm(sidoItem.bsnmCd) ? "ALL" : "");
|
||||
(sidoItem && !isDoSido(sidoItem) ? "ALL" : "");
|
||||
return {
|
||||
sido: d.sido,
|
||||
gugun,
|
||||
@@ -105,7 +120,7 @@ function buildSearchFormValues(
|
||||
return {
|
||||
sido: useFirstSido ? first.sido : "",
|
||||
gugun:
|
||||
useFirstSido && first && !isDoListBsnm(first.bsnmCd) ? "ALL" : "",
|
||||
useFirstSido && first && !isDoSido(first) ? "ALL" : "",
|
||||
year: d?.year || defaultYear.toString(),
|
||||
order: d?.order || "",
|
||||
fctName: d?.fctName || "",
|
||||
@@ -171,7 +186,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
: {
|
||||
...prev,
|
||||
sido: first.sido,
|
||||
gugun: isDoListBsnm(first.bsnmCd) ? "" : "ALL",
|
||||
gugun: isDoSido(first) ? "" : "ALL",
|
||||
bsnmCd: first.bsnmCd,
|
||||
},
|
||||
);
|
||||
@@ -186,10 +201,12 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
(item) => item.sido === values.sido,
|
||||
);
|
||||
const gugunSkipYn = selectedSido?.gugunSkipYn ?? "Y";
|
||||
const useSggAllList =
|
||||
gugunSkipYn === "N" && !isDoListBsnm(selectedSido?.bsnmCd);
|
||||
const sggKey: [string, string, string, "Y" | "N"] | null =
|
||||
values.sido && values.sido !== "ALL" && sidoList?.data
|
||||
? [
|
||||
gugunSkipYn === "N" ? "sgg-all" : "sgg-list",
|
||||
useSggAllList ? "sgg-all" : "sgg-list",
|
||||
values.sido,
|
||||
instSeCd,
|
||||
gugunSkipYn,
|
||||
@@ -217,45 +234,49 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
},
|
||||
);
|
||||
const sggOptions = sggList?.data ?? [];
|
||||
const isDoSelected = isDoSido(selectedSido);
|
||||
const firstConcreteSggOption = firstConcreteSgg(sggOptions);
|
||||
const gugunSelectValue =
|
||||
isDoSelected && isAllGugunValue(values.gugun)
|
||||
? (firstConcreteSggOption?.gugun ?? undefined)
|
||||
: values.gugun || undefined;
|
||||
|
||||
// 도(doList): 시군구 목록 첫 항목. 광역시·세종·제주: 전체
|
||||
// 도: 전체(ALL/"")가 아니라 그 다음 시군구. 그 외: 전체
|
||||
useEffect(() => {
|
||||
if (!mergedProps.gugun) return;
|
||||
if (props.defaultRegionAll) return;
|
||||
if (props.defaultValues?.gugun) return;
|
||||
if (!values.sido || values.sido === "ALL") return;
|
||||
if (!selectedSido) return;
|
||||
|
||||
if (!isDoListBsnm(selectedSido.bsnmCd)) {
|
||||
if (!isDoSelected) {
|
||||
setValues((prev) => {
|
||||
if (prev.gugun) return prev;
|
||||
return {
|
||||
...prev,
|
||||
gugun: "ALL",
|
||||
bsnmCd: selectedSido?.bsnmCd || prev.bsnmCd,
|
||||
bsnmCd: selectedSido.bsnmCd || prev.bsnmCd,
|
||||
};
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const firstSgg = sggList?.data?.[0];
|
||||
if (!firstSgg) return;
|
||||
if (!firstConcreteSggOption) return;
|
||||
|
||||
setValues((prev) => {
|
||||
if (prev.gugun) return prev;
|
||||
if (!isAllGugunValue(prev.gugun)) return prev;
|
||||
return {
|
||||
...prev,
|
||||
gugun: firstSgg.gugun,
|
||||
bsnmCd: firstSgg.bsnmCd,
|
||||
gugun: firstConcreteSggOption.gugun,
|
||||
bsnmCd: firstConcreteSggOption.bsnmCd,
|
||||
};
|
||||
});
|
||||
}, [
|
||||
sggList,
|
||||
values.sido,
|
||||
selectedSido?.bsnmCd,
|
||||
firstConcreteSggOption,
|
||||
isDoSelected,
|
||||
mergedProps.gugun,
|
||||
props.defaultValues?.gugun,
|
||||
props.defaultRegionAll,
|
||||
selectedSido,
|
||||
values.sido,
|
||||
]);
|
||||
|
||||
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
|
||||
@@ -269,15 +290,13 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
};
|
||||
|
||||
const handleSidoChange = (value: string) => {
|
||||
const selectedBsnm =
|
||||
const selected =
|
||||
value === "ALL"
|
||||
? ""
|
||||
: sidoList?.data?.sidoList?.find((v) => v.sido === value)
|
||||
?.bsnmCd || "";
|
||||
? undefined
|
||||
: sidoList?.data?.sidoList?.find((v) => v.sido === value);
|
||||
const selectedBsnm = selected?.bsnmCd || "";
|
||||
const gugunAll =
|
||||
props.defaultRegionAll ||
|
||||
value === "ALL" ||
|
||||
!isDoListBsnm(selectedBsnm);
|
||||
props.defaultRegionAll || value === "ALL" || !isDoSido(selected);
|
||||
setValues((prev) => ({
|
||||
...prev,
|
||||
sido: value,
|
||||
@@ -341,7 +360,7 @@ const SearchForm = (props: SearchFormProps = {}) => {
|
||||
<div className="flex items-center gap-2">
|
||||
<Label className="text-label">시군구</Label>
|
||||
<Select
|
||||
value={values.gugun}
|
||||
value={gugunSelectValue}
|
||||
onValueChange={handleSggChange}
|
||||
>
|
||||
<SelectTrigger className="w-[12rem]">
|
||||
|
||||
@@ -98,6 +98,7 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
{ name: "vrfcMthdCd", fieldName: "vrfcMthdCd", header: { text: "검증유형코드" }, editable: false, visible: false },
|
||||
{ name: "ctpvNm", fieldName: "ctpvNm", header: { text: "시도" }, editable: false },
|
||||
{ name: "admdstNm", fieldName: "admdstNm", header: { text: "행정구역" }, editable: false },
|
||||
{ name: "fcltNm", fieldName: "fcltNm", header: { text: "시설명" }, editable: false },
|
||||
{ name: "vrfcTrgtNm", fieldName: "vrfcTrgtNm", header: { text: "검증대상" }, editable: false },
|
||||
{ name: "rsnExpln", fieldName: "rsnExpln", header: { text: "사유" }, editable: false },
|
||||
{ name: "nowVlCn", fieldName: "nowVlCn", header: { text: "입력값" }, editable: false },
|
||||
@@ -116,6 +117,7 @@ const verificationSubmitResultColumnInfo: ColumnInfo = {
|
||||
"vrfcMthdNm",
|
||||
"ctpvNm",
|
||||
"admdstNm",
|
||||
"fcltNm",
|
||||
"vrfcTrgtNm",
|
||||
"rsnExpln",
|
||||
"nowVlCn",
|
||||
@@ -206,6 +208,7 @@ export function VerificationResultDialog({
|
||||
vrfcMthdNm: VRFC_MTHD_NAME_MAP[vrfcMthdCd] ?? item.vrfcMthdNm,
|
||||
ctpvNm: item.ctpvNm,
|
||||
admdstNm: item.admdstNm ?? item.sggNm ?? "-",
|
||||
fcltNm: item.fcltNm ?? "-",
|
||||
vrfcTrgtNm: item.vrfcTrgtNm,
|
||||
rsnExpln: item.rsnExpln,
|
||||
nowVlCn: item.nowVlCn ?? "-",
|
||||
|
||||
@@ -810,6 +810,7 @@ const ExcretaTreatmentFacilityInp = () => {
|
||||
lae_sbmsn_stts_cd:
|
||||
statusInfo?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -835,7 +835,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
const userInfo = await getUserInfo();
|
||||
setGlobalProgressLabel("일반 검증 진행 중...");
|
||||
|
||||
let response: { status: string; message?: string; data?: { finalStatus?: string; nvpassed?: boolean } };
|
||||
let response: { status: string; message?: string; data?: { finalStatus?: string; nvpassed?: boolean; isHbPassed?: string } };
|
||||
|
||||
if (isSewageTreatTab) {
|
||||
const param: SewageTreatFactStatusSubmitRequest = {
|
||||
@@ -888,6 +888,7 @@ const PrivateSewageFacilityInp = () => {
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -434,6 +434,7 @@ const PublicSewageFacilityInp = () => {
|
||||
stats_yr: lastSearchedValues.year ?? '',
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === '4' ? '4' : '2',
|
||||
gen_verify_stts_cd: nvpassed ? '1' : '2',
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -793,6 +793,7 @@ const PumpingStationInp = () => {
|
||||
lae_sbmsn_stts_cd:
|
||||
statusInfo?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -1486,6 +1486,7 @@ const SewageExcretaRelatedInp = () => {
|
||||
lae_sbmsn_stts_cd:
|
||||
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -1489,6 +1489,7 @@ const SewageExcretaSlurgeInp = () => {
|
||||
lae_sbmsn_stts_cd:
|
||||
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -175,12 +175,13 @@ const applyPipeTypeStatusColumnLayout = (
|
||||
return walk(layout ?? []);
|
||||
};
|
||||
|
||||
/** ST00020003: 입력기간 통과 시 조회 연도의 매설연장(y{year}LyngPrlg)만 편집 가능, 해당 연도 철거연장 컬럼 숨김 */
|
||||
/** ST00020003: 일반 사용자는 조회 연도 매설연장만, 관리자는 매설연장 컬럼 전체 편집 가능. 해당 연도 철거연장 컬럼 숨김 */
|
||||
const applyPipeTypeStatusEditableColumns = (
|
||||
cols: ColumnConfig[] | undefined,
|
||||
canEdit: boolean,
|
||||
periodEnterable: boolean,
|
||||
statsYr: string | null,
|
||||
isAdmin: boolean,
|
||||
): ColumnConfig[] => {
|
||||
const hideDmltnYear = shouldHidePipeTypeEditableYearDmltn(canEdit, periodEnterable, statsYr);
|
||||
|
||||
@@ -205,11 +206,12 @@ const applyPipeTypeStatusEditableColumns = (
|
||||
if (fieldName === "lyngPrlg" || isYearLyngField) {
|
||||
const editableLyngField = periodEnterable ? resolvePipeTypeEditableLyngField(statsYr) : null;
|
||||
const isTargetYear = editableLyngField !== null && fieldName === editableLyngField;
|
||||
if (isTargetYear) {
|
||||
const { styleName: _readonlyStyle, ...rest } = col;
|
||||
if (isAdmin || isTargetYear) {
|
||||
const { styleName, ...rest } = col;
|
||||
return {
|
||||
...rest,
|
||||
editable: true as const,
|
||||
...(styleName && styleName !== "rg-cell-readonly" ? { styleName } : {}),
|
||||
};
|
||||
}
|
||||
return { ...col, editable: false as const, styleName: col.styleName ?? "rg-cell-readonly" };
|
||||
@@ -640,6 +642,7 @@ const SewagePipeInp = () => {
|
||||
canEdit,
|
||||
inputPeriodEnterable,
|
||||
lastSearchedYear,
|
||||
isAdminInfo,
|
||||
)
|
||||
: (cols ?? []).map((col: ColumnConfig) =>
|
||||
canEdit
|
||||
@@ -1289,7 +1292,6 @@ const SewagePipeInp = () => {
|
||||
fcltSn: nullableTrimmedString(d.fcltSn),
|
||||
swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(d.swtrmtFcltNm),
|
||||
prcsFcltCpct: formatNumber(d.prcsFcltCpct, "prcsFcltCpct"),
|
||||
prcsZoneArea: formatNumber(d.prcsZoneArea, "prcsZoneArea"),
|
||||
cbsysPrcsZoneArea: formatNumber(d.cbsysPrcsZoneArea, "cbsysPrcsZoneArea"),
|
||||
sprsysPrcsZoneArea: formatNumber(d.sprsysPrcsZoneArea, "sprsysPrcsZoneArea"),
|
||||
@@ -1493,6 +1495,7 @@ const SewagePipeInp = () => {
|
||||
stats_yr: values.year ?? "",
|
||||
lae_sbmsn_stts_cd: statusForTab?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -1772,7 +1775,6 @@ const SewagePipeInp = () => {
|
||||
fcltSn: nullableTrimmedString(d.fcltSn),
|
||||
swtrmtFcltCd: nullableTrimmedString(d.swtrmtFcltCd),
|
||||
swtrmtFcltNm: nullableTrimmedString(d.swtrmtFcltNm),
|
||||
prcsFcltCpct: formatNumber(d.prcsFcltCpct, "prcsFcltCpct"),
|
||||
prcsZoneArea: formatNumber(d.prcsZoneArea, "prcsZoneArea"),
|
||||
cbsysPrcsZoneArea: formatNumber(d.cbsysPrcsZoneArea, "cbsysPrcsZoneArea"),
|
||||
sprsysPrcsZoneArea: formatNumber(d.sprsysPrcsZoneArea, "sprsysPrcsZoneArea"),
|
||||
|
||||
@@ -675,6 +675,7 @@ const SewagePipeMaintainInp = () => {
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -860,6 +860,7 @@ const SewageReservoirPumpInp = () => {
|
||||
lae_sbmsn_stts_cd:
|
||||
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -945,6 +945,7 @@ const SewerageFinanceInp = () => {
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -559,6 +559,7 @@ const SeweragePenetrationRateInp = () => {
|
||||
stats_yr: searchValues?.year ?? '',
|
||||
lae_sbmsn_stts_cd: (statusInfo?.laeSbmsnSttsCd === '4' ? '4' : '2'),
|
||||
gen_verify_stts_cd: response.data.nvpassed ? '1' : '2',
|
||||
hb_pass_yn: response.data.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -837,6 +837,7 @@ const SewerageServiceLevelInp = () => {
|
||||
stats_yr: lastSearchedValues.year ?? "",
|
||||
lae_sbmsn_stts_cd: activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -1195,7 +1195,7 @@ const WaterReuseInp = () => {
|
||||
let response: {
|
||||
status: string;
|
||||
message?: string;
|
||||
data?: { finalStatus: string; nvpassed: boolean };
|
||||
data?: { finalStatus: string; nvpassed: boolean; isHbPassed?: string };
|
||||
};
|
||||
|
||||
if (isPublicSewageTab) {
|
||||
@@ -1352,6 +1352,7 @@ const WaterReuseInp = () => {
|
||||
lae_sbmsn_stts_cd:
|
||||
activeTabStatus?.laeSbmsnSttsCd === "4" ? "4" : "2",
|
||||
gen_verify_stts_cd: nvpassed ? "1" : "2",
|
||||
hb_pass_yn: response.data?.isHbPassed,
|
||||
};
|
||||
|
||||
try {
|
||||
|
||||
@@ -14,7 +14,7 @@ export const FactInstallStatusInpColumn = {
|
||||
// 하수처리장 정보
|
||||
{ name: 'swtrmtFcltNm', fieldName: 'swtrmtFcltNm', header: { text: '하수처리장명' }, footer: { text: '합계' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'admdstNm', fieldName: 'admdstNm', header: { text: '행정구역명' }, editable: false, styleName: 'rg-cell-readonly' },
|
||||
{ name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '하수처리시설용량(m³/일)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0' },
|
||||
{ name: 'prcsFcltCpct', fieldName: 'prcsFcltCpct', header: { text: '하수처리시설용량(m³/일)' }, editable: false, footer: footerInt, valueType: ValueType.NUMBER, dataFormat: '#,##0', styleName: 'rg-cell-readonly' },
|
||||
|
||||
// 처리구역면적(km²)
|
||||
{
|
||||
|
||||
@@ -88,6 +88,23 @@ export function useDashboardEcptReqCount(params: DashboardSearchParams | null) {
|
||||
});
|
||||
}
|
||||
|
||||
// 지자체/광역시도/공단 3단계 처리상태 조회
|
||||
export function useDashboardThreeStageStatus(
|
||||
params: DashboardSearchParams | null,
|
||||
) {
|
||||
return useQuery({
|
||||
queryKey: createDashboardQueryKey("three-stage-status", params),
|
||||
queryFn: async () => {
|
||||
if (!params)
|
||||
throw new Error("Dashboard search params are required.");
|
||||
const response = await dashboardApi.getThreeStageStatus(params);
|
||||
return response.data;
|
||||
},
|
||||
enabled: hasRequiredDashboardParams(params),
|
||||
refetchOnWindowFocus: false,
|
||||
});
|
||||
}
|
||||
|
||||
function createDashboardQueryKey(
|
||||
name: string,
|
||||
params: DashboardSearchParams | null,
|
||||
|
||||
@@ -6,6 +6,7 @@ import type {
|
||||
DashboardSearchParams,
|
||||
DashboardService,
|
||||
EcptReqCountResponse,
|
||||
ThreeStageStatusResponse,
|
||||
StatsSubmissionResponse,
|
||||
UnSubmitCountResponse,
|
||||
VrfcErrCountResponse,
|
||||
@@ -57,10 +58,22 @@ async function getEcptReqCount(
|
||||
return response.data;
|
||||
}
|
||||
|
||||
async function getThreeStageStatus(
|
||||
params: DashboardSearchParams,
|
||||
): Promise<DashboardApiResponse<ThreeStageStatusResponse>> {
|
||||
const response = await stClient.get<DashboardApiResponse<ThreeStageStatusResponse>>(
|
||||
`${ENDPOINT.ST_SERVICE.DASHBOARD}/three-stage-status`,
|
||||
{ params },
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
|
||||
export const dashboardApi: DashboardService = {
|
||||
getVrfcReport,
|
||||
getVrfcErrCount,
|
||||
getUnSubmitCount,
|
||||
getStatsSubmissionRate,
|
||||
getEcptReqCount,
|
||||
getThreeStageStatus,
|
||||
};
|
||||
|
||||
@@ -65,6 +65,16 @@ export interface EcptReqCountResponse {
|
||||
ecptReqCount: number;
|
||||
}
|
||||
|
||||
export interface ThreeStageStatusResponse {
|
||||
statsYr: string;
|
||||
laeStatusCd: string;
|
||||
laeStatusNm: string;
|
||||
ctpvStatusCd: string;
|
||||
ctpvStatusNm: string;
|
||||
pbcpStatusCd: string;
|
||||
pbcpStatusNm: string;
|
||||
}
|
||||
|
||||
export interface DashboardService {
|
||||
getVrfcReport(
|
||||
params: DashboardSearchParams,
|
||||
@@ -81,4 +91,7 @@ export interface DashboardService {
|
||||
getEcptReqCount(
|
||||
params: DashboardSearchParams,
|
||||
): Promise<DashboardApiResponse<EcptReqCountResponse>>;
|
||||
getThreeStageStatus(
|
||||
params: DashboardSearchParams,
|
||||
): Promise<DashboardApiResponse<ThreeStageStatusResponse>>;
|
||||
}
|
||||
|
||||
+1
@@ -182,6 +182,7 @@ export interface ExcretaTreatmentFacilitySubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+3
@@ -270,6 +270,7 @@ export interface SewageTreatFactStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -356,6 +357,7 @@ export interface SepticTankStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -419,6 +421,7 @@ export interface PrivateSewageDesigStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -175,6 +175,7 @@ export interface PumpingStationSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -303,6 +303,7 @@ export interface SewageExcretaRelatedSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+4
@@ -203,6 +203,7 @@ export interface SlurgeProcessingFacStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -312,6 +313,7 @@ export interface DigestorOperatingStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -417,6 +419,7 @@ export interface SewageSlurgeProcessingFacStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -523,6 +526,7 @@ export interface ExcretaSlurgeProcessingStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -157,6 +157,7 @@ export interface SewagePipeMaintainRepairSubmitResponse {
|
||||
data: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
message?: string;
|
||||
status: string;
|
||||
@@ -228,6 +229,7 @@ export interface SewagePipeMaintainDredgingSubmitResponse {
|
||||
data: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
message?: string;
|
||||
status: string;
|
||||
|
||||
+1
@@ -252,6 +252,7 @@ export interface SewageReservoirSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -168,6 +168,7 @@ export interface SewerageFinanceSettleSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -195,6 +196,7 @@ export interface SewerageFinanceSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -249,6 +249,7 @@ export interface SewerageServiceLevelSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -167,6 +167,7 @@ export interface PublicSewageTreatWaterReuseSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -280,6 +281,7 @@ export interface RecycledWaterStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -395,6 +397,7 @@ export interface RainFallUtilFactStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -495,6 +498,7 @@ export interface TreatWaterReuseDesignConstStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -595,6 +599,7 @@ export interface TreatWaterReuseBusinessApprStatusSubmitResponse {
|
||||
data?: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -277,6 +277,7 @@ export interface PublicSewageFacilitySubmitResponse {
|
||||
data: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
}
|
||||
message?: string;
|
||||
status: string;
|
||||
|
||||
@@ -114,6 +114,7 @@ export type SeweragePipeInputFactInstallStatusSaveItem = Omit<
|
||||
| "fcltSn"
|
||||
| "swtrmtFcltCd"
|
||||
| "swtrmtFcltNm"
|
||||
| "prcsFcltCpct"
|
||||
| "rmrk"
|
||||
> & {
|
||||
admdstCd: string | null;
|
||||
@@ -168,6 +169,7 @@ export interface SeweragePipeInputRegionalFactStatusSubmitResponse {
|
||||
data: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
}
|
||||
message?: string;
|
||||
status: string;
|
||||
@@ -198,6 +200,7 @@ export interface SeweragePipeInputFactInstallStatusSubmitResponse {
|
||||
data: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
}
|
||||
message?: string;
|
||||
status: string;
|
||||
@@ -208,6 +211,7 @@ export interface SeweragePipeInputSubmitResponse {
|
||||
data: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
}
|
||||
message?: string;
|
||||
status: string;
|
||||
|
||||
+1
@@ -147,6 +147,7 @@ export interface SewerageSupplyRateSubmitResponse {
|
||||
data: {
|
||||
finalStatus: string;
|
||||
nvpassed: boolean;
|
||||
isHbPassed?: string;
|
||||
}
|
||||
message?: string;
|
||||
status: string;
|
||||
|
||||
@@ -138,6 +138,7 @@ export interface ImrRequest {
|
||||
stats_yr: string;
|
||||
lae_sbmsn_stts_cd: string;
|
||||
gen_verify_stts_cd?: string;
|
||||
hb_pass_yn?: string;
|
||||
}
|
||||
|
||||
export interface ImrResponse {
|
||||
|
||||
@@ -2,9 +2,10 @@ import type { DashboardSearchParams } from "@/entities/statistics/model/dashboar
|
||||
import type { ScheduleInfoSearchParams } from "@/entities/statistics/model/dashboard/schedule-info-interface";
|
||||
|
||||
import { NoticePanel } from "../ui/panels/notice-panel";
|
||||
import { ScheduleCalendarPanel } from "../ui/panels/schedule-calendar-panel";
|
||||
// import { ScheduleCalendarPanel } from "../ui/panels/schedule-calendar-panel";
|
||||
import { ScheduleInfoPanel } from "../ui/panels/schedule-info-panel";
|
||||
import { StatsSubmissionPanel } from "../ui/panels/stats-submission-panel";
|
||||
import { ThreeStageStatusPanel } from "../ui/panels/three-stage-status-panel";
|
||||
import { UnSubmitPanel } from "../ui/panels/unsubmit-panel";
|
||||
// import { VrfcErrPanel } from "../ui/panels/vrfc-err-panel";
|
||||
import { VrfcReportPanel } from "../ui/panels/vrfc-report-panel";
|
||||
@@ -33,7 +34,7 @@ export function getInitialPanels({
|
||||
},
|
||||
{
|
||||
id: "panel-c",
|
||||
component: <NoticePanel />,
|
||||
component: <ThreeStageStatusPanel params={dashboardParams} />,
|
||||
className: "",
|
||||
},
|
||||
// {
|
||||
@@ -51,10 +52,15 @@ export function getInitialPanels({
|
||||
component: <VrfcReportPanel params={dashboardParams} />,
|
||||
className: "",
|
||||
},
|
||||
// {
|
||||
// id: "panel-f",
|
||||
// component: <ScheduleCalendarPanel />,
|
||||
// className: "",
|
||||
// },
|
||||
{
|
||||
id: "panel-f",
|
||||
component: <ScheduleCalendarPanel />,
|
||||
component: <NoticePanel />,
|
||||
className: "",
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
"use client";
|
||||
|
||||
import type { DashboardSearchParams } from "@/entities/statistics/model/dashboard/dashboard-interface";
|
||||
import { useDashboardThreeStageStatus } from "@/entities/statistics/api/dashboard/dashboard-queries";
|
||||
import { LoadingWithAnimation } from "@common/components";
|
||||
|
||||
import { useDragHandle } from "../sortable-panel";
|
||||
|
||||
type StepKey = "input" | "submit" | "approve" | "review" | "verify";
|
||||
|
||||
interface StepDef {
|
||||
key: StepKey;
|
||||
label: string;
|
||||
subLabel: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const STEPS: StepDef[] = [
|
||||
{ key: "input", label: "입력", subLabel: "작성자", description: "담당자가 통계 자료를 입력합니다." },
|
||||
{ key: "submit", label: "제출", subLabel: "작성자", description: "입력을 마친 자료를 제출합니다." },
|
||||
{ key: "approve", label: "승인", subLabel: "승인자", description: "지자체 승인자가 제출 내용을 승인합니다." },
|
||||
{ key: "review", label: "검토", subLabel: "시도", description: "광역시도 담당자가 자료를 검토합니다." },
|
||||
{ key: "verify", label: "검증", subLabel: "공단", description: "환경공단이 최종 검증을 진행합니다." },
|
||||
];
|
||||
|
||||
/**
|
||||
* lae_sbmsn_stts_cd(지자체) : 0=미작성 1=작성중 2=제출완료 3=승인
|
||||
* stat_pic_sbmsn_stts_cd(광역시도), pbcp_sbmsn_stts_cd(공단) 도 동일 코드 도메인 가정
|
||||
* - 실제 코드값 의미는 STINPUTSTP 코드 그룹 기준 재확인 필요
|
||||
*/
|
||||
function resolveCheckedSteps(data?: {
|
||||
laeStatusCd: string;
|
||||
ctpvStatusCd: string;
|
||||
pbcpStatusCd: string;
|
||||
}): boolean[] {
|
||||
if (!data) return STEPS.map(() => false);
|
||||
|
||||
const lae = Number(data.laeStatusCd);
|
||||
const ctpv = Number(data.ctpvStatusCd);
|
||||
const pbcp = Number(data.pbcpStatusCd);
|
||||
|
||||
return [
|
||||
lae >= 1,
|
||||
lae >= 2,
|
||||
lae >= 3,
|
||||
lae >= 3 && ctpv >= 3,
|
||||
lae >= 3 && ctpv >= 3 && pbcp >= 3,
|
||||
];
|
||||
}
|
||||
|
||||
interface ThreeStageStatusPanelProps {
|
||||
params: DashboardSearchParams | null;
|
||||
}
|
||||
|
||||
export function ThreeStageStatusPanel({ params }: ThreeStageStatusPanelProps) {
|
||||
const { dragHandleProps, isDragging } = useDragHandle();
|
||||
const { data, isLoading, isFetching } = useDashboardThreeStageStatus(params);
|
||||
|
||||
const checkedList = resolveCheckedSteps(data);
|
||||
const currentIndex = checkedList.findIndex((v) => !v);
|
||||
const isAllDone = currentIndex === -1;
|
||||
const currentStep = STEPS[isAllDone ? STEPS.length - 1 : currentIndex]!;
|
||||
const currentLabel = isAllDone ? "전 단계 완료" : `${currentStep.label} 대기중`;
|
||||
|
||||
return (
|
||||
<div className="card card-border !m-0 flex h-full flex-col border-border bg-background text-foreground">
|
||||
<div
|
||||
className={`card-header flex cursor-grab items-center justify-between border-border active:cursor-grabbing ${isDragging ? "opacity-50" : ""}`}
|
||||
{...dragHandleProps}
|
||||
title="헤더를 드래그하여 패널 순서 변경"
|
||||
>
|
||||
<h3 className="text-2xl font-semibold">통계 처리 현황</h3>
|
||||
<span
|
||||
className={`rounded-full px-4 py-1.5 text-base font-medium ${
|
||||
isAllDone
|
||||
? "bg-blue-50 text-blue-600"
|
||||
: "bg-amber-50 text-amber-600"
|
||||
}`}
|
||||
>
|
||||
{currentLabel}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="card-body relative flex flex-1 flex-col items-center justify-center px-8">
|
||||
<div className="flex w-full max-w-3xl flex-col items-center gap-10">
|
||||
<div className="flex w-full items-start">
|
||||
{STEPS.map((step, index) => {
|
||||
const isChecked = checkedList[index];
|
||||
const isCurrent = index === currentIndex;
|
||||
const isLast = index === STEPS.length - 1;
|
||||
const lineFilled = checkedList[index];
|
||||
|
||||
return (
|
||||
<div
|
||||
key={step.key}
|
||||
className="flex flex-1 flex-col items-center last:flex-none"
|
||||
>
|
||||
<div className="flex w-full items-center">
|
||||
<div className="relative flex flex-col items-center">
|
||||
{isCurrent && (
|
||||
<span className="absolute inset-0 -m-1.5 animate-ping rounded-full bg-blue-400 opacity-40" />
|
||||
)}
|
||||
<div
|
||||
className={`relative flex size-20 items-center justify-center rounded-full border-4 transition-colors ${
|
||||
isChecked
|
||||
? "border-blue-600 bg-blue-600 shadow-lg shadow-blue-600/20"
|
||||
: isCurrent
|
||||
? "border-blue-500 bg-white"
|
||||
: "border-gray-200 bg-white"
|
||||
}`}
|
||||
>
|
||||
{isChecked ? (
|
||||
<svg
|
||||
className="size-9 text-white"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
stroke="currentColor"
|
||||
strokeWidth={3}
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<polyline points="20 6 9 17 4 12" />
|
||||
</svg>
|
||||
) : (
|
||||
<span
|
||||
className={`size-4 rounded-full ${
|
||||
isCurrent ? "bg-blue-500" : "bg-gray-200"
|
||||
}`}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{!isLast && (
|
||||
<div className="mx-3 h-1.5 flex-1 rounded-full bg-gray-200 self-center">
|
||||
<div
|
||||
className="h-full rounded-full bg-blue-600 transition-all duration-500"
|
||||
style={{ width: lineFilled ? "100%" : "0%" }}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-4 text-center">
|
||||
<p
|
||||
className={`text-xl font-bold ${
|
||||
isChecked
|
||||
? "text-blue-700"
|
||||
: isCurrent
|
||||
? "text-blue-600"
|
||||
: "text-gray-400"
|
||||
}`}
|
||||
>
|
||||
{step.label}
|
||||
</p>
|
||||
<p className="mt-1 text-base text-muted-foreground">
|
||||
({step.subLabel})
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="w-full rounded-2xl border border-border bg-blue-50/40 px-7 py-6 text-center">
|
||||
<p className="text-base font-medium text-blue-600">
|
||||
{isAllDone ? "현재 단계" : "다음 진행 단계"} · {currentStep.label}
|
||||
</p>
|
||||
<p className="mt-2 text-lg text-foreground">
|
||||
{isAllDone
|
||||
? "모든 절차가 완료되었습니다."
|
||||
: currentStep.description}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{(isLoading || isFetching) && (
|
||||
<div className="bg-background/50 absolute inset-0 flex items-center justify-center">
|
||||
<div className="relative size-[5rem]">
|
||||
<LoadingWithAnimation color="primary" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user