@extends('layouts.app')

@section('title', '일반전표입력')

@push('styles')
<style>
    @media print { .no-print { display: none !important; } }
</style>
@endpush

@section('content')
<meta name="csrf-token" content="{{ csrf_token() }}">
<div id="journal-entries-root"></div>
@endsection

@push('scripts')
@include('partials.react-cdn')
<script src="https://unpkg.com/lucide@0.469.0?v={{ time() }}"></script>
@verbatim
<script type="text/babel">
const { useState, useRef, useEffect, useCallback } = React;

// ============================================================
// 공통 유틸리티
// ============================================================
const createIcon = (name) => {
    const _def=((n)=>{const a={'check-circle':'CircleCheck','alert-circle':'CircleAlert','alert-triangle':'TriangleAlert','clipboard-check':'ClipboardCheck','arrow-up-circle':'CircleArrowUp','arrow-down-circle':'CircleArrowDown'};if(a[n]&&lucide[a[n]])return lucide[a[n]];const p=n.split('-').map(w=>w.charAt(0).toUpperCase()+w.slice(1)).join('');return lucide[p]||(lucide.icons&&lucide.icons[n])||null;})(name);
    const _c = s => s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
    return ({ className = "w-5 h-5", ...props }) => {
        if (!_def) return null;
        const [, attrs, children = []] = _def;
        const sp = { className };
        Object.entries(attrs).forEach(([k, v]) => { sp[_c(k)] = v; });
        Object.assign(sp, props);
        return React.createElement("svg", sp, ...children.map(([tag, ca], i) => {
            const cp = { key: i };
            if (ca) Object.entries(ca).forEach(([k, v]) => { cp[_c(k)] = v; });
            return React.createElement(tag, cp);
        }));
    };
};

const Plus = createIcon('plus');
const Search = createIcon('search');
const FileText = createIcon('file-text');
const Trash2 = createIcon('trash-2');
const Save = createIcon('save');
const ArrowLeft = createIcon('arrow-left');
const X = createIcon('x');
const Edit = createIcon('edit');
const Edit3 = createIcon('edit-3');
const CheckCircle = createIcon('check-circle');
const AlertTriangle = createIcon('alert-triangle');
const Landmark = createIcon('landmark');
const ArrowDownCircle = createIcon('arrow-down-circle');
const ArrowUpCircle = createIcon('arrow-up-circle');
const RefreshCw = createIcon('refresh-cw');
const Settings = createIcon('settings');
const Calendar = createIcon('calendar');
const CreditCard = createIcon('credit-card');

const CSRF_TOKEN = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content');

const formatCurrency = (num) => num ? Number(num).toLocaleString() : '0';
const formatInputCurrency = (value) => {
    if (!value && value !== 0) return '';
    const num = String(value).replace(/[^\d]/g, '');
    return num ? Number(num).toLocaleString() : '';
};
const parseInputCurrency = (value) => {
    const num = String(value).replace(/[^\d]/g, '');
    return num ? parseInt(num, 10) : 0;
};

const notify = (message, type = 'info') => {
    if (typeof window.showToast === 'function') {
        window.showToast(message, type);
    } else {
        alert(message);
    }
};

// 한국 시간대 날짜
const getKoreanDate = (offset = 0) => {
    const d = new Date();
    d.setDate(d.getDate() + offset);
    return d.toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' });
};

const formatDate = (dateStr) => {
    if (!dateStr || dateStr.length < 8) return dateStr;
    const d = dateStr.replace(/[^0-9]/g, '');
    return `${d.slice(0,4)}-${d.slice(4,6)}-${d.slice(6,8)}`;
};

const formatTime = (timeStr) => {
    if (!timeStr || timeStr.length < 4) return timeStr;
    const t = timeStr.replace(/[^0-9]/g, '');
    return `${t.slice(0,2)}:${t.slice(2,4)}`;
};

