136 lines
5.4 KiB
TypeScript
136 lines
5.4 KiB
TypeScript
import { useFoStore } from '@/app/store';
|
|
import type { facilityListProps } from '@/app/store/processFacility';
|
|
import { Label } from '@common/components/ui';
|
|
import React, { useEffect, useState, useRef, useCallback } from 'react'
|
|
|
|
type thisProps = {
|
|
label: string | undefined;
|
|
setValue : (fcltyCd?: string, fcltyNm?: string, fcltyCpc?: number | '', trtFrfNm?: string | '', efflWqltStndrdNm?: string | '') => void;
|
|
sidoSigun? : string | undefined;
|
|
fcltyName? : string | undefined;
|
|
}
|
|
|
|
function useDebouncedValue<T>(value: T, delay = 500) {
|
|
const [v, setV] = useState(value);
|
|
useEffect(() => {
|
|
const t = setTimeout(() => setV(value), delay);
|
|
return () => clearTimeout(t);
|
|
}, [value, delay]);
|
|
return v;
|
|
}
|
|
|
|
export default function SearchInputText({label=undefined, setValue=()=>{}, sidoSigun=undefined, fcltyName=undefined} :thisProps) {
|
|
|
|
const { ProcessFacilityList, searchProcessFacilitySearching } = useFoStore((state) => state);
|
|
const [isOpen, setIsOpen] = useState(false);
|
|
const [textValue, setTextValue] = useState(fcltyName || '');
|
|
const debouncedTextValue = useDebouncedValue(textValue);
|
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
const processFacilityList = ProcessFacilityList?.processFacilityList || [];
|
|
|
|
const handleInputChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const value = e.target.value;
|
|
setTextValue(value);
|
|
if (value) {
|
|
setIsOpen(true); // 추천 목록 표시
|
|
// 삭제된 기존 코드:
|
|
// if(sidoSigun) {
|
|
// searchProcessFacilitySearching(1, 4999, 'A', sidoSigun, '0', '0', '0', value);
|
|
// } else {
|
|
// searchProcessFacilitySearching(1, 4999, '0', '0', '0', '0', '0', value);
|
|
// }
|
|
// onChange 안에서 바로 검색하면 글자를 입력할 때마다 요청이 발생해서
|
|
// debounce가 사실상 동작하지 않습니다.
|
|
// 실제 검색은 아래 debounced useEffect에서만 실행되도록 변경했습니다.
|
|
} else {
|
|
setIsOpen(false);
|
|
}
|
|
}, []);
|
|
|
|
const onInputFocus = useCallback(() => {
|
|
setIsOpen(true);
|
|
if (textValue.trim()) return;
|
|
|
|
// 입력값이 없을 때만 기본 목록을 조회합니다.
|
|
// 이미 입력 중인 경우에는 기존 값으로 다시 전체/광범위 조회하지 않습니다.
|
|
if(sidoSigun) {
|
|
searchProcessFacilitySearching(1, 4999, 'A', sidoSigun, '0', '0', '0');
|
|
} else {
|
|
searchProcessFacilitySearching(1, 4999, '0', '0', '0', '0', '0');
|
|
}
|
|
}, [searchProcessFacilitySearching, sidoSigun, textValue]);
|
|
|
|
useEffect(() => {
|
|
setTextValue(fcltyName || '');
|
|
}, [fcltyName]);
|
|
|
|
useEffect(() => {
|
|
const keyword = debouncedTextValue.trim();
|
|
|
|
if (!keyword) return;
|
|
|
|
// debounce가 적용된 값이 확정된 뒤에만 검색해서
|
|
// 시설명을 입력하는 도중의 과도한 검색 요청을 막습니다.
|
|
if(sidoSigun) {
|
|
searchProcessFacilitySearching(1, 4999, 'A', sidoSigun, '0', '0', '0', keyword);
|
|
} else {
|
|
searchProcessFacilitySearching(1, 4999, '0', '0', '0', '0', '0', keyword);
|
|
}
|
|
}, [debouncedTextValue, searchProcessFacilitySearching, sidoSigun]);
|
|
|
|
const handleSuggestionClick = useCallback((bscFctCd: string, bscFctNm: string, fctCpc: number | '', trtFrfNm: string | '', efflWqltStndrdNm: string | '') => {
|
|
setTextValue(bscFctNm);
|
|
setValue(bscFctCd, bscFctNm, fctCpc, trtFrfNm, efflWqltStndrdNm);
|
|
setIsOpen(false);
|
|
}, [setValue]);
|
|
|
|
useEffect(() => {
|
|
const handleClickOutside = (event: MouseEvent) => {
|
|
if (wrapperRef.current && !wrapperRef.current.contains(event.target as Node)) {
|
|
setIsOpen(false);
|
|
}
|
|
};
|
|
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => {
|
|
document.removeEventListener('mousedown', handleClickOutside);
|
|
};
|
|
}, []);
|
|
|
|
return (
|
|
<div className={'relative'} ref={wrapperRef}>
|
|
{label && <Label className="w-20 justify-self-center text-xl text-label">{label}</Label>}
|
|
<input
|
|
type="text"
|
|
value={textValue}
|
|
onChange={handleInputChange}
|
|
onFocus={onInputFocus}
|
|
className="ml-6 w-[200px] rounded-md border border-input bg-form px-3 py-2 text-foreground focus:outline-none focus:ring-2 focus:ring-ring"
|
|
placeholder={`${label}을(를) 입력하세요`}
|
|
/>
|
|
{isOpen && processFacilityList.length > 0 && (
|
|
<div className="absolute z-10 ml-20 !mt-5 h-[90px] w-[300px] overflow-auto rounded-md border border-border bg-popover px-2 py-1 text-popover-foreground shadow-lg">
|
|
<div className="max-h-72">
|
|
{processFacilityList.map((item : facilityListProps, index : number) => (
|
|
<div
|
|
key={index}
|
|
className="w-full cursor-pointer overflow-auto py-2 pl-1 pr-10 text-left hover:bg-accent hover:text-accent-foreground"
|
|
onClick={() => handleSuggestionClick(item.bscFctCd, item.bscFctNm, Number(item.fctCpc), item.trtFrfNm, item.efflWqltStndrdNm)}
|
|
role="button"
|
|
tabIndex={0}
|
|
onKeyDown={(e) => {
|
|
if (e.key === 'Enter' || e.key === ' ') {
|
|
handleSuggestionClick(item.bscFctCd, item.bscFctNm, Number(item.fctCpc), item.trtFrfNm, item.efflWqltStndrdNm);
|
|
}
|
|
}}
|
|
>
|
|
{item.sido} {item.gugun} <span className="float-right">{item.bscFctNm}</span>
|
|
</div>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
)
|
|
}
|