// Kassa: Records, Debts. Plus Reports. — Wired to real API. // LOKAL sana (UTC emas) — hisobot backend'da klinika mahalliy kuni sifatida talqin qilinadi. // toISOString() UTC beradi: mahalliy 00:00–05:00 da kechagi kun chiqib, hisobot noto'g'ri kunni olardi. function localISO(d) { const pad = n => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; } function todayISO() { return localISO(new Date()); } function daysAgoISO(n) { const d = new Date(); d.setDate(d.getDate() - n); return localISO(d); } function KassaPage({ onNav, toast }) { const [dateRange, setDateRange] = useState('today'); const [doctorFilter, setDoctorFilter] = useState([]); const [statusFilter, setStatusFilter] = useState([]); const [page, setPage] = useState(1); const pageSize = 12; const [visits, setVisits] = useState([]); const [doctors, setDoctors] = useState([]); const [summary, setSummary] = useState(null); const [loading, setLoading] = useState(true); const queryArgs = useMemo(() => { const t = new Date(); let date_from, date_to; const todayStart = new Date(t); todayStart.setHours(0,0,0,0); if (dateRange === 'today') { date_from = todayStart.toISOString(); } else if (dateRange === 'yesterday') { const ys = new Date(todayStart); ys.setDate(ys.getDate() - 1); const ye = new Date(todayStart); date_from = ys.toISOString(); date_to = ye.toISOString(); } else if (dateRange === 'week') { const ws = new Date(todayStart); ws.setDate(ws.getDate() - 7); date_from = ws.toISOString(); } else if (dateRange === 'month') { const ms = new Date(todayStart); ms.setMonth(ms.getMonth() - 1); date_from = ms.toISOString(); } const doctor_id = doctorFilter[0] || null; const payment_status = statusFilter.length === 1 ? statusFilter[0] : null; return { date_from, date_to, doctor_id, payment_status }; }, [dateRange, doctorFilter, statusFilter]); useEffect(() => { let mounted = true; window.API.listDoctors().then(d => { if (mounted) setDoctors(d.map(window.normalizeDoctor)); }); return () => { mounted = false; }; }, []); useEffect(() => { let mounted = true; setLoading(true); Promise.all([ window.API.listKassa({ ...queryArgs, limit: 500 }), window.API.kassaSummary(queryArgs), ]).then(([v, s]) => { if (!mounted) return; // multi-status filter — client side let filtered = v; if (statusFilter.length > 1) { filtered = v.filter(x => statusFilter.includes(x.payment_status)); } setVisits(filtered); setSummary(s); }).catch(err => toast({ kind: 'danger', title: 'Yuklash xato', msg: err.message })) .finally(() => mounted && setLoading(false)); return () => { mounted = false; }; }, [queryArgs, statusFilter, toast]); const totals = { total: summary?.total_amount ?? 0, paid: summary?.paid_amount ?? 0, debt: summary?.debt_amount ?? 0, count: summary?.visits_count ?? visits.length, }; const paged = visits.slice((page - 1) * pageSize, page * pageSize); const handleExcelDownload = async () => { try { if (dateRange === 'today') { await window.API.downloadDaily(todayISO()); } else { // period const from = queryArgs.date_from ? queryArgs.date_from.slice(0, 10) : daysAgoISO(7); const to = todayISO(); await window.API.downloadPeriod(from, to); } toast({ kind: 'success', title: 'Hisobot yuklab olindi' }); } catch (err) { toast({ kind: 'danger', title: 'Excel xato', msg: err.message }); } }; return (
} /> {/* Filters */}
Davr
Holat
Shifokor
{/* Totals */}
{/* Table */} {!loading && paged.length === 0 ? ( ) : ( {paged.map(v => ( onNav('patient-detail', { patientId: v.patient.id })}> ))}
Sana Bemor Shifokor Jami To'lov Holat
{uzDateTime(v.created_at)} {v.patient.full_name} {v.doctor?.full_name || } {uzMoneyShort(v.total_amount)}
)} {paged.length > 0 && }
); } /* ========================================================= DEBTS ========================================================= */ function DebtsPage({ onNav, toast }) { const [debts, setDebts] = useState([]); const [paymentModal, setPaymentModal] = useState(null); const [loading, setLoading] = useState(true); const load = () => { setLoading(true); window.API.listDebts({ limit: 500 }) .then(d => setDebts(d)) .catch(err => toast({ kind: 'danger', title: 'Xato', msg: err.message })) .finally(() => setLoading(false)); }; useEffect(() => { load(); }, []); const totalDebt = debts.reduce((s, v) => s + (Number(v.total_amount) - Number(v.paid_amount)), 0); const handlePayment = async (amount, method) => { try { await window.API.addPayment(paymentModal.id, { amount, method }); toast({ kind: 'success', title: "To'lov qabul qilindi", msg: `${uzMoney(amount)} — ${paymentModal.patient.full_name}` }); setPaymentModal(null); load(); } catch (err) { toast({ kind: 'danger', title: 'Xato', msg: err.message }); } }; return (
{debts.length > 0 && (
{debts.length} ta bemorda qarz mavjud
Jami summa: {uzMoney(totalDebt)}
)} {!loading && debts.length === 0 ? ( ) : ( {debts.map(v => { const debt = Number(v.total_amount) - Number(v.paid_amount); const daysAgo = Math.floor((Date.now() - new Date(v.created_at).getTime()) / 86400000); return ( ); })}
Bemor Telefon Sana Jami To'langan Qarz Vaqt
{v.patient.full_name}
{v.patient.phone || '—'} {uzDate(v.created_at)} {uzMoneyShort(v.total_amount)} {uzMoneyShort(v.paid_amount)} {uzMoneyShort(debt)} 7 ? 'danger' : daysAgo > 3 ? 'warning' : 'neutral'}>{daysAgo > 0 ? `${daysAgo} kun` : 'Bugun'} e.stopPropagation()}>
)}
{paymentModal && ( setPaymentModal(null)} onSubmit={handlePayment} /> )}
); } function PaymentModal({ visit, onClose, onSubmit }) { const debt = Number(visit._debt ?? (Number(visit.total_amount) - Number(visit.paid_amount))); const [amount, setAmount] = useState(debt); const [type, setType] = useState('cash'); const [submitting, setSubmitting] = useState(false); const valid = amount > 0 && amount <= debt; const errorMsg = amount <= 0 ? "Summa noldan katta bo'lishi kerak" : amount > debt ? `Qoldiq qarzdan (${uzMoneyShort(debt)}) oshmasligi kerak` : null; const handle = async () => { if (!valid) return; setSubmitting(true); try { await onSubmit(amount, type); } finally { setSubmitting(false); } }; return ( }>
Bemor
{visit.patient.full_name}
Jami
{uzMoneyShort(visit.total_amount)}
To'landi
{uzMoneyShort(visit.paid_amount)}
Qarz
{uzMoneyShort(debt)}
setAmount(parseInt(e.target.value.replace(/\D/g,'')) || 0)} /> {errorMsg && (
{errorMsg}
)}
To'lov turi
{[{id:'cash',l:'Naqd',i:'money'},{id:'card',l:'Karta',i:'credit-card'},{id:'transfer',l:"O'tkazma",i:'arrows-left-right'}].map(t => ( ))}
); } /* ========================================================= REPORTS ========================================================= */ function ReportsPage({ toast }) { const [tab, setTab] = useState('daily'); const [dailyDate, setDailyDate] = useState(todayISO()); const [from, setFrom] = useState(daysAgoISO(7)); const [to, setTo] = useState(todayISO()); const [downloading, setDownloading] = useState(false); const downloadDaily = async () => { setDownloading(true); try { await window.API.downloadDaily(dailyDate); toast({ kind: 'success', title: 'Yuklab olindi' }); } catch (err) { toast({ kind: 'danger', title: 'Xato', msg: err.message }); } finally { setDownloading(false); } }; const downloadPeriod = async () => { setDownloading(true); try { await window.API.downloadPeriod(from, to); toast({ kind: 'success', title: 'Yuklab olindi' }); } catch (err) { toast({ kind: 'danger', title: 'Xato', msg: err.message }); } finally { setDownloading(false); } }; return (
{tab === 'daily' && (
Kunlik kassa hisobot

Bir kunlik barcha vizitlar, xizmatlar, to'lovlar va qarz jadval bilan Excel'ga yuklanadi.

setDailyDate(e.target.value)} style={{ width: 200 }} />
)} {tab === 'period' && (
Davriy hisobot

Tanlangan davr uchun: umumiy ro'yxat, xizmat bo'yicha, shifokor bo'yicha, to'lov turi bo'yicha alohida sahifalar.

setFrom(e.target.value)} style={{ width: 180 }} /> setTo(e.target.value)} style={{ width: 180 }} />
)}
); } Object.assign(window, { KassaPage, DebtsPage, PaymentModal, ReportsPage });