Merge branch 'iipbs/st/dev'

This commit is contained in:
2026-08-28 17:06:20 +09:00
5 changed files with 161 additions and 50 deletions
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useMemo, useState } from "react";
import type { Dispatch, SetStateAction } from "react";
import { useEffect, useMemo, useRef, useState } from "react";
import { Line } from "react-chartjs-2";
import { keepPreviousData, useQuery } from "@tanstack/react-query";
@@ -72,6 +73,7 @@ type DateRangeValue = {
};
const EMPTY_DATA: never[] = [];
const REGION_ALL_VALUE = "ALL";
function getImrDetailGridColumnInfo(statsCd: string): ColumnInfo {
const visible =
@@ -96,6 +98,21 @@ function getVerificationYearOptions(): string[] {
);
}
function applySingleOptionRegionDefault<T>(
list: T[] | undefined,
setValue: Dispatch<SetStateAction<string>>,
resolveValue: (item: T) => string,
) {
if (!list || list.length !== 1) return;
const onlyItem = list[0];
if (!onlyItem) return;
setValue((prev) =>
prev === REGION_ALL_VALUE ? resolveValue(onlyItem) : prev,
);
}
const ImrVerificationHistory = () => {
const yearOptions = useMemo(() => getVerificationYearOptions(), []);
const defaultYear: string =
@@ -104,8 +121,9 @@ const ImrVerificationHistory = () => {
: "";
// Area A Search States
const [searchSido, setSearchSido] = useState<string>("");
const [searchSigungu, setSearchSigungu] = useState<string>("");
const [searchSido, setSearchSido] = useState<string>(REGION_ALL_VALUE);
const [searchSigungu, setSearchSigungu] =
useState<string>(REGION_ALL_VALUE);
const [searchStatsYr, setSearchStatsYr] = useState<string>(defaultYear);
const [searchStatsCd, setSearchStatsCd] = useState<string>("");
const [dateRange, setDateRange] = useState<DateRangeValue | undefined>({
@@ -150,6 +168,28 @@ const ImrVerificationHistory = () => {
gugunSkipYn: doList.includes(selectedSidoBsnmCd) ? "N" : "Y",
});
const sigunguOptions = sigunguData ?? [];
const allowRegionAutoDefaultRef = useRef(true);
useEffect(() => {
if (!allowRegionAutoDefaultRef.current) return;
applySingleOptionRegionDefault(
sidoData?.sidoList,
setSearchSido,
(item) => item.sido,
);
}, [sidoData?.sidoList]);
useEffect(() => {
if (!allowRegionAutoDefaultRef.current) return;
if (searchSido === REGION_ALL_VALUE) return;
applySingleOptionRegionDefault(
sigunguData,
setSearchSigungu,
(item) => item.gugun,
);
}, [sigunguData, searchSido]);
const imrDetailColumnInfo = useMemo(
() => getImrDetailGridColumnInfo(selectedHistory?.statsCd ?? ""),
@@ -248,12 +288,12 @@ const ImrVerificationHistory = () => {
vrfcMthdCd: "IMR",
sort: "anlsSn,desc",
};
if (searchSido !== "") {
if (searchSido !== "" && searchSido !== REGION_ALL_VALUE) {
const selectedSggBsnmCd = sigunguOptions.find(
(item) => item.gugun === searchSigungu,
)?.bsnmCd;
params.sewerBzmnCd =
searchSigungu !== ""
searchSigungu !== "" && searchSigungu !== REGION_ALL_VALUE
? (selectedSggBsnmCd ?? "")
: selectedSidoBsnmCd;
}
@@ -271,8 +311,9 @@ const ImrVerificationHistory = () => {
};
const handleReset = () => {
setSearchSido("");
setSearchSigungu("");
allowRegionAutoDefaultRef.current = false;
setSearchSido(REGION_ALL_VALUE);
setSearchSigungu(REGION_ALL_VALUE);
setSearchStatsCd("");
const defaultRange = {
from: dayjs().subtract(1, "month").toDate(),
@@ -410,13 +451,16 @@ const ImrVerificationHistory = () => {
value={searchSido}
onValueChange={(val) => {
setSearchSido(val);
setSearchSigungu("");
setSearchSigungu(REGION_ALL_VALUE);
}}
>
<SelectTrigger className="w-[14rem]">
<SelectValue placeholder="시도 선택" />
<SelectValue placeholder="전체" />
</SelectTrigger>
<SelectContent>
<SelectItem value={REGION_ALL_VALUE}>
전체
</SelectItem>
{sidoOptions.map((opt) => (
<SelectItem
key={opt.adstDstrCd}
@@ -436,12 +480,18 @@ const ImrVerificationHistory = () => {
<Select
value={searchSigungu}
onValueChange={setSearchSigungu}
disabled={searchSido === ""}
disabled={
searchSido === "" ||
searchSido === REGION_ALL_VALUE
}
>
<SelectTrigger className="w-[14rem]">
<SelectValue placeholder="시군구 선택" />
<SelectValue placeholder="전체" />
</SelectTrigger>
<SelectContent>
<SelectItem value={REGION_ALL_VALUE}>
전체
</SelectItem>
{sigunguOptions.map((opt) => (
<SelectItem
key={opt.adstDstrCd}
@@ -126,6 +126,8 @@ function useGridCheckboxSelection<T>(
return { handleItemChecked, handleAllChecked };
}
const REGION_ALL_OPTION = { label: "전체", value: "ALL" };
const DEFAULT_FILTERS: StatisticsVerificationFilterState = {
ctpvNm: null,
sggNm: null,
@@ -133,6 +135,8 @@ const DEFAULT_FILTERS: StatisticsVerificationFilterState = {
sewerBzmnCd: null,
statsCd: null,
pbcpSbmsnSttsCd: null,
ctpvSewerBzmnCd: "ALL",
sggSewerBzmnCd: "ALL",
};
const DEFAULT_SUB_FILTERS: StatisticsVerificationSubFilterState = {
@@ -422,14 +426,25 @@ function StatisticsVerificationReport() {
// 시도 변경 시
if (key === "ctpvNm") {
setFilters((prev) => ({
...prev,
sewerBzmnCd: value,
ctpvNm: optionData?.label,
ctpvSewerBzmnCd: value,
sggNm: "",
sggSewerBzmnCd: "",
}));
if (value === "ALL") {
setFilters((prev) => ({
...prev,
ctpvNm: null,
sggNm: null,
sewerBzmnCd: null,
ctpvSewerBzmnCd: "ALL",
sggSewerBzmnCd: "ALL",
}));
} else {
setFilters((prev) => ({
...prev,
sewerBzmnCd: value,
ctpvNm: optionData?.label,
ctpvSewerBzmnCd: value,
sggNm: null,
sggSewerBzmnCd: "ALL",
}));
}
setSubFilters((prev) => ({
...prev,
ctpvNm: "",
@@ -441,12 +456,25 @@ function StatisticsVerificationReport() {
// 시군구 변경 시
if (key === "sggNm") {
setFilters((prev) => ({
...prev,
sewerBzmnCd: value,
sggNm: optionData?.label,
sggSewerBzmnCd: value,
}));
if (value === "ALL") {
setFilters((prev) => ({
...prev,
sggNm: null,
sggSewerBzmnCd: "ALL",
sewerBzmnCd:
prev.ctpvSewerBzmnCd &&
prev.ctpvSewerBzmnCd !== "ALL"
? prev.ctpvSewerBzmnCd
: null,
}));
} else {
setFilters((prev) => ({
...prev,
sewerBzmnCd: value,
sggNm: optionData?.label,
sggSewerBzmnCd: value,
}));
}
return;
}
@@ -588,23 +616,31 @@ function StatisticsVerificationReport() {
<div className="flex items-center gap-6">
<FilterSelect
label="시도"
value={filters.ctpvSewerBzmnCd ?? ""}
value={filters.ctpvSewerBzmnCd ?? "ALL"}
onChange={handleFilterChange("ctpvNm")}
options={sidoList.map((item) => ({
label: item.sido,
value: item.bsnmCd,
}))}
options={[
REGION_ALL_OPTION,
...sidoList.map((item) => ({
label: item.sido,
value: item.bsnmCd,
})),
]}
/>
<FilterSelect
label="시군구"
value={filters.sggSewerBzmnCd ?? ""}
value={filters.sggSewerBzmnCd ?? "ALL"}
onChange={handleFilterChange("sggNm")}
options={
sigunguData?.map((item) => ({
disabled={
!filters.ctpvSewerBzmnCd ||
filters.ctpvSewerBzmnCd === "ALL"
}
options={[
REGION_ALL_OPTION,
...(sigunguData?.map((item) => ({
label: item.gugun,
value: item.bsnmCd,
})) ?? []
}
})) ?? []),
]}
/>
<FilterSelect
label="년도"
@@ -752,6 +788,7 @@ interface FilterSelectProps {
) => void;
options: Array<{ label: string; value: string }>;
value?: string;
disabled?: boolean;
}
function FilterCombobox({
@@ -818,12 +855,19 @@ function FilterCombobox({
);
}
function FilterSelect({ label, onChange, options, value }: FilterSelectProps) {
function FilterSelect({
label,
onChange,
options,
value,
disabled,
}: FilterSelectProps) {
return (
<div className="flex items-center gap-2">
<Label className="whitespace-nowrap">{label}</Label>
<Select
value={value}
disabled={disabled}
onValueChange={(value) =>
onChange(
value,
@@ -40,8 +40,8 @@ const HbVerificationDetail: React.FC<HbVerificationDetailProps> = ({
);
const [checkedSns, setCheckedSns] = useState<string[]>([]);
const [appliedFilter, setAppliedFilter] = useState<AppliedFilter>({
sido: "",
gugun: "",
sido: "ALL",
gugun: "ALL",
status: "",
});
const [selectedRowId, setSelectedRowId] = useState<number | null>(null);
@@ -26,6 +26,7 @@ import type { AppliedFilter } from "./HbVerificationDetail";
import DataGrid from "@/components/module/DataGrid";
const EMPTY_ADST_LIST: AdstZoneResponseDto[] = [];
const REGION_ALL_VALUE = "ALL";
interface RegionalGridProps {
anlsSn: number;
@@ -53,7 +54,10 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
>([]);
const { data: sidoData } = useStAnlsSidoCombo();
const { data: gugunData } = useStAnlsSigunguCombo(appliedFilter.sido ?? "");
const sidoCd = appliedFilter.sido ?? "";
const { data: gugunData } = useStAnlsSigunguCombo(
sidoCd !== REGION_ALL_VALUE ? sidoCd : "",
);
const sidoOptions = sidoData ?? EMPTY_ADST_LIST;
const gugunOptions = gugunData ?? EMPTY_ADST_LIST;
@@ -66,14 +70,14 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
let data = (resultData?.content ?? []) as StAnlsRsltResponse[];
{
const sidoCd = appliedFilter.sido;
if (sidoCd) {
if (sidoCd && sidoCd !== REGION_ALL_VALUE) {
data = data.filter((item) =>
item.admdstCd.startsWith(sidoCd.slice(0, 2)),
);
}
const gugunCd = appliedFilter.gugun;
if (gugunCd) {
if (gugunCd && gugunCd !== REGION_ALL_VALUE) {
data = data.filter((item) => item.admdstCd === gugunCd);
}
@@ -107,13 +111,17 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
}, [resultData, appliedFilter]);
const handleSidoChange = (val: string) => {
onFilterChange({ ...appliedFilter, sido: val, gugun: "" });
onFilterChange({
...appliedFilter,
sido: val,
gugun: REGION_ALL_VALUE,
});
};
const handleReset = () => {
onFilterChange({
sido: "",
gugun: "",
sido: REGION_ALL_VALUE,
gugun: REGION_ALL_VALUE,
status: "",
});
};
@@ -156,13 +164,16 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
<div className="flex justify-between">
<div className="flex items-center gap-2">
<Select
value={appliedFilter.sido || ""}
value={appliedFilter.sido || REGION_ALL_VALUE}
onValueChange={handleSidoChange}
>
<SelectTrigger className="w-[12rem] bg-white">
<SelectValue placeholder="시도 선택" />
<SelectValue placeholder="전체" />
</SelectTrigger>
<SelectContent>
<SelectItem value={REGION_ALL_VALUE}>
전체
</SelectItem>
{sidoOptions.map((opt) => (
<SelectItem key={opt.adstCd} value={opt.adstCd}>
{opt.adstNm}
@@ -171,16 +182,22 @@ const RegionalGrid: React.FC<RegionalGridProps> = ({
</SelectContent>
</Select>
<Select
value={appliedFilter.gugun || ""}
value={appliedFilter.gugun || REGION_ALL_VALUE}
onValueChange={(val) =>
onFilterChange({ ...appliedFilter, gugun: val })
}
disabled={!appliedFilter.sido}
disabled={
!appliedFilter.sido ||
appliedFilter.sido === REGION_ALL_VALUE
}
>
<SelectTrigger className="w-[12rem] bg-white">
<SelectValue placeholder="시군구 선택" />
<SelectValue placeholder="전체" />
</SelectTrigger>
<SelectContent>
<SelectItem value={REGION_ALL_VALUE}>
전체
</SelectItem>
{gugunOptions.map((opt) => (
<SelectItem key={opt.adstCd} value={opt.adstCd}>
{opt.adstNm}
@@ -73,7 +73,7 @@ export const useStAnlsSigunguCombo = (sidoCd: string) => {
return useQuery({
queryKey: [ENDPOINT.STATVERIFY_SERVICE.ST_ANLS_MST, "sigungu", sidoCd],
queryFn: () => statisticsAnalysisMasterService.getSigunguCombo(sidoCd),
enabled: Boolean(sidoCd),
enabled: Boolean(sidoCd) && sidoCd !== "ALL",
});
};