POS vista lista, modulo Tickets para gerencia con cancelacion auditada, y saldo a favor (recargas de anticipo desde expediente y agenda, canje en POS)
This commit is contained in:
@@ -1,9 +1,11 @@
|
||||
import type { FC } from 'react';
|
||||
import { useEffect, useState, useCallback, useRef, useMemo } from 'react';
|
||||
import { Clock, CheckCircle, XCircle, CheckCheck, UserCheck, UserX, Plus, PackageCheck, Sparkles, RotateCcw, LayoutGrid, List, Rows3, CalendarRange, CalendarClock, ChevronLeft, ChevronRight, Ban, Trash2, Phone, Cake, CreditCard, Pencil, AlertTriangle, Printer, PhoneCall } from 'lucide-react';
|
||||
import { Clock, CheckCircle, XCircle, CheckCheck, UserCheck, UserX, Plus, PackageCheck, Sparkles, RotateCcw, LayoutGrid, List, Rows3, CalendarRange, CalendarClock, ChevronLeft, ChevronRight, Ban, Trash2, Phone, Cake, CreditCard, Pencil, AlertTriangle, Printer, PhoneCall, Wallet } from 'lucide-react';
|
||||
import Layout from '../components/Layout';
|
||||
import { usePatientDrawer } from '../components/PatientDrawer';
|
||||
import AgendaPrint from '../components/AgendaPrint';
|
||||
import RecargarSaldoModal from '../components/RecargarSaldoModal';
|
||||
import ReciboPrint from '../components/ReciboPrint';
|
||||
import {
|
||||
Card,
|
||||
Button,
|
||||
@@ -20,7 +22,7 @@ import {
|
||||
badgeForAppointmentState,
|
||||
} from '../components/ui';
|
||||
import { useSearchParams, useNavigate } from 'react-router-dom';
|
||||
import { odooApi, type Appointment, type Patient, type Service, type Doctor, type Bloqueo } from '../services/odoo';
|
||||
import { odooApi, type Appointment, type Patient, type Service, type Doctor, type Bloqueo, type PatientWallet, type ReceiptData } from '../services/odoo';
|
||||
import { useAuth } from '../lib/auth';
|
||||
|
||||
const stateOptions = [
|
||||
@@ -790,8 +792,20 @@ const Agenda: FC = () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Saldo a favor del paciente de la cita abierta (para verlo al llegar)
|
||||
const [saldoPaciente, setSaldoPaciente] = useState<PatientWallet | null>(null);
|
||||
const [recargaAptOpen, setRecargaAptOpen] = useState(false);
|
||||
const [reciboApt, setReciboApt] = useState<ReceiptData | null>(null);
|
||||
|
||||
const openAptDetail = (apt: Appointment) => {
|
||||
setDetailApt(apt);
|
||||
setSaldoPaciente(null);
|
||||
setRecargaAptOpen(false);
|
||||
if (apt.patient_id) {
|
||||
odooApi.getPatientWallet(apt.patient_id)
|
||||
.then((w) => { if (w.status === 'success') setSaldoPaciente(w); })
|
||||
.catch(() => setSaldoPaciente(null));
|
||||
}
|
||||
setEditMode(false);
|
||||
setEditForm({
|
||||
date: apt.date,
|
||||
@@ -2503,6 +2517,24 @@ const Agenda: FC = () => {
|
||||
<p className="text-xs text-theme-muted mb-1">Precio</p>
|
||||
<p className="text-sm font-medium text-theme-heading">${detailApt.price}</p>
|
||||
</div>
|
||||
<div className="p-3 rounded-xl border border-emerald-200 bg-emerald-50/60 col-span-2 flex flex-wrap items-center justify-between gap-2">
|
||||
<div>
|
||||
<p className="text-xs text-theme-muted mb-1 flex items-center gap-1">
|
||||
<Wallet size={11} /> Saldo a favor del paciente
|
||||
</p>
|
||||
<p className="text-sm font-semibold text-theme-heading">
|
||||
${(saldoPaciente?.equivalent_mxn ?? 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 })}
|
||||
{saldoPaciente?.equivalent_usd != null && (
|
||||
<span className="ml-1.5 text-xs font-normal text-theme-muted">
|
||||
≈ {saldoPaciente.equivalent_usd.toLocaleString('en-US', { style: 'currency', currency: 'USD' })} USD
|
||||
</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Button size="sm" variant="outline" onClick={() => setRecargaAptOpen(true)}>
|
||||
<Wallet size={14} className="mr-1.5" /> Recargar saldo
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
{detailApt.notes && (
|
||||
<div className="p-3 bg-theme-bg rounded-xl">
|
||||
@@ -2630,6 +2662,26 @@ const Agenda: FC = () => {
|
||||
)}
|
||||
</Modal>
|
||||
|
||||
{/* Recarga de saldo a favor del paciente de la cita: se monta después del
|
||||
modal de detalle para quedar por encima (mismo z-index). */}
|
||||
<RecargarSaldoModal
|
||||
isOpen={recargaAptOpen}
|
||||
onClose={() => setRecargaAptOpen(false)}
|
||||
patientId={detailApt?.patient_id ?? null}
|
||||
patientName={detailApt?.patient}
|
||||
citaId={detailApt?.id ?? null}
|
||||
saldoActual={saldoPaciente?.equivalent_mxn ?? 0}
|
||||
onDone={(receipt) => {
|
||||
setReciboApt(receipt);
|
||||
if (detailApt?.patient_id) {
|
||||
odooApi.getPatientWallet(detailApt.patient_id)
|
||||
.then((w) => { if (w.status === 'success') setSaldoPaciente(w); })
|
||||
.catch(() => {});
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ReciboPrint receipt={reciboApt} onClose={() => setReciboApt(null)} />
|
||||
|
||||
{/* Modal de conflicto de agenda: mensaje humano + ver la cita que choca */}
|
||||
<Modal
|
||||
isOpen={!!conflict}
|
||||
|
||||
Reference in New Issue
Block a user