Files
scoreodonto.com/frontend/components/AgendaDetailModal.tsx
T
VPS 4 Builder aae881fa6d feat(agenda): import como migracao (status importado) + confirmar/vincular paciente - Fase 2
- import entra como status 'importado' (provisorio): separa titulo NOME x PROCEDIMENTO, traz description do Google em observacoes, nao pontua
- 'importado' no CHECK de status
- falta bloqueada sem paciente vinculado / importado
- POST /agendamentos/:id/confirmar: vincula paciente, EXIGE cpf+telefone (enriquece cadastro), vira agendado
- pendencia 'a reagendar' so com paciente vinculado
- frontend: modal do importado mostra 'A CONFIRMAR' + Confirmar paciente (busca com desambiguacao homonimos: cpf/tel/nascimento + criar novo) + Remover; ScoreBadge

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 17:59:37 +02:00

411 lines
24 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import React, { useEffect, useState } from 'react';
import { StackModal, StackPage, useStackModal } from './StackModal.tsx';
import { HybridBackend } from '../services/backend.ts';
import { useToast } from '../contexts/ToastContext.tsx';
import {
User, Stethoscope, Clock, CalendarRange, AlertCircle, CheckCircle2,
History, RotateCcw, CalendarClock, Ban, Users, Pencil, ClipboardCheck, ShieldAlert,
Search, UserPlus, Link2, Trash2
} from 'lucide-react';
interface Props {
isOpen: boolean;
onClose: () => void;
appointment: any | null;
dentists: any[] | null;
onChanged: () => void; // recarrega a agenda
onEditar: () => void; // abre o modal de edição/reagendar (mover de horário)
onAtender?: () => void;
podeAtender?: boolean;
}
// Cache simples na sessão do modal (evita refetch ao voltar/avançar).
const _cache: Record<string, { faltas?: number; familia?: { grupo: any; membros: any[] }; score?: { faltas: number; remarcacoes: number; nivel: string } }> = {};
const low = (s: any) => String(s || '').toLowerCase();
const STATUS_LABEL: Record<string, string> = {
importado: 'A CONFIRMAR · GOOGLE',
agendado: 'AGENDADO', confirmado: 'CONFIRMADO', reagendado: 'REAGENDADO',
remarcar: 'A REAGENDAR', cancelado: 'CANCELADO', falta: 'FALTOU', atendido: 'ATENDIDO',
pendente: 'PENDENTE', concluido: 'CONCLUÍDO', faltou: 'FALTOU',
};
const STATUS_COLOR: Record<string, string> = {
importado: 'bg-violet-50 text-violet-700',
cancelado: 'bg-red-50 text-red-600', falta: 'bg-red-50 text-red-600', faltou: 'bg-red-50 text-red-600',
remarcar: 'bg-amber-50 text-amber-600', confirmado: 'bg-emerald-50 text-emerald-600',
atendido: 'bg-emerald-50 text-emerald-600', concluido: 'bg-emerald-50 text-emerald-600',
};
export const FamiliaChips: React.FC<{ pacienteId: string; onPick?: (m: any) => void }> = ({ pacienteId, onPick }) => {
const [membros, setMembros] = useState<any[] | null>(_cache[pacienteId]?.familia?.membros ?? null);
useEffect(() => {
if (_cache[pacienteId]?.familia) { setMembros(_cache[pacienteId].familia!.membros); return; }
let alive = true;
HybridBackend.getFamiliaPaciente(pacienteId).then(f => {
_cache[pacienteId] = { ..._cache[pacienteId], familia: f };
if (alive) setMembros(f.membros);
}).catch(() => alive && setMembros([]));
return () => { alive = false; };
}, [pacienteId]);
if (!membros || membros.length <= 1) return null;
return (
<div className="space-y-1.5">
<label className="text-[9px] font-black text-gray-400 uppercase tracking-widest flex items-center gap-1"><Users size={11} /> Grupo familiar</label>
<div className="flex gap-1.5 overflow-x-auto custom-scrollbar pb-1 -mx-0.5 px-0.5">
{membros.map(m => {
const inner = (<>{m.nome}{m.grupofamiliarrelacao ? <span className="text-indigo-400">· {m.grupofamiliarrelacao}</span> : null}</>);
const cls = 'shrink-0 inline-flex items-center gap-1 bg-indigo-50 text-indigo-700 rounded-full px-2.5 py-1 text-[10px] font-bold uppercase whitespace-nowrap';
return onPick
? <button type="button" key={m.id} onClick={() => onPick(m)} className={cls + ' hover:bg-indigo-100 transition-colors'}>{inner}</button>
: <span key={m.id} className={cls}>{inner}</span>;
})}
</div>
</div>
);
};
export const FaltasBadge: React.FC<{ pacienteId: string }> = ({ pacienteId }) => {
const [n, setN] = useState<number | null>(_cache[pacienteId]?.faltas ?? null);
useEffect(() => {
if (typeof _cache[pacienteId]?.faltas === 'number') { setN(_cache[pacienteId].faltas!); return; }
let alive = true;
HybridBackend.getFaltasPaciente(pacienteId).then(v => {
_cache[pacienteId] = { ..._cache[pacienteId], faltas: v };
if (alive) setN(v);
}).catch(() => alive && setN(0));
return () => { alive = false; };
}, [pacienteId]);
if (!n) return null;
return (
<span className="inline-flex items-center gap-1 bg-red-50 text-red-600 rounded-full px-2.5 py-1 text-[10px] font-black uppercase">
<ShieldAlert size={12} /> {n} falta{n > 1 ? 's' : ''}
</span>
);
};
// Selo de SCORE do paciente: nível (ATENÇÃO/RISCO) + contadores (faltas · remarcações).
// Não aparece quando o paciente está limpo (0 faltas e 0 remarcações).
export const ScoreBadge: React.FC<{ pacienteId: string }> = ({ pacienteId }) => {
const [sc, setSc] = useState<{ faltas: number; remarcacoes: number; nivel: string } | null>(_cache[pacienteId]?.score ?? null);
useEffect(() => {
if (_cache[pacienteId]?.score) { setSc(_cache[pacienteId].score!); return; }
let alive = true;
HybridBackend.getScorePaciente(pacienteId).then(s => {
_cache[pacienteId] = { ..._cache[pacienteId], score: s };
if (alive) setSc(s);
}).catch(() => { });
return () => { alive = false; };
}, [pacienteId]);
if (!sc || (sc.faltas === 0 && sc.remarcacoes === 0)) return null;
const cor = sc.nivel === 'RISCO' ? 'bg-red-50 text-red-700' : 'bg-amber-50 text-amber-700';
const label = sc.nivel === 'RISCO' ? 'RISCO' : 'ATENÇÃO';
const parts: string[] = [];
if (sc.faltas) parts.push(`${sc.faltas} falta${sc.faltas > 1 ? 's' : ''}`);
if (sc.remarcacoes) parts.push(`${sc.remarcacoes} remarc.`);
return (
<span className={`inline-flex items-center gap-1 rounded-full px-2.5 py-1 text-[10px] font-black uppercase ${cor}`}>
<ShieldAlert size={12} /> {label} · {parts.join(' · ')}
</span>
);
};
const HistoryPage: React.FC<{ appointmentId: string }> = ({ appointmentId }) => {
const [hist, setHist] = useState<any[] | null>(null);
useEffect(() => {
let alive = true;
HybridBackend.getHistoricoAgendamento(appointmentId).then(h => alive && setHist(h)).catch(() => alive && setHist([]));
return () => { alive = false; };
}, [appointmentId]);
if (hist === null) return <p className="text-xs text-gray-400 font-bold uppercase py-8 text-center">CARREGANDO...</p>;
if (!hist.length) return <p className="text-xs text-gray-400 font-bold uppercase py-8 text-center">SEM HISTÓRICO.</p>;
const ACAO: Record<string, string> = { criou: 'criou', reagendou: 'reagendou', editou: 'editou', cancelou: 'cancelou', faltou: 'marcou falta', remarcou: 'pediu remarcação', restaurou: 'restaurou' };
return (
<div className="space-y-3">
{hist.map((h, i) => (
<div key={i} className="flex gap-3">
<div className="flex flex-col items-center">
<div className="w-2 h-2 rounded-full bg-blue-500 mt-1.5" />
{i < hist.length - 1 && <div className="w-px flex-1 bg-gray-200 my-1" />}
</div>
<div className="pb-2 flex-1">
<p className="text-xs font-bold text-gray-800">
<span className="text-blue-600">{h.actor_nome || 'Alguém'}</span> {ACAO[h.acao] || h.acao}
</p>
<p className="text-[10px] text-gray-400 font-medium">{new Date(h.at).toLocaleString('pt-BR')}</p>
{h.detalhes?.de && h.detalhes?.para && (
<p className="text-[10px] text-gray-500 mt-0.5">
{new Date(h.detalhes.de.start).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })}
{' → '}
{new Date(h.detalhes.para.start).toLocaleString('pt-BR', { day: '2-digit', month: '2-digit', hour: '2-digit', minute: '2-digit' })}
</p>
)}
</div>
</div>
))}
</div>
);
};
// Página de CONFIRMAÇÃO de um importado: busca/desambigua paciente, exige CPF+telefone, vincula.
const ConfirmImportadoPage: React.FC<{ ag: any; onConfirmed: () => void }> = ({ ag, onConfirmed }) => {
const { close } = useStackModal();
const toast = useToast();
const [q, setQ] = useState('');
const [results, setResults] = useState<any[]>([]);
const [sel, setSel] = useState<any | null>(null);
const [novo, setNovo] = useState(false);
const [nome, setNome] = useState(ag.pacienteNome || ag.pacientenome || '');
const [cpf, setCpf] = useState('');
const [tel, setTel] = useState('');
const [proc, setProc] = useState(ag.procedimento || '');
const [saving, setSaving] = useState(false);
useEffect(() => {
let alive = true;
const t = setTimeout(() => {
HybridBackend.getPacientes(q, 'recentes').then(r => alive && setResults((r.data || []).slice(0, 8))).catch(() => { });
}, 300);
return () => { alive = false; clearTimeout(t); };
}, [q]);
const pick = (pac: any) => { setSel(pac); setNovo(false); setCpf(pac.cpf || ''); setTel(pac.telefone || ''); };
const iniciarNovo = () => { setNovo(true); setSel(null); setCpf(''); setTel(''); };
const confirmar = async () => {
if (!cpf.trim() || !tel.trim()) { toast.error('CPF E TELEFONE SÃO OBRIGATÓRIOS.'); return; }
setSaving(true);
try {
let pacienteId = sel?.id;
if (novo) {
if (!nome.trim()) { toast.error('INFORME O NOME.'); setSaving(false); return; }
pacienteId = `p_${Date.now()}`;
await HybridBackend.savePaciente({ id: pacienteId, nome, cpf, telefone: tel } as any);
}
if (!pacienteId) { toast.error('SELECIONE UM PACIENTE OU CRIE UM NOVO.'); setSaving(false); return; }
const res = await HybridBackend.confirmarAgendamento(ag.id, { pacienteId, cpf, telefone: tel, procedimento: proc || undefined });
if (!res.success) { toast.error(res.message || 'ERRO AO CONFIRMAR.'); return; }
toast.success('AGENDAMENTO CONFIRMADO!');
onConfirmed(); close();
} catch { toast.error('ERRO AO CONFIRMAR.'); } finally { setSaving(false); }
};
const faltaDados = !cpf.trim() || !tel.trim();
return (
<div className="space-y-3">
<p className="text-[11px] text-gray-500 leading-snug">Vincule o paciente do cadastro. Se houver homônimos, use CPF/telefone/nascimento para identificar. CPF e telefone são obrigatórios.</p>
{!novo && (
<>
<div className="relative">
<Search size={15} className="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400" />
<input autoFocus value={q} onChange={e => setQ(e.target.value)} placeholder="Buscar por nome, CPF ou telefone..."
className="w-full pl-9 pr-3 py-2.5 border border-gray-200 rounded-xl text-sm focus:outline-none focus:border-blue-400" />
</div>
<div className="max-h-52 overflow-y-auto custom-scrollbar space-y-1.5">
{results.map(pac => (
<button key={pac.id} onClick={() => pick(pac)}
className={`w-full text-left p-2.5 rounded-xl border transition-colors ${sel?.id === pac.id ? 'border-blue-400 bg-blue-50' : 'border-gray-100 hover:bg-gray-50'}`}>
<p className="text-xs font-black text-gray-800 uppercase truncate">{pac.nome}</p>
<p className="text-[10px] text-gray-400 font-bold">
{pac.cpf ? `CPF ${pac.cpf}` : 'sem CPF'} · {pac.telefone || 'sem tel'}{pac.datanascimento ? ` · ${pac.datanascimento}` : ''}
</p>
</button>
))}
{q && results.length === 0 && <p className="text-[11px] text-gray-400 text-center py-3 uppercase font-bold">Nenhum paciente encontrado.</p>}
</div>
<button onClick={iniciarNovo} className="w-full flex items-center justify-center gap-1.5 py-2.5 rounded-xl border-2 border-dashed border-gray-200 text-[11px] font-black uppercase text-gray-500 hover:border-blue-300 hover:text-blue-600 transition-colors">
<UserPlus size={15} /> Criar novo paciente
</button>
</>
)}
{novo && (
<div className="space-y-2">
<label className="text-[10px] font-black text-gray-500 uppercase">Novo paciente</label>
<input value={nome} onChange={e => setNome(e.target.value.toUpperCase())} placeholder="NOME COMPLETO"
className="w-full px-3 py-2.5 border border-gray-200 rounded-xl text-sm font-bold focus:outline-none focus:border-blue-400" />
<button onClick={() => setNovo(false)} className="text-[10px] font-black text-blue-600 uppercase"> voltar à busca</button>
</div>
)}
{(sel || novo) && (
<div className="bg-gray-50 rounded-xl p-3 space-y-2">
{sel && <p className="text-xs font-black text-gray-800 uppercase">{sel.nome}</p>}
<div className="grid grid-cols-2 gap-2">
<div>
<label className="text-[9px] font-black text-gray-400 uppercase">CPF *</label>
<input value={cpf} onChange={e => setCpf(e.target.value)} placeholder="000.000.000-00"
className={`w-full px-2.5 py-2 border rounded-lg text-xs font-bold focus:outline-none ${!cpf.trim() ? 'border-amber-300 bg-amber-50' : 'border-gray-200 focus:border-blue-400'}`} />
</div>
<div>
<label className="text-[9px] font-black text-gray-400 uppercase">Telefone *</label>
<input value={tel} onChange={e => setTel(e.target.value)} placeholder="(00) 00000-0000"
className={`w-full px-2.5 py-2 border rounded-lg text-xs font-bold focus:outline-none ${!tel.trim() ? 'border-amber-300 bg-amber-50' : 'border-gray-200 focus:border-blue-400'}`} />
</div>
</div>
<div>
<label className="text-[9px] font-black text-gray-400 uppercase">Procedimento</label>
<input value={proc} onChange={e => setProc(e.target.value.toUpperCase())} placeholder="EX: AVALIAÇÃO"
className="w-full px-2.5 py-2 border border-gray-200 rounded-lg text-xs font-bold focus:outline-none focus:border-blue-400" />
</div>
{faltaDados && <p className="text-[10px] text-amber-600 font-bold">Preencha CPF e telefone para confirmar.</p>}
<button onClick={confirmar} disabled={saving || faltaDados}
className="w-full py-2.5 bg-emerald-600 text-white rounded-xl text-[11px] font-black uppercase hover:bg-emerald-700 transition-all disabled:opacity-50 flex items-center justify-center gap-1.5">
<Link2 size={15} /> {saving ? 'CONFIRMANDO...' : 'Confirmar e vincular'}
</button>
</div>
)}
</div>
);
};
const DetailPage: React.FC<Props> = (p) => {
const { push, close } = useStackModal();
const toast = useToast();
const ag = p.appointment || {};
const status = low(ag.status);
const provisorio = status === 'importado';
const inativo = ['cancelado', 'falta', 'remarcar', 'faltou'].includes(status);
const pacienteId = ag.pacienteid || ag.pacienteId || '';
const dentNome = ag.dentistaNome || p.dentists?.find(d => d.id === (ag.dentistaId || ag.dentistaid))?.nome || '—';
const dentCor = p.dentists?.find(d => d.id === (ag.dentistaId || ag.dentistaid))?.corAgenda;
const run = async (fn: () => Promise<any>, ok: string) => {
try { await fn(); toast.success(ok); p.onChanged(); close(); }
catch (e: any) { toast.error(e?.message || 'ERRO NA OPERAÇÃO.'); }
};
const nome = ag.pacienteNome || ag.pacientenome || '';
// Descrição = observações OU o texto longo (ex.: títulos dos eventos importados do Google).
const descricao = ag.observacoes || (nome.length > 40 ? nome : '');
return (
<div className="space-y-3">
<div className="flex items-start gap-2.5">
<div className="p-2.5 bg-blue-50 rounded-xl shrink-0"><User size={20} className="text-blue-600" /></div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-black text-gray-900 leading-tight uppercase tracking-tight truncate">{nome}</h3>
<div className="flex items-center gap-2 flex-wrap mt-1">
<span className={`text-[10px] font-black uppercase px-2 py-0.5 rounded-full ${STATUS_COLOR[status] || 'bg-gray-100 text-gray-600'}`}>{STATUS_LABEL[status] || status || '—'}</span>
{pacienteId ? <ScoreBadge pacienteId={pacienteId} /> : null}
{ag.procedimento ? (
<span className="inline-flex items-center gap-1 text-[10px] font-bold text-gray-600 uppercase bg-gray-50 px-2 py-0.5 rounded-full">
<Stethoscope size={11} className="text-blue-400" /> {ag.procedimento}
</span>
) : null}
</div>
</div>
</div>
{descricao ? (
<div>
<label className="text-[9px] font-black text-gray-400 uppercase tracking-widest">Descrição</label>
<div className="mt-1 text-[11px] text-gray-600 bg-gray-50 rounded-xl p-2.5 max-h-20 overflow-y-auto custom-scrollbar whitespace-pre-wrap break-words">{descricao}</div>
</div>
) : null}
<div className="grid grid-cols-2 gap-4 py-2 border-y border-gray-100">
<div className="space-y-1.5">
<label className="text-[9px] font-black text-gray-400 uppercase tracking-widest">Data e hora</label>
<p className="flex items-center gap-1.5 text-xs font-bold text-gray-700"><CalendarRange size={14} className="text-blue-500" />{ag.start ? new Date(ag.start).toLocaleDateString('pt-BR', { weekday: 'short', day: 'numeric', month: 'short' }) : '—'}</p>
<p className="flex items-center gap-1.5 text-xs font-bold text-gray-700"><Clock size={14} className="text-blue-500" />{ag.start ? new Date(ag.start).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) : '—'}</p>
</div>
<div className="space-y-1.5">
<label className="text-[9px] font-black text-gray-400 uppercase tracking-widest">Profissional</label>
<p className="flex items-center gap-1.5 text-xs font-bold text-gray-700">
<span className="w-3.5 h-3.5 rounded-full border-2 border-white shadow-sm" style={{ backgroundColor: dentCor }} />{dentNome}
</p>
</div>
</div>
{pacienteId ? <FamiliaChips pacienteId={pacienteId} /> : null}
{/* Autoria */}
<div className="space-y-1 text-[10px] text-gray-400 font-medium">
{ag.created_by_nome && <p>Criado por <b className="text-gray-600">{ag.created_by_nome}</b></p>}
{ag.updated_by_nome && <p>Última alteração por <b className="text-gray-600">{ag.updated_by_nome}</b></p>}
{status === 'cancelado' && ag.canceled_by_nome && <p className="text-red-400">Cancelado por <b className="text-red-600">{ag.canceled_by_nome}</b></p>}
</div>
{/* Importado do Google (provisório): só confirmar/vincular ou remover. Não pontua. */}
{provisorio ? (
<div className="space-y-2">
<div className="bg-violet-50 border border-violet-100 rounded-xl p-3 text-[11px] text-violet-700 leading-snug">
Importado do Google. <b>Confirme o paciente</b> para virar um agendamento do sistema ( passa a contar faltas/remarcações).
</div>
<button onClick={() => push({ title: 'CONFIRMAR PACIENTE', render: () => <ConfirmImportadoPage ag={ag} onConfirmed={p.onChanged} /> })}
className="w-full py-3 bg-emerald-600 text-white rounded-xl text-[11px] font-black uppercase hover:bg-emerald-700 transition-all flex items-center justify-center gap-1.5">
<Link2 size={15} /> Confirmar paciente
</button>
<div className="flex gap-2">
<button onClick={() => push({ title: 'HISTÓRICO', render: () => <HistoryPage appointmentId={ag.id} /> })}
className="flex-1 py-2.5 rounded-xl bg-gray-50 hover:bg-gray-100 text-xs font-black uppercase text-gray-600 flex items-center justify-center gap-1.5">
<History size={15} /> Histórico
</button>
<button onClick={() => { if (confirm('REMOVER ESTE IMPORTADO DA AGENDA?')) run(() => HybridBackend.excluirAgendamento(ag.id), 'IMPORTADO REMOVIDO.'); }}
className="flex-1 py-2.5 rounded-xl border-2 border-gray-100 text-xs font-black uppercase text-gray-500 hover:border-red-200 hover:text-red-600 transition-all flex items-center justify-center gap-1.5">
<Trash2 size={15} /> Remover
</button>
</div>
</div>
) : (<>
{/* Ações de status */}
<div className="grid grid-cols-2 gap-2">
<button onClick={() => run(() => HybridBackend.atualizarAgendamento(ag.id, { status: 'confirmado' as any }), 'CONFIRMADO!')}
className="flex items-center justify-center gap-1.5 py-2.5 rounded-xl text-[11px] font-black uppercase border-2 bg-white border-gray-100 text-gray-500 hover:border-emerald-200 hover:text-emerald-600 transition-all">
<CheckCircle2 size={15} /> Confirmar
</button>
<button onClick={() => run(() => HybridBackend.marcarFalta(ag.id), 'FALTA REGISTRADA.')}
className="flex items-center justify-center gap-1.5 py-2.5 rounded-xl text-[11px] font-black uppercase border-2 bg-white border-gray-100 text-gray-500 hover:border-red-200 hover:text-red-600 transition-all">
<AlertCircle size={15} /> Marcar falta
</button>
<button onClick={() => run(() => HybridBackend.remarcarAgendamento(ag.id), 'ENVIADO PARA "A REAGENDAR".')}
title="Sem data definida — vai para a lista 'A reagendar'"
className="flex items-center justify-center gap-1.5 py-2.5 rounded-xl text-[11px] font-black uppercase border-2 bg-white border-gray-100 text-gray-500 hover:border-amber-200 hover:text-amber-600 transition-all">
<CalendarClock size={15} /> Reagendar depois
</button>
{inativo ? (
<button onClick={() => run(() => HybridBackend.restaurarAgendamento(ag.id), 'AGENDAMENTO RESTAURADO.')}
className="flex items-center justify-center gap-1.5 py-2.5 rounded-xl text-[11px] font-black uppercase border-2 bg-white border-gray-100 text-gray-500 hover:border-blue-200 hover:text-blue-600 transition-all">
<RotateCcw size={15} /> Restaurar
</button>
) : (
<button onClick={() => { if (confirm('CANCELAR ESTE AGENDAMENTO?')) run(() => HybridBackend.excluirAgendamento(ag.id), 'AGENDAMENTO CANCELADO.'); }}
className="flex items-center justify-center gap-1.5 py-2.5 rounded-xl text-[11px] font-black uppercase border-2 bg-white border-gray-100 text-gray-500 hover:border-red-200 hover:text-red-600 transition-all">
<Ban size={15} /> Cancelar
</button>
)}
</div>
{/* Navegação / ações principais */}
<div className="space-y-2">
<button onClick={() => push({ title: 'HISTÓRICO', render: () => <HistoryPage appointmentId={ag.id} /> })}
className="w-full flex items-center justify-between px-4 py-2.5 rounded-xl bg-gray-50 hover:bg-gray-100 transition-colors text-xs font-black uppercase text-gray-600">
<span className="flex items-center gap-2"><History size={15} /> Histórico</span>
<span className="text-gray-300"></span>
</button>
<div className="flex gap-2">
{p.podeAtender && (
<button onClick={() => { p.onAtender?.(); }}
className="flex-1 py-2.5 bg-emerald-600 text-white rounded-xl text-[11px] font-black uppercase hover:bg-emerald-700 transition-all flex items-center justify-center gap-1.5">
<ClipboardCheck size={15} /> Atender
</button>
)}
<button onClick={() => { p.onEditar(); }}
title="Mover para um novo horário agora"
className="flex-1 py-2.5 bg-blue-600 text-white rounded-xl text-[11px] font-black uppercase hover:bg-blue-700 transition-all flex items-center justify-center gap-1.5">
<Pencil size={15} /> Mudar horário
</button>
</div>
</div>
</>)}
</div>
);
};
export const AgendaDetailModal: React.FC<Props> = (props) => {
if (!props.appointment) return null;
const root: StackPage = { title: 'AGENDAMENTO', render: () => <DetailPage {...props} /> };
return <StackModal isOpen={props.isOpen} onClose={props.onClose} root={root} />;
};