feat:계정과목 선택에 검색 기능 추가

- 단순 select를 검색 가능한 커스텀 드롭다운으로 변경
- 코드 또는 이름으로 실시간 검색
- 선택값 초기화(X) 버튼 추가
- 외부 클릭 시 드롭다운 닫힘

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
pro
2026-01-23 12:34:25 +09:00
parent 91dd23cd1e
commit d3078c7f4a

View File

@@ -154,22 +154,123 @@ className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
</div>
);
// AccountCodeSelect Component
const AccountCodeSelect = ({ value, onChange, accountCodes }) => (
<select
value={value || ''}
onChange={(e) => {
const selected = accountCodes.find(c => c.code === e.target.value);
onChange(e.target.value, selected?.name || '');
}}
className="w-full px-2 py-1 text-xs border border-stone-200 rounded focus:ring-2 focus:ring-emerald-500 outline-none bg-white"
>
<option value="">선택</option>
{accountCodes.map(code => (
<option key={code.code} value={code.code}>{code.code} {code.name}</option>
))}
</select>
);
// AccountCodeSelect Component (검색 가능한 드롭다운)
const AccountCodeSelect = ({ value, onChange, accountCodes }) => {
const [isOpen, setIsOpen] = useState(false);
const [search, setSearch] = useState('');
const containerRef = useRef(null);
// 선택된 값의 표시 텍스트
const selectedItem = accountCodes.find(c => c.code === value);
const displayText = selectedItem ? `${selectedItem.code} ${selectedItem.name}` : '';
// 검색 필터링
const filteredCodes = accountCodes.filter(code => {
if (!search) return true;
const searchLower = search.toLowerCase();
return code.code.toLowerCase().includes(searchLower) ||
code.name.toLowerCase().includes(searchLower);
});
// 외부 클릭 시 닫기
useEffect(() => {
const handleClickOutside = (e) => {
if (containerRef.current && !containerRef.current.contains(e.target)) {
setIsOpen(false);
setSearch('');
}
};
document.addEventListener('mousedown', handleClickOutside);
return () => document.removeEventListener('mousedown', handleClickOutside);
}, []);
const handleSelect = (code) => {
const selected = accountCodes.find(c => c.code === code.code);
onChange(code.code, selected?.name || '');
setIsOpen(false);
setSearch('');
};
const handleClear = (e) => {
e.stopPropagation();
onChange('', '');
setSearch('');
};
return (
<div ref={containerRef} className="relative">
{/* 선택 버튼 */}
<div
onClick={() => setIsOpen(!isOpen)}
className={`w-full px-2 py-1 text-xs border rounded cursor-pointer flex items-center justify-between gap-1 ${
isOpen ? 'border-emerald-500 ring-2 ring-emerald-500' : 'border-stone-200'
} bg-white`}
>
<span className={displayText ? 'text-stone-900' : 'text-stone-400'}>
{displayText || '선택'}
</span>
<div className="flex items-center gap-1">
{value && (
<button
onClick={handleClear}
className="text-stone-400 hover:text-stone-600"
>
<svg className="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button>
)}
<svg className={`w-3 h-3 text-stone-400 transition-transform ${isOpen ? 'rotate-180' : ''}`} fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
</svg>
</div>
</div>
{/* 드롭다운 */}
{isOpen && (
<div className="absolute z-50 mt-1 w-48 bg-white border border-stone-200 rounded-lg shadow-lg">
{/* 검색 입력 */}
<div className="p-2 border-b border-stone-100">
<input
type="text"
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="코드 또는 이름 검색..."
className="w-full px-2 py-1 text-xs border border-stone-200 rounded focus:ring-1 focus:ring-emerald-500 outline-none"
autoFocus
/>
</div>
{/* 옵션 목록 */}
<div className="max-h-48 overflow-y-auto">
{filteredCodes.length === 0 ? (
<div className="px-3 py-2 text-xs text-stone-400 text-center">
검색 결과 없음
</div>
) : (
filteredCodes.slice(0, 50).map(code => (
<div
key={code.code}
onClick={() => handleSelect(code)}
className={`px-3 py-1.5 text-xs cursor-pointer hover:bg-emerald-50 ${
value === code.code ? 'bg-emerald-100 text-emerald-700' : 'text-stone-700'
}`}
>
<span className="font-mono text-emerald-600">{code.code}</span>
<span className="ml-1">{code.name}</span>
</div>
))
)}
{filteredCodes.length > 50 && (
<div className="px-3 py-1 text-xs text-stone-400 text-center border-t">
+{filteredCodes.length - 50} 있음
</div>
)}
</div>
</div>
)}
</div>
);
};
// AccountCodeSettingsModal Component
const AccountCodeSettingsModal = ({ isOpen, onClose, onUpdate }) => {