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

259 lines
11 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import useSWR from 'swr';
import {
Button,
Input,
Label,
Select,
SelectTrigger,
SelectContent,
SelectItem,
SelectValue
}
from "@common/components";
import { useAlertStore } from "@/shared/lib";
import { type SearchFormProps, type SearchFormValues, defaultSearchFormProps } from "../type/SearchForm.type";
import { statisticsAPi } from "@/entities/statistics/api/statistics-service";
import type { ApiResponse, SearchCondition, Sgg } from "@/entities/statistics/model/statistics-interface";
const getSidoListFetcher = async (): Promise<ApiResponse<SearchCondition>> => {
return statisticsAPi.getSidoList();
};
const getSggListFetcher = async (key: [string, string]): Promise<ApiResponse<Sgg[]>> => {
return statisticsAPi.getSggList(key[1]);
};
const SearchForm = (props: SearchFormProps = {}) => {
const { setMessage: alert } = useAlertStore((state) => state);
const mergedProps = { ...defaultSearchFormProps, ...props };
const currentYear = new Date().getFullYear();
const defaultYear = currentYear - 1;
const { data: sidoList } = useSWR<ApiResponse<SearchCondition>>(
"sido-list",
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 || '',
});
const sggKey: [string, string] | null =
values.sido && values.sido !== "ALL"
? ["sgg-list", values.sido]
: 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: '',
};
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="선택"></SelectValue>
</SelectTrigger>
<SelectContent>
<SelectItem value="ALL">선택</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.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="flex items-center gap-2 ml-auto">
<Button color="green" 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="h-[3.2rem] w-[14rem] rounded-sm !text-[1.3rem] mr-4"
id="fctNmSearchInput"
value={values.fctName}
onChange={(e) => setValues({ ...values, fctName: e.target.value })}
autoComplete="off"
/>
</div>
}
{mergedProps.userMenual &&
<div className="flex items-center gap-2 ml-auto">
<Button className="h-[3.2rem] w-[124px]" type="button">
<i className="diveicon di-person-fill" />
사용자 메뉴얼
</Button>
</div>
}
</div>
}
</form>
</div>
);
};
export default SearchForm;