Files
frontend/apps/st/src/components/module/SearchForm.tsx
T

369 lines
15 KiB
TypeScript

"use client";
import { useEffect, useState } from "react";
import useSWR from "swr";
import { useAlertStore, useUserStore } from "@/shared/lib";
import { statisticsApi } from "@/entities/statistics/api/statistics-service";
import type {
ApiResponse,
SearchCondition,
Sgg,
} from "@/entities/statistics/model/statistics-interface";
import {
Button,
Checkbox,
Input,
Label,
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@common/components";
import {
defaultSearchFormProps,
type SearchFormProps,
type SearchFormValues,
} from "../type/SearchForm.type";
import { doList } from "../util/column/input/DoList";
const getSidoListFetcher = async (
key: [string, string],
): Promise<ApiResponse<SearchCondition>> => {
return statisticsApi.getSidoList({ instSeCd: key[1] });
};
const getSggListFetcher = async (
key: [string, string, string, string],
): Promise<ApiResponse<Sgg[]>> => {
const gugunSkipYn = doList.includes(key[3]) ? "N" : "Y";
return statisticsApi.getSggAllList(key[1], {
instSeCd: key[2],
gugunSkipYn,
});
};
const SearchForm = (props: SearchFormProps = {}) => {
const { setMessage: alert } = useAlertStore((state) => state);
const userInfo = useUserStore((state) => state.userInfo);
const mergedProps = { ...defaultSearchFormProps, ...props };
const instSeCd = userInfo?.instSeCd ?? "";
const sidoSelectText =
props.sidoSelectText && props.sidoSelectText.trim() !== ""
? props.sidoSelectText
: "선택";
const currentYear = new Date().getFullYear();
const defaultYear = currentYear - 1;
const { data: sidoList } = useSWR<ApiResponse<SearchCondition>>(
["sido-list", instSeCd],
getSidoListFetcher,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: false,
dedupingInterval: 2000,
shouldRetryOnError: false,
onSuccess: (data) => {
if (data.status !== "success") {
alert(`시도 목록을 불러오는데 실패했습니다.
새로고침 후 다시 시도해주세요.`);
}
},
onError: () => {
alert(`시도 목록을 불러오는데 실패했습니다.
새로고침 후 다시 시도해주세요.`);
},
},
);
const [values, setValues] = useState<SearchFormValues>({
sido: props.defaultValues?.sido || "",
gugun: props.defaultValues?.gugun || "",
year: props.defaultValues?.year || defaultYear.toString(),
order: props.defaultValues?.order || "",
fctName: props.defaultValues?.fctName || "",
bsnmCd: props.defaultValues?.bsnmCd || "",
isDetail: false,
isSummary: props.defaultValues?.isSummary || "",
});
const sggKey: [string, string, string, string] | null =
values.sido && values.sido !== "ALL"
? ["sgg-list", values.sido, instSeCd, values.bsnmCd || ""]
: null;
const { data: sggList } = useSWR<ApiResponse<Sgg[]>>(
sggKey,
getSggListFetcher,
{
revalidateOnFocus: false,
revalidateOnReconnect: false,
revalidateIfStale: false,
dedupingInterval: 2000,
shouldRetryOnError: false,
onSuccess: (data) => {
if (data.status !== "success") {
alert(`시군구 목록을 불러오는데 실패했습니다.
새로고침 후 다시 시도해주세요.`);
}
},
onError: () => {
alert(`시군구 목록을 불러오는데 실패했습니다.
새로고침 후 다시 시도해주세요.`);
},
},
);
const sggOptions = sggList?.data ?? [];
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
props.onSubmit?.(values);
};
const handleReset = () => {
const resetValues: SearchFormValues = {
sido: "",
gugun: "",
year: String(defaultYear),
order: "",
fctName: "",
bsnmCd: "",
isDetail: false,
isSummary: "",
};
setValues(resetValues);
props.onReset?.();
};
const handleSidoChange = (value: string) => {
const selectedBsnm =
sidoList?.data.sidoList.find((v) => v.sido === value)?.bsnmCd || "";
setValues((prev) => ({
...prev,
sido: value,
gugun: "",
bsnmCd: selectedBsnm,
}));
};
const handleSggChange = (value: string) => {
const listForBsnm = sggList?.data ?? [];
const selectedBsnm =
value === "ALL"
? sidoList?.data.sidoList.find((v) => v.sido === values.sido)
?.bsnmCd || ""
: listForBsnm.find((v) => v.gugun === value)?.bsnmCd || "";
setValues((prev) => ({
...prev,
gugun: value,
bsnmCd: selectedBsnm,
}));
};
useEffect(() => {
props.onValuesChange?.(values);
}, [values]);
return (
<div className="card card-border">
<form onSubmit={handleSubmit}>
<div className="flex items-center gap-8 p-2">
{mergedProps.sido && (
<div className="flex items-center gap-2">
<Label className="text-label">시도</Label>
<Select
value={values.sido}
onValueChange={handleSidoChange}
>
<SelectTrigger className="w-[12rem]">
<SelectValue
placeholder={sidoSelectText}
></SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">
{sidoSelectText}
</SelectItem>
{sidoList?.data?.sidoList?.map((sido) => (
<SelectItem
key={sido.adstDstrCd}
value={sido.sido}
>
{sido.sido}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{mergedProps.gugun && (
<div className="flex items-center gap-2">
<Label className="text-label">시군구</Label>
<Select
value={values.gugun}
onValueChange={handleSggChange}
>
<SelectTrigger className="w-[12rem]">
<SelectValue placeholder="전체"></SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체</SelectItem>
{sggOptions?.map((sgg) => (
<SelectItem
key={sgg.adstDstrCd}
value={sgg.gugun}
>
{sgg.gugun}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{mergedProps.year && (
<div className="flex items-center gap-2">
<Label className="text-label">년도</Label>
<Select
value={values.year}
onValueChange={(value) =>
setValues({ ...values, year: value })
}
>
<SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택"></SelectValue>
</SelectTrigger>
<SelectContent>
{sidoList?.data?.yearList.map((year) => (
<SelectItem
key={year}
value={String(year)}
>
{year}년
</SelectItem>
))}
</SelectContent>
</Select>
</div>
)}
{mergedProps.isDetail && (
<div className="flex items-center gap-2">
<Label className="text-label cursor-pointer" htmlFor="isDetail">
세부 데이터 포함
</Label>
<Checkbox
id="isDetail"
checked={values.isDetail}
onCheckedChange={(value) =>
setValues({
...values,
isDetail: value === true,
})
}
/>
</div>
)}
{mergedProps.isSummary && (
<div className="flex items-center gap-2">
<Label className="text-label cursor-pointer">
요약표 포함
</Label>
<Select
value={values.isSummary}
onValueChange={(value) =>
setValues({ ...values, isSummary: value })
}
>
<SelectTrigger className="w-[10rem]">
<SelectValue placeholder="선택 안함"></SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">선택 안함</SelectItem>
{[1,2,3,4,5,6,7,8,9,10].map((i) => (
<SelectItem value={i.toString()} key={i.toString()}>{`${i}개년`}</SelectItem>
))
}
</SelectContent>
</Select>
</div>
)}
{mergedProps.order && (
<div className="flex items-center gap-2">
<Label className="text-label">차수</Label>
<Select
value={values.order}
onValueChange={(value) =>
setValues({ ...values, order: value })
}
>
<SelectTrigger className="w-[10rem]">
<SelectValue placeholder="전체"></SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">전체</SelectItem>
</SelectContent>
</Select>
</div>
)}
<div className="ml-auto flex items-center gap-2">
<Button
color="white"
className="h-[3.2rem] w-[96px]"
type="button"
onClick={handleReset}
>
초기화
</Button>
<Button className="h-[3.2rem] w-[96px]" type="submit">
조회
</Button>
</div>
</div>
{(mergedProps.fctName || mergedProps.userMenual) && (
<div className="flex items-center p-2">
{mergedProps.fctName && (
<div className="flex items-center gap-2">
<Label
htmlFor="fctNmSearchInput"
className="mr-2 text-label"
>
시설명
</Label>
<Input
className="mr-4 h-[3.2rem] w-[14rem] rounded-sm !text-[1.3rem]"
id="fctNmSearchInput"
value={values.fctName}
onChange={(e) =>
setValues({
...values,
fctName: e.target.value,
})
}
autoComplete="off"
/>
</div>
)}
{mergedProps.userMenual && (
<div className="ml-auto flex items-center gap-2">
<Button
className="h-[3.2rem] w-[124px]"
type="button"
>
<i className="diveicon di-person-fill" />
사용자 메뉴얼
</Button>
</div>
)}
</div>
)}
</form>
</div>
);
};
export default SearchForm;