fase 2.3: portal publico de reservas en linea (5 endpoints anonimos con rate limit y token HMAC, wizard movil, gestion/cancelacion por enlace, distintivo en agenda, editor y enlace publico en Configuracion); apagado por defecto
This commit is contained in:
@@ -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, Play, Check } 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, Globe } from 'lucide-react';
|
||||
import Layout from '../components/Layout';
|
||||
import { usePatientDrawer } from '../components/PatientDrawer';
|
||||
import AgendaPrint from '../components/AgendaPrint';
|
||||
@@ -399,6 +399,12 @@ const Agenda: FC = () => {
|
||||
<Cake size={size} className="inline shrink-0 text-pink-500" aria-label="Cumpleaños del paciente" />
|
||||
) : null;
|
||||
|
||||
// Distintivo de origen: la cita se agendó desde el portal público de reservas.
|
||||
const portalIcon = (apt: Appointment, size = 11) =>
|
||||
apt.booking_source === 'public_portal' ? (
|
||||
<Globe size={size} className="inline shrink-0 text-sky-600" aria-label="Reservada en línea por el cliente" />
|
||||
) : null;
|
||||
|
||||
const confirmTitle = (apt: Appointment): string => {
|
||||
if (['arrived', 'in_progress', 'done'].includes(apt.state)) return '2da confirmación: llegó a la clínica';
|
||||
if (apt.phone_confirmed) {
|
||||
@@ -2088,7 +2094,7 @@ const Agenda: FC = () => {
|
||||
onClick={() => openAptDetail(apt)}
|
||||
className="rounded-xl border border-sky-700 bg-theme-surface px-3 py-2 text-left shadow-card transition-shadow hover:shadow-soft"
|
||||
>
|
||||
<p className="text-sm font-semibold text-theme-heading">{apt.time} · {apt.patient}</p>
|
||||
<p className="text-sm font-semibold text-theme-heading">{portalIcon(apt)} {apt.time} · {apt.patient}</p>
|
||||
<p className="text-xs text-theme-muted truncate max-w-[240px]">
|
||||
{apt.service}{!isMedico && apt.doctor ? ` · ${apt.doctor}` : ''}
|
||||
</p>
|
||||
@@ -2230,6 +2236,7 @@ const Agenda: FC = () => {
|
||||
<div className="font-semibold truncate">
|
||||
<span title={confirmTitle(apt)} className="mr-1 align-middle">{confirmIndicator(apt)}</span>
|
||||
{apt.patient_birthday && <span title="Cumpleaños del paciente" className="mr-0.5 align-middle">{birthdayIcon(apt)}</span>}
|
||||
{portalIcon(apt) && <span title="Reservada en línea" className="mr-0.5 align-middle">{portalIcon(apt)}</span>}
|
||||
{apt.time} · {apt.patient}
|
||||
</div>
|
||||
<div className="truncate opacity-75">
|
||||
@@ -2365,6 +2372,7 @@ const Agenda: FC = () => {
|
||||
<div className="font-semibold truncate">
|
||||
<span title={confirmTitle(apt)} className="mr-0.5 align-middle">{confirmIndicator(apt, 10)}</span>
|
||||
{apt.patient_birthday && <span title="Cumpleaños del paciente" className="mr-0.5 align-middle">{birthdayIcon(apt, 10)}</span>}
|
||||
{portalIcon(apt, 10) && <span title="Reservada en línea" className="mr-0.5 align-middle">{portalIcon(apt, 10)}</span>}
|
||||
{apt.time} · {firstName(apt.patient)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -2483,6 +2491,7 @@ const Agenda: FC = () => {
|
||||
>
|
||||
<span className="mr-0.5 align-middle">{confirmIndicator(apt, 10)}</span>
|
||||
{apt.patient_birthday && <span title="Cumpleaños del paciente" className="mr-0.5 align-middle">{birthdayIcon(apt, 10)}</span>}
|
||||
{portalIcon(apt, 10) && <span title="Reservada en línea" className="mr-0.5 align-middle">{portalIcon(apt, 10)}</span>}
|
||||
<span className="font-semibold">{apt.time}</span> {firstName(apt.patient)}
|
||||
</div>
|
||||
))
|
||||
@@ -2560,6 +2569,7 @@ const Agenda: FC = () => {
|
||||
title="Abrir ficha del paciente"
|
||||
>
|
||||
{apt.patient_birthday && <span title="Cumpleaños del paciente" className="mr-1 align-middle">{birthdayIcon(apt, 12)}</span>}
|
||||
{portalIcon(apt, 12) && <span title="Reservada en línea" className="mr-1 align-middle">{portalIcon(apt, 12)}</span>}
|
||||
{apt.patient}
|
||||
</button>
|
||||
</td>
|
||||
|
||||
Reference in New Issue
Block a user