feat:계정과목 선택에 검색 기능 추가
- 단순 select를 검색 가능한 커스텀 드롭다운으로 변경 - 코드 또는 이름으로 실시간 검색 - 선택값 초기화(X) 버튼 추가 - 외부 클릭 시 드롭다운 닫힘 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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 }) => {
|
||||
|
||||
Reference in New Issue
Block a user