UX Pacientes: drawer de ficha clicable global, cumpleanos en dashboard/agenda, badges VIP/alergias/adeudo en ficha y POS
This commit is contained in:
@@ -5,6 +5,7 @@ import {
|
||||
Search, Plus, Minus, X, Store, AlertTriangle, ShoppingBag, Star, Printer, Wallet, Trash2, Package,
|
||||
} from 'lucide-react';
|
||||
import Layout from '../components/Layout';
|
||||
import { usePatientDrawer } from '../components/PatientDrawer';
|
||||
import {
|
||||
Card,
|
||||
Button,
|
||||
@@ -44,6 +45,7 @@ interface TicketLine {
|
||||
}
|
||||
|
||||
const Pos: FC = () => {
|
||||
const { openPatient } = usePatientDrawer();
|
||||
// Catálogo
|
||||
const [catMode, setCatMode] = useState<'servicios' | 'articulos'>('servicios');
|
||||
const [services, setServices] = useState<Service[]>([]);
|
||||
@@ -57,6 +59,7 @@ const Pos: FC = () => {
|
||||
const [lines, setLines] = useState<TicketLine[]>([]);
|
||||
const [discount, setDiscount] = useState('');
|
||||
const [paciente, setPaciente] = useState<Patient | null>(null);
|
||||
const [pacienteAdeudo, setPacienteAdeudo] = useState(0);
|
||||
const [pacienteQuery, setPacienteQuery] = useState('');
|
||||
const [pacienteResults, setPacienteResults] = useState<Patient[]>([]);
|
||||
const [altaRapida, setAltaRapida] = useState(false);
|
||||
@@ -310,28 +313,71 @@ const Pos: FC = () => {
|
||||
/>
|
||||
);
|
||||
|
||||
// Al seleccionar paciente: traer su adeudo para el badge de alerta en caja
|
||||
const selectPaciente = (p: Patient) => {
|
||||
setPaciente(p);
|
||||
setPacienteResults([]);
|
||||
setPacienteAdeudo(0);
|
||||
odooApi.getPatientHistory(p.id)
|
||||
.then((res) => { if (res.status === 'success') setPacienteAdeudo(res.estado_cuenta?.adeudo ?? 0); })
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
const nuevaVenta = () => {
|
||||
setResultado(null);
|
||||
setLines([]);
|
||||
setDiscount('');
|
||||
setPaciente(null);
|
||||
setPacienteAdeudo(0);
|
||||
setPacienteQuery('');
|
||||
setDoctorId('');
|
||||
};
|
||||
|
||||
// ---- Bloques del ticket (desktop sticky y modal móvil) ----
|
||||
const pacienteBlock = paciente ? (
|
||||
<div className="flex items-center justify-between p-2.5 bg-theme-bg rounded-xl">
|
||||
<div className="min-w-0">
|
||||
<p className="text-sm font-medium text-theme-heading truncate">{paciente.name}</p>
|
||||
<p className="text-xs text-theme-muted flex items-center gap-1.5">
|
||||
{paciente.phone}
|
||||
{puntosPaciente > 0 && (
|
||||
<span className="inline-flex items-center gap-0.5"><Wallet size={11} /> {puntosPaciente} pts</span>
|
||||
)}
|
||||
</p>
|
||||
<div className="p-2.5 bg-theme-bg rounded-xl space-y-1.5">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="min-w-0">
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => openPatient(paciente.id)}
|
||||
className="text-sm font-medium text-theme-heading truncate hover:underline hover:text-theme-accent"
|
||||
title="Abrir ficha del paciente"
|
||||
>
|
||||
{paciente.name}
|
||||
</button>
|
||||
<p className="text-xs text-theme-muted flex items-center gap-1.5">
|
||||
{paciente.phone}
|
||||
{puntosPaciente > 0 && (
|
||||
<span className="inline-flex items-center gap-0.5"><Wallet size={11} /> {puntosPaciente} pts</span>
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => { setPaciente(null); setPacienteAdeudo(0); setPacienteQuery(''); }}>Cambiar</Button>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" onClick={() => { setPaciente(null); setPacienteQuery(''); }}>Cambiar</Button>
|
||||
{/* Alertas para caja antes de cobrar: VIP / Alergias / Adeudo */}
|
||||
{(paciente.is_vip || paciente.allergies || pacienteAdeudo > 0) && (
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{paciente.is_vip && (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-theme-accent bg-theme-accent-bg px-2 py-0.5 text-[11px] font-medium text-theme-heading">
|
||||
<Star size={11} /> VIP
|
||||
</span>
|
||||
)}
|
||||
{paciente.allergies && (
|
||||
<span
|
||||
className="inline-flex items-center gap-1 rounded-full border border-rose-300 bg-rose-50 px-2 py-0.5 text-[11px] font-medium text-rose-700"
|
||||
title={`Alergias: ${paciente.allergies}`}
|
||||
>
|
||||
<AlertTriangle size={11} /> Alergias
|
||||
</span>
|
||||
)}
|
||||
{pacienteAdeudo > 0 && (
|
||||
<span className="inline-flex items-center gap-1 rounded-full border border-amber-300 bg-amber-50 px-2 py-0.5 text-[11px] font-medium text-amber-700">
|
||||
Adeudo {fmtMoney(pacienteAdeudo)}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) : altaRapida ? (
|
||||
<div className="p-2.5 bg-theme-bg rounded-xl space-y-2">
|
||||
@@ -357,7 +403,7 @@ const Pos: FC = () => {
|
||||
<button
|
||||
key={p.id}
|
||||
type="button"
|
||||
onClick={() => { setPaciente(p); setPacienteResults([]); }}
|
||||
onClick={() => selectPaciente(p)}
|
||||
className="w-full text-left px-3 py-2 text-sm hover:bg-theme-bg transition"
|
||||
>
|
||||
<span className="font-medium text-theme-heading">{p.name}</span>
|
||||
|
||||
Reference in New Issue
Block a user