// ============================================================
// AccountCodeSelect (emerald 테마)
// ============================================================
const AccountCodeSelect = ({ value, onChange, accountCodes, onTab, triggerId }) => {
    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);
    const displayText = selectedItem ? `${selectedItem.code} ${selectedItem.name}` : '';

    const filteredCodes = accountCodes.filter(code => {
        if (!search) return true;
        const s = search.toLowerCase();
        return code.code.toLowerCase().includes(s) || code.name.toLowerCase().includes(s);
    });

    useEffect(() => { setHighlightIndex(-1); }, [search]);

    useEffect(() => {
        const handleClickOutside = (e) => {
            if (containerRef.current && !containerRef.current.contains(e.target) &&
                (!dropdownRef.current || !dropdownRef.current.contains(e.target))) {
                setIsOpen(false); setSearch(''); setHighlightIndex(-1);
            }
        };
        document.addEventListener('mousedown', handleClickOutside);
        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);
        setTimeout(() => triggerRef.current?.focus(), 0);
    };
    const handleClear = (e) => { e.stopPropagation(); onChange('', ''); setSearch(''); };

    const handleKeyDown = (e) => {
        const maxIndex = Math.min(filteredCodes.length, 50) - 1;
        if (e.key === 'Tab') {
            e.preventDefault();
            if (filteredCodes.length > 0 && highlightIndex >= 0) {
                onChange(filteredCodes[highlightIndex].code, filteredCodes[highlightIndex].name);
            }
            setIsOpen(false); setSearch(''); setHighlightIndex(-1);
            setTimeout(() => onTab && onTab(), 0);
        } else if (e.key === 'ArrowDown') {
            e.preventDefault();
            const ni = highlightIndex < maxIndex ? highlightIndex + 1 : 0;
            setHighlightIndex(ni);
            setTimeout(() => { listRef.current?.children[ni]?.scrollIntoView({ block: 'nearest' }); }, 0);
        } else if (e.key === 'ArrowUp') {
            e.preventDefault();
            const ni = highlightIndex > 0 ? highlightIndex - 1 : maxIndex;
            setHighlightIndex(ni);
            setTimeout(() => { listRef.current?.children[ni]?.scrollIntoView({ block: 'nearest' }); }, 0);
        } else if (e.key === 'Enter' && filteredCodes.length > 0) {
            e.preventDefault();
            handleSelect(filteredCodes[highlightIndex >= 0 ? highlightIndex : 0]);
        } else if (e.key === 'Escape') {
            setIsOpen(false); setSearch(''); setHighlightIndex(-1);
        }
    };

    return (
        <div ref={containerRef} className="relative">
            <div ref={triggerRef} id={triggerId} tabIndex={0}
                onClick={() => { if (!isOpen) calcDropdownPos(); setIsOpen(!isOpen); }}
                onKeyDown={(e) => {
                    if (e.key === 'Tab') {
                        e.preventDefault();
                        if (isOpen) { setIsOpen(false); setSearch(''); setHighlightIndex(-1); }
                        setTimeout(() => onTab && onTab(), 0);
                    } else if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault();
                        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">
                    {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 && 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 />
                    </div>
                    <div ref={listRef} 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, index) => (
                            <div key={code.code} onClick={() => handleSelect(code)}
                                className={`px-3 py-1.5 text-xs cursor-pointer ${index === highlightIndex ? 'bg-emerald-600 text-white font-semibold' : value === code.code ? 'bg-emerald-100 text-emerald-700' : 'text-stone-700 hover:bg-emerald-50'}`}>
                                <span className={`font-mono ${index === highlightIndex ? 'text-white' : '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>,
                document.body
            )}
        </div>
    );
};

// ============================================================
// AccountCodeSettingsModal (계정과목 설정)
// ============================================================
const AccountCodeSettingsModal = ({ isOpen, onClose, onUpdate }) => {
    const [codes, setCodes] = useState([]);
    const [loading, setLoading] = useState(false);
    const [newCode, setNewCode] = useState('');
    const [newName, setNewName] = useState('');
    const [newCategory, setNewCategory] = useState('');
    const [filter, setFilter] = useState('');
    const [categoryFilter, setCategoryFilter] = useState('');

    const categories = ['자산', '부채', '자본', '수익', '비용'];

    useEffect(() => { if (isOpen) loadCodes(); }, [isOpen]);

    const loadCodes = async () => {
        setLoading(true);
        try {
            const res = await fetch('/finance/journal-entries/account-codes/all');
            const data = await res.json();
            if (data.success) setCodes(data.data || []);
        } catch (err) {
            notify('계정과목 로드 실패', 'error');
        } finally {
            setLoading(false);
        }
    };

    const handleAdd = async () => {
        if (!newCode.trim() || !newName.trim()) { notify('코드와 이름을 입력해주세요.', 'warning'); return; }
        try {
            const res = await fetch('/finance/journal-entries/account-codes', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF_TOKEN },
                body: JSON.stringify({ code: newCode.trim(), name: newName.trim(), category: newCategory || null }),
            });
            const data = await res.json();
            if (data.success) {
                notify('계정과목이 추가되었습니다.', 'success');
                setNewCode(''); setNewName(''); setNewCategory('');
                loadCodes(); onUpdate();
            } else {
                notify(data.error || '추가 실패', 'error');
            }
        } catch (err) { notify('추가 실패: ' + err.message, 'error'); }
    };

    const handleToggleActive = async (item) => {
        try {
            const res = await fetch(`/finance/journal-entries/account-codes/${item.id}`, {
                method: 'PUT',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF_TOKEN },
                body: JSON.stringify({ is_active: !item.is_active }),
            });
            const data = await res.json();
            if (data.success) { loadCodes(); onUpdate(); }
        } catch (err) { notify('변경 실패', 'error'); }
    };

    const handleDelete = async (item) => {
        if (!confirm(`"${item.code} ${item.name}" 계정과목을 삭제하시겠습니까?`)) return;
        try {
            const res = await fetch(`/finance/journal-entries/account-codes/${item.id}`, {
                method: 'DELETE',
                headers: { 'X-CSRF-TOKEN': CSRF_TOKEN },
            });
            const data = await res.json();
            if (data.success) { notify('삭제되었습니다.', 'success'); loadCodes(); onUpdate(); }
            else { notify(data.error || '삭제 실패', 'error'); }
        } catch (err) { notify('삭제 실패: ' + err.message, 'error'); }
    };

    const filteredCodes = codes.filter(c => {
        const matchText = filter === '' || c.code.includes(filter) || c.name.includes(filter);
        const matchCategory = categoryFilter === '' || c.category === categoryFilter;
        return matchText && matchCategory;
    });

    if (!isOpen) return null;

    return (
        <div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
            <div className="bg-white rounded-xl shadow-2xl w-full max-w-4xl max-h-[90vh] overflow-hidden">
                <div className="flex items-center justify-between px-6 py-4 border-b border-stone-200 bg-stone-50">
                    <h2 className="text-lg font-bold text-stone-900">계정과목 설정</h2>
                    <button onClick={onClose} className="text-stone-400 hover:text-stone-600 text-2xl">&times;</button>
                </div>
                <div className="px-6 py-4 border-b border-stone-100 bg-emerald-50/50">
                    <div className="flex gap-2 items-end">
                        <div className="flex-1">
                            <label className="block text-xs font-medium text-stone-600 mb-1">코드</label>
                            <input type="text" value={newCode} onChange={(e) => setNewCode(e.target.value)} placeholder="예: 101"
                                className="w-full px-3 py-2 border border-stone-200 rounded-lg text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
                        </div>
                        <div className="flex-[2]">
                            <label className="block text-xs font-medium text-stone-600 mb-1">계정과목명</label>
                            <input type="text" value={newName} onChange={(e) => setNewName(e.target.value)} placeholder="예: 현금"
                                className="w-full px-3 py-2 border border-stone-200 rounded-lg text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
                        </div>
                        <div className="flex-1">
                            <label className="block text-xs font-medium text-stone-600 mb-1">분류</label>
                            <select value={newCategory} onChange={(e) => setNewCategory(e.target.value)}
                                className="w-full px-3 py-2 border border-stone-200 rounded-lg text-sm focus:ring-2 focus:ring-emerald-500 outline-none">
                                <option value="">선택</option>
                                {categories.map(cat => <option key={cat} value={cat}>{cat}</option>)}
                            </select>
                        </div>
                        <button onClick={handleAdd} className="px-4 py-2 bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 text-sm font-medium">추가</button>
                    </div>
                </div>
                <div className="px-6 py-3 border-b border-stone-100 flex gap-3">
                    <input type="text" value={filter} onChange={(e) => setFilter(e.target.value)} placeholder="코드 또는 이름 검색..."
                        className="flex-1 px-3 py-2 border border-stone-200 rounded-lg text-sm" />
                    <select value={categoryFilter} onChange={(e) => setCategoryFilter(e.target.value)}
                        className="px-3 py-2 border border-stone-200 rounded-lg text-sm">
                        <option value="">전체 분류</option>
                        {categories.map(cat => <option key={cat} value={cat}>{cat}</option>)}
                    </select>
                    <span className="text-sm text-stone-500 py-2">{filteredCodes.length}개</span>
                </div>
                <div className="overflow-y-auto" style={ {maxHeight: '400px'} }>
                    {loading ? (
                        <div className="p-8 text-center text-stone-400">로딩 중...</div>
                    ) : (
                        <table className="w-full text-sm">
                            <thead className="bg-stone-50 sticky top-0">
                                <tr>
                                    <th className="px-4 py-3 text-left font-medium text-stone-600">코드</th>
                                    <th className="px-4 py-3 text-left font-medium text-stone-600">계정과목명</th>
                                    <th className="px-4 py-3 text-left font-medium text-stone-600">분류</th>
                                    <th className="px-4 py-3 text-center font-medium text-stone-600">상태</th>
                                    <th className="px-4 py-3 text-center font-medium text-stone-600">작업</th>
                                </tr>
                            </thead>
                            <tbody className="divide-y divide-stone-100">
                                {filteredCodes.map(item => (
                                    <tr key={item.id} className={`hover:bg-stone-50 ${!item.is_active ? 'opacity-50' : ''}`}>
                                        <td className="px-4 py-2 font-mono">{item.code}</td>
                                        <td className="px-4 py-2">{item.name}</td>
                                        <td className="px-4 py-2">
                                            <span className={`px-2 py-0.5 rounded-full text-xs ${
                                                item.category === '자산' ? 'bg-blue-100 text-blue-700' :
                                                item.category === '부채' ? 'bg-red-100 text-red-700' :
                                                item.category === '자본' ? 'bg-purple-100 text-purple-700' :
                                                item.category === '수익' ? 'bg-green-100 text-green-700' :
                                                item.category === '비용' ? 'bg-orange-100 text-orange-700' :
                                                'bg-stone-100 text-stone-600'
                                            }`}>{item.category || '-'}</span>
                                        </td>
                                        <td className="px-4 py-2 text-center">
                                            <button onClick={() => handleToggleActive(item)}
                                                className={`px-2 py-1 rounded text-xs ${item.is_active ? 'bg-green-100 text-green-700 hover:bg-green-200' : 'bg-stone-100 text-stone-500 hover:bg-stone-200'}`}>
                                                {item.is_active ? '사용중' : '미사용'}
                                            </button>
                                        </td>
                                        <td className="px-4 py-2 text-center">
                                            <button onClick={() => handleDelete(item)} className="text-red-500 hover:text-red-700 text-xs">삭제</button>
                                        </td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    )}
                </div>
                <div className="px-6 py-4 border-t border-stone-200 bg-stone-50 flex justify-end">
                    <button onClick={onClose} className="px-6 py-2 bg-stone-600 text-white rounded-lg hover:bg-stone-700 font-medium">닫기</button>
                </div>
            </div>
        </div>
    );
};

// ============================================================
// AddTradingPartnerModal
// ============================================================
const AddTradingPartnerModal = ({ isOpen, onClose, onSaved }) => {
    const [saving, setSaving] = useState(false);
    const [form, setForm] = useState({
        name: '', type: 'vendor', category: '기타', bizNo: '', contact: '', manager: '', memo: '',
    });
    const categories = ['기타', '제조업', '도소매업', '서비스업', '건설업', 'IT', '금융', '물류'];

    const handleSave = async () => {
        if (!form.name.trim()) { alert('거래처명을 입력하세요.'); return; }
        setSaving(true);
        try {
            const res = await fetch('/finance/partners/store', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': CSRF_TOKEN },
                body: JSON.stringify(form),
            });
            const data = await res.json();
            if (data.success) {
                onSaved({ id: data.data.id, name: data.data.name, biz_no: data.data.bizNo, type: data.data.type });
                setForm({ name: '', type: 'vendor', category: '기타', bizNo: '', contact: '', manager: '', memo: '' });
                onClose();
            } else {
                alert(data.message || '저장에 실패했습니다.');
            }
        } catch (err) {
            alert('저장 중 오류가 발생했습니다.');
        } finally {
            setSaving(false);
        }
    };

    if (!isOpen) return null;

    return (
        <div className="fixed inset-0 z-[100] flex items-center justify-center">
            <div className="absolute inset-0 bg-black/40" />
            <div className="relative bg-white rounded-xl shadow-2xl w-full max-w-md mx-4">
                <div className="flex items-center justify-between px-5 py-4 border-b border-stone-200">
                    <h3 className="text-base font-bold text-stone-800">거래처 추가</h3>
                    <button onClick={onClose} className="p-1 text-stone-400 hover:text-stone-600 rounded"><X className="w-5 h-5" /></button>
                </div>
                <div className="px-5 py-4 space-y-3">
                    <div>
                        <label className="block text-xs font-medium text-stone-500 mb-1">거래처명 <span className="text-red-500">*</span></label>
                        <input type="text" value={form.name} onChange={(e) => setForm({ ...form, name: e.target.value })}
                            placeholder="거래처명을 입력하세요"
                            className="w-full px-3 py-2 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none" autoFocus />
                    </div>
                    <div className="grid grid-cols-2 gap-3">
                        <div>
                            <label className="block text-xs font-medium text-stone-500 mb-1">구분</label>
                            <select value={form.type} onChange={(e) => setForm({ ...form, type: e.target.value })}
                                className="w-full px-3 py-2 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none">
                                <option value="vendor">거래처</option>
                                <option value="freelancer">프리랜서</option>
                            </select>
                        </div>
                        <div>
                            <label className="block text-xs font-medium text-stone-500 mb-1">업종</label>
                            <select value={form.category} onChange={(e) => setForm({ ...form, category: e.target.value })}
                                className="w-full px-3 py-2 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none">
                                {categories.map(c => <option key={c} value={c}>{c}</option>)}
                            </select>
                        </div>
                    </div>
                    <div>
                        <label className="block text-xs font-medium text-stone-500 mb-1">사업자번호</label>
                        <input type="text" value={form.bizNo} onChange={(e) => setForm({ ...form, bizNo: e.target.value })}
                            placeholder="000-00-00000"
                            className="w-full px-3 py-2 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none" />
                    </div>
                </div>
                <div className="flex justify-end gap-2 px-5 py-4 border-t border-stone-200">
                    <button onClick={onClose} className="px-4 py-2 text-sm text-stone-600 bg-stone-100 rounded-lg hover:bg-stone-200 transition-colors">취소</button>
                    <button onClick={handleSave} disabled={saving}
                        className="px-4 py-2 text-sm font-medium text-white bg-emerald-600 rounded-lg hover:bg-emerald-700 transition-colors disabled:opacity-50">
                        {saving ? '저장 중...' : '저장'}
                    </button>
                </div>
            </div>
        </div>
    );
};

// ============================================================
// TradingPartnerSelect
// ============================================================
const TradingPartnerSelect = ({ value, valueName, onChange, tradingPartners, onAddPartner, onTab, triggerId }) => {
    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 || '';

    const filteredPartners = tradingPartners.filter(p => {
        if (!search) return true;
        const s = search.toLowerCase();
        return p.name.toLowerCase().includes(s) || (p.biz_no && p.biz_no.includes(search));
    });

    useEffect(() => { setHighlightIndex(-1); }, [search]);
    useEffect(() => {
        const handleClickOutside = (e) => {
            if (containerRef.current && !containerRef.current.contains(e.target) &&
                (!dropdownRef.current || !dropdownRef.current.contains(e.target))) {
                setIsOpen(false); setSearch(''); setHighlightIndex(-1);
            }
        };
        document.addEventListener('mousedown', handleClickOutside);
        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);
        setTimeout(() => triggerRef.current?.focus(), 0);
    };
    const handleClear = (e) => { e.stopPropagation(); onChange(null, ''); setSearch(''); };

    const handleKeyDown = (e) => {
        const maxIndex = Math.min(filteredPartners.length, 50) - 1;
        if (e.key === 'Tab') {
            e.preventDefault();
            if (filteredPartners.length > 0 && highlightIndex >= 0) {
                onChange(filteredPartners[highlightIndex].id, filteredPartners[highlightIndex].name);
            }
            setIsOpen(false); setSearch(''); setHighlightIndex(-1);
            setTimeout(() => onTab && onTab(), 0);
        } else if (e.key === 'ArrowDown') {
            e.preventDefault();
            const ni = highlightIndex < maxIndex ? highlightIndex + 1 : 0;
            setHighlightIndex(ni);
            setTimeout(() => { listRef.current?.children[ni]?.scrollIntoView({ block: 'nearest' }); }, 0);
        } else if (e.key === 'ArrowUp') {
            e.preventDefault();
            const ni = highlightIndex > 0 ? highlightIndex - 1 : maxIndex;
            setHighlightIndex(ni);
            setTimeout(() => { listRef.current?.children[ni]?.scrollIntoView({ block: 'nearest' }); }, 0);
        } else if (e.key === 'Enter' && filteredPartners.length > 0) {
            e.preventDefault();
            handleSelect(filteredPartners[highlightIndex >= 0 ? highlightIndex : 0]);
        } else if (e.key === 'Escape') {
            setIsOpen(false); setSearch(''); setHighlightIndex(-1);
        }
    };

    return (
        <div ref={containerRef} className="relative">
            <div ref={triggerRef} id={triggerId} tabIndex={0}
                onClick={() => { if (!isOpen) calcDropdownPos(); setIsOpen(!isOpen); }}
                onKeyDown={(e) => {
                    if (e.key === 'Tab') {
                        e.preventDefault();
                        if (isOpen) { setIsOpen(false); setSearch(''); setHighlightIndex(-1); }
                        setTimeout(() => onTab && onTab(), 0);
                    } else if (e.key === 'Enter' || e.key === ' ') {
                        e.preventDefault();
                        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">
                    {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 && 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 />
                    </div>
                    <div className="border-b border-stone-100">
                        <button onClick={(e) => { e.stopPropagation(); setIsOpen(false); onAddPartner && onAddPartner(); }}
                            className="w-full px-3 py-2 text-xs text-emerald-600 hover:bg-emerald-50 font-medium flex items-center gap-1 justify-center transition-colors">
                            <Plus className="w-3.5 h-3.5" /> 거래처 추가
                        </button>
                    </div>
                    <div ref={listRef} className="max-h-48 overflow-y-auto">
                        {filteredPartners.length === 0 ? (
                            <div className="px-3 py-2 text-xs text-stone-400 text-center">검색 결과 없음</div>
                        ) : filteredPartners.slice(0, 50).map((p, index) => (
                            <div key={p.id} onClick={() => handleSelect(p)}
                                className={`px-3 py-1.5 text-xs cursor-pointer ${index === highlightIndex ? 'bg-emerald-600 text-white font-semibold' : value === p.id ? 'bg-emerald-100 text-emerald-700' : 'text-stone-700 hover:bg-emerald-50'}`}>
                                <span className="font-medium">{p.name}</span>
                                {p.biz_no && <span className={`ml-1 ${index === highlightIndex ? 'text-emerald-100' : 'text-stone-400'}`}>({p.biz_no})</span>}
                            </div>
                        ))}
                    </div>
                </div>,
                document.body
            )}
        </div>
    );
};

// ============================================================
// 탭1: BankTransactionTab (은행거래 분개)
// ============================================================
const BankTransactionTab = ({ accountCodes, tradingPartners, onPartnerAdded }) => {
    const [transactions, setTransactions] = useState([]);
    const [loading, setLoading] = useState(false);
    const [summary, setSummary] = useState({});
    const [journalStats, setJournalStats] = useState({});
    const [accounts, setAccounts] = useState([]);
    const [dateRange, setDateRange] = useState({
        start: getKoreanDate(-30).replace(/-/g, ''),
        end: getKoreanDate().replace(/-/g, ''),
    });
    const [selectedAccount, setSelectedAccount] = useState('');
    const [filterJournal, setFilterJournal] = useState('all');

    // 기간 빠른 선택 헬퍼
    const setMonthRange = (offset) => {
        const now = new Date();
        const target = new Date(now.getFullYear(), now.getMonth() + offset, 1);
        const start = target.toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' }).replace(/-/g, '');
        const endDate = offset === 0
            ? getKoreanDate().replace(/-/g, '')
            : new Date(target.getFullYear(), target.getMonth() + 1, 0).toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' }).replace(/-/g, '');
        setDateRange({ start, end: endDate });
    };

    // 모달 상태
    const [showModal, setShowModal] = useState(false);
    const [modalTransaction, setModalTransaction] = useState(null);

    const fetchTransactions = async () => {
        setLoading(true);
        try {
            const params = new URLSearchParams({
                startDate: dateRange.start,
                endDate: dateRange.end,
            });
            if (selectedAccount) params.set('accountNum', selectedAccount);

            const res = await fetch(`/finance/journal-entries/bank-transactions?${params}`);
            const data = await res.json();
            if (data.success) {
                setTransactions(data.data?.logs || []);
                setSummary(data.data?.summary || {});
                setJournalStats(data.data?.journalStats || {});
                if (data.data?.accounts) setAccounts(data.data.accounts);
            } else {
                notify(data.error || data.message || '조회 실패', 'error');
                setTransactions([]);
            }
        } catch (err) {
            console.error('은행거래 조회 실패:', err);
            notify('은행거래 조회 중 오류가 발생했습니다.', 'error');
        } finally {
            setLoading(false);
        }
    };

    useEffect(() => { fetchTransactions(); }, []);

    // 분개 필터 적용
    const filteredTransactions = transactions.filter(t => {
        if (filterJournal === 'journaled') return t.hasJournal;
        if (filterJournal === 'unjournaled') return !t.hasJournal;
        return true;
    });

    // 분개 버튼 클릭
    const handleJournal = (tx) => {
        setModalTransaction(tx);
        setShowModal(true);
    };

    // 분개 저장 완료 후
    const handleJournalSaved = () => {
        setShowModal(false);
        setModalTransaction(null);
        fetchTransactions();
    };

    // 분개 삭제 완료 후
    const handleJournalDeleted = () => {
        setShowModal(false);
        setModalTransaction(null);
        fetchTransactions();
    };

    const totalCount = filteredTransactions.length;
    const depositSum = filteredTransactions.reduce((s, t) => s + (parseFloat(t.deposit) || 0), 0);
    const withdrawSum = filteredTransactions.reduce((s, t) => s + (parseFloat(t.withdraw) || 0), 0);

    return (
        <div>
            {/* 필터 바 */}
            <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-4 mb-4">
                <div className="flex flex-wrap items-center gap-3">
                    <label className="text-sm text-stone-500 font-medium">기간</label>
                    <input type="date" value={formatDate(dateRange.start)} onChange={(e) => setDateRange(prev => ({ ...prev, start: e.target.value.replace(/-/g, '') }))}
                        className="rounded-lg border border-stone-200 px-3 py-1.5 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
                    <span className="text-stone-400">~</span>
                    <input type="date" value={formatDate(dateRange.end)} onChange={(e) => setDateRange(prev => ({ ...prev, end: e.target.value.replace(/-/g, '') }))}
                        className="rounded-lg border border-stone-200 px-3 py-1.5 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
                    <div className="flex items-center gap-2">
                        <button onClick={() => setMonthRange(0)} className="px-3 py-1.5 text-sm bg-emerald-50 text-emerald-600 rounded-lg hover:bg-emerald-100 transition-colors font-medium">이번 달</button>
                        <button onClick={() => setMonthRange(-1)} className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">지난달</button>
                        <button onClick={() => setMonthRange(-2)} className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-2월</button>
                        <button onClick={() => setMonthRange(-3)} className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-3월</button>
                        <button onClick={() => setMonthRange(-4)} className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-4월</button>
                        <button onClick={() => setMonthRange(-5)} className="px-3 py-1.5 text-sm bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-5월</button>
                        <button onClick={fetchTransactions} disabled={loading}
                            className="px-4 py-1.5 text-sm bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 transition-colors font-medium flex items-center gap-2 disabled:opacity-50">
                            <Search className="w-4 h-4" /> 조회
                        </button>
                    </div>
                    <span className="text-sm text-stone-500">
                        조회: <span className="font-semibold text-stone-700">{totalCount}</span>건
                    </span>
                </div>
                {/* 2행: 계좌 + 분개상태 필터 */}
                <div className="flex flex-wrap items-center gap-3 mt-3">
                    <label className="text-sm text-stone-500 font-medium">계좌</label>
                    <select value={selectedAccount} onChange={(e) => setSelectedAccount(e.target.value)}
                        className="px-3 py-1.5 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none">
                        <option value="">전체 계좌</option>
                        {accounts.map(a => (
                            <option key={a.bank_account_num} value={a.bank_account_num}>
                                {a.bank_name} {a.bank_account_num}
                            </option>
                        ))}
                    </select>
                    <label className="text-sm text-stone-500 font-medium ml-2">분개</label>
                    <select value={filterJournal} onChange={(e) => setFilterJournal(e.target.value)}
                        className="px-3 py-1.5 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none">
                        <option value="all">전체</option>
                        <option value="unjournaled">미분개</option>
                        <option value="journaled">분개완료</option>
                    </select>
                </div>
            </div>

            {/* 통계 카드 */}
            <div className="grid grid-cols-2 lg:grid-cols-5 gap-3 mb-4">
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-4">
                    <div className="flex items-center gap-3">
                        <div className="p-2 bg-stone-50 rounded-lg"><FileText className="w-5 h-5 text-stone-600" /></div>
                        <div>
                            <p className="text-xs text-stone-500">전체</p>
                            <p className="text-lg font-bold text-stone-800">{totalCount}건</p>
                        </div>
                    </div>
                </div>
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-4">
                    <div className="flex items-center gap-3">
                        <div className="p-2 bg-blue-50 rounded-lg"><ArrowDownCircle className="w-5 h-5 text-blue-600" /></div>
                        <div>
                            <p className="text-xs text-stone-500">입금</p>
                            <p className="text-lg font-bold text-blue-700">{formatCurrency(depositSum)}</p>
                        </div>
                    </div>
                </div>
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-4">
                    <div className="flex items-center gap-3">
                        <div className="p-2 bg-red-50 rounded-lg"><ArrowUpCircle className="w-5 h-5 text-red-600" /></div>
                        <div>
                            <p className="text-xs text-stone-500">출금</p>
                            <p className="text-lg font-bold text-red-700">{formatCurrency(withdrawSum)}</p>
                        </div>
                    </div>
                </div>
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-4">
                    <div className="flex items-center gap-3">
                        <div className="p-2 bg-emerald-50 rounded-lg"><CheckCircle className="w-5 h-5 text-emerald-600" /></div>
                        <div>
                            <p className="text-xs text-stone-500">분개완료</p>
                            <p className="text-lg font-bold text-emerald-700">{journalStats.journaledCount || 0}건</p>
                        </div>
                    </div>
                </div>
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-4">
                    <div className="flex items-center gap-3">
                        <div className="p-2 bg-amber-50 rounded-lg"><AlertTriangle className="w-5 h-5 text-amber-600" /></div>
                        <div>
                            <p className="text-xs text-stone-500">미분개</p>
                            <p className="text-lg font-bold text-amber-700">{journalStats.unjournaledCount || 0}건</p>
                        </div>
                    </div>
                </div>
            </div>

            {/* 거래 목록 테이블 */}
            <div className="bg-white rounded-xl shadow-sm border border-stone-100 overflow-hidden">
                {loading ? (
                    <div className="p-12 text-center text-stone-400">
                        <div className="animate-spin w-8 h-8 border-4 border-emerald-200 border-t-emerald-600 rounded-full mx-auto mb-3"></div>
                        은행거래 조회 중...
                    </div>
                ) : filteredTransactions.length === 0 ? (
                    <div className="p-12 text-center text-stone-400">
                        <Landmark className="w-12 h-12 mx-auto mb-3 text-stone-300" />
                        <p>조회된 거래가 없습니다.</p>
                    </div>
                ) : (
                    <div className="overflow-x-auto">
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="bg-stone-50 border-b border-stone-200">
                                    <th className="px-3 py-2.5 text-left font-medium text-stone-600 w-[90px]">날짜</th>
                                    <th className="px-3 py-2.5 text-left font-medium text-stone-600 w-[50px]">시간</th>
                                    {!selectedAccount && <th className="px-3 py-2.5 text-left font-medium text-stone-600 w-[120px]">계좌</th>}
                                    <th className="px-3 py-2.5 text-left font-medium text-stone-600">적요</th>
                                    <th className="px-3 py-2.5 text-right font-medium text-stone-600 w-[120px]">입금</th>
                                    <th className="px-3 py-2.5 text-right font-medium text-stone-600 w-[120px]">출금</th>
                                    <th className="px-3 py-2.5 text-right font-medium text-stone-600 w-[120px]">잔액</th>
                                    <th className="px-3 py-2.5 text-center font-medium text-stone-600 w-[90px]">분개</th>
                                </tr>
                            </thead>
                            <tbody>
                                {filteredTransactions.map((tx, idx) => (
                                    <tr key={tx.uniqueKey || idx} className="border-b border-stone-100 hover:bg-stone-50 transition-colors">
                                        <td className="px-3 py-2 text-stone-600 font-mono text-xs">{formatDate(tx.transDate)}</td>
                                        <td className="px-3 py-2 text-stone-400 text-xs">{formatTime(tx.transTime)}</td>
                                        {!selectedAccount && <td className="px-3 py-2 text-stone-500 text-xs truncate max-w-[120px]">{tx.bankName}</td>}
                                        <td className="px-3 py-2 text-stone-800 max-w-[250px] truncate" title={tx.summary}>{tx.summary || '-'}</td>
                                        <td className="px-3 py-2 text-right font-medium text-blue-600">
                                            {parseFloat(tx.deposit) > 0 ? formatCurrency(tx.deposit) : ''}
                                        </td>
                                        <td className="px-3 py-2 text-right font-medium text-red-600">
                                            {parseFloat(tx.withdraw) > 0 ? formatCurrency(tx.withdraw) : ''}
                                        </td>
                                        <td className="px-3 py-2 text-right text-stone-600 text-xs">{formatCurrency(tx.balance)}</td>
                                        <td className="px-3 py-2 text-center">
                                            {tx.hasJournal ? (
                                                <button onClick={() => handleJournal(tx)}
                                                    className="px-2.5 py-1 text-xs font-medium bg-emerald-100 text-emerald-700 rounded-full hover:bg-emerald-200 transition-colors">
                                                    완료
                                                </button>
                                            ) : (
                                                <button onClick={() => handleJournal(tx)}
                                                    className="px-2.5 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-full hover:bg-amber-200 transition-colors">
                                                    분개
                                                </button>
                                            )}
                                        </td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>
                )}
            </div>

            {/* 분개 모달 */}
            {showModal && modalTransaction && (
                <JournalEntryModal
                    transaction={modalTransaction}
                    accountCodes={accountCodes}
                    tradingPartners={tradingPartners}
                    onClose={() => { setShowModal(false); setModalTransaction(null); }}
                    onSaved={handleJournalSaved}
                    onDeleted={handleJournalDeleted}
                    onPartnerAdded={onPartnerAdded}
                />
            )}
        </div>
    );
};

// ============================================================
// JournalEntryList (은행거래 + 수동전표 통합 목록)
// ============================================================
const JournalEntryList = ({ accountCodes, tradingPartners, onPartnerAdded, refreshKey, onEdit }) => {
    const [transactions, setTransactions] = useState([]);
    const [cardTransactionsList, setCardTransactionsList] = useState([]);
    const [journalEntries, setJournalEntries] = useState([]);
    const [loading, setLoading] = useState(false);
    const [accounts, setAccounts] = useState([]);
    const [cards, setCards] = useState([]);
    const [journalStats, setJournalStats] = useState({});
    const [cardJournalStats, setCardJournalStats] = useState({});
    const [dateRange, setDateRange] = useState({
        start: getKoreanDate(-30).replace(/-/g, ''),
        end: getKoreanDate().replace(/-/g, ''),
    });
    const [selectedAccount, setSelectedAccount] = useState('');
    const [selectedCard, setSelectedCard] = useState('');
    const [viewFilter, setViewFilter] = useState('all'); // all, bank, card, manual, unjournaled

    // 분개 모달 상태 (은행거래 → 전표 생성)
    const [showJournalModal, setShowJournalModal] = useState(false);
    const [modalTransaction, setModalTransaction] = useState(null);

    // 카드 분개 모달 상태
    const [showCardJournalModal, setShowCardJournalModal] = useState(false);
    const [cardModalTransaction, setCardModalTransaction] = useState(null);

    const setMonthRange = (offset) => {
        const now = new Date();
        const target = new Date(now.getFullYear(), now.getMonth() + offset, 1);
        const start = target.toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' }).replace(/-/g, '');
        const endDate = offset === 0
            ? getKoreanDate().replace(/-/g, '')
            : new Date(target.getFullYear(), target.getMonth() + 1, 0).toLocaleDateString('en-CA', { timeZone: 'Asia/Seoul' }).replace(/-/g, '');
        setDateRange({ start, end: endDate });
    };

    const fetchData = async () => {
        setLoading(true);
        try {
            const bankParams = new URLSearchParams({ startDate: dateRange.start, endDate: dateRange.end });
            if (selectedAccount) bankParams.set('accountNum', selectedAccount);
            const cardParams = new URLSearchParams({ startDate: dateRange.start, endDate: dateRange.end });
            if (selectedCard) cardParams.set('cardNum', selectedCard);
            const journalParams = new URLSearchParams({
                start_date: formatDate(dateRange.start),
                end_date: formatDate(dateRange.end),
            });

            const [bankRes, cardRes, journalRes] = await Promise.all([
                fetch(`/finance/journal-entries/bank-transactions?${bankParams}`),
                fetch(`/finance/journal-entries/card-transactions?${cardParams}`),
                fetch(`/finance/journal-entries/list?${journalParams}`),
            ]);
            const bankData = await bankRes.json();
            const cardData = await cardRes.json();
            const journalData = await journalRes.json();

            if (bankData.success) {
                setTransactions(bankData.data?.logs || []);
                setJournalStats(bankData.data?.journalStats || {});
                if (bankData.data?.accounts) setAccounts(bankData.data.accounts);
            }
            if (cardData.success) {
                setCardTransactionsList(cardData.data?.logs || []);
                setCardJournalStats(cardData.data?.journalStats || {});
                if (cardData.data?.cards) setCards(cardData.data.cards);
            }
            if (journalData.success) {
                setJournalEntries(journalData.data || []);
            }
        } catch (err) {
            console.error('데이터 조회 실패:', err);
            notify('데이터 조회 중 오류가 발생했습니다.', 'error');
        } finally {
            setLoading(false);
        }
    };

    useEffect(() => { fetchData(); }, [refreshKey]);

    // 전표 맵 생성 (journalId → entry)
    const journalMap = {};
    journalEntries.forEach(je => { journalMap[je.id] = je; });

    // 통합 행 생성
    const linkedJournalIds = new Set();
    const rows = [];

    // 1) 은행거래 행
    transactions.forEach(tx => {
        const je = tx.journalId ? journalMap[tx.journalId] : null;
        if (tx.journalId) linkedJournalIds.add(tx.journalId);
        rows.push({
            type: 'bank',
            key: `bank-${tx.uniqueKey}`,
            date: tx.transDate,
            time: tx.transTime,
            description: tx.summary || '-',
            deposit: parseFloat(tx.deposit) || 0,
            withdraw: parseFloat(tx.withdraw) || 0,
            balance: tx.balance,
            hasJournal: tx.hasJournal,
            journalId: tx.journalId,
            entryNo: tx.journalEntryNo || (je && je.entry_no) || null,
            lines: je ? je.lines : [],
            totalDebit: je ? je.total_debit : 0,
            totalCredit: je ? je.total_credit : 0,
            bankTx: tx,
            cardTx: null,
            sortKey: tx.transDate + (tx.transTime || '000000'),
        });
    });

    // 2) 카드거래 행
    cardTransactionsList.forEach(ctx => {
        const je = ctx.journalId ? journalMap[ctx.journalId] : null;
        if (ctx.journalId) linkedJournalIds.add(ctx.journalId);
        rows.push({
            type: 'card',
            key: `card-${ctx.uniqueKey}`,
            date: ctx.useDate,
            time: ctx.useTime,
            description: ctx.merchantName || '-',
            deposit: 0,
            withdraw: ctx.approvalAmount || 0,
            balance: null,
            hasJournal: ctx.hasJournal,
            journalId: ctx.journalId,
            entryNo: ctx.journalEntryNo || (je && je.entry_no) || null,
            lines: je ? je.lines : [],
            totalDebit: je ? je.total_debit : 0,
            totalCredit: je ? je.total_credit : 0,
            bankTx: null,
            cardTx: ctx,
            sortKey: ctx.useDate + (ctx.useTime || '000000'),
        });
    });

    // 3) 수동 전표 행 (은행/카드거래에 연결되지 않은 것만)
    journalEntries.forEach(je => {
        if (linkedJournalIds.has(je.id)) return;
        if (je.source_type === 'bank_transaction' || je.source_type === 'ecard_transaction') return;
        rows.push({
            type: 'manual',
            key: `manual-${je.id}`,
            date: je.entry_date.replace(/-/g, ''),
            time: '',
            description: je.description || '-',
            deposit: 0,
            withdraw: 0,
            balance: null,
            hasJournal: true,
            journalId: je.id,
            entryNo: je.entry_no,
            lines: je.lines || [],
            totalDebit: je.total_debit,
            totalCredit: je.total_credit,
            bankTx: null,
            cardTx: null,
            sortKey: je.entry_date.replace(/-/g, '') + '999999',
        });
    });

    // 최신순 정렬
    rows.sort((a, b) => b.sortKey.localeCompare(a.sortKey));

    // 필터 적용
    const filtered = rows.filter(r => {
        if (viewFilter === 'bank') return r.type === 'bank';
        if (viewFilter === 'card') return r.type === 'card';
        if (viewFilter === 'manual') return r.type === 'manual';
        if (viewFilter === 'unjournaled') return (r.type === 'bank' || r.type === 'card') && !r.hasJournal;
        return true;
    });

    // 통계
    const bankRows = rows.filter(r => r.type === 'bank');
    const cardRows = rows.filter(r => r.type === 'card');
    const manualRows = rows.filter(r => r.type === 'manual');
    const depositSum = bankRows.reduce((s, r) => s + r.deposit, 0);
    const withdrawSum = bankRows.reduce((s, r) => s + r.withdraw, 0);
    const cardAmountSum = cardRows.reduce((s, r) => s + r.withdraw, 0);
    const totalDebit = filtered.reduce((s, r) => s + (r.totalDebit || 0), 0);
    const totalCredit = filtered.reduce((s, r) => s + (r.totalCredit || 0), 0);
    const allJournaledCount = (journalStats.journaledCount || 0) + (cardJournalStats.journaledCount || 0);
    const allUnjournaledCount = (journalStats.unjournaledCount || 0) + (cardJournalStats.unjournaledCount || 0);

    const handleJournal = (tx) => {
        setModalTransaction(tx);
        setShowJournalModal(true);
    };

    const handleCardJournal = (ctx) => {
        setCardModalTransaction(ctx);
        setShowCardJournalModal(true);
    };

    const handleJournalSaved = () => {
        setShowJournalModal(false);
        setModalTransaction(null);
        fetchData();
    };

    const handleJournalDeleted = () => {
        setShowJournalModal(false);
        setModalTransaction(null);
        fetchData();
    };

    const handleCardJournalSaved = () => {
        setShowCardJournalModal(false);
        setCardModalTransaction(null);
        fetchData();
    };

    const handleCardJournalDeleted = () => {
        setShowCardJournalModal(false);
        setCardModalTransaction(null);
        fetchData();
    };

    return (
        <div>
            {/* 필터 바 */}
            <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-4 mb-4">
                <div className="flex flex-wrap items-center gap-3">
                    <label className="text-sm text-stone-500 font-medium">기간</label>
                    <input type="date" value={formatDate(dateRange.start)} onChange={(e) => setDateRange(prev => ({ ...prev, start: e.target.value.replace(/-/g, '') }))}
                        className="rounded-lg border border-stone-200 px-3 py-1.5 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
                    <span className="text-stone-400">~</span>
                    <input type="date" value={formatDate(dateRange.end)} onChange={(e) => setDateRange(prev => ({ ...prev, end: e.target.value.replace(/-/g, '') }))}
                        className="rounded-lg border border-stone-200 px-3 py-1.5 text-sm focus:ring-2 focus:ring-emerald-500 outline-none" />
                    <div className="flex items-center gap-1.5">
                        <button onClick={() => setMonthRange(0)} className="px-2.5 py-1.5 text-xs bg-emerald-50 text-emerald-600 rounded-lg hover:bg-emerald-100 transition-colors font-medium">이번달</button>
                        <button onClick={() => setMonthRange(-1)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">지난달</button>
                        <button onClick={() => setMonthRange(-2)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-2월</button>
                        <button onClick={() => setMonthRange(-3)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-3월</button>
                        <button onClick={() => setMonthRange(-4)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-4월</button>
                        <button onClick={() => setMonthRange(-5)} className="px-2.5 py-1.5 text-xs bg-stone-100 text-stone-600 rounded-lg hover:bg-stone-200 transition-colors font-medium">D-5월</button>
                    </div>
                    <button onClick={fetchData} disabled={loading}
                        className="px-4 py-1.5 text-sm bg-emerald-600 text-white rounded-lg hover:bg-emerald-700 transition-colors font-medium flex items-center gap-2 disabled:opacity-50">
                        <Search className="w-4 h-4" /> 조회
                    </button>
                </div>
                <div className="flex flex-wrap items-center gap-3 mt-3">
                    <label className="text-sm text-stone-500 font-medium">계좌</label>
                    <select value={selectedAccount} onChange={(e) => setSelectedAccount(e.target.value)}
                        className="px-3 py-1.5 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none">
                        <option value="">전체 계좌</option>
                        {accounts.map(a => (
                            <option key={a.bank_account_num} value={a.bank_account_num}>
                                {a.bank_name} {a.bank_account_num}
                            </option>
                        ))}
                    </select>
                    <label className="text-sm text-stone-500 font-medium ml-2">카드</label>
                    <select value={selectedCard} onChange={(e) => setSelectedCard(e.target.value)}
                        className="px-3 py-1.5 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none">
                        <option value="">전체 카드</option>
                        {cards.map(c => (
                            <option key={c.card_num} value={c.card_num}>
                                {c.card_company_name} {c.card_num.slice(-4)}
                            </option>
                        ))}
                    </select>
                    <label className="text-sm text-stone-500 font-medium ml-2">구분</label>
                    <div className="flex items-center gap-1">
                        {[
                            ['all', '전체', rows.length],
                            ['bank', '은행거래', bankRows.length],
                            ['card', '카드거래', cardRows.length],
                            ['manual', '수동전표', manualRows.length],
                            ['unjournaled', '미분개', rows.filter(r => (r.type === 'bank' || r.type === 'card') && !r.hasJournal).length],
                        ].map(([val, label, count]) => (
                            <button key={val} onClick={() => setViewFilter(val)}
                                className={`px-2.5 py-1 text-xs rounded-full font-medium transition-colors ${viewFilter === val
                                    ? 'bg-emerald-600 text-white'
                                    : 'bg-stone-100 text-stone-500 hover:bg-stone-200'}`}>
                                {label} <span className="font-bold">{count}</span>
                            </button>
                        ))}
                    </div>
                </div>
            </div>

            {/* 통계 카드 */}
            <div className="grid grid-cols-2 lg:grid-cols-6 gap-3 mb-4">
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
                    <div className="flex items-center gap-2.5">
                        <div className="p-1.5 bg-stone-50 rounded-lg"><FileText className="w-4 h-4 text-stone-600" /></div>
                        <div>
                            <p className="text-[10px] text-stone-400 leading-tight">전체</p>
                            <p className="text-base font-bold text-stone-800">{rows.length}건</p>
                        </div>
                    </div>
                </div>
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
                    <div className="flex items-center gap-2.5">
                        <div className="p-1.5 bg-blue-50 rounded-lg"><ArrowDownCircle className="w-4 h-4 text-blue-600" /></div>
                        <div>
                            <p className="text-[10px] text-stone-400 leading-tight">입금</p>
                            <p className="text-base font-bold text-blue-700">{formatCurrency(depositSum)}</p>
                        </div>
                    </div>
                </div>
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
                    <div className="flex items-center gap-2.5">
                        <div className="p-1.5 bg-red-50 rounded-lg"><ArrowUpCircle className="w-4 h-4 text-red-600" /></div>
                        <div>
                            <p className="text-[10px] text-stone-400 leading-tight">출금</p>
                            <p className="text-base font-bold text-red-700">{formatCurrency(withdrawSum)}</p>
                        </div>
                    </div>
                </div>
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
                    <div className="flex items-center gap-2.5">
                        <div className="p-1.5 bg-orange-50 rounded-lg"><CreditCard className="w-4 h-4 text-orange-600" /></div>
                        <div>
                            <p className="text-[10px] text-stone-400 leading-tight">카드사용</p>
                            <p className="text-base font-bold text-orange-700">{formatCurrency(cardAmountSum)}</p>
                        </div>
                    </div>
                </div>
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
                    <div className="flex items-center gap-2.5">
                        <div className="p-1.5 bg-emerald-50 rounded-lg"><CheckCircle className="w-4 h-4 text-emerald-600" /></div>
                        <div>
                            <p className="text-[10px] text-stone-400 leading-tight">분개완료</p>
                            <p className="text-base font-bold text-emerald-700">{allJournaledCount}건</p>
                        </div>
                    </div>
                </div>
                <div className="bg-white rounded-xl shadow-sm border border-stone-100 p-3">
                    <div className="flex items-center gap-2.5">
                        <div className="p-1.5 bg-amber-50 rounded-lg"><AlertTriangle className="w-4 h-4 text-amber-600" /></div>
                        <div>
                            <p className="text-[10px] text-stone-400 leading-tight">미분개</p>
                            <p className="text-base font-bold text-amber-700">{allUnjournaledCount}건</p>
                        </div>
                    </div>
                </div>
            </div>

            {/* 통합 테이블 */}
            <div className="bg-white rounded-xl shadow-sm border border-stone-100 overflow-hidden">
                {loading ? (
                    <div className="p-12 text-center text-stone-400">
                        <div className="animate-spin w-8 h-8 border-4 border-emerald-200 border-t-emerald-600 rounded-full mx-auto mb-3"></div>
                        조회 중...
                    </div>
                ) : filtered.length === 0 ? (
                    <div className="p-12 text-center text-stone-400">
                        <Landmark className="w-12 h-12 mx-auto mb-3 text-stone-300" />
                        <p>조회된 데이터가 없습니다.</p>
                    </div>
                ) : (
                    <div className="overflow-x-auto">
                        <table className="w-full text-sm">
                            <thead>
                                <tr className="bg-stone-50 border-b border-stone-200">
                                    <th className="px-3 py-3 text-left text-sm font-semibold text-stone-600 w-[90px]">날짜</th>
                                    <th className="px-3 py-3 text-left text-sm font-semibold text-stone-600">적요</th>
                                    <th className="px-3 py-3 text-right text-sm font-semibold text-stone-600 w-[110px]">입금</th>
                                    <th className="px-3 py-3 text-right text-sm font-semibold text-stone-600 w-[110px]">출금</th>
                                    <th className="px-3 py-3 text-right text-sm font-semibold text-stone-600 w-[110px]">잔액</th>
                                    <th className="px-3 py-3 text-left text-sm font-semibold text-stone-600">분개 내역</th>
                                    <th className="px-3 py-3 text-right text-sm font-semibold text-stone-600 w-[100px]">차변</th>
                                    <th className="px-3 py-3 text-right text-sm font-semibold text-stone-600 w-[100px]">대변</th>
                                    <th className="px-3 py-3 text-center text-sm font-semibold text-stone-600 w-[60px]">분개</th>
                                </tr>
                            </thead>
                            <tbody>
                                {filtered.map(row => (
                                    <tr key={row.key} className={`border-b border-stone-100 hover:bg-stone-50/50 transition-colors group ${row.type === 'manual' ? 'bg-purple-50/30' : row.type === 'card' ? 'bg-orange-50/30' : ''}`}>
                                        <td className="px-3 py-2.5 text-stone-600 text-sm whitespace-nowrap">
                                            <div>{formatDate(row.date)}</div>
                                            {row.time && <div className="text-stone-400 text-xs">{formatTime(row.time)}</div>}
                                        </td>
                                        <td className="px-3 py-2.5 text-stone-800 text-sm max-w-[220px]">
                                            <div className="flex items-center gap-1.5">
                                                <span className={`w-2 h-2 rounded-full flex-shrink-0 ${row.type === 'manual' ? 'bg-purple-500' : row.type === 'card' ? 'bg-orange-500' : 'bg-blue-500'}`}></span>
                                                {row.type === 'card' && <CreditCard className="w-3.5 h-3.5 text-orange-500 flex-shrink-0" />}
                                                <span className="truncate" title={row.description}>{row.description}</span>
                                                {row.type === 'card' && row.cardTx && (
                                                    <span className={`px-1.5 py-0.5 rounded text-[10px] font-medium flex-shrink-0 ${row.cardTx.deductionType === 'non_deductible' ? 'bg-red-100 text-red-600' : 'bg-emerald-100 text-emerald-600'}`}>
                                                        {row.cardTx.deductionType === 'non_deductible' ? '불공제' : '공제'}
                                                    </span>
                                                )}
                                            </div>
                                        </td>
                                        <td className="px-3 py-2.5 text-right font-medium text-blue-600 text-sm">
                                            {row.deposit > 0 ? formatCurrency(row.deposit) : ''}
                                        </td>
                                        <td className="px-3 py-2.5 text-right font-medium text-red-600 text-sm">
                                            {row.withdraw > 0 ? formatCurrency(row.withdraw) : ''}
                                        </td>
                                        <td className="px-3 py-2.5 text-right text-stone-500 text-sm">
                                            {row.balance !== null ? formatCurrency(row.balance) : ''}
                                        </td>
                                        <td className="px-3 py-2.5">
                                            {row.hasJournal && row.lines.length > 0 ? (
                                                <div className="space-y-0.5">
                                                    {row.lines.map((l, i) => (
                                                        <div key={l.id || i} className="flex items-center gap-1.5 text-[13px]">
                                                            <span className={`px-1 py-0.5 rounded text-[11px] font-bold leading-none ${l.dc_type === 'debit' ? 'bg-blue-50 text-blue-600' : 'bg-red-50 text-red-600'}`}>
                                                                {l.dc_type === 'debit' ? '차' : '대'}
                                                            </span>
                                                            <span className="text-stone-500 truncate max-w-[100px]">{l.account_name}</span>
                                                            <span className="font-medium text-stone-700">{formatCurrency(l.debit_amount || l.credit_amount)}</span>
                                                        </div>
                                                    ))}
                                                </div>
                                            ) : row.hasJournal ? (
                                                <span className="text-emerald-600 text-sm">분개완료</span>
                                            ) : null}
                                        </td>
                                        <td className="px-3 py-2.5 text-right font-medium text-blue-700 text-sm">
                                            {row.totalDebit > 0 ? formatCurrency(row.totalDebit) : ''}
                                        </td>
                                        <td className="px-3 py-2.5 text-right font-medium text-red-700 text-sm">
                                            {row.totalCredit > 0 ? formatCurrency(row.totalCredit) : ''}
                                        </td>
                                        <td className="px-3 py-2.5 text-center">
                                            {(row.type === 'bank' || row.type === 'card') && !row.hasJournal ? (
                                                <button onClick={() => row.type === 'card' ? handleCardJournal(row.cardTx) : handleJournal(row.bankTx)}
                                                    className="px-2.5 py-1 text-xs font-medium bg-amber-100 text-amber-700 rounded-full hover:bg-amber-200 transition-colors">
                                                    분개
                                                </button>
                                            ) : row.hasJournal ? (
                                                <button onClick={() => row.type === 'bank' ? handleJournal(row.bankTx) : row.type === 'card' ? handleCardJournal(row.cardTx) : onEdit(row.journalId)}
                                                    className="p-1 text-stone-300 group-hover:text-emerald-500 hover:bg-emerald-50 rounded transition-colors" title="수정">
                                                    <Edit3 className="w-4 h-4" />
                                                </button>
                                            ) : null}
                                        </td>
                                    </tr>
                                ))}
                            </tbody>
                            <tfoot>
                                <tr className="bg-stone-50 border-t-2 border-stone-300">
                                    <td colSpan={2} className="px-3 py-2.5 text-sm text-stone-500 font-medium">합계 ({filtered.length}건)</td>
                                    <td className="px-3 py-2.5 text-right font-bold text-blue-700 text-sm">{formatCurrency(filtered.reduce((s, r) => s + r.deposit, 0))}</td>
                                    <td className="px-3 py-2.5 text-right font-bold text-red-700 text-sm">{formatCurrency(filtered.reduce((s, r) => s + r.withdraw, 0))}</td>
                                    <td></td>
                                    <td></td>
                                    <td className="px-3 py-2.5 text-right font-bold text-blue-700 text-sm">{formatCurrency(totalDebit)}</td>
                                    <td className="px-3 py-2.5 text-right font-bold text-red-700 text-sm">{formatCurrency(totalCredit)}</td>
                                    <td></td>
                                </tr>
                            </tfoot>
                        </table>
                    </div>
                )}
            </div>

            {/* 분개 모달 (은행거래 → 전표 생성/수정) */}
            {showJournalModal && modalTransaction && (
                <JournalEntryModal
                    transaction={modalTransaction}
                    accountCodes={accountCodes}
                    tradingPartners={tradingPartners}
                    onClose={() => { setShowJournalModal(false); setModalTransaction(null); }}
                    onSaved={handleJournalSaved}
                    onDeleted={handleJournalDeleted}
                    onPartnerAdded={onPartnerAdded}
                />
            )}

            {/* 카드 분개 모달 */}
            {showCardJournalModal && cardModalTransaction && (
                <CardJournalEntryModal
                    transaction={cardModalTransaction}
                    accountCodes={accountCodes}
                    tradingPartners={tradingPartners}
                    onClose={() => { setShowCardJournalModal(false); setCardModalTransaction(null); }}
                    onSaved={handleCardJournalSaved}
                    onDeleted={handleCardJournalDeleted}
                    onPartnerAdded={onPartnerAdded}
                />
            )}
        </div>
    );
};

// ============================================================
// ManualJournalModal (수동 전표 생성/수정 모달)
// ============================================================
const ManualJournalModal = ({ entryId, accountCodes, tradingPartners, onClose, onSaved, onDeleted, onPartnerAdded }) => {
    const isEditMode = !!entryId;
    const [saving, setSaving] = useState(false);
    const [loadingEntry, setLoadingEntry] = useState(false);
    const [entryDate, setEntryDate] = useState(getKoreanDate());
    const [description, setDescription] = useState('');
    const [previewEntryNo, setPreviewEntryNo] = useState('');
    const [existingEntryNo, setExistingEntryNo] = useState('');
    const [showAddPartnerModal, setShowAddPartnerModal] = useState(false);
    const [addPartnerLineIndex, setAddPartnerLineIndex] = useState(null);

    const getDefaultLines = () => [
        { key: Date.now(), dc_type: 'debit', account_code: '', account_name: '', trading_partner_id: null, trading_partner_name: '', debit_amount: 0, credit_amount: 0, description: '' },
        { key: Date.now() + 1, dc_type: 'credit', account_code: '', account_name: '', trading_partner_id: null, trading_partner_name: '', debit_amount: 0, credit_amount: 0, description: '' },
    ];

    const [lines, setLines] = useState(getDefaultLines());

    // 전표번호 미리보기
    const fetchPreviewEntryNo = async (date) => {
        try {
            const res = await fetch(`/finance/journal-entries/next-entry-no?date=${date}`);
            const data = await res.json();
            if (data.success) setPreviewEntryNo(data.entry_no);
        } catch (err) { /* ignore */ }
    };

    useEffect(() => {
        if (!isEditMode) fetchPreviewEntryNo(entryDate);
    }, [entryDate]);

    // 수정 모드: 기존 전표 로드
    useEffect(() => {
        if (isEditMode) {
            setLoadingEntry(true);
            fetch(`/finance/journal-entries/${entryId}`)
                .then(r => r.json())
                .then(data => {
                    if (data.success && data.data) {
                        const d = data.data;
                        setEntryDate(d.entry_date);
                        setDescription(d.description || '');
                        setExistingEntryNo(d.entry_no);
                        if (d.lines && d.lines.length > 0) {
                            setLines(d.lines.map((l, i) => ({ ...l, key: l.id || Date.now() + i })));
                        }
                    }
                })
                .catch(err => notify('전표 로드 실패', 'error'))
                .finally(() => setLoadingEntry(false));
        }
    }, [entryId]);

    const totalDebit = lines.reduce((sum, l) => sum + (parseInt(l.debit_amount) || 0), 0);
    const totalCredit = lines.reduce((sum, l) => sum + (parseInt(l.credit_amount) || 0), 0);
    const isBalanced = totalDebit === totalCredit && totalDebit > 0;
    const difference = totalDebit - totalCredit;

    const updateLine = (index, field, value) => {
        const updated = [...lines];
        updated[index] = { ...updated[index], [field]: value };
        if (field === 'dc_type') {
            if (value === 'debit') { updated[index].credit_amount = 0; }
            else { updated[index].debit_amount = 0; }
        }
        setLines(updated);
    };

    const toggleDcType = (index) => {
        setLines(prev => prev.map((l, i) => {
            if (i !== index) return l;
            const newType = l.dc_type === 'debit' ? 'credit' : 'debit';
            return { ...l, dc_type: newType, debit_amount: l.credit_amount, credit_amount: l.debit_amount };
        }));
    };

    const addLine = () => setLines([...lines, {
        key: Date.now() + Math.random(), dc_type: 'debit', account_code: '', account_name: '',
        trading_partner_id: null, trading_partner_name: '', debit_amount: 0, credit_amount: 0, description: '',
    }]);

    const removeLine = (index) => {
        if (lines.length <= 2) return;
        setLines(lines.filter((_, i) => i !== index));
    };

    const handleSave = async () => {
        if (!entryDate) { notify('전표일자를 입력해주세요.', 'warning'); return; }
        const emptyLine = lines.find(l => !l.account_code);
        if (emptyLine) { notify('모든 분개 라인의 계정과목을 선택해주세요.', 'warning'); return; }
        if (!isBalanced) { notify('차변과 대변의 합계가 일치하지 않습니다.', 'warning'); return; }

        setSaving(true);
        try {
            const payload = {
                entry_date: entryDate,
                description: description,
                lines: lines.map(l => ({
                    dc_type: l.dc_type, account_code: l.account_code, account_name: l.account_name,
                    trading_partner_id: l.trading_partner_id, trading_partner_name: l.trading_partner_name,
                    debit_amount: parseInt(l.debit_amount) || 0, credit_amount: parseInt(l.credit_amount) || 0,
                    description: l.description,
                })),
            };

            const url = isEditMode ? `/finance/journal-entries/${entryId}` : '/finance/journal-entries/store';
            const method = isEditMode ? 'PUT' : 'POST';

            const res = await fetch(url, {
                method, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': CSRF_TOKEN },
                body: JSON.stringify(payload),
            });

            if (!res.ok) {
                const text = await res.text();
                try {
                    const errData = JSON.parse(text);
                    const msg = errData.message || (errData.errors ? Object.values(errData.errors).flat().join(', ') : `저장 실패 (${res.status})`);
                    notify(msg, 'error');
                } catch { notify(`저장 실패: 서버 오류 (${res.status})`, 'error'); }
                return;
            }

            const data = await res.json();

            if (data.success) {
                notify(data.message || (isEditMode ? '전표가 수정되었습니다.' : '전표가 저장되었습니다.'), 'success');
                onSaved();
            } else {
                notify(data.message || '저장 실패', 'error');
            }
        } catch (err) {
            console.error('전표 저장 오류:', err);
            notify('저장 중 오류가 발생했습니다: ' + err.message, 'error');
        } finally {
            setSaving(false);
        }
    };

    const handleDelete = async () => {
        if (!entryId || !confirm('이 전표를 삭제하시겠습니까?')) return;
        setSaving(true);
        try {
            const res = await fetch(`/finance/journal-entries/${entryId}`, {
                method: 'DELETE', headers: { 'X-CSRF-TOKEN': CSRF_TOKEN },
            });
            const data = await res.json();
            if (data.success) { notify('전표가 삭제되었습니다.', 'success'); onDeleted(); }
            else { notify(data.message || '삭제 실패', 'error'); }
        } catch (err) {
            notify('삭제 중 오류가 발생했습니다.', 'error');
        } finally {
            setSaving(false);
        }
    };

    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 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 flex-shrink-0">
                    <h3 className="text-lg font-bold text-stone-900">
                        {isEditMode ? `전표 수정 (${existingEntryNo})` : '수동 전표 생성'}
                    </h3>
                    <button onClick={onClose} className="p-2 hover:bg-stone-100 rounded-lg transition-colors">
                        <X className="w-5 h-5 text-stone-500" />
                    </button>
                </div>

                <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>
                            <span className="ml-2 text-sm text-stone-500">전표 데이터 로딩중...</span>
                        </div>
                    ) : (
                        <>
                            {/* 전표 정보 */}
                            <div className="bg-stone-50 rounded-xl p-4 border border-stone-100">
                                <h4 className="text-sm font-semibold text-stone-700 mb-3">전표 정보</h4>
                                <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
                                    <div>
                                        <label className="block text-xs font-medium text-stone-500 mb-1">전표일자 <span className="text-red-500">*</span></label>
                                        <input type="date" value={entryDate} onChange={(e) => setEntryDate(e.target.value)}
                                            className="w-full px-3 py-2 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none" />
                                    </div>
                                    <div>
                                        <label className="block text-xs font-medium text-stone-500 mb-1">전표번호</label>
                                        <div className="w-full px-3 py-2 text-sm bg-white border border-stone-200 rounded-lg text-stone-500 font-mono">
                                            {isEditMode ? existingEntryNo : (previewEntryNo || '자동 생성')}
                                        </div>
                                    </div>
                                    <div>
                                        <label className="block text-xs font-medium text-stone-500 mb-1">적요</label>
                                        <input type="text" value={description} onChange={(e) => setDescription(e.target.value)}
                                            placeholder="전표 적요를 입력하세요"
                                            className="w-full px-3 py-2 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none" />
                                    </div>
                                </div>
                            </div>

                            {/* 분개 내역 테이블 */}
                            <div>
                                <h4 className="text-sm font-semibold text-stone-700 mb-3">분개 내역</h4>
                                <div className="overflow-x-auto">
                                    <table className="w-full text-sm">
                                        <thead>
                                            <tr className="bg-stone-50 border-b border-stone-200">
                                                <th className="px-3 py-2 text-center font-medium text-stone-600 w-[70px]">구분</th>
                                                <th className="px-3 py-2 text-left font-medium text-stone-600 w-[180px]">계정과목</th>
                                                <th className="px-3 py-2 text-left font-medium text-stone-600 w-[160px]">거래처</th>
                                                <th className="px-3 py-2 text-right font-medium text-stone-600 w-[120px]">차변</th>
                                                <th className="px-3 py-2 text-right font-medium text-stone-600 w-[120px]">대변</th>
                                                <th className="px-3 py-2 text-left font-medium text-stone-600">적요</th>
                                                <th className="px-3 py-2 text-center font-medium text-stone-600 w-[40px]"></th>
                                            </tr>
                                        </thead>
                                        <tbody>
                                            {lines.map((line, index) => (
                                                <tr key={line.key || index} className="border-b border-stone-100">
                                                    <td className="px-3 py-2 text-center">
                                                        <button type="button" onClick={() => toggleDcType(index)}
                                                            className={`px-2 py-0.5 rounded text-xs font-medium cursor-pointer hover:opacity-80 transition-opacity ${line.dc_type === 'debit' ? 'bg-blue-100 text-blue-700' : 'bg-red-100 text-red-700'}`}
                                                            title="클릭하여 차변/대변 전환">
                                                            {line.dc_type === 'debit' ? '차변' : '대변'}
                                                        </button>
                                                    </td>
                                                    <td className="px-3 py-2">
                                                        <AccountCodeSelect value={line.account_code} accountCodes={accountCodes}
                                                            triggerId={`m-account-${index}`}
                                                            onTab={() => document.getElementById(`m-partner-${index}`)?.focus()}
                                                            onChange={(code, name) => {
                                                                const updated = [...lines];
                                                                updated[index] = { ...updated[index], account_code: code, account_name: name };
                                                                setLines(updated);
                                                            }} />
                                                    </td>
                                                    <td className="px-3 py-2">
                                                        <TradingPartnerSelect value={line.trading_partner_id} valueName={line.trading_partner_name}
                                                            tradingPartners={tradingPartners}
                                                            triggerId={`m-partner-${index}`}
                                                            onTab={() => document.getElementById(`m-amount-${line.dc_type}-${index}`)?.focus()}
                                                            onChange={(id, name) => {
                                                                const updated = [...lines];
                                                                updated[index] = { ...updated[index], trading_partner_id: id, trading_partner_name: name };
                                                                setLines(updated);
                                                            }}
                                                            onAddPartner={() => { setAddPartnerLineIndex(index); setShowAddPartnerModal(true); }} />
                                                    </td>
                                                    <td className="px-3 py-2">
                                                        <input type="text" id={`m-amount-debit-${index}`}
                                                            value={line.dc_type === 'debit' && line.debit_amount ? formatInputCurrency(line.debit_amount) : ''}
                                                            onChange={(e) => updateLine(index, 'debit_amount', parseInputCurrency(e.target.value))}
                                                            onKeyDown={(e) => { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); document.getElementById(`m-desc-${index}`)?.focus(); } }}
                                                            disabled={line.dc_type !== 'debit'}
                                                            placeholder={line.dc_type === 'debit' ? '금액' : ''}
                                                            className={`w-full px-2 py-1.5 text-xs text-right border rounded outline-none font-medium ${line.dc_type === 'debit' ? 'border-stone-200 focus:ring-2 focus:ring-blue-500 text-blue-700' : 'bg-stone-100 border-stone-100 text-stone-300'}`} />
                                                    </td>
                                                    <td className="px-3 py-2">
                                                        <input type="text" id={`m-amount-credit-${index}`}
                                                            value={line.dc_type === 'credit' && line.credit_amount ? formatInputCurrency(line.credit_amount) : ''}
                                                            onChange={(e) => updateLine(index, 'credit_amount', parseInputCurrency(e.target.value))}
                                                            onKeyDown={(e) => { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); document.getElementById(`m-desc-${index}`)?.focus(); } }}
                                                            disabled={line.dc_type !== 'credit'}
                                                            placeholder={line.dc_type === 'credit' ? '금액' : ''}
                                                            className={`w-full px-2 py-1.5 text-xs text-right border rounded outline-none font-medium ${line.dc_type === 'credit' ? 'border-stone-200 focus:ring-2 focus:ring-red-500 text-red-700' : 'bg-stone-100 border-stone-100 text-stone-300'}`} />
                                                    </td>
                                                    <td className="px-3 py-2">
                                                        <input type="text" id={`m-desc-${index}`} value={line.description || ''}
                                                            onChange={(e) => updateLine(index, 'description', e.target.value)}
                                                            onKeyDown={(e) => { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); const next = document.getElementById(`m-account-${index + 1}`); if (next) next.focus(); } }}
                                                            placeholder="적요"
                                                            className="w-full px-2 py-1.5 text-xs border border-stone-200 rounded focus:ring-2 focus:ring-emerald-500 outline-none" />
                                                    </td>
                                                    <td className="px-3 py-2 text-center">
                                                        <button tabIndex={-1} onClick={() => removeLine(index)} disabled={lines.length <= 2}
                                                            className={`p-1 rounded ${lines.length <= 2 ? 'text-stone-200 cursor-not-allowed' : 'text-stone-400 hover:text-red-500 hover:bg-red-50'}`}>
                                                            <Trash2 className="w-3.5 h-3.5" />
                                                        </button>
                                                    </td>
                                                </tr>
                                            ))}
                                        </tbody>
                                        <tfoot>
                                            <tr className="bg-stone-50 border-t-2 border-stone-300">
                                                <td colSpan={3} className="px-3 py-2">
                                                    <button onClick={addLine} className="text-xs text-emerald-600 hover:text-emerald-700 font-medium flex items-center gap-1">
                                                        <Plus className="w-3.5 h-3.5" /> 행 추가
                                                    </button>
                                                </td>
                                                <td className="px-3 py-2 text-right">
                                                    <span className="text-xs text-stone-500">차변</span>
                                                    <p className="font-bold text-blue-700 text-sm">{formatCurrency(totalDebit)}</p>
                                                </td>
                                                <td className="px-3 py-2 text-right">
                                                    <span className="text-xs text-stone-500">대변</span>
                                                    <p className="font-bold text-red-700 text-sm">{formatCurrency(totalCredit)}</p>
                                                </td>
                                                <td colSpan={2} className="px-3 py-2">
                                                    {difference !== 0 ? (
                                                        <div className="flex items-center gap-1 text-xs text-red-600 font-medium">
                                                            <AlertTriangle className="w-3.5 h-3.5" /> 차이: {formatCurrency(Math.abs(difference))}
                                                        </div>
                                                    ) : totalDebit > 0 ? (
                                                        <div className="flex items-center gap-1 text-xs text-emerald-600 font-medium">
                                                            <CheckCircle className="w-3.5 h-3.5" /> 대차 균형
                                                        </div>
                                                    ) : null}
                                                </td>
                                            </tr>
                                        </tfoot>
                                    </table>
                                </div>
                            </div>
                        </>
                    )}
                </div>

                {/* 하단 버튼 */}
                <div className="px-6 py-4 border-t border-stone-100 flex justify-between flex-shrink-0">
                    <div>
                        {isEditMode && (
                            <button onClick={handleDelete} disabled={saving}
                                className="px-4 py-2 bg-red-50 text-red-600 rounded-lg text-sm font-medium hover:bg-red-100 transition-colors disabled:opacity-50">
                                <Trash2 className="w-4 h-4 inline mr-1" /> 삭제
                            </button>
                        )}
                    </div>
                    <div className="flex gap-2">
                        <button onClick={onClose} className="px-4 py-2 text-sm text-stone-600 bg-stone-100 rounded-lg hover:bg-stone-200 transition-colors">취소</button>
                        <button onClick={handleSave} disabled={!isBalanced || saving}
                            className={`px-6 py-2 text-sm font-medium rounded-lg flex items-center gap-1 transition-colors ${isBalanced && !saving ? 'bg-emerald-600 text-white hover:bg-emerald-700' : 'bg-stone-200 text-stone-400 cursor-not-allowed'}`}>
                            <Save className="w-4 h-4" /> {saving ? '저장 중...' : isEditMode ? '수정' : '저장'}
                        </button>
                    </div>
                </div>
            </div>

            {/* 거래처 추가 모달 */}
            <AddTradingPartnerModal
                isOpen={showAddPartnerModal}
                onClose={() => { setShowAddPartnerModal(false); setAddPartnerLineIndex(null); }}
                onSaved={(newPartner) => {
                    if (onPartnerAdded) onPartnerAdded(newPartner);
                    if (addPartnerLineIndex !== null) {
                        const updated = [...lines];
                        updated[addPartnerLineIndex] = {
                            ...updated[addPartnerLineIndex],
                            trading_partner_id: newPartner.id,
                            trading_partner_name: newPartner.name,
                        };
                        setLines(updated);
                    }
                    setAddPartnerLineIndex(null);
                }}
            />
        </div>
    );
};

// ============================================================
// CardJournalEntryModal (카드거래 분개 모달)
// ============================================================
const CardJournalEntryModal = ({ transaction, accountCodes, tradingPartners, onClose, onSaved, onDeleted, onPartnerAdded }) => {
    const ctx = transaction;
    const amount = ctx.approvalAmount || 0;
    const supplyAmount = ctx.supplyAmount || 0;
    const taxAmount = ctx.taxAmount || 0;
    const isDeductible = ctx.deductionType !== 'non_deductible';

    const [saving, setSaving] = useState(false);
    const [loadingJournal, setLoadingJournal] = useState(false);
    const [isEditMode, setIsEditMode] = useState(false);
    const [existingJournalId, setExistingJournalId] = useState(null);
    const [description, setDescription] = useState(ctx.merchantName || '');
    const [showAddPartnerModal, setShowAddPartnerModal] = useState(false);
    const [addPartnerLineIndex, setAddPartnerLineIndex] = useState(null);

    // 기본 분개 라인 생성
    const getDefaultLines = () => {
        const expenseCode = ctx.accountCode || '826';
        const expenseName = ctx.accountName || '잡비';

        if (isDeductible) {
            // 공제: 차변(비용=공급가) + 차변(부가세대급금=세액) / 대변(미지급금=합계)
            return [
                { key: 1, dc_type: 'debit', account_code: expenseCode, account_name: expenseName, trading_partner_id: null, trading_partner_name: '', debit_amount: supplyAmount, credit_amount: 0, description: '' },
                { key: 2, dc_type: 'debit', account_code: '135', account_name: '부가세대급금', trading_partner_id: null, trading_partner_name: '', debit_amount: taxAmount, credit_amount: 0, description: '' },
                { key: 3, dc_type: 'credit', account_code: '253', account_name: '미지급금', trading_partner_id: null, trading_partner_name: '', debit_amount: 0, credit_amount: amount, description: '' },
            ];
        } else {
            // 불공제: 차변(비용=합계) / 대변(미지급금=합계)
            return [
                { key: 1, dc_type: 'debit', account_code: expenseCode, account_name: expenseName, trading_partner_id: null, trading_partner_name: '', debit_amount: amount, credit_amount: 0, description: '' },
                { key: 2, dc_type: 'credit', account_code: '253', account_name: '미지급금', trading_partner_id: null, trading_partner_name: '', debit_amount: 0, credit_amount: amount, description: '' },
            ];
        }
    };

    const [lines, setLines] = useState(getDefaultLines());

    // 기존 분개 로드
    useEffect(() => {
        if (ctx.hasJournal) {
            setLoadingJournal(true);
            fetch(`/finance/journal-entries/card-journals?source_key=${encodeURIComponent(ctx.uniqueKey)}`)
                .then(r => r.json())
                .then(data => {
                    if (data.success && data.data) {
                        setIsEditMode(true);
                        setExistingJournalId(data.data.id);
                        setDescription(data.data.description || '');
                        if (data.data.lines && data.data.lines.length > 0) {
                            setLines(data.data.lines.map((l, i) => ({ ...l, key: l.id || Date.now() + i })));
                        }
                    }
                })
                .catch(err => console.error('기존 분개 로드 오류:', err))
                .finally(() => setLoadingJournal(false));
        }
    }, []);

    const totalDebit = lines.reduce((sum, l) => sum + (parseInt(l.debit_amount) || 0), 0);
    const totalCredit = lines.reduce((sum, l) => sum + (parseInt(l.credit_amount) || 0), 0);
    const isBalanced = totalDebit === totalCredit && totalDebit > 0;
    const difference = totalDebit - totalCredit;

    const updateLine = (index, field, value) => {
        const updated = [...lines];
        updated[index] = { ...updated[index], [field]: value };
        if (field === 'dc_type') {
            if (value === 'debit') { updated[index].credit_amount = 0; }
            else { updated[index].debit_amount = 0; }
        }
        setLines(updated);
    };

    const toggleDcType = (index) => {
        setLines(prev => prev.map((l, i) => {
            if (i !== index) return l;
            const newType = l.dc_type === 'debit' ? 'credit' : 'debit';
            return { ...l, dc_type: newType, debit_amount: l.credit_amount, credit_amount: l.debit_amount };
        }));
    };

    const addLine = () => setLines([...lines, {
        key: Date.now() + Math.random(), dc_type: 'debit', account_code: '', account_name: '',
        trading_partner_id: null, trading_partner_name: '', debit_amount: 0, credit_amount: 0, description: '',
    }]);

    const removeLine = (index) => {
        if (lines.length <= 2) return;
        setLines(lines.filter((_, i) => i !== index));
    };

    const entryDate = ctx.useDate ? `${ctx.useDate.slice(0,4)}-${ctx.useDate.slice(4,6)}-${ctx.useDate.slice(6,8)}` : getKoreanDate();

    const handleSave = async () => {
        const emptyLine = lines.find(l => !l.account_code);
        if (emptyLine) { notify('모든 분개 라인의 계정과목을 선택해주세요.', 'warning'); return; }
        if (!isBalanced) { notify('차변과 대변의 합계가 일치하지 않습니다.', 'warning'); return; }

        setSaving(true);
        try {
            if (isEditMode && existingJournalId) {
                await fetch(`/finance/journal-entries/card-journal/${existingJournalId}`, {
                    method: 'DELETE', headers: { 'X-CSRF-TOKEN': CSRF_TOKEN },
                });
            }

            const payload = {
                source_key: ctx.uniqueKey,
                entry_date: entryDate,
                description: description,
                lines: lines.map(l => ({
                    dc_type: l.dc_type, account_code: l.account_code, account_name: l.account_name,
                    trading_partner_id: l.trading_partner_id, trading_partner_name: l.trading_partner_name,
                    debit_amount: parseInt(l.debit_amount) || 0, credit_amount: parseInt(l.credit_amount) || 0,
                    description: l.description,
                })),
            };

            const res = await fetch('/finance/journal-entries/store-from-card', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': CSRF_TOKEN },
                body: JSON.stringify(payload),
            });

            if (!res.ok) {
                const text = await res.text();
                try {
                    const errData = JSON.parse(text);
                    const msg = errData.message || (errData.errors ? Object.values(errData.errors).flat().join(', ') : `저장 실패 (${res.status})`);
                    notify(msg, 'error');
                } catch { notify(`저장 실패: 서버 오류 (${res.status})`, 'error'); }
                return;
            }

            const data = await res.json();
            if (data.success) { notify(data.message || '분개가 저장되었습니다.', 'success'); onSaved(); }
            else { notify(data.message || '분개 저장 실패', 'error'); }
        } catch (err) {
            console.error('카드 분개 저장 오류:', err);
            notify('분개 저장 중 오류가 발생했습니다: ' + err.message, 'error');
        } finally {
            setSaving(false);
        }
    };

    const handleDelete = async () => {
        if (!existingJournalId || !confirm('분개를 삭제하시겠습니까?')) return;
        setSaving(true);
        try {
            const res = await fetch(`/finance/journal-entries/card-journal/${existingJournalId}`, {
                method: 'DELETE', headers: { 'X-CSRF-TOKEN': CSRF_TOKEN },
            });
            const data = await res.json();
            if (data.success) { notify('분개가 삭제되었습니다.', 'success'); onDeleted(); }
        } catch (err) {
            notify('삭제 중 오류가 발생했습니다.', 'error');
        } finally {
            setSaving(false);
        }
    };

    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 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 flex-shrink-0">
                    <h3 className="text-lg font-bold text-stone-900 flex items-center gap-2">
                        <CreditCard className="w-5 h-5 text-orange-600" />
                        {isEditMode ? '카드 분개 수정' : '카드 분개 생성'}
                    </h3>
                    <button onClick={onClose} className="p-2 hover:bg-stone-100 rounded-lg transition-colors">
                        <X className="w-5 h-5 text-stone-500" />
                    </button>
                </div>

                <div className="px-6 py-5 overflow-y-auto flex-1 space-y-5">
                    {/* 카드거래 정보 카드 */}
                    <div className="rounded-xl p-4 bg-orange-50 border border-orange-100">
                        <h4 className="text-sm font-semibold text-stone-700 mb-3">카드거래 정보</h4>
                        <div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
                            <div>
                                <span className="text-stone-500">날짜: </span>
                                <span className="font-medium">{formatDate(ctx.useDate)}</span>
                            </div>
                            <div>
                                <span className="text-stone-500">카드: </span>
                                <span className="font-medium">{ctx.cardCompanyName} ...{(ctx.cardNum || '').slice(-4)}</span>
                            </div>
                            <div>
                                <span className="text-stone-500">가맹점: </span>
                                <span className="font-medium">{ctx.merchantName || '-'}</span>
                            </div>
                            <div>
                                <span className="text-stone-500">승인금액: </span>
                                <span className="font-bold text-orange-700">{formatCurrency(amount)}원</span>
                            </div>
                        </div>
                        <div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm mt-2">
                            <div>
                                <span className="text-stone-500">공급가액: </span>
                                <span className="font-medium">{formatCurrency(supplyAmount)}원</span>
                            </div>
                            <div>
                                <span className="text-stone-500">부가세: </span>
                                <span className="font-medium">{formatCurrency(taxAmount)}원</span>
                            </div>
                            <div>
                                <span className="text-stone-500">공제: </span>
                                <span className={`font-medium ${isDeductible ? 'text-emerald-600' : 'text-red-600'}`}>
                                    {isDeductible ? '공제' : '불공제'}
                                </span>
                            </div>
                            {ctx.approvalNum && (
                                <div>
                                    <span className="text-stone-500">승인번호: </span>
                                    <span className="font-medium text-stone-600">{ctx.approvalNum}</span>
                                </div>
                            )}
                        </div>
                    </div>

                    {/* 전표 적요 */}
                    <div>
                        <label className="block text-xs font-medium text-stone-500 mb-1">전표 적요</label>
                        <input type="text" value={description} onChange={(e) => setDescription(e.target.value)}
                            placeholder="전표 적요를 입력하세요"
                            className="w-full px-3 py-2 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none" />
                    </div>

                    {loadingJournal ? (
                        <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>
                            <span className="ml-2 text-sm text-stone-500">분개 데이터 로딩중...</span>
                        </div>
                    ) : (
                        <div>
                            <h4 className="text-sm font-semibold text-stone-700 mb-3">분개 내역</h4>
                            <div className="overflow-x-auto">
                                <table className="w-full text-sm">
                                    <thead>
                                        <tr className="bg-stone-50 border-b border-stone-200">
                                            <th className="px-3 py-2 text-center font-medium text-stone-600 w-[70px]">구분</th>
                                            <th className="px-3 py-2 text-left font-medium text-stone-600 w-[180px]">계정과목</th>
                                            <th className="px-3 py-2 text-left font-medium text-stone-600 w-[160px]">거래처</th>
                                            <th className="px-3 py-2 text-right font-medium text-stone-600 w-[120px]">차변</th>
                                            <th className="px-3 py-2 text-right font-medium text-stone-600 w-[120px]">대변</th>
                                            <th className="px-3 py-2 text-left font-medium text-stone-600">적요</th>
                                            <th className="px-3 py-2 text-center font-medium text-stone-600 w-[40px]"></th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        {lines.map((line, index) => (
                                            <tr key={line.key || index} className="border-b border-stone-100">
                                                <td className="px-3 py-2 text-center">
                                                    <button type="button" onClick={() => toggleDcType(index)}
                                                        className={`px-2 py-0.5 rounded text-xs font-medium cursor-pointer hover:opacity-80 transition-opacity ${line.dc_type === 'debit' ? 'bg-blue-100 text-blue-700' : 'bg-red-100 text-red-700'}`}
                                                        title="클릭하여 차변/대변 전환">
                                                        {line.dc_type === 'debit' ? '차변' : '대변'}
                                                    </button>
                                                </td>
                                                <td className="px-3 py-2">
                                                    <AccountCodeSelect value={line.account_code} accountCodes={accountCodes}
                                                        triggerId={`c-account-${index}`}
                                                        onTab={() => document.getElementById(`c-partner-${index}`)?.focus()}
                                                        onChange={(code, name) => {
                                                            const updated = [...lines];
                                                            updated[index] = { ...updated[index], account_code: code, account_name: name };
                                                            setLines(updated);
                                                        }} />
                                                </td>
                                                <td className="px-3 py-2">
                                                    <TradingPartnerSelect value={line.trading_partner_id} valueName={line.trading_partner_name}
                                                        tradingPartners={tradingPartners}
                                                        triggerId={`c-partner-${index}`}
                                                        onTab={() => document.getElementById(`c-amount-${line.dc_type}-${index}`)?.focus()}
                                                        onChange={(id, name) => {
                                                            const updated = [...lines];
                                                            updated[index] = { ...updated[index], trading_partner_id: id, trading_partner_name: name };
                                                            setLines(updated);
                                                        }}
                                                        onAddPartner={() => { setAddPartnerLineIndex(index); setShowAddPartnerModal(true); }} />
                                                </td>
                                                <td className="px-3 py-2">
                                                    <input type="text" id={`c-amount-debit-${index}`}
                                                        value={line.dc_type === 'debit' && line.debit_amount ? formatInputCurrency(line.debit_amount) : ''}
                                                        onChange={(e) => updateLine(index, 'debit_amount', parseInputCurrency(e.target.value))}
                                                        onKeyDown={(e) => { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); document.getElementById(`c-desc-${index}`)?.focus(); } }}
                                                        disabled={line.dc_type !== 'debit'}
                                                        placeholder={line.dc_type === 'debit' ? '금액' : ''}
                                                        className={`w-full px-2 py-1.5 text-xs text-right border rounded outline-none font-medium ${line.dc_type === 'debit' ? 'border-stone-200 focus:ring-2 focus:ring-blue-500 text-blue-700' : 'bg-stone-100 border-stone-100 text-stone-300'}`} />
                                                </td>
                                                <td className="px-3 py-2">
                                                    <input type="text" id={`c-amount-credit-${index}`}
                                                        value={line.dc_type === 'credit' && line.credit_amount ? formatInputCurrency(line.credit_amount) : ''}
                                                        onChange={(e) => updateLine(index, 'credit_amount', parseInputCurrency(e.target.value))}
                                                        onKeyDown={(e) => { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); document.getElementById(`c-desc-${index}`)?.focus(); } }}
                                                        disabled={line.dc_type !== 'credit'}
                                                        placeholder={line.dc_type === 'credit' ? '금액' : ''}
                                                        className={`w-full px-2 py-1.5 text-xs text-right border rounded outline-none font-medium ${line.dc_type === 'credit' ? 'border-stone-200 focus:ring-2 focus:ring-red-500 text-red-700' : 'bg-stone-100 border-stone-100 text-stone-300'}`} />
                                                </td>
                                                <td className="px-3 py-2">
                                                    <input type="text" id={`c-desc-${index}`} value={line.description || ''}
                                                        onChange={(e) => updateLine(index, 'description', e.target.value)}
                                                        onKeyDown={(e) => { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); const next = document.getElementById(`c-account-${index + 1}`); if (next) next.focus(); } }}
                                                        placeholder="적요"
                                                        className="w-full px-2 py-1.5 text-xs border border-stone-200 rounded focus:ring-2 focus:ring-emerald-500 outline-none" />
                                                </td>
                                                <td className="px-3 py-2 text-center">
                                                    <button tabIndex={-1} onClick={() => removeLine(index)} disabled={lines.length <= 2}
                                                        className={`p-1 rounded ${lines.length <= 2 ? 'text-stone-200 cursor-not-allowed' : 'text-stone-400 hover:text-red-500 hover:bg-red-50'}`}>
                                                        <Trash2 className="w-3.5 h-3.5" />
                                                    </button>
                                                </td>
                                            </tr>
                                        ))}
                                    </tbody>
                                    <tfoot>
                                        <tr className="bg-stone-50 border-t-2 border-stone-300">
                                            <td colSpan={3} className="px-3 py-2">
                                                <button onClick={addLine} className="text-xs text-emerald-600 hover:text-emerald-700 font-medium flex items-center gap-1">
                                                    <Plus className="w-3.5 h-3.5" /> 행 추가
                                                </button>
                                            </td>
                                            <td className="px-3 py-2 text-right">
                                                <span className="text-xs text-stone-500">차변</span>
                                                <p className="font-bold text-blue-700 text-sm">{formatCurrency(totalDebit)}</p>
                                            </td>
                                            <td className="px-3 py-2 text-right">
                                                <span className="text-xs text-stone-500">대변</span>
                                                <p className="font-bold text-red-700 text-sm">{formatCurrency(totalCredit)}</p>
                                            </td>
                                            <td colSpan={2} className="px-3 py-2">
                                                {difference !== 0 ? (
                                                    <div className="flex items-center gap-1 text-xs text-red-600 font-medium">
                                                        <AlertTriangle className="w-3.5 h-3.5" /> 차이: {formatCurrency(Math.abs(difference))}
                                                    </div>
                                                ) : totalDebit > 0 ? (
                                                    <div className="flex items-center gap-1 text-xs text-emerald-600 font-medium">
                                                        <CheckCircle className="w-3.5 h-3.5" /> 대차 균형
                                                    </div>
                                                ) : null}
                                            </td>
                                        </tr>
                                    </tfoot>
                                </table>
                            </div>
                        </div>
                    )}
                </div>

                {/* 하단 버튼 */}
                <div className="px-6 py-4 border-t border-stone-100 flex justify-between flex-shrink-0">
                    <div>
                        {isEditMode && (
                            <button onClick={handleDelete} disabled={saving}
                                className="px-4 py-2 bg-red-50 text-red-600 rounded-lg text-sm font-medium hover:bg-red-100 transition-colors disabled:opacity-50">
                                <Trash2 className="w-4 h-4 inline mr-1" /> 분개 삭제
                            </button>
                        )}
                    </div>
                    <div className="flex gap-2">
                        <button onClick={onClose} className="px-4 py-2 text-sm text-stone-600 bg-stone-100 rounded-lg hover:bg-stone-200 transition-colors">취소</button>
                        <button onClick={handleSave} disabled={!isBalanced || saving}
                            className={`px-6 py-2 text-sm font-medium rounded-lg flex items-center gap-1 transition-colors ${isBalanced && !saving ? 'bg-emerald-600 text-white hover:bg-emerald-700' : 'bg-stone-200 text-stone-400 cursor-not-allowed'}`}>
                            <Save className="w-4 h-4" /> {saving ? '저장 중...' : isEditMode ? '수정' : '저장'}
                        </button>
                    </div>
                </div>
            </div>

            {/* 거래처 추가 모달 */}
            <AddTradingPartnerModal
                isOpen={showAddPartnerModal}
                onClose={() => { setShowAddPartnerModal(false); setAddPartnerLineIndex(null); }}
                onSaved={(newPartner) => {
                    if (onPartnerAdded) onPartnerAdded(newPartner);
                    if (addPartnerLineIndex !== null) {
                        const updated = [...lines];
                        updated[addPartnerLineIndex] = {
                            ...updated[addPartnerLineIndex],
                            trading_partner_id: newPartner.id,
                            trading_partner_name: newPartner.name,
                        };
                        setLines(updated);
                    }
                    setAddPartnerLineIndex(null);
                }}
            />
        </div>
    );
};

// ============================================================
// JournalEntryModal (은행거래 분개 모달)
// ============================================================
const JournalEntryModal = ({ transaction, accountCodes, tradingPartners, onClose, onSaved, onDeleted, onPartnerAdded }) => {
    const tx = transaction;
    const isDeposit = parseFloat(tx.deposit) > 0;
    const amount = isDeposit ? parseInt(tx.deposit) : parseInt(tx.withdraw);

    const [saving, setSaving] = useState(false);
    const [loadingJournal, setLoadingJournal] = useState(false);
    const [isEditMode, setIsEditMode] = useState(false);
    const [existingJournalId, setExistingJournalId] = useState(null);
    const [description, setDescription] = useState(tx.summary || '');
    const [showAddPartnerModal, setShowAddPartnerModal] = useState(false);
    const [addPartnerLineIndex, setAddPartnerLineIndex] = useState(null);

    // 기본 분개 라인: 입금=차변 보통예금/대변 미정, 출금=차변 미정/대변 보통예금
    const getDefaultLines = () => {
        if (isDeposit) {
            return [
                { key: 1, dc_type: 'debit', account_code: '103', account_name: '보통예금', trading_partner_id: null, trading_partner_name: '', debit_amount: amount, credit_amount: 0, description: '' },
                { key: 2, dc_type: 'credit', account_code: '', account_name: '', trading_partner_id: null, trading_partner_name: '', debit_amount: 0, credit_amount: amount, description: '' },
            ];
        } else {
            return [
                { key: 1, dc_type: 'debit', account_code: '', account_name: '', trading_partner_id: null, trading_partner_name: '', debit_amount: amount, credit_amount: 0, description: '' },
                { key: 2, dc_type: 'credit', account_code: '103', account_name: '보통예금', trading_partner_id: null, trading_partner_name: '', debit_amount: 0, credit_amount: amount, description: '' },
            ];
        }
    };

    const [lines, setLines] = useState(getDefaultLines());

    // 기존 분개 로드
    useEffect(() => {
        if (tx.hasJournal) {
            setLoadingJournal(true);
            fetch(`/finance/journal-entries/bank-journals?source_key=${encodeURIComponent(tx.uniqueKey)}`)
                .then(r => r.json())
                .then(data => {
                    if (data.success && data.data) {
                        setIsEditMode(true);
                        setExistingJournalId(data.data.id);
                        setDescription(data.data.description || '');
                        if (data.data.lines && data.data.lines.length > 0) {
                            setLines(data.data.lines.map((l, i) => ({
                                ...l,
                                key: l.id || Date.now() + i,
                            })));
                        }
                    }
                })
                .catch(err => console.error('기존 분개 로드 오류:', err))
                .finally(() => setLoadingJournal(false));
        }
    }, []);

    const totalDebit = lines.reduce((sum, l) => sum + (parseInt(l.debit_amount) || 0), 0);
    const totalCredit = lines.reduce((sum, l) => sum + (parseInt(l.credit_amount) || 0), 0);
    const isBalanced = totalDebit === totalCredit && totalDebit > 0;
    const difference = totalDebit - totalCredit;

    const updateLine = (index, field, value) => {
        const updated = [...lines];
        updated[index] = { ...updated[index], [field]: value };
        if (field === 'dc_type') {
            if (value === 'debit') {
                updated[index].credit_amount = 0;
            } else {
                updated[index].debit_amount = 0;
            }
        }
        setLines(updated);
    };

    const toggleDcType = (index) => {
        setLines(prev => prev.map((l, i) => {
            if (i !== index) return l;
            const newType = l.dc_type === 'debit' ? 'credit' : 'debit';
            return {
                ...l,
                dc_type: newType,
                debit_amount: l.credit_amount,
                credit_amount: l.debit_amount,
            };
        }));
    };

    const addLine = () => setLines([...lines, {
        key: Date.now() + Math.random(),
        dc_type: 'debit',
        account_code: '',
        account_name: '',
        trading_partner_id: null,
        trading_partner_name: '',
        debit_amount: 0,
        credit_amount: 0,
        description: '',
    }]);

    const removeLine = (index) => {
        if (lines.length <= 2) return;
        setLines(lines.filter((_, i) => i !== index));
    };

    // 거래일자를 entry_date 형식으로 변환
    const entryDate = tx.transDate ? `${tx.transDate.slice(0,4)}-${tx.transDate.slice(4,6)}-${tx.transDate.slice(6,8)}` : getKoreanDate();

    const handleSave = async () => {
        const emptyLine = lines.find(l => !l.account_code);
        if (emptyLine) { notify('모든 분개 라인의 계정과목을 선택해주세요.', 'warning'); return; }
        if (!isBalanced) { notify('차변과 대변의 합계가 일치하지 않습니다.', 'warning'); return; }

        setSaving(true);
        try {
            // 기존 분개가 있으면 삭제 후 새로 생성
            if (isEditMode && existingJournalId) {
                await fetch(`/finance/journal-entries/bank-journal/${existingJournalId}`, {
                    method: 'DELETE',
                    headers: { 'X-CSRF-TOKEN': CSRF_TOKEN },
                });
            }

            const payload = {
                source_key: tx.uniqueKey,
                entry_date: entryDate,
                description: description,
                lines: lines.map(l => ({
                    dc_type: l.dc_type,
                    account_code: l.account_code,
                    account_name: l.account_name,
                    trading_partner_id: l.trading_partner_id,
                    trading_partner_name: l.trading_partner_name,
                    debit_amount: parseInt(l.debit_amount) || 0,
                    credit_amount: parseInt(l.credit_amount) || 0,
                    description: l.description,
                })),
            };

            const res = await fetch('/finance/journal-entries/store-from-bank', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': CSRF_TOKEN },
                body: JSON.stringify(payload),
            });

            if (!res.ok) {
                const text = await res.text();
                try {
                    const errData = JSON.parse(text);
                    const msg = errData.message || (errData.errors ? Object.values(errData.errors).flat().join(', ') : `저장 실패 (${res.status})`);
                    notify(msg, 'error');
                } catch { notify(`저장 실패: 서버 오류 (${res.status})`, 'error'); }
                return;
            }

            const data = await res.json();

            if (data.success) {
                notify(data.message || '분개가 저장되었습니다.', 'success');
                onSaved();
            } else {
                notify(data.message || '분개 저장 실패', 'error');
            }
        } catch (err) {
            console.error('분개 저장 오류:', err);
            notify('분개 저장 중 오류가 발생했습니다: ' + err.message, 'error');
        } finally {
            setSaving(false);
        }
    };

    const handleDelete = async () => {
        if (!existingJournalId || !confirm('분개를 삭제하시겠습니까?')) return;
        setSaving(true);
        try {
            const res = await fetch(`/finance/journal-entries/bank-journal/${existingJournalId}`, {
                method: 'DELETE',
                headers: { 'X-CSRF-TOKEN': CSRF_TOKEN },
            });
            const data = await res.json();
            if (data.success) {
                notify('분개가 삭제되었습니다.', 'success');
                onDeleted();
            }
        } catch (err) {
            notify('삭제 중 오류가 발생했습니다.', 'error');
        } finally {
            setSaving(false);
        }
    };

    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 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 flex-shrink-0">
                    <h3 className="text-lg font-bold text-stone-900">
                        {isEditMode ? '분개 수정' : '분개 생성'}
                    </h3>
                    <button onClick={onClose} className="p-2 hover:bg-stone-100 rounded-lg transition-colors">
                        <X className="w-5 h-5 text-stone-500" />
                    </button>
                </div>

                <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>
                        <div className="grid grid-cols-2 md:grid-cols-4 gap-3 text-sm">
                            <div>
                                <span className="text-stone-500">날짜: </span>
                                <span className="font-medium">{formatDate(tx.transDate)}</span>
                            </div>
                            <div>
                                <span className="text-stone-500">구분: </span>
                                <span className={`font-medium ${isDeposit ? 'text-blue-700' : 'text-red-700'}`}>
                                    {isDeposit ? '입금' : '출금'}
                                </span>
                            </div>
                            <div>
                                <span className="text-stone-500">금액: </span>
                                <span className="font-bold">{formatCurrency(amount)}원</span>
                            </div>
                            <div>
                                <span className="text-stone-500">적요: </span>
                                <span className="font-medium">{tx.summary || '-'}</span>
                            </div>
                        </div>
                        {tx.bankName && (
                            <div className="mt-2 text-xs text-stone-500">
                                계좌: {tx.bankName} {tx.bankAccountNum}
                            </div>
                        )}
                    </div>

                    {/* 전표 적요 */}
                    <div>
                        <label className="block text-xs font-medium text-stone-500 mb-1">전표 적요</label>
                        <input type="text" value={description} onChange={(e) => setDescription(e.target.value)}
                            placeholder="전표 적요를 입력하세요"
                            className="w-full px-3 py-2 text-sm border border-stone-200 rounded-lg focus:ring-2 focus:ring-emerald-500 outline-none" />
                    </div>

                    {loadingJournal ? (
                        <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>
                            <span className="ml-2 text-sm text-stone-500">분개 데이터 로딩중...</span>
                        </div>
                    ) : (
                        /* 분개 라인 테이블 */
                        <div>
                            <h4 className="text-sm font-semibold text-stone-700 mb-3">분개 내역</h4>
                            <div className="overflow-x-auto">
                                <table className="w-full text-sm">
                                    <thead>
                                        <tr className="bg-stone-50 border-b border-stone-200">
                                            <th className="px-3 py-2 text-center font-medium text-stone-600 w-[70px]">구분</th>
                                            <th className="px-3 py-2 text-left font-medium text-stone-600 w-[180px]">계정과목</th>
                                            <th className="px-3 py-2 text-left font-medium text-stone-600 w-[160px]">거래처</th>
                                            <th className="px-3 py-2 text-right font-medium text-stone-600 w-[120px]">차변</th>
                                            <th className="px-3 py-2 text-right font-medium text-stone-600 w-[120px]">대변</th>
                                            <th className="px-3 py-2 text-left font-medium text-stone-600">적요</th>
                                            <th className="px-3 py-2 text-center font-medium text-stone-600 w-[40px]"></th>
                                        </tr>
                                    </thead>
                                    <tbody>
                                        {lines.map((line, index) => (
                                            <tr key={line.key || index} className="border-b border-stone-100">
                                                <td className="px-3 py-2 text-center">
                                                    <button type="button" onClick={() => toggleDcType(index)}
                                                        className={`px-2 py-0.5 rounded text-xs font-medium cursor-pointer hover:opacity-80 transition-opacity ${line.dc_type === 'debit' ? 'bg-blue-100 text-blue-700' : 'bg-red-100 text-red-700'}`}
                                                        title="클릭하여 차변/대변 전환">
                                                        {line.dc_type === 'debit' ? '차변' : '대변'}
                                                    </button>
                                                </td>
                                                <td className="px-3 py-2">
                                                    <AccountCodeSelect
                                                        value={line.account_code}
                                                        accountCodes={accountCodes}
                                                        triggerId={`b-account-${index}`}
                                                        onTab={() => document.getElementById(`b-partner-${index}`)?.focus()}
                                                        onChange={(code, name) => {
                                                            const updated = [...lines];
                                                            updated[index] = { ...updated[index], account_code: code, account_name: name };
                                                            setLines(updated);
                                                        }}
                                                    />
                                                </td>
                                                <td className="px-3 py-2">
                                                    <TradingPartnerSelect
                                                        value={line.trading_partner_id}
                                                        valueName={line.trading_partner_name}
                                                        tradingPartners={tradingPartners}
                                                        triggerId={`b-partner-${index}`}
                                                        onTab={() => document.getElementById(`b-amount-${line.dc_type}-${index}`)?.focus()}
                                                        onChange={(id, name) => {
                                                            const updated = [...lines];
                                                            updated[index] = { ...updated[index], trading_partner_id: id, trading_partner_name: name };
                                                            setLines(updated);
                                                        }}
                                                        onAddPartner={() => { setAddPartnerLineIndex(index); setShowAddPartnerModal(true); }}
                                                    />
                                                </td>
                                                <td className="px-3 py-2">
                                                    <input type="text" id={`b-amount-debit-${index}`}
                                                        value={line.dc_type === 'debit' && line.debit_amount ? formatInputCurrency(line.debit_amount) : ''}
                                                        onChange={(e) => updateLine(index, 'debit_amount', parseInputCurrency(e.target.value))}
                                                        onKeyDown={(e) => { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); document.getElementById(`b-desc-${index}`)?.focus(); } }}
                                                        disabled={line.dc_type !== 'debit'}
                                                        placeholder={line.dc_type === 'debit' ? '금액' : ''}
                                                        className={`w-full px-2 py-1.5 text-xs text-right border rounded outline-none font-medium ${line.dc_type === 'debit' ? 'border-stone-200 focus:ring-2 focus:ring-blue-500 text-blue-700' : 'bg-stone-100 border-stone-100 text-stone-300'}`}
                                                    />
                                                </td>
                                                <td className="px-3 py-2">
                                                    <input type="text" id={`b-amount-credit-${index}`}
                                                        value={line.dc_type === 'credit' && line.credit_amount ? formatInputCurrency(line.credit_amount) : ''}
                                                        onChange={(e) => updateLine(index, 'credit_amount', parseInputCurrency(e.target.value))}
                                                        onKeyDown={(e) => { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); document.getElementById(`b-desc-${index}`)?.focus(); } }}
                                                        disabled={line.dc_type !== 'credit'}
                                                        placeholder={line.dc_type === 'credit' ? '금액' : ''}
                                                        className={`w-full px-2 py-1.5 text-xs text-right border rounded outline-none font-medium ${line.dc_type === 'credit' ? 'border-stone-200 focus:ring-2 focus:ring-red-500 text-red-700' : 'bg-stone-100 border-stone-100 text-stone-300'}`}
                                                    />
                                                </td>
                                                <td className="px-3 py-2">
                                                    <input type="text" id={`b-desc-${index}`} value={line.description || ''}
                                                        onChange={(e) => updateLine(index, 'description', e.target.value)}
                                                        onKeyDown={(e) => { if (e.key === 'Tab' && !e.shiftKey) { e.preventDefault(); const next = document.getElementById(`b-account-${index + 1}`); if (next) next.focus(); } }}
                                                        placeholder="적요"
                                                        className="w-full px-2 py-1.5 text-xs border border-stone-200 rounded focus:ring-2 focus:ring-emerald-500 outline-none" />
                                                </td>
                                                <td className="px-3 py-2 text-center">
                                                    <button tabIndex={-1} onClick={() => removeLine(index)}
                                                        disabled={lines.length <= 2}
                                                        className={`p-1 rounded ${lines.length <= 2 ? 'text-stone-200 cursor-not-allowed' : 'text-stone-400 hover:text-red-500 hover:bg-red-50'}`}>
                                                        <Trash2 className="w-3.5 h-3.5" />
                                                    </button>
                                                </td>
                                            </tr>
                                        ))}
                                    </tbody>
                                    <tfoot>
                                        <tr className="bg-stone-50 border-t-2 border-stone-300">
                                            <td colSpan={3} className="px-3 py-2">
                                                <button onClick={addLine} className="text-xs text-emerald-600 hover:text-emerald-700 font-medium flex items-center gap-1">
                                                    <Plus className="w-3.5 h-3.5" /> 행 추가
                                                </button>
                                            </td>
                                            <td className="px-3 py-2 text-right">
                                                <span className="text-xs text-stone-500">차변</span>
                                                <p className="font-bold text-blue-700 text-sm">{formatCurrency(totalDebit)}</p>
                                            </td>
                                            <td className="px-3 py-2 text-right">
                                                <span className="text-xs text-stone-500">대변</span>
                                                <p className="font-bold text-red-700 text-sm">{formatCurrency(totalCredit)}</p>
                                            </td>
                                            <td colSpan={2} className="px-3 py-2">
                                                {difference !== 0 ? (
                                                    <div className="flex items-center gap-1 text-xs text-red-600 font-medium">
                                                        <AlertTriangle className="w-3.5 h-3.5" />
                                                        차이: {formatCurrency(Math.abs(difference))}
                                                    </div>
                                                ) : totalDebit > 0 ? (
                                                    <div className="flex items-center gap-1 text-xs text-emerald-600 font-medium">
                                                        <CheckCircle className="w-3.5 h-3.5" /> 대차 균형
                                                    </div>
                                                ) : null}
                                            </td>
                                        </tr>
                                    </tfoot>
                                </table>
                            </div>
                        </div>
                    )}
                </div>

                {/* 하단 버튼 */}
                <div className="px-6 py-4 border-t border-stone-100 flex justify-between flex-shrink-0">
                    <div>
                        {isEditMode && (
                            <button onClick={handleDelete} disabled={saving}
                                className="px-4 py-2 bg-red-50 text-red-600 rounded-lg text-sm font-medium hover:bg-red-100 transition-colors disabled:opacity-50">
                                <Trash2 className="w-4 h-4 inline mr-1" /> 분개 삭제
                            </button>
                        )}
                    </div>
                    <div className="flex gap-2">
                        <button onClick={onClose} className="px-4 py-2 text-sm text-stone-600 bg-stone-100 rounded-lg hover:bg-stone-200 transition-colors">
                            취소
                        </button>
                        <button onClick={handleSave} disabled={!isBalanced || saving}
                            className={`px-6 py-2 text-sm font-medium rounded-lg flex items-center gap-1 transition-colors ${isBalanced && !saving ? 'bg-emerald-600 text-white hover:bg-emerald-700' : 'bg-stone-200 text-stone-400 cursor-not-allowed'}`}>
                            <Save className="w-4 h-4" /> {saving ? '저장 중...' : isEditMode ? '수정' : '저장'}
                        </button>
                    </div>
                </div>
            </div>

            {/* 거래처 추가 모달 */}
            <AddTradingPartnerModal
                isOpen={showAddPartnerModal}
                onClose={() => { setShowAddPartnerModal(false); setAddPartnerLineIndex(null); }}
                onSaved={(newPartner) => {
                    if (onPartnerAdded) onPartnerAdded(newPartner);
                    if (addPartnerLineIndex !== null) {
                        const updated = [...lines];
                        updated[addPartnerLineIndex] = {
                            ...updated[addPartnerLineIndex],
                            trading_partner_id: newPartner.id,
                            trading_partner_name: newPartner.name,
                        };
                        setLines(updated);
                    }
                    setAddPartnerLineIndex(null);
                }}
            />
        </div>
    );
};

function App() {
    const [accountCodes, setAccountCodes] = useState([]);
    const [tradingPartners, setTradingPartners] = useState([]);
    const [showSettingsModal, setShowSettingsModal] = useState(false);
    const [showManualModal, setShowManualModal] = useState(false);
    const [editEntryId, setEditEntryId] = useState(null);
    const [journalListRefreshKey, setJournalListRefreshKey] = useState(0);

    const fetchMasterData = async () => {
        try {
            const [acRes, tpRes] = await Promise.all([
                fetch('/finance/journal-entries/account-codes'),
                fetch('/finance/journal-entries/trading-partners'),
            ]);
            const acData = await acRes.json();
            const tpData = await tpRes.json();
            if (acData.success) setAccountCodes(acData.data);
            if (tpData.success) setTradingPartners(tpData.data);
        } catch (err) {
            console.error('마스터 데이터 로딩 실패:', err);
        }
    };

    useEffect(() => { fetchMasterData(); }, []);

    const handlePartnerAdded = (newPartner) => {
        setTradingPartners(prev => [...prev, newPartner]);
    };

    const refreshJournalList = () => {
        setJournalListRefreshKey(prev => prev + 1);
    };

    const handleManualSaved = () => {
        setShowManualModal(false);
        setEditEntryId(null);
        refreshJournalList();
    };

    const handleManualDeleted = () => {
        setShowManualModal(false);
        setEditEntryId(null);
        refreshJournalList();
    };

    const handleEditEntry = (entryId) => {
        setEditEntryId(entryId);
        setShowManualModal(true);
    };

    return (
        <div>
            {/* 페이지 헤더 */}
            <div className="mb-6 flex items-center justify-between">
                <div>
                    <h1 className="text-2xl font-bold text-stone-800">일반전표입력</h1>
                    <p className="text-sm text-stone-500 mt-1">계좌입출금 및 카드사용내역을 기반으로 분개 전표를 생성합니다</p>
                </div>
                <div className="flex items-center gap-2">
                    <button
                        onClick={() => { setEditEntryId(null); setShowManualModal(true); }}
                        className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-white bg-emerald-600 rounded-lg hover:bg-emerald-700 transition-colors"
                    >
                        <Plus className="w-4 h-4" />
                        전표 추가
                    </button>
                    <button
                        onClick={() => setShowSettingsModal(true)}
                        className="inline-flex items-center gap-1.5 px-3 py-2 text-sm font-medium text-stone-700 bg-white border border-stone-300 rounded-lg hover:bg-stone-50 transition-colors"
                    >
                        <Settings className="w-4 h-4" />
                        계정과목 설정
                    </button>
                </div>
            </div>

            <JournalEntryList
                accountCodes={accountCodes}
                tradingPartners={tradingPartners}
                onPartnerAdded={handlePartnerAdded}
                refreshKey={journalListRefreshKey}
                onEdit={handleEditEntry}
            />

            <AccountCodeSettingsModal
                isOpen={showSettingsModal}
                onClose={() => setShowSettingsModal(false)}
                onUpdate={fetchMasterData}
            />

            {showManualModal && (
                <ManualJournalModal
                    entryId={editEntryId}
                    accountCodes={accountCodes}
                    tradingPartners={tradingPartners}
                    onClose={() => { setShowManualModal(false); setEditEntryId(null); }}
                    onSaved={handleManualSaved}
                    onDeleted={handleManualDeleted}
                    onPartnerAdded={handlePartnerAdded}
                />
            )}
        </div>
    );
}

const root = ReactDOM.createRoot(document.getElementById('journal-entries-root'));
root.render(<App />);
</script>
@endverbatim
@endpush
