Agenda: colores de estado mas fuertes y boton de En curso

- Badge y bloques de la cuadricula: cada estado con su color propio y
  fuerte (pendiente=ambar, confirmada=esmeralda, llego/en cabina=cielo,
  en curso=violeta, completada=gris, cancelo/no show=rosa). Antes varios
  compartian tonos pastel casi iguales. La leyenda del grid usa los
  mismos colores.
- badgeForAppointmentState: llego e en_curso tenian variant de pendiente
  (se veian igual); ahora arrived y progress, y no_show/waitlist tienen
  las suyas.
- Boton 'Marcar en curso' (accion start del backend) en las acciones
  rapidas de la vista de lista y tarjetas moviles, entre Llegada y
  Completar.
- Mini-checklist del flujo en el tooltip de la cuadricula (solo lectura;
  click abre el detalle con el checklist completo interactivo).
This commit is contained in:
2026-09-29 23:11:58 +00:00
parent 5385fc3a53
commit 7b54e40104
3 changed files with 74 additions and 23 deletions

View File

@@ -13,7 +13,9 @@ export type BadgeVariant =
| 'warning'
| 'danger'
| 'success'
| 'primary';
| 'primary'
| 'arrived'
| 'progress';
interface BadgeProps {
children: ReactNode;
@@ -21,19 +23,24 @@ interface BadgeProps {
className?: string;
}
// Colores de estado fuertes y distinguibles entre sí (petición de la agenda):
// pendiente=ámbar, confirmada=esmeralda, llegó=cielo, en curso=violeta,
// completada=gris neutro, cancelada/no-show=rosa.
const variants: Record<BadgeVariant, string> = {
confirmed: 'bg-theme-success text-theme-success-text',
pending: 'bg-theme-accent-bg text-theme-heading',
done: 'bg-theme-lilac text-theme-heading',
cancelled: 'bg-rose-100 text-rose-700',
paid: 'bg-theme-success text-theme-success-text',
unpaid: 'bg-rose-100 text-rose-700',
confirmed: 'bg-emerald-200 text-emerald-900',
pending: 'bg-amber-100 text-amber-900',
done: 'bg-slate-200 text-slate-700',
cancelled: 'bg-rose-200 text-rose-900',
paid: 'bg-emerald-200 text-emerald-900',
unpaid: 'bg-rose-200 text-rose-900',
info: 'bg-theme-sky text-theme-heading',
default: 'bg-theme-accent-bg text-theme-muted',
primary: 'bg-theme-accent text-theme-inverse',
warning: 'bg-theme-warning text-theme-warning-text',
danger: 'bg-rose-100 text-rose-700',
success: 'bg-theme-success text-theme-success-text',
warning: 'bg-amber-200 text-amber-900',
danger: 'bg-rose-200 text-rose-900',
success: 'bg-emerald-200 text-emerald-900',
arrived: 'bg-sky-200 text-sky-900',
progress: 'bg-violet-300 text-violet-950',
};
const normalizeVariant = (variant: BadgeVariant): BadgeVariant => {
@@ -41,6 +48,7 @@ const normalizeVariant = (variant: BadgeVariant): BadgeVariant => {
if (variant === 'pending' || variant === 'warning') return variant;
if (variant === 'done' || variant === 'info') return variant;
if (variant === 'cancelled' || variant === 'danger' || variant === 'unpaid') return variant;
if (variant === 'arrived' || variant === 'progress') return variant;
return 'default';
};

View File

@@ -4,11 +4,19 @@ export const badgeForAppointmentState = (state: string): BadgeVariant => {
switch (state) {
case 'confirmed':
return 'confirmed';
case 'arrived':
return 'arrived';
case 'in_progress':
return 'progress';
case 'done':
case 'completed':
return 'done';
case 'cancelled':
return 'cancelled';
case 'no_show':
return 'danger';
case 'waitlist':
return 'info';
default:
return 'pending';
}

View File

@@ -1,6 +1,6 @@
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, Phone, Cake, CreditCard, Pencil, AlertTriangle, Printer, PhoneCall, Wallet, ChevronDown, CalendarOff, Users } from 'lucide-react';
import { Clock, CheckCircle, XCircle, CheckCheck, UserCheck, UserX, Plus, PackageCheck, Sparkles, RotateCcw, LayoutGrid, List, Rows3, CalendarRange, CalendarClock, ChevronLeft, ChevronRight, Ban, Phone, Cake, CreditCard, Pencil, AlertTriangle, Printer, PhoneCall, Wallet, ChevronDown, CalendarOff, Users, Play, Check } from 'lucide-react';
import Layout from '../components/Layout';
import { usePatientDrawer } from '../components/PatientDrawer';
import AgendaPrint from '../components/AgendaPrint';
@@ -9,7 +9,7 @@ import RecargarSaldoModal from '../components/RecargarSaldoModal';
import ReciboPrint from '../components/ReciboPrint';
import BloqueosModal, { quickCloseOptions, type BloqueoQuickClose } from '../components/agenda/BloqueosModal';
import { ListaEsperaPanel, ListaEsperaModal, whatsappLink } from '../components/agenda/ListaEspera';
import { EstadoChecklist } from '../components/agenda/EstadoChecklist';
import { EstadoChecklist, CHECKLIST_STEPS, stepIndex } from '../components/agenda/EstadoChecklist';
import {
Card,
Button,
@@ -625,12 +625,13 @@ const Agenda: FC = () => {
// Colores por estado siguiendo la paleta de la marca (coral/mint/sky/lilac).
// El color se lleva en el fondo; la barra lateral izquierda indica la categoría del servicio.
const gridStateClass: Record<string, string> = {
pending: 'bg-theme-accent-bg text-theme-heading',
confirmed: 'bg-brand-mint text-theme-heading',
arrived: 'bg-brand-sky text-theme-heading',
in_progress: 'bg-brand-lilac text-theme-heading',
done: 'bg-theme-bg text-theme-muted',
no_show: 'bg-rose-50 text-rose-700',
pending: 'bg-amber-100 text-amber-900',
confirmed: 'bg-emerald-200 text-emerald-900',
arrived: 'bg-sky-200 text-sky-900',
in_progress: 'bg-violet-300 text-violet-950',
done: 'bg-slate-200 text-slate-600',
no_show: 'bg-rose-200 text-rose-900',
cancelled: 'bg-rose-100 text-rose-700 line-through',
};
// Barra lateral por categoría del servicio (service_category ya viene del API)
@@ -667,11 +668,11 @@ const Agenda: FC = () => {
};
const gridStateLegend: { state: string; label: string; dot: string }[] = [
{ state: 'pending', label: 'Pendiente', dot: 'bg-brand-coral' },
{ state: 'confirmed', label: 'Confirmada', dot: 'bg-brand-mint border border-emerald-700' },
{ state: 'arrived', label: 'Llegó', dot: 'bg-brand-sky border border-sky-700' },
{ state: 'in_progress', label: 'En curso', dot: 'bg-brand-lilac border border-violet-600' },
{ state: 'done', label: 'Completada', dot: 'bg-theme-border-strong' },
{ state: 'pending', label: 'Pendiente', dot: 'bg-amber-300' },
{ state: 'confirmed', label: 'Confirmada', dot: 'bg-emerald-400' },
{ state: 'arrived', label: 'Llegó', dot: 'bg-sky-400' },
{ state: 'in_progress', label: 'En curso', dot: 'bg-violet-500' },
{ state: 'done', label: 'Completada', dot: 'bg-slate-400' },
];
const firstName = (full: string) => (full || '').trim().split(' ')[0] || '-';
@@ -1607,6 +1608,17 @@ const Agenda: FC = () => {
<UserCheck size={16} className="text-blue-600" />
</Button>
)}
{apt.state === 'arrived' && (
<Button
variant="ghost"
size="sm"
onClick={() => handleStatus(apt.id, 'start')}
disabled={updating === apt.id}
title="Marcar en curso"
>
<Play size={16} className="text-violet-600" />
</Button>
)}
{apt.state !== 'done' && apt.state !== 'cancelled' && (
<Button
variant="ghost"
@@ -2552,6 +2564,29 @@ const Agenda: FC = () => {
<p className="text-theme-muted">Tel: {tooltip.apt.phone || '—'}</p>
<p className="text-theme-muted">Médico: {tooltip.apt.doctor || 'Sin asignar'}</p>
<p className="mt-1 text-theme-muted">{confirmTitle(tooltip.apt)}</p>
{/* Mini-checklist del flujo (visual; el click abre el detalle con el checklist completo) */}
<div className="mt-2 pt-2 border-t border-theme-border flex items-center gap-1">
{CHECKLIST_STEPS.map((step) => {
const i = stepIndex(tooltip.apt.state);
const hecho = i >= 0 && stepIndex(step.state) <= i;
return (
<span
key={step.state}
title={step.label}
className={`inline-flex items-center gap-0.5 rounded-full px-1.5 py-0.5 text-[9px] font-medium ${
stepIndex(tooltip.apt.state) === stepIndex(step.state)
? 'bg-violet-300 text-violet-950'
: hecho
? 'bg-emerald-200 text-emerald-900'
: 'bg-theme-bg text-theme-muted'
}`}
>
{hecho && <Check size={9} />}
{step.label}
</span>
);
})}
</div>
</div>
)}