"use client"; import { useEffect, useState } from "react"; import useSWR from "swr"; import { useAlertStore, useUserStore } from "@/shared/lib"; import { fetchSearchCondition, fetchSearchConditionInput, SEARCH_CONDITION_SWR_KEY, } from "@/shared/lib/use-search-condition-sido-list"; 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 { doList, doNameList } from "@/components/util/column/input/DoList"; import { defaultSearchFormProps, type SearchFormClsfCd, type SearchFormProps, type SearchFormValues, } from "../type/SearchForm.type"; 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> => { const [kind, sidoName, instSeCd, gugunSkipYn] = key; const params = { instSeCd, gugunSkipYn }; if (kind === "sgg-all") { return statisticsApi.getSggAllList(sidoName, params); } return statisticsApi.getSggList(sidoName, params); }; function buildSearchFormValues( props: SearchFormProps, sidoList: ApiResponse | undefined, defaultYear: number, ): SearchFormValues { const d = props.defaultValues; const initialClsfCd: SearchFormClsfCd = d?.clsfCd === "ALL" || d?.clsfCd === "INPT" || d?.clsfCd === "VRFC" ? d.clsfCd : "ALL"; if (d?.sido) { const sidoItem = sidoList?.data?.sidoList?.find( (item) => item.sido === d.sido, ); const gugun = d.gugun || (sidoItem && !isDoSido(sidoItem) ? "ALL" : ""); return { sido: d.sido, gugun, year: d.year || defaultYear.toString(), order: d.order || "", fctName: d.fctName || "", bsnmCd: d.bsnmCd || "", isDetail: false, isSummary: d.isSummary || "", clsfCd: initialClsfCd, capacityGbn: d.capacityGbn || "ALL", }; } if (props.defaultRegionAll) { return { sido: "ALL", gugun: "ALL", year: d?.year || defaultYear.toString(), order: d?.order || "", fctName: d?.fctName || "", bsnmCd: "", isDetail: false, isSummary: d?.isSummary || "", clsfCd: initialClsfCd, capacityGbn: d?.capacityGbn || "ALL", }; } const first = sidoList?.data?.sidoList?.[0]; const useFirstSido = (props.sido ?? defaultSearchFormProps.sido) && first; return { sido: useFirstSido ? first.sido : "", gugun: useFirstSido && first && !isDoSido(first) ? "ALL" : "", year: d?.year || defaultYear.toString(), order: d?.order || "", fctName: d?.fctName || "", bsnmCd: useFirstSido ? first.bsnmCd : "", isDetail: false, isSummary: d?.isSummary || "", clsfCd: initialClsfCd, capacityGbn: d?.capacityGbn || "ALL", }; } 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 isInput = Boolean(props.isInput); const isDetailInputId = props.tabKey ? `${props.tabKey}-isDetail` : "isDetail"; const sidoListKind = isInput ? "input" : "default"; const { data: sidoList } = useSWR>( SEARCH_CONDITION_SWR_KEY(instSeCd, sidoListKind), isInput ? fetchSearchConditionInput : fetchSearchCondition, { revalidateOnFocus: false, revalidateOnReconnect: false, revalidateIfStale: false, dedupingInterval: 2000, shouldRetryOnError: false, onSuccess: (data) => { if (data.status !== "success") { alert(`시도 목록을 불러오는데 실패했습니다. 새로고침 후 다시 시도해주세요.`); } }, onError: () => { alert(`시도 목록을 불러오는데 실패했습니다. 새로고침 후 다시 시도해주세요.`); }, }, ); // 마운트 시 SWR 캐시의 시도 목록으로 초기값 설정 (탭 닫았다 열 때 API 재호출 없이 동작) const [values, setValues] = useState(() => buildSearchFormValues(props, sidoList, defaultYear), ); // 최초 진입만 시도 API가 늦게 오는 경우 useEffect(() => { if (props.defaultRegionAll) return; if (props.defaultValues?.sido) return; const first = sidoList?.data?.sidoList?.[0]; if (!first || !mergedProps.sido) return; setValues((prev) => prev.sido ? prev : { ...prev, sido: first.sido, gugun: isDoSido(first) ? "" : "ALL", bsnmCd: first.bsnmCd, }, ); }, [ sidoList, props.defaultValues?.sido, props.defaultRegionAll, mergedProps.sido, ]); const selectedSido = sidoList?.data?.sidoList?.find( (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 ? [ useSggAllList ? "sgg-all" : "sgg-list", values.sido, instSeCd, gugunSkipYn, ] : null; const { data: sggList } = useSWR>( 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 isDoSelected = isDoSido(selectedSido); const firstConcreteSggOption = firstConcreteSgg(sggOptions); // 도: 빈 값일 때만 첫 시군구로 보이게. ALL은 사용자가 고른 전체 유지 const gugunSelectValue = isDoSelected && !values.gugun ? (firstConcreteSggOption?.gugun ?? undefined) : values.gugun || undefined; // 도: 초기/시도 변경(빈 값)만 첫 시군구. 전체(ALL)는 유지. 그 외: 전체 useEffect(() => { if (!mergedProps.gugun) return; if (props.defaultRegionAll) return; if (!values.sido || values.sido === "ALL") return; if (!selectedSido) return; if (!isDoSelected) { setValues((prev) => { if (prev.gugun) return prev; return { ...prev, gugun: "ALL", bsnmCd: selectedSido.bsnmCd || prev.bsnmCd, }; }); return; } if (!firstConcreteSggOption) return; setValues((prev) => { if (prev.gugun) return prev; return { ...prev, gugun: firstConcreteSggOption.gugun, bsnmCd: firstConcreteSggOption.bsnmCd, }; }); }, [ firstConcreteSggOption, isDoSelected, mergedProps.gugun, props.defaultRegionAll, selectedSido, values.sido, ]); const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); props.onSubmit?.(values); }; const handleReset = () => { setValues(buildSearchFormValues(props, sidoList, defaultYear)); props.onReset?.(); }; const handleSidoChange = (value: string) => { const selected = value === "ALL" ? undefined : sidoList?.data?.sidoList?.find((v) => v.sido === value); const selectedBsnm = selected?.bsnmCd || ""; const gugunAll = props.defaultRegionAll || value === "ALL" || !isDoSido(selected); setValues((prev) => ({ ...prev, sido: value, gugun: gugunAll ? "ALL" : "", 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 (
{mergedProps.sido && (
)} {mergedProps.gugun && (
)} {mergedProps.year && (
)} {mergedProps.clsf && (
)} {mergedProps.fctName && (
setValues({ ...values, fctName: e.target.value, }) } autoComplete="off" />
)} {mergedProps.capacityGbn && (
)} {mergedProps.isDetail && (
setValues({ ...values, isDetail: value === true, }) } />
)} {mergedProps.isSummary && (
)} {mergedProps.order && (
)}
{mergedProps.isReset && ( )}
); }; export default SearchForm;