fix:분개 모달 높이 확대 및 드롭다운 잘림 현상 수정
- 모달 기본 높이 min-h-[80vh]로 확대 (기존 대비 1.5배) - AccountCodeSelect, TradingPartnerSelect 드롭다운을 ReactDOM.createPortal로 변경 - 드롭다운이 모달 overflow에 의해 잘리지 않도록 fixed 포지셔닝 적용 - 공간 부족 시 위쪽으로 열리는 자동 방향 전환 지원
This commit is contained in:
@@ -103,7 +103,10 @@
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [highlightIndex, setHighlightIndex] = useState(-1);
|
||||
const [dropdownStyle, setDropdownStyle] = useState({});
|
||||
const containerRef = useRef(null);
|
||||
const triggerRef = useRef(null);
|
||||
const dropdownRef = useRef(null);
|
||||
const listRef = useRef(null);
|
||||
|
||||
const selectedItem = accountCodes.find(c => c.code === value);
|
||||
@@ -119,7 +122,8 @@
|
||||
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e) => {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target) &&
|
||||
(!dropdownRef.current || !dropdownRef.current.contains(e.target))) {
|
||||
setIsOpen(false); setSearch(''); setHighlightIndex(-1);
|
||||
}
|
||||
};
|
||||
@@ -127,6 +131,20 @@
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const calcDropdownPos = () => {
|
||||
if (!triggerRef.current) return;
|
||||
const rect = triggerRef.current.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const openUp = spaceBelow < 260;
|
||||
setDropdownStyle({
|
||||
position: 'fixed',
|
||||
left: rect.left,
|
||||
width: Math.max(rect.width, 224),
|
||||
zIndex: 9999,
|
||||
...(openUp ? { bottom: window.innerHeight - rect.top + 4 } : { top: rect.bottom + 4 }),
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelect = (code) => {
|
||||
onChange(code.code, code.name);
|
||||
setIsOpen(false); setSearch(''); setHighlightIndex(-1);
|
||||
@@ -155,7 +173,7 @@
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="relative">
|
||||
<div onClick={() => setIsOpen(!isOpen)}
|
||||
<div ref={triggerRef} onClick={() => { if (!isOpen) calcDropdownPos(); setIsOpen(!isOpen); }}
|
||||
className={`w-full px-2 py-1.5 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 truncate' : 'text-stone-400'}>{displayText || '계정과목 선택'}</span>
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
@@ -165,8 +183,8 @@ className={`w-full px-2 py-1.5 text-xs border rounded cursor-pointer flex items-
|
||||
<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-56 bg-white border border-stone-200 rounded-lg shadow-lg">
|
||||
{isOpen && ReactDOM.createPortal(
|
||||
<div ref={dropdownRef} style={dropdownStyle} className="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)} onKeyDown={handleKeyDown}
|
||||
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 />
|
||||
@@ -183,7 +201,8 @@ className={`px-3 py-1.5 text-xs cursor-pointer ${index === highlightIndex ? 'bg-
|
||||
))}
|
||||
{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>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -456,7 +475,10 @@ className="px-4 py-2 text-sm font-medium text-white bg-emerald-600 rounded-lg ho
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [search, setSearch] = useState('');
|
||||
const [highlightIndex, setHighlightIndex] = useState(-1);
|
||||
const [dropdownStyle, setDropdownStyle] = useState({});
|
||||
const containerRef = useRef(null);
|
||||
const triggerRef = useRef(null);
|
||||
const dropdownRef = useRef(null);
|
||||
const listRef = useRef(null);
|
||||
|
||||
const displayText = valueName || '';
|
||||
@@ -470,7 +492,8 @@ className="px-4 py-2 text-sm font-medium text-white bg-emerald-600 rounded-lg ho
|
||||
useEffect(() => { setHighlightIndex(-1); }, [search]);
|
||||
useEffect(() => {
|
||||
const handleClickOutside = (e) => {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target)) {
|
||||
if (containerRef.current && !containerRef.current.contains(e.target) &&
|
||||
(!dropdownRef.current || !dropdownRef.current.contains(e.target))) {
|
||||
setIsOpen(false); setSearch(''); setHighlightIndex(-1);
|
||||
}
|
||||
};
|
||||
@@ -478,6 +501,20 @@ className="px-4 py-2 text-sm font-medium text-white bg-emerald-600 rounded-lg ho
|
||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const calcDropdownPos = () => {
|
||||
if (!triggerRef.current) return;
|
||||
const rect = triggerRef.current.getBoundingClientRect();
|
||||
const spaceBelow = window.innerHeight - rect.bottom;
|
||||
const openUp = spaceBelow < 280;
|
||||
setDropdownStyle({
|
||||
position: 'fixed',
|
||||
left: rect.left,
|
||||
width: Math.max(rect.width, 224),
|
||||
zIndex: 9999,
|
||||
...(openUp ? { bottom: window.innerHeight - rect.top + 4 } : { top: rect.bottom + 4 }),
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelect = (partner) => {
|
||||
onChange(partner.id, partner.name);
|
||||
setIsOpen(false); setSearch(''); setHighlightIndex(-1);
|
||||
@@ -506,7 +543,7 @@ className="px-4 py-2 text-sm font-medium text-white bg-emerald-600 rounded-lg ho
|
||||
|
||||
return (
|
||||
<div ref={containerRef} className="relative">
|
||||
<div onClick={() => setIsOpen(!isOpen)}
|
||||
<div ref={triggerRef} onClick={() => { if (!isOpen) calcDropdownPos(); setIsOpen(!isOpen); }}
|
||||
className={`w-full px-2 py-1.5 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 truncate' : 'text-stone-400'}>{displayText || '거래처 선택'}</span>
|
||||
<div className="flex items-center gap-1 flex-shrink-0">
|
||||
@@ -516,8 +553,8 @@ className={`w-full px-2 py-1.5 text-xs border rounded cursor-pointer flex items-
|
||||
<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-56 bg-white border border-stone-200 rounded-lg shadow-lg">
|
||||
{isOpen && ReactDOM.createPortal(
|
||||
<div ref={dropdownRef} style={dropdownStyle} className="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)} onKeyDown={handleKeyDown}
|
||||
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 />
|
||||
@@ -539,7 +576,8 @@ className={`px-3 py-1.5 text-xs cursor-pointer ${index === highlightIndex ? 'bg-
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>,
|
||||
document.body
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
@@ -962,9 +1000,9 @@ className="px-2.5 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-f
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-6xl mx-4 max-h-[90vh] overflow-hidden">
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-6xl mx-4 min-h-[80vh] max-h-[95vh] flex flex-col overflow-hidden">
|
||||
{/* 헤더 */}
|
||||
<div className="px-6 py-4 border-b border-stone-100 flex items-center justify-between">
|
||||
<div className="px-6 py-4 border-b border-stone-100 flex items-center justify-between flex-shrink-0">
|
||||
<h3 className="text-lg font-bold text-stone-900">
|
||||
{isEditMode ? `전표 수정 (${existingEntryNo})` : '수동 전표 생성'}
|
||||
</h3>
|
||||
@@ -973,7 +1011,7 @@ className="px-2.5 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-f
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-5 overflow-y-auto max-h-[68vh] space-y-5">
|
||||
<div className="px-6 py-5 overflow-y-auto flex-1 space-y-5">
|
||||
{loadingEntry ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<div className="animate-spin rounded-full h-6 w-6 border-2 border-emerald-600 border-t-transparent"></div>
|
||||
@@ -1116,7 +1154,7 @@ className={`p-1 rounded ${lines.length <= 2 ? 'text-stone-200 cursor-not-allowed
|
||||
</div>
|
||||
|
||||
{/* 하단 버튼 */}
|
||||
<div className="px-6 py-4 border-t border-stone-100 flex justify-between">
|
||||
<div className="px-6 py-4 border-t border-stone-100 flex justify-between flex-shrink-0">
|
||||
<div>
|
||||
{isEditMode && (
|
||||
<button onClick={handleDelete} disabled={saving}
|
||||
@@ -1339,9 +1377,9 @@ className={`px-6 py-2 text-sm font-medium rounded-lg flex items-center gap-1 tra
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-6xl mx-4 max-h-[90vh] overflow-hidden">
|
||||
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-6xl mx-4 min-h-[80vh] max-h-[95vh] flex flex-col overflow-hidden">
|
||||
{/* 헤더 */}
|
||||
<div className="px-6 py-4 border-b border-stone-100 flex items-center justify-between">
|
||||
<div className="px-6 py-4 border-b border-stone-100 flex items-center justify-between flex-shrink-0">
|
||||
<h3 className="text-lg font-bold text-stone-900">
|
||||
{isEditMode ? '분개 수정' : '분개 생성'}
|
||||
</h3>
|
||||
@@ -1350,7 +1388,7 @@ className={`px-6 py-2 text-sm font-medium rounded-lg flex items-center gap-1 tra
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-5 overflow-y-auto max-h-[68vh] space-y-5">
|
||||
<div className="px-6 py-5 overflow-y-auto flex-1 space-y-5">
|
||||
{/* 거래 정보 카드 */}
|
||||
<div className={`rounded-xl p-4 ${isDeposit ? 'bg-blue-50 border border-blue-100' : 'bg-red-50 border border-red-100'}`}>
|
||||
<h4 className="text-sm font-semibold text-stone-700 mb-3">거래 정보</h4>
|
||||
@@ -1515,7 +1553,7 @@ className={`p-1 rounded ${lines.length <= 2 ? 'text-stone-200 cursor-not-allowed
|
||||
</div>
|
||||
|
||||
{/* 하단 버튼 */}
|
||||
<div className="px-6 py-4 border-t border-stone-100 flex justify-between">
|
||||
<div className="px-6 py-4 border-t border-stone-100 flex justify-between flex-shrink-0">
|
||||
<div>
|
||||
{isEditMode && (
|
||||
<button onClick={handleDelete} disabled={saving}
|
||||
|
||||
Reference in New Issue
Block a user