wr: 페이징 수정
This commit is contained in:
@@ -40,11 +40,10 @@ import { useTranslation } from "react-i18next";
|
||||
const defaultSigunOptions: SelectDataType[] = [{ text: "전체", val: "0" }];
|
||||
|
||||
export default function HtdflSttn() {
|
||||
const { auth, code, htdflSttn, htdflSttnSearch, initHtdfl, getLaeSeCdCheck } = useWrStore((state) => state);
|
||||
const { auth, code, htdflSttn, htdflSttnSearch, getLaeSeCdCheck } = useWrStore((state) => state);
|
||||
const { laeSeCd } = auth; // 지자체구분코드
|
||||
const { htdflSttnRes, htdflSttnResCount } = htdflSttn;
|
||||
const isDownloadTriggered = useRef(false);
|
||||
const skipNextSearchRef = useRef(true);
|
||||
const initializedSearchKeyRef = useRef<string | null>(null);
|
||||
const { t } = useTranslation('base');
|
||||
const [pageNation, setPageNation] = useState(1);
|
||||
const [isChHead, setIsChHead] = useState(false);
|
||||
@@ -61,6 +60,10 @@ export default function HtdflSttn() {
|
||||
// chBuilding: chgBuilding[];
|
||||
// }>({ isOpen: false, chBuilding: [] });
|
||||
const [fcltNm, setFcltNm] = useState<string>("");
|
||||
const [appliedSearch, setAppliedSearch] = useState<{
|
||||
conValue: string;
|
||||
fcltNm: string;
|
||||
} | null>(null);
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const isAllAreaUser = laeSeCd === "0";
|
||||
const isLaeSeCdReady =
|
||||
@@ -89,7 +92,7 @@ export default function HtdflSttn() {
|
||||
setObjSearch({ ...objSearch, sgg: String(val) })
|
||||
};
|
||||
|
||||
const getConValue = (): string => {
|
||||
const getConValue = useCallback((): string => {
|
||||
if (!objSearch.city || objSearch.city === "0") {
|
||||
return "0";
|
||||
}
|
||||
@@ -97,7 +100,7 @@ export default function HtdflSttn() {
|
||||
return String(objSearch.sgg);
|
||||
}
|
||||
return String(objSearch.city);
|
||||
};
|
||||
}, [objSearch]);
|
||||
|
||||
const getFixedConValue = useCallback((): string => {
|
||||
if (laeSeCd === undefined || laeSeCd === "0") {
|
||||
@@ -105,7 +108,7 @@ export default function HtdflSttn() {
|
||||
}
|
||||
|
||||
return laeSeCd.length === 5 ? laeSeCd : laeSeCd.slice(0, 2);
|
||||
}, [laeSeCd, objSearch]);
|
||||
}, [laeSeCd, getConValue]);
|
||||
|
||||
const cellClick = (index: number | undefined, headId: string) => {
|
||||
if (
|
||||
@@ -117,14 +120,39 @@ export default function HtdflSttn() {
|
||||
}
|
||||
};
|
||||
|
||||
const excelClick = useCallback(() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
const excelClick = useCallback(async () => {
|
||||
if (!isLaeSeCdReady || !appliedSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
isDownloadTriggered.current = true;
|
||||
htdflSttnSearch(true, 1, htdflSttnResCount, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd);
|
||||
}, [htdflSttnSearch, fcltNm, htdflSttnResCount, code.cityCd, code.sggCd, getFixedConValue, isLaeSeCdReady]);
|
||||
const excelData = await htdflSttnSearch(
|
||||
true,
|
||||
1,
|
||||
htdflSttnResCount,
|
||||
appliedSearch.conValue,
|
||||
appliedSearch.fcltNm,
|
||||
code.cityCd,
|
||||
code.sggCd
|
||||
);
|
||||
if (excelData && excelData.length > 0) {
|
||||
const sheetArray: SheetParameter[] = [{
|
||||
data: excelData,
|
||||
headerNum: 3,
|
||||
headerRange: "A1:R3",
|
||||
sheetName: "sheet1",
|
||||
startCol: "A",
|
||||
decimalArray0: [6, 11, 13, 15],
|
||||
}];
|
||||
excelDownloadNumStyle(
|
||||
"온배수시설 현황",
|
||||
"sample_htdflSttn.xlsx",
|
||||
sheetArray
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
alert(t("excelDownErrMsg"));
|
||||
}, [htdflSttnSearch, htdflSttnResCount, appliedSearch, code.cityCd, code.sggCd, isLaeSeCdReady, alert, t]);
|
||||
|
||||
useEffect(() => {
|
||||
getLaeSeCdCheck();
|
||||
@@ -150,52 +178,39 @@ export default function HtdflSttn() {
|
||||
});
|
||||
}, [laeSeCd, code.sggCd, isLaeSeCdReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDownloadTriggered.current) {
|
||||
return;
|
||||
}
|
||||
if (htdflSttn.excelList.length > 0) {
|
||||
const sheetArray: SheetParameter[] = [{
|
||||
data: htdflSttn.excelList,
|
||||
headerNum: 3,
|
||||
headerRange: "A1:R3",
|
||||
sheetName: 'sheet1',
|
||||
startCol: 'A',
|
||||
decimalArray0: [6,11,13,15], // 0부터 시작
|
||||
}];
|
||||
excelDownloadNumStyle(
|
||||
"온배수시설 현황",
|
||||
"sample_htdflSttn.xlsx",
|
||||
sheetArray
|
||||
);
|
||||
} else {
|
||||
alert(t('excelDownErrMsg'));
|
||||
}
|
||||
isDownloadTriggered.current = false;
|
||||
}, [htdflSttn.excelList, code]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLaeSeCdReady || htdflSttn.isInit) {
|
||||
return;
|
||||
}
|
||||
|
||||
skipNextSearchRef.current = true;
|
||||
initHtdfl(false, 1, htdflSttnSize, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd);
|
||||
}, [htdflSttn.isInit, initHtdfl, fcltNm, code.cityCd, code.sggCd, getFixedConValue, isLaeSeCdReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (skipNextSearchRef.current) {
|
||||
skipNextSearchRef.current = false;
|
||||
const initialSearchKey = laeSeCd ?? "0";
|
||||
if (initializedSearchKeyRef.current === initialSearchKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
htdflSttnSearch(false, pageNation, htdflSttnSize, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd);
|
||||
const initialConValue =
|
||||
laeSeCd === undefined || laeSeCd === "0"
|
||||
? "0"
|
||||
: laeSeCd.length === 5
|
||||
? laeSeCd
|
||||
: laeSeCd.slice(0, 2);
|
||||
|
||||
initializedSearchKeyRef.current = initialSearchKey;
|
||||
setAppliedSearch({
|
||||
conValue: initialConValue,
|
||||
fcltNm: "",
|
||||
});
|
||||
setPageNation(1);
|
||||
}, [isLaeSeCdReady, laeSeCd]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLaeSeCdReady || !appliedSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
htdflSttnSearch(false, pageNation, htdflSttnSize, appliedSearch.conValue, appliedSearch.fcltNm, code.cityCd, code.sggCd);
|
||||
isResetPage(false)
|
||||
}, [pageNation, htdflSttnSearch, fcltNm, code.cityCd, code.sggCd, getFixedConValue, isLaeSeCdReady]);
|
||||
}, [pageNation, htdflSttnSearch, appliedSearch, code.cityCd, code.sggCd, isLaeSeCdReady]);
|
||||
|
||||
useEffect(() => {
|
||||
const data = tableData(htdflSttnRes, { code });
|
||||
@@ -215,6 +230,19 @@ export default function HtdflSttn() {
|
||||
setBdstTable(chData);
|
||||
}, [code, htdflSttnRes, htdflSttn.htdflSttHeadList]);
|
||||
|
||||
const handleSearch = useCallback(() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
setAppliedSearch({
|
||||
conValue: String(getFixedConValue()),
|
||||
fcltNm,
|
||||
});
|
||||
isResetPage(true);
|
||||
setPageNation(1);
|
||||
}, [fcltNm, getFixedConValue, isLaeSeCdReady]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ContentsDiv>
|
||||
@@ -266,13 +294,7 @@ export default function HtdflSttn() {
|
||||
<div className="flex flex-row space-x-2 items-center">
|
||||
<Button
|
||||
className={btnClass}
|
||||
onClick={() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
return;
|
||||
}
|
||||
htdflSttnSearch(false, 1,htdflSttnSize, String(getFixedConValue()), fcltNm, code.cityCd, code.sggCd);
|
||||
isResetPage(true)
|
||||
}}
|
||||
onClick={handleSearch}
|
||||
>
|
||||
조회
|
||||
</Button>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* 빗물이용시설 현황
|
||||
* 재이용시설 현황 > 빗물이용시설 현황
|
||||
*/
|
||||
"use client";
|
||||
import { useWrStore } from "@/app/store";
|
||||
@@ -38,7 +38,7 @@ import { useTranslation } from "react-i18next";
|
||||
const defaultSigunOptions: SelectDataType[] = [{ text: "전체", val: "0" }];
|
||||
|
||||
export default function RainSttn() {
|
||||
const { auth, code, rainSttn, rainSttnSearch, rainSttnInit, getLaeSeCdCheck } = useWrStore(
|
||||
const { auth, code, rainSttn, rainSttnSearch, getLaeSeCdCheck } = useWrStore(
|
||||
(state) => state
|
||||
);
|
||||
const { rainSttnRes, rainSttnResCount } = rainSttn;
|
||||
@@ -49,7 +49,6 @@ export default function RainSttn() {
|
||||
const [bdstTable, setBdstTable] = useState<customRainBdst[]>();
|
||||
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>(defaultSigunOptions);
|
||||
const [resetPage, isResetPage] = useState(false);
|
||||
const [isDownloadTriggered, setIsDownloadTriggered] = useState(false);
|
||||
const [objSearch, setObjSearch] = useState<objSearchType>({
|
||||
sgg: "",
|
||||
city: "",
|
||||
@@ -60,14 +59,13 @@ export default function RainSttn() {
|
||||
}>({ data: undefined, isOpen: false });
|
||||
|
||||
const [fcltNm, setFcltNm] = useState<string>("");
|
||||
const [appliedSearch, setAppliedSearch] = useState({
|
||||
conValue: "0",
|
||||
fcltNm: "",
|
||||
});
|
||||
const [searchTrigger, setSearchTrigger] = useState(0);
|
||||
const [appliedSearch, setAppliedSearch] = useState<{
|
||||
conValue: string;
|
||||
fcltNm: string;
|
||||
} | null>(null);
|
||||
const { t } = useTranslation("base");
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const skipNextSearchRef = useRef(true);
|
||||
const initializedSearchKeyRef = useRef<string | null>(null);
|
||||
const isAllAreaUser = laeSeCd === "0";
|
||||
const isLaeSeCdReady =
|
||||
laeSeCd === "0" || laeSeCd.length === 2 || laeSeCd.length === 5;
|
||||
@@ -100,7 +98,7 @@ export default function RainSttn() {
|
||||
setObjSearch({ ...objSearch, sgg: String(val) });
|
||||
};
|
||||
|
||||
const getConValue = (): string => {
|
||||
const getConValue = useCallback((): string => {
|
||||
if (!objSearch.city || objSearch.city === "0") {
|
||||
return "0";
|
||||
}
|
||||
@@ -108,7 +106,7 @@ export default function RainSttn() {
|
||||
return String(objSearch.sgg);
|
||||
}
|
||||
return String(objSearch.city);
|
||||
};
|
||||
}, [objSearch]);
|
||||
|
||||
const getFixedConValue = useCallback((): string => {
|
||||
if (laeSeCd === undefined || laeSeCd === "0") {
|
||||
@@ -116,7 +114,7 @@ export default function RainSttn() {
|
||||
}
|
||||
|
||||
return laeSeCd.length === 5 ? laeSeCd : laeSeCd.slice(0, 2);
|
||||
}, [laeSeCd, objSearch]);
|
||||
}, [laeSeCd, getConValue]);
|
||||
|
||||
useEffect(() => {
|
||||
getLaeSeCdCheck();
|
||||
@@ -143,7 +141,12 @@ export default function RainSttn() {
|
||||
}, [laeSeCd, code.sggCd, isLaeSeCdReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLaeSeCdReady || rainSttn.isInit) {
|
||||
if (!isLaeSeCdReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialSearchKey = laeSeCd ?? "0";
|
||||
if (initializedSearchKeyRef.current === initialSearchKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,32 +157,16 @@ export default function RainSttn() {
|
||||
? laeSeCd
|
||||
: laeSeCd.slice(0, 2);
|
||||
|
||||
skipNextSearchRef.current = true;
|
||||
initializedSearchKeyRef.current = initialSearchKey;
|
||||
setAppliedSearch({
|
||||
conValue: initialConValue,
|
||||
fcltNm: "",
|
||||
});
|
||||
rainSttnInit(
|
||||
false,
|
||||
1,
|
||||
rainSttnSize,
|
||||
initialConValue,
|
||||
"",
|
||||
code as unknown as Record<string, SelectDataType[]>
|
||||
);
|
||||
}, [rainSttn.isInit, rainSttnInit, code, isLaeSeCdReady, laeSeCd]);
|
||||
setPageNation(1);
|
||||
}, [isLaeSeCdReady, laeSeCd]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (skipNextSearchRef.current) {
|
||||
skipNextSearchRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchTrigger === 0) {
|
||||
if (!isLaeSeCdReady || !appliedSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -194,49 +181,18 @@ export default function RainSttn() {
|
||||
isResetPage(false);
|
||||
}, [
|
||||
pageNation,
|
||||
searchTrigger,
|
||||
rainSttnSearch,
|
||||
code,
|
||||
appliedSearch.conValue,
|
||||
appliedSearch.fcltNm,
|
||||
appliedSearch,
|
||||
isLaeSeCdReady,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
const excelClick = useCallback(async () => {
|
||||
if (!isLaeSeCdReady || !appliedSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isDownloadTriggered) {
|
||||
return;
|
||||
}
|
||||
if (rainSttn.excelList.length > 0) {
|
||||
const sheetArray: SheetParameter[] = [{
|
||||
data: rainSttn.excelList,
|
||||
headerNum: 3,
|
||||
headerRange: "A1:W3",
|
||||
sheetName: 'sheet1',
|
||||
startCol: 'A',
|
||||
decimalArray0: [6,10,12,14,16] // 0부터 시작
|
||||
}];
|
||||
excelDownloadNumStyle(
|
||||
"빗물이용시설 현황",
|
||||
"sample_rainSttn.xlsx", // public 폴더에 있는 template 파일 경로
|
||||
sheetArray
|
||||
);
|
||||
} else {
|
||||
alert(t("excelDownErrMsg"));
|
||||
}
|
||||
setIsDownloadTriggered(false);
|
||||
}, [rainSttn.excelList, isDownloadTriggered, alert, t, isLaeSeCdReady]);
|
||||
|
||||
const excelClick = useCallback(() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsDownloadTriggered(true);
|
||||
rainSttnSearch(
|
||||
const excelData = await rainSttnSearch(
|
||||
true,
|
||||
1,
|
||||
rainSttnResCount,
|
||||
@@ -244,16 +200,39 @@ export default function RainSttn() {
|
||||
appliedSearch.fcltNm,
|
||||
code as unknown as Record<string, SelectDataType[]>
|
||||
);
|
||||
if (excelData && excelData.length > 0) {
|
||||
const sheetArray: SheetParameter[] = [{
|
||||
data: excelData,
|
||||
headerNum: 3,
|
||||
headerRange: "A1:W3",
|
||||
sheetName: "sheet1",
|
||||
startCol: "A",
|
||||
decimalArray0: [6, 10, 12, 14, 16],
|
||||
}];
|
||||
excelDownloadNumStyle(
|
||||
"빗물이용시설 현황",
|
||||
"sample_rainSttn.xlsx",
|
||||
sheetArray
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
alert(t("excelDownErrMsg"));
|
||||
}, [
|
||||
rainSttnSearch,
|
||||
rainSttnResCount,
|
||||
code,
|
||||
appliedSearch.conValue,
|
||||
appliedSearch.fcltNm,
|
||||
appliedSearch,
|
||||
isLaeSeCdReady,
|
||||
alert,
|
||||
t,
|
||||
]);
|
||||
|
||||
const handleSearch = useCallback(() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextSearch = {
|
||||
conValue: String(getFixedConValue()),
|
||||
fcltNm,
|
||||
@@ -262,8 +241,7 @@ export default function RainSttn() {
|
||||
setAppliedSearch(nextSearch);
|
||||
isResetPage(true);
|
||||
setPageNation(1);
|
||||
setSearchTrigger((prev) => prev + 1);
|
||||
}, [fcltNm, getFixedConValue]);
|
||||
}, [fcltNm, getFixedConValue, isLaeSeCdReady]);
|
||||
|
||||
useEffect(() => {
|
||||
const data = tableData(rainSttnRes, { code });
|
||||
|
||||
@@ -38,7 +38,7 @@ import { useTranslation } from "react-i18next";
|
||||
const defaultSigunOptions: SelectDataType[] = [{ text: "전체", val: "0" }];
|
||||
|
||||
export default function RswtSttn() {
|
||||
const { auth, code, rswtSttn, rswtSttnSearch, rswtSttnInit, getLaeSeCdCheck } = useWrStore(
|
||||
const { auth, code, rswtSttn, rswtSttnSearch, getLaeSeCdCheck } = useWrStore(
|
||||
(state) => state
|
||||
);
|
||||
|
||||
@@ -49,17 +49,15 @@ export default function RswtSttn() {
|
||||
const [isChHead, setIsChHead] = useState(false);
|
||||
const [resetPage, isResetPage] = useState(false);
|
||||
const [bdstTable, setBdstTable] = useState<customRswtBdst[]>();
|
||||
const isDownloadTriggered = useRef(false);
|
||||
const skipNextSearchRef = useRef(true);
|
||||
const initializedSearchKeyRef = useRef<string | null>(null);
|
||||
const [selectSgg, setSelectSgg] = useState<SelectDataType[] | []>(defaultSigunOptions)
|
||||
const [objSearch, setObjSearch] = useState<objSearchType>({ sgg: "", city: "" });
|
||||
const [detailModal, setDetailModal] = useState<{ isOpen: boolean, data: customRswtBdst | undefined }>({ data: undefined, isOpen: false })
|
||||
const [fcltNm, setFcltNm] = useState<string>("");
|
||||
const [appliedSearch, setAppliedSearch] = useState({
|
||||
conValue: "0",
|
||||
fcltNm: "",
|
||||
});
|
||||
const [searchTrigger, setSearchTrigger] = useState(0);
|
||||
const [appliedSearch, setAppliedSearch] = useState<{
|
||||
conValue: string;
|
||||
fcltNm: string;
|
||||
} | null>(null);
|
||||
const { t } = useTranslation('base');
|
||||
const alert = useAlertStore((state) => state.setMessage);
|
||||
const isAllAreaUser = laeSeCd === "0";
|
||||
@@ -95,7 +93,7 @@ export default function RswtSttn() {
|
||||
setObjSearch({ ...objSearch, sgg: String(val) })
|
||||
};
|
||||
|
||||
const getConValue = (): string => {
|
||||
const getConValue = useCallback((): string => {
|
||||
if (!objSearch.city || objSearch.city === "0") {
|
||||
return "0";
|
||||
}
|
||||
@@ -103,7 +101,7 @@ export default function RswtSttn() {
|
||||
return String(objSearch.sgg);
|
||||
}
|
||||
return String(objSearch.city);
|
||||
};
|
||||
}, [objSearch]);
|
||||
|
||||
const getFixedConValue = useCallback((): string => {
|
||||
if (laeSeCd === undefined || laeSeCd === "0") {
|
||||
@@ -111,7 +109,7 @@ export default function RswtSttn() {
|
||||
}
|
||||
|
||||
return laeSeCd.length === 5 ? laeSeCd : laeSeCd.slice(0, 2);
|
||||
}, [laeSeCd, objSearch]);
|
||||
}, [laeSeCd, getConValue]);
|
||||
|
||||
useEffect(() => {
|
||||
getLaeSeCdCheck();
|
||||
@@ -138,7 +136,12 @@ export default function RswtSttn() {
|
||||
}, [laeSeCd, code.sggCd, isLaeSeCdReady]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLaeSeCdReady || rswtSttn.isInit) {
|
||||
if (!isLaeSeCdReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
const initialSearchKey = laeSeCd ?? "0";
|
||||
if (initializedSearchKeyRef.current === initialSearchKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -149,20 +152,13 @@ export default function RswtSttn() {
|
||||
? laeSeCd
|
||||
: laeSeCd.slice(0, 2);
|
||||
|
||||
skipNextSearchRef.current = true;
|
||||
initializedSearchKeyRef.current = initialSearchKey;
|
||||
setAppliedSearch({
|
||||
conValue: initialConValue,
|
||||
fcltNm: "",
|
||||
});
|
||||
rswtSttnInit(
|
||||
false,
|
||||
1,
|
||||
rswtSttnSize,
|
||||
initialConValue,
|
||||
"",
|
||||
code as unknown as Record<string, SelectDataType[]>
|
||||
);
|
||||
}, [rswtSttn.isInit, rswtSttnInit, code, isLaeSeCdReady, laeSeCd]);
|
||||
setPageNation(1);
|
||||
}, [isLaeSeCdReady, laeSeCd]);
|
||||
|
||||
useEffect(() => {
|
||||
const data = tableData(rswtSttnRes, { code });
|
||||
@@ -194,56 +190,47 @@ export default function RswtSttn() {
|
||||
setBdstTable(chData);
|
||||
}, [code, rswtSttnRes]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isDownloadTriggered.current) {
|
||||
const excelClick = useCallback(async () => {
|
||||
if (!isLaeSeCdReady || !appliedSearch) {
|
||||
return;
|
||||
}
|
||||
if (rswtSttn.excelList.length > 0) {
|
||||
|
||||
const excelData = await rswtSttnSearch(
|
||||
true,
|
||||
1,
|
||||
rswtSttnResCount,
|
||||
appliedSearch.conValue,
|
||||
appliedSearch.fcltNm,
|
||||
code as unknown as Record<string, SelectDataType[]>
|
||||
);
|
||||
if (excelData && excelData.length > 0) {
|
||||
const sheetArray: SheetParameter[] = [{
|
||||
data: rswtSttn.excelList,
|
||||
data: excelData,
|
||||
headerNum: 3,
|
||||
headerRange: "A1:W3",
|
||||
sheetName: 'sheet1',
|
||||
startCol: 'A',
|
||||
decimalArray0: [6,10,12,16], // 0부터 시작
|
||||
sheetName: "sheet1",
|
||||
startCol: "A",
|
||||
decimalArray0: [6, 10, 12, 16],
|
||||
}];
|
||||
excelDownloadNumStyle(
|
||||
"중수도시설 현황",
|
||||
"sample_rswtSttn.xlsx",
|
||||
sheetArray
|
||||
);
|
||||
} else {
|
||||
alert(t('excelDownErrMsg'));
|
||||
}
|
||||
isDownloadTriggered.current = false;
|
||||
}, [rswtSttn.excelList]);
|
||||
|
||||
const excelClick = useCallback(() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
isDownloadTriggered.current = true;
|
||||
rswtSttnSearch(true, 1, rswtSttnResCount, appliedSearch.conValue, appliedSearch.fcltNm, code as unknown as Record<string, SelectDataType[]>);
|
||||
}, [rswtSttnSearch, rswtSttnResCount, code, appliedSearch.conValue, appliedSearch.fcltNm, isLaeSeCdReady]);
|
||||
alert(t("excelDownErrMsg"));
|
||||
}, [rswtSttnSearch, rswtSttnResCount, code, appliedSearch, isLaeSeCdReady, alert, t]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (skipNextSearchRef.current) {
|
||||
skipNextSearchRef.current = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchTrigger === 0) {
|
||||
if (!isLaeSeCdReady || !appliedSearch) {
|
||||
return;
|
||||
}
|
||||
|
||||
rswtSttnSearch(false, pageNation, rswtSttnSize, appliedSearch.conValue, appliedSearch.fcltNm, code as unknown as Record<string, SelectDataType[]>);
|
||||
isResetPage(false)
|
||||
}, [pageNation, searchTrigger, rswtSttnSearch, code, appliedSearch.conValue, appliedSearch.fcltNm, isLaeSeCdReady]);
|
||||
}, [pageNation, rswtSttnSearch, code, appliedSearch, isLaeSeCdReady]);
|
||||
|
||||
const handleSearch = useCallback(() => {
|
||||
if (!isLaeSeCdReady) {
|
||||
@@ -258,7 +245,6 @@ export default function RswtSttn() {
|
||||
setAppliedSearch(nextSearch);
|
||||
isResetPage(true);
|
||||
setPageNation(1);
|
||||
setSearchTrigger((prev) => prev + 1);
|
||||
}, [fcltNm, getFixedConValue, isLaeSeCdReady]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { CommonTableProps } from "@/app/store/common/type/header";
|
||||
import { fetchType, getFetchWithCount } from "@/components/lib/api";
|
||||
import { getFetchWithCount } from "@/components/lib/api";
|
||||
import type { SelectDataType } from "@common/business_components/ui";
|
||||
import type { z } from "zod";
|
||||
import type { customHtdflBdst } from "../../fcltReg/htdflReg";
|
||||
@@ -26,10 +26,10 @@ export type htdflSttnState = {
|
||||
type htdflSttnActions = {
|
||||
initHtdfl: (isExcel: boolean, page: number, total: number, con: string, txt: string,
|
||||
sidoCode: SelectDataType[],
|
||||
sggCode: SelectDataType[]) => void;
|
||||
sggCode: SelectDataType[]) => Promise<string[][] | void>;
|
||||
htdflSttnSearch: (isExcel: boolean, page: number, total: number, con: string, txt: string,
|
||||
sidoCode: SelectDataType[],
|
||||
sggCode: SelectDataType[]) => void;
|
||||
sggCode: SelectDataType[]) => Promise<string[][] | void>;
|
||||
htdflChSttnHead: (
|
||||
headList: z.infer<typeof sttnHeadListType>,
|
||||
) => void;
|
||||
@@ -63,36 +63,51 @@ const initFetch = async (page: number, total: number, con: string, txt: string)
|
||||
export const htdflSttnActionsExport: (set: any) => htdflSttnActions = (
|
||||
set: any
|
||||
) => {
|
||||
const fetchHtdflSttn = async (
|
||||
isExcel: boolean,
|
||||
page: number,
|
||||
total: number,
|
||||
con: string,
|
||||
txt: string,
|
||||
sidoCode: SelectDataType[],
|
||||
sggCode: SelectDataType[]
|
||||
): Promise<string[][] | void> => {
|
||||
const htdflSttnRes = await initFetch(page, total, con, txt);
|
||||
if (isExcel) {
|
||||
const htdflExcel = excelHtdflSttnParsing(htdflSttnRes, sidoCode, sggCode);
|
||||
set((state: htdflSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
htdflSttn: {
|
||||
...state.htdflSttn,
|
||||
excelList: htdflExcel,
|
||||
htdflSttnResCount: htdflSttnRes.count,
|
||||
},
|
||||
};
|
||||
});
|
||||
return htdflExcel;
|
||||
}
|
||||
|
||||
set((state: htdflSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
htdflSttn: {
|
||||
...state.htdflSttn,
|
||||
htdflSttnRes: htdflSttnRes.data,
|
||||
htdflSttnResCount: htdflSttnRes.count,
|
||||
isInit: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
return {
|
||||
initHtdfl: async (isExcel: boolean, page: number, total: number, con: string, txt: string,
|
||||
sidoCode: SelectDataType[],
|
||||
sggCode: SelectDataType[]) => {
|
||||
try {
|
||||
const htdflSttnRes = await initFetch(page, total, con, txt);
|
||||
if (isExcel) {
|
||||
const htdflExcel = excelHtdflSttnParsing(htdflSttnRes, sidoCode, sggCode);
|
||||
set((state: htdflSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
htdflSttn: {
|
||||
...state.htdflSttn,
|
||||
excelList: htdflExcel,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
set((state: htdflSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
htdflSttn: {
|
||||
...state.htdflSttn,
|
||||
htdflSttnRes: htdflSttnRes.data,
|
||||
htdflSttnResCount: htdflSttnRes.count,
|
||||
isInit: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
return await fetchHtdflSttn(isExcel, page, total, con, txt, sidoCode, sggCode);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
@@ -101,38 +116,7 @@ export const htdflSttnActionsExport: (set: any) => htdflSttnActions = (
|
||||
sidoCode: SelectDataType[],
|
||||
sggCode: SelectDataType[]) => {
|
||||
try {
|
||||
const htdflSttnRes = await getFetchWithCount(
|
||||
fetchType.WR,
|
||||
`/drain/facilityList` +
|
||||
(page ? `/${page}` : "/1") +
|
||||
(total ? `/${total}` : "/20") +
|
||||
(con ? `/${con}` : "/0") +
|
||||
(txt ? `/${txt}` : "")
|
||||
);
|
||||
|
||||
if (isExcel) {
|
||||
const htdflExcel = excelHtdflSttnParsing(htdflSttnRes, sidoCode, sggCode);
|
||||
set((state: htdflSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
htdflSttn: {
|
||||
...state.htdflSttn,
|
||||
excelList: htdflExcel,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else
|
||||
set((state: htdflSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
htdflSttn: {
|
||||
...state.htdflSttn,
|
||||
htdflSttnRes: htdflSttnRes.data,
|
||||
htdflSttnResCount: htdflSttnRes.count,
|
||||
isInit: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
return await fetchHtdflSttn(isExcel, page, total, con, txt, sidoCode, sggCode);
|
||||
} catch (erTxt) {
|
||||
console.log("error", erTxt);
|
||||
}
|
||||
@@ -154,4 +138,4 @@ export const htdflSttnActionsExport: (set: any) => htdflSttnActions = (
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,8 +102,8 @@ export type rainSttnState = {
|
||||
};
|
||||
};
|
||||
type rainSttnActions = {
|
||||
rainSttnInit: (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string, SelectDataType[]> ) => void
|
||||
rainSttnSearch: (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string, SelectDataType[]>) => void;
|
||||
rainSttnInit: (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string, SelectDataType[]> ) => Promise<string[][] | void>
|
||||
rainSttnSearch: (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string, SelectDataType[]>) => Promise<string[][] | void>;
|
||||
rainChSttnHead: (
|
||||
headList: z.infer<typeof sttnHeadListType>,
|
||||
) => void;
|
||||
@@ -129,81 +129,63 @@ export const rainSttnStateExport: rainSttnState = {
|
||||
export const rainSttnActionsExport: (set: any) => rainSttnActions = (
|
||||
set: any
|
||||
) => {
|
||||
const fetchRainSttn = async (
|
||||
isExcel: boolean,
|
||||
page: number,
|
||||
total: number,
|
||||
con: string,
|
||||
txt: string,
|
||||
code: Record<string, SelectDataType[]>
|
||||
): Promise<string[][] | void> => {
|
||||
const rainSttnRes = await getFetchWithCount(
|
||||
fetchType.WR,
|
||||
`/reuseFc/rainyInfoList` +
|
||||
(page ? `/${page}` : "/1") +
|
||||
(total ? `/${total}` : "/15") +
|
||||
(con ? `/${con}` : "/0") +
|
||||
(txt ? `/${txt}` : "")
|
||||
);
|
||||
|
||||
if (isExcel) {
|
||||
const rainExcel = excelRainSttnParsing(rainSttnRes, code);
|
||||
set((state: rainSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
rainSttn: {
|
||||
...state.rainSttn,
|
||||
excelList: rainExcel,
|
||||
rainSttnResCount: rainSttnRes.count,
|
||||
},
|
||||
};
|
||||
});
|
||||
return rainExcel;
|
||||
}
|
||||
|
||||
set((state: rainSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
rainSttn: {
|
||||
...state.rainSttn,
|
||||
rainSttnRes: rainSttnRes.data,
|
||||
rainSttnResCount: rainSttnRes.count,
|
||||
isInit: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
return {
|
||||
rainSttnInit: async (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string, SelectDataType[]>) => {
|
||||
try {
|
||||
|
||||
const rainSttnRes = await getFetchWithCount(
|
||||
fetchType.WR,
|
||||
`/reuseFc/rainyInfoList` +
|
||||
(page ? `/${page}` : "/1") +
|
||||
(total ? `/${total}` : "/15") +
|
||||
(con ? `/${con}` : "/0") +
|
||||
(txt ? `/${txt}` : "")
|
||||
);
|
||||
if (isExcel) {
|
||||
const rainExcel = excelRainSttnParsing(rainSttnRes, code);
|
||||
set((state: rainSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
rainSttn: {
|
||||
...state.rainSttn,
|
||||
excelList: rainExcel,
|
||||
rainSttnResCount: rainSttnRes.count,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else {
|
||||
set((state: rainSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
rainSttn: {
|
||||
...state.rainSttn,
|
||||
rainSttnRes: rainSttnRes.data,
|
||||
rainSttnResCount: rainSttnRes.count,
|
||||
isInit: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
await fetchRainSttn(isExcel, page, total, con, txt, code);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
},
|
||||
rainSttnSearch: async (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string, SelectDataType[]>) => {
|
||||
try {
|
||||
const rainSttnRes = await getFetchWithCount(
|
||||
fetchType.WR,
|
||||
`/reuseFc/rainyInfoList` +
|
||||
(page ? `/${page}` : "/1") +
|
||||
(total ? `/${total}` : "/15") +
|
||||
(con ? `/${con}` : "/0") +
|
||||
(txt ? `/${txt}` : "")
|
||||
);
|
||||
if (isExcel) {
|
||||
const rainExcel = excelRainSttnParsing(rainSttnRes,code);
|
||||
set((state: rainSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
rainSttn: {
|
||||
...state.rainSttn,
|
||||
excelList: rainExcel,
|
||||
rainSttnResCount: rainSttnRes.count,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else
|
||||
set((state: rainSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
rainSttn: {
|
||||
...state.rainSttn,
|
||||
rainSttnRes: rainSttnRes.data,
|
||||
rainSttnResCount: rainSttnRes.count,
|
||||
isInit: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
await fetchRainSttn(isExcel, page, total, con, txt, code);
|
||||
} catch (erTxt) {
|
||||
console.log("error", erTxt);
|
||||
}
|
||||
|
||||
@@ -30,8 +30,8 @@ export type rswtSttnState = {
|
||||
};
|
||||
|
||||
type rswtSttnActions = {
|
||||
rswtSttnInit: (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string,SelectDataType[]>) => void;
|
||||
rswtSttnSearch: (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string,SelectDataType[]>) => void;
|
||||
rswtSttnInit: (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string,SelectDataType[]>) => Promise<string[][] | void>;
|
||||
rswtSttnSearch: (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string,SelectDataType[]>) => Promise<string[][] | void>;
|
||||
rswtChSttnHead: (
|
||||
headList: z.infer<typeof sttnHeadListType>,
|
||||
) => void;
|
||||
@@ -55,79 +55,62 @@ export const rswtSttnStateExport: rswtSttnState = {
|
||||
export const rswtSttnActionsExport: (set: any) => rswtSttnActions = (
|
||||
set: any
|
||||
) => {
|
||||
const fetchRswtSttn = async (
|
||||
isExcel: boolean,
|
||||
page: number,
|
||||
total: number,
|
||||
con: string,
|
||||
txt: string,
|
||||
code: Record<string, SelectDataType[]>
|
||||
): Promise<string[][] | void> => {
|
||||
const rswtSttnRes = await getFetchWithCount(
|
||||
fetchType.WR,
|
||||
`/reuseFc/waterreuseFc` +
|
||||
(page ? `/${page}` : "/1") +
|
||||
(total ? `/${total}` : "/10") +
|
||||
(con ? `/${con}` : "/0") +
|
||||
(txt ? `/${txt}` : "")
|
||||
);
|
||||
if (isExcel) {
|
||||
const rswtExcel = excelRSwtSttnParsing(rswtSttnRes, code);
|
||||
set((state: rswtSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
rswtSttn: {
|
||||
...state.rswtSttn,
|
||||
excelList: rswtExcel,
|
||||
rswtSttnResCount: rswtSttnRes.count,
|
||||
},
|
||||
};
|
||||
});
|
||||
return rswtExcel;
|
||||
}
|
||||
|
||||
set((state: rswtSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
rswtSttn: {
|
||||
...state.rswtSttn,
|
||||
rswtSttnRes: rswtSttnRes.data,
|
||||
rswtSttnResCount: rswtSttnRes.count,
|
||||
isInit: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
return {
|
||||
rswtSttnInit: async (isExcel: boolean, page: number, total: number, con: string, txt: string, code: Record<string,SelectDataType[]>) => {
|
||||
try {
|
||||
const rswtSttnRes = await getFetchWithCount(
|
||||
fetchType.WR,
|
||||
`/reuseFc/waterreuseFc` +
|
||||
(page ? `/${page}` : "/1") +
|
||||
(total ? `/${total}` : "/10") +
|
||||
(con ? `/${con}` : "/0") +
|
||||
(txt ? `/${txt}` : "")
|
||||
);
|
||||
if (isExcel) {
|
||||
const rswtExcel = excelRSwtSttnParsing(rswtSttnRes, code);
|
||||
set((state: rswtSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
rswtSttn: {
|
||||
...state.rswtSttn,
|
||||
excelList: rswtExcel,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
set((state: rswtSttnState) => {
|
||||
|
||||
return {
|
||||
...state,
|
||||
rswtSttn: {
|
||||
...state.rswtSttn,
|
||||
rswtSttnRes: rswtSttnRes.data,
|
||||
rswtSttnResCount: rswtSttnRes.count,
|
||||
isInit: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
return await fetchRswtSttn(isExcel, page, total, con, txt, code);
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
},
|
||||
rswtSttnSearch: async (isExcel: boolean, page: number, total: number, con: string, txt: string,code: Record<string,SelectDataType[]>) => {
|
||||
try {
|
||||
const rswtSttnRes = await getFetchWithCount(
|
||||
fetchType.WR,
|
||||
`/reuseFc/waterreuseFc` +
|
||||
(page ? `/${page}` : "/1") +
|
||||
(total ? `/${total}` : "/10") +
|
||||
(con ? `/${con}` : "/0") +
|
||||
(txt ? `/${txt}` : "")
|
||||
);
|
||||
if (isExcel) {
|
||||
const rswtExcel = excelRSwtSttnParsing(rswtSttnRes, code);
|
||||
set((state: rswtSttnState) => {
|
||||
return {
|
||||
...state,
|
||||
rswtSttn: {
|
||||
...state.rswtSttn,
|
||||
excelList: rswtExcel,
|
||||
},
|
||||
};
|
||||
});
|
||||
} else
|
||||
set((state: rswtSttnState) => {
|
||||
|
||||
return {
|
||||
...state,
|
||||
rswtSttn: {
|
||||
...state.rswtSttn,
|
||||
rswtSttnRes: rswtSttnRes.data,
|
||||
rswtSttnResCount: rswtSttnRes.count,
|
||||
isInit: false,
|
||||
},
|
||||
};
|
||||
});
|
||||
return await fetchRswtSttn(isExcel, page, total, con, txt, code);
|
||||
} catch (erTxt) {
|
||||
console.log("error", erTxt);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
# apps/wr 호출 병합 2차 작업계획
|
||||
|
||||
작성일: 2026-06-25
|
||||
대상 앱: `D:\iipbs\frontend\apps\wr`
|
||||
대상 화면:
|
||||
- `apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/rswtSttn/page.tsx`
|
||||
- `apps/wr/app/(with-layout)/ruseFcltMng/fcltSttn/htdflSttn/page.tsx`
|
||||
|
||||
## 1. 작업 배경
|
||||
|
||||
- `rainSttn`에서 정리한 호출 병합 방향을 `rswtSttn`, `htdflSttn`에도 확장할 필요가 있다.
|
||||
- `rswtSttn`은 `searchTrigger`, `skipNextSearchRef`, `rswtSttnInit` 분리 구조가 그대로 남아 있어 `rainSttn`과 동일한 페이징 불량 가능성이 높다.
|
||||
- `htdflSttn`은 `searchTrigger`는 없지만, 조회 버튼/페이징/엑셀 다운로드가 모두 `objSearch`, `fcltNm`을 직접 읽고 있어 “입력 중 값”과 “실제 적용된 값”이 섞일 수 있다.
|
||||
- 두 화면 모두 엑셀 다운로드는 store의 `excelList` 반영 타이밍에 의존하고 있어, 첫 클릭 시 "다운로드할 데이터가 없습니다."가 발생할 수 있다.
|
||||
|
||||
## 2. 현재 구조 분석
|
||||
|
||||
### 2.1 rswtSttn 현재 흐름
|
||||
|
||||
- 초기 진입:
|
||||
- `getLaeSeCdCheck()` 실행
|
||||
- `laeSeCd` 준비 후 `rswtSttnInit(false, 1, rswtSttnSize, ...)` 호출
|
||||
- 이후 페이징/조회:
|
||||
- `pageNation`, `searchTrigger`, `appliedSearch`를 감시하는 effect에서 `rswtSttnSearch(...)` 호출
|
||||
- 추가 제어:
|
||||
- `skipNextSearchRef`로 초기 effect 1회를 건너뜀
|
||||
- `searchTrigger === 0`이면 검색 effect를 중단
|
||||
- 엑셀:
|
||||
- `isDownloadTriggered.current = true` 설정
|
||||
- `rswtSttnSearch(true, ...)` 호출
|
||||
- 이후 `rswtSttn.excelList` 변경 effect에서 다운로드 실행
|
||||
|
||||
### 2.2 htdflSttn 현재 흐름
|
||||
|
||||
- 초기 진입:
|
||||
- `getLaeSeCdCheck()` 실행
|
||||
- `initHtdfl(false, 1, htdflSttnSize, String(getFixedConValue()), fcltNm, ...)` 호출
|
||||
- 조회 버튼:
|
||||
- 클릭 시 즉시 `htdflSttnSearch(false, 1, ..., String(getFixedConValue()), fcltNm, ...)` 호출
|
||||
- `pageNation`은 1로 돌리지 않고 `resetPageNo`만 켬
|
||||
- 페이징:
|
||||
- `pageNation` 변경 effect에서 `String(getFixedConValue())`, `fcltNm`을 직접 읽어 `htdflSttnSearch(...)` 호출
|
||||
- 엑셀:
|
||||
- `isDownloadTriggered.current = true` 설정
|
||||
- `htdflSttnSearch(true, ...)` 호출
|
||||
- 이후 `htdflSttn.excelList` 변경 effect에서 다운로드 실행
|
||||
|
||||
### 2.3 실제 문제 지점
|
||||
|
||||
- `rswtSttn`
|
||||
- 사용자가 조회 버튼을 누르기 전에는 `searchTrigger === 0` 때문에 페이지 이동 시 API 호출이 막힌다.
|
||||
- 초기 조회와 이후 조회가 `init/search`로 나뉘어 동일한 목록 조회인데도 상태 플래그에 의존한다.
|
||||
- `htdflSttn`
|
||||
- 페이지 이동 시 `objSearch`, `fcltNm`의 현재 입력값을 그대로 읽으므로, 마지막 조회 결과와 다른 조건으로 페이지가 바뀔 수 있다.
|
||||
- 조회 버튼이 `pageNation`과 분리되어 있어 "1페이지로 되돌아간 뒤 같은 조건으로 재조회" 흐름이 일관되지 않다.
|
||||
- 공통
|
||||
- 엑셀 다운로드는 클릭 직후 `excelList`를 검사할 수 있어 비동기 응답 전에 빈 배열로 판정될 수 있다.
|
||||
- 첫 클릭 뒤 뒤늦게 store에 엑셀 데이터가 남으면, 다음 클릭에서 이전 응답 결과로 다운로드될 위험도 있다.
|
||||
|
||||
## 3. 목표 구조
|
||||
|
||||
### 3.1 조회 호출 단일화
|
||||
|
||||
- 초기 진입도 `search` 계열 호출로 통일한다.
|
||||
- 같은 파라미터라면 초기 진입, 조회 버튼, 페이지 이동, 엑셀 다운로드가 모두 동일한 조건 계산 경로를 사용한다.
|
||||
- 조회 버튼은 “적용 조건 확정 + 페이지 1 초기화”만 담당하고, 실제 목록 조회는 하나의 경로로만 실행한다.
|
||||
|
||||
### 3.2 적용 조건 분리
|
||||
|
||||
- 입력 중 값은 `objSearch`, `fcltNm`으로 유지한다.
|
||||
- 실제 조회 기준 값은 `appliedSearch`로 분리한다.
|
||||
- 페이지 이동, 재조회, 엑셀 다운로드는 반드시 `appliedSearch`만 사용한다.
|
||||
|
||||
### 3.3 엑셀 다운로드 직접 처리
|
||||
|
||||
- `excelClick`에서 `await ...Search(true, ...)` 완료를 직접 기다린다.
|
||||
- store의 `excelList` 변경 effect를 기다리지 않고, 반환받은 엑셀 데이터로 즉시 다운로드를 실행한다.
|
||||
- store는 필요하면 기존 `excelList`도 유지할 수 있으나, 다운로드 성공 여부는 반환값 기준으로 판단한다.
|
||||
|
||||
## 4. 수정 대상
|
||||
|
||||
### 4.1 화면
|
||||
|
||||
- `D:\iipbs\frontend\apps\wr\app\(with-layout)\ruseFcltMng\fcltSttn\rswtSttn\page.tsx`
|
||||
- `D:\iipbs\frontend\apps\wr\app\(with-layout)\ruseFcltMng\fcltSttn\htdflSttn\page.tsx`
|
||||
|
||||
### 4.2 store
|
||||
|
||||
- `D:\iipbs\frontend\apps\wr\app\store\ruseFcltMng\fcltSttn\rswtSttn\index.tsx`
|
||||
- `D:\iipbs\frontend\apps\wr\app\store\ruseFcltMng\fcltSttn\htdflSttn\index.tsx`
|
||||
|
||||
## 5. 권장 구현 방향
|
||||
|
||||
### 5.1 rswtSttn 화면 정리
|
||||
|
||||
- `rswtSttnInit()` 직접 호출 제거
|
||||
- `searchTrigger` 제거
|
||||
- `skipNextSearchRef` 제거 또는 최소화
|
||||
- `appliedSearch`를 `null` 가능 상태로 두고, 초기 진입 시 1회 세팅
|
||||
- 목록 조회 effect는 `pageNation`, `appliedSearch`, `isLaeSeCdReady`만 감시
|
||||
- 조회 버튼은 `appliedSearch` 갱신 후 `setPageNation(1)`만 수행
|
||||
- 엑셀 다운로드는 `await rswtSttnSearch(true, ...)` 후 반환값으로 직접 다운로드
|
||||
|
||||
### 5.2 rswtSttn store 정리
|
||||
|
||||
- `rswtSttnInit`는 삭제 `rswtSttnSearch`으로 병합.
|
||||
- `isExcel === true`일 때 파싱된 엑셀 행 데이터를 반환하도록 시그니처 확장
|
||||
- 목록 조회일 때는 기존처럼 `rswtSttnRes`, `rswtSttnResCount`를 갱신
|
||||
|
||||
### 5.3 htdflSttn 화면 정리
|
||||
|
||||
- `objSearch`, `fcltNm`와 별도로 `appliedSearch`를 추가
|
||||
- 초기 진입 시 `laeSeCd` 기준 초기 `appliedSearch`를 세팅하고, 목록 조회는 `htdflSttnSearch(false, 1, ...)` 경로로 통일
|
||||
- 조회 버튼은 `appliedSearch` 갱신 후 `setPageNation(1)`만 수행
|
||||
- 페이지 이동 effect는 반드시 `appliedSearch` 기준으로만 호출
|
||||
- 현재 `initHtdfl(false, ..., String(getFixedConValue()), fcltNm, ...)`처럼 입력 중 값에 직접 의존하는 구조를 제거
|
||||
- 엑셀 다운로드는 `await htdflSttnSearch(true, ...)` 후 반환값으로 직접 다운로드
|
||||
|
||||
### 5.4 htdflSttn store 정리
|
||||
|
||||
- `initHtdfl`는 삭제 `htdflSttnSearch` 으로 병합
|
||||
- `isExcel === true`일 때 파싱된 엑셀 행 데이터를 반환하도록 시그니처 확장
|
||||
- 목록 조회 시 `htdflSttnRes`, `htdflSttnResCount` 갱신 경로는 유지
|
||||
|
||||
## 6. 상세 작업 단계
|
||||
|
||||
### 단계 1. rswtSttn 호출 병합
|
||||
|
||||
- `page.tsx`
|
||||
- `rswtSttnInit` 제거
|
||||
- `searchTrigger` 제거
|
||||
- `skipNextSearchRef` 제거
|
||||
- 초기 effect에서 `appliedSearch`만 세팅
|
||||
- 목록 조회 effect는 `appliedSearch` 준비 후 항상 `rswtSttnSearch(false, pageNation, ...)` 호출
|
||||
- `index.tsx`
|
||||
- 공통 fetch 함수 도입
|
||||
- `rswtSttnSearch(true, ...)`가 `string[][]` 반환 가능하도록 수정
|
||||
|
||||
### 단계 2. rswtSttn 엑셀 다운로드 직접화
|
||||
|
||||
- `isDownloadTriggered` ref 제거
|
||||
- `rswtSttn.excelList` 감시 effect 제거
|
||||
- `excelClick`를 `async`로 변경
|
||||
- `const excelData = await rswtSttnSearch(true, ...)` 후:
|
||||
- 데이터가 있으면 즉시 `excelDownloadNumStyle(...)`
|
||||
- 없으면 `excelDownErrMsg` 노출
|
||||
|
||||
### 단계 3. htdflSttn 적용 조건 분리
|
||||
|
||||
- `appliedSearch` 상태 추가
|
||||
- 초기 진입 시 `laeSeCd` 기준 기본 조건을 `appliedSearch`에 반영
|
||||
- 조회 버튼은 입력값을 `appliedSearch`로 확정한 뒤 페이지를 1로 초기화
|
||||
- 페이지 이동 effect는 `appliedSearch`만 기준으로 목록 조회
|
||||
- `resetPageNo`는 UI 초기화용으로만 유지
|
||||
|
||||
### 단계 4. htdflSttn store 병합 및 엑셀 직접화
|
||||
|
||||
- `initHtdfl`는 삭제 `htdflSttnSearch` 으로 병합
|
||||
- `htdflSttnSearch(true, ...)`가 엑셀 데이터 반환 가능하도록 수정
|
||||
- 화면에서 `await htdflSttnSearch(true, ...)` 결과로 직접 다운로드
|
||||
- `isDownloadTriggered` ref 및 `htdflSttn.excelList` 감시 effect 제거
|
||||
|
||||
## 7. 구현 시 주의사항
|
||||
|
||||
- `objSearch`는 입력 UI 상태이고, API 호출 기준은 `appliedSearch`다.
|
||||
- `setPageNation(1)` 직후 중복 조회가 생기지 않도록, 조회 버튼에서 직접 API를 다시 호출하지 말고 하나의 effect 경로만 사용한다.
|
||||
- `htdflSttn`은 현재 조회 버튼이 `setPageNation(1)` 없이 직접 API를 호출하고 있으므로 이 부분을 반드시 통일해야 한다.
|
||||
- 엑셀은 전체 건수 기준 조회를 유지하되, 조건 계산은 일반 목록 조회와 동일해야 한다.
|
||||
- store에서 엑셀 데이터 반환을 추가하더라도 기존 목록 갱신 로직은 깨지지 않게 분기해야 한다.
|
||||
|
||||
## 8. 검증 계획
|
||||
|
||||
### 8.1 rswtSttn 검증
|
||||
|
||||
- 초기 진입 시 1페이지 목록이 조회된다.
|
||||
- 초기 진입 직후 조회 버튼을 누르지 않고 2페이지로 이동하면 API가 호출된다.
|
||||
- 조회 조건 변경 후 조회 버튼 클릭 시 1페이지로 이동하며 API가 1회만 호출된다.
|
||||
- 페이지 2, 3으로 이동해도 같은 `appliedSearch` 조건이 유지된다.
|
||||
- 첫 엑셀다운로드 클릭에서 즉시 파일이 내려오고, "다운로드할 데이터가 없습니다."가 잘못 뜨지 않는다.
|
||||
|
||||
### 8.2 htdflSttn 검증
|
||||
|
||||
- 초기 진입 시 1페이지 목록이 조회된다.
|
||||
- 조회 버튼 클릭 시 항상 1페이지 기준으로 재조회된다.
|
||||
- 조회 후 입력값만 바꾸고 조회 버튼을 누르지 않은 상태에서 페이지 이동해도, 마지막 조회 조건으로 페이지가 유지된다.
|
||||
- 첫 엑셀다운로드 클릭에서도 현재 적용 조건 기준으로 정상 다운로드된다.
|
||||
|
||||
### 8.3 회귀 검증
|
||||
|
||||
- 상세 모달 열기 동작 유지
|
||||
- 권한별 `laeSeCd` 초기 조건 유지
|
||||
- 시도/시군구 선택 제한 유지
|
||||
- 조회항목설정 모달 동작 유지
|
||||
|
||||
## 9. 결론
|
||||
|
||||
- `rswtSttn`은 `rainSttn`과 거의 동일한 구조적 문제를 갖고 있으므로 같은 방식으로 정리하는 것이 맞다.
|
||||
- `htdflSttn`은 페이징 가드 문제는 없지만, 적용 조건 분리와 엑셀 다운로드 처리 방식은 동일하게 손봐야 한다.
|
||||
- 이번 2차 작업의 핵심은 다음 두 가지다.
|
||||
- “목록 조회는 항상 `appliedSearch` 기준의 단일 경로로 통일”
|
||||
- “엑셀 다운로드는 store 반영 effect가 아니라 `await ...Search(true, ...)` 반환값으로 직접 처리”
|
||||
Reference in New Issue
Block a user