feat(newwhats): agenda inteligente — especialidades, situações, dúvidas, checklist do dono e pendências

Satélite da Secretária IA:
- /contexto (dentistas+especialidades+situações+cautelas) e /duvida (encaminhar
  ao humano sem agendar); agenda_pedidos ganha tipo=duvida + resumo.
- Situações da clínica: tabela agenda_situacoes + config (dono) + aba na UI.
- Checklist do dono (checklist-defs.js + agenda_checklist): perguntas Sim/Não
  conferidas contra o banco (dentistas/pacientes) → pendências de config +
  cautelas p/ a IA não chutar.
- Área de PENDÊNCIAS DO DONO (PendenciasDono.tsx): engloba config + fila
  operacional (horários/dúvidas); botão+badge no /wa-secretaria (só o dono).
- Fila humana (PedidosPendentes): cards de dúvida + resolver; /resolver.
- Botão DESLIGAR global da Secretária no ThinNav (secPowerApi, visível a todos).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-07-06 15:40:51 +02:00
parent f4c2c6861a
commit 40bed96515
9 changed files with 597 additions and 13 deletions
+28 -2
View File
@@ -2,7 +2,7 @@
// registrou ("vou confirmar e retorno"). A humana confirma (vira agendamento) ou
// recusa. Consome /api/nw/agenda-config/clinica/:id/pedidos*.
import React, { useState, useEffect, useCallback } from 'react';
import { X, CalendarClock, Check, Ban, Loader2, Phone, User } from 'lucide-react';
import { X, CalendarClock, Check, Ban, Loader2, Phone, User, HelpCircle } from 'lucide-react';
const API = (import.meta as any).env?.VITE_API_URL || '/api';
const BASE = `${API}/nw/agenda-config`;
@@ -50,6 +50,11 @@ export const PedidosPendentes: React.FC<{ isOpen: boolean; onClose: () => void;
try { await req(`/clinica/${clinicaId}/pedidos/${p.id}/recusar`, { method: 'POST' }); await carregar(); onChange?.(); }
catch (e: any) { setErro(e.message); } finally { setBusy(null); }
};
const resolver = async (p: any) => {
setBusy(p.id); setErro(null);
try { await req(`/clinica/${clinicaId}/pedidos/${p.id}/resolver`, { method: 'POST' }); await carregar(); onChange?.(); }
catch (e: any) { setErro(e.message); } finally { setBusy(null); }
};
if (!isOpen) return null;
return (
@@ -73,7 +78,28 @@ export const PedidosPendentes: React.FC<{ isOpen: boolean; onClose: () => void;
<div className="flex items-center justify-center py-16"><Loader2 size={22} className="animate-spin text-gray-400" /></div>
) : pedidos.length === 0 ? (
<p className="text-center text-sm text-gray-400 py-16">Nenhum pedido pendente. 🎉</p>
) : pedidos.map((p) => (
) : pedidos.map((p) => p.tipo === 'duvida' ? (
<div key={p.id} className="border-2 border-indigo-100 rounded-2xl p-4 bg-indigo-50/40">
<div className="flex items-start justify-between gap-3 flex-wrap">
<div className="space-y-1.5 max-w-[75%]">
<div className="flex items-center gap-1.5 text-[10px] font-black text-indigo-600 uppercase tracking-widest"><HelpCircle size={13} /> Dúvida p/ decidir</div>
<div className="text-sm font-bold text-gray-800 leading-snug whitespace-pre-wrap">{p.resumo}</div>
<div className="flex items-center gap-2 text-xs text-gray-600 font-bold"><User size={13} className="text-gray-400" /> {p.paciente_nome || 'Cliente'}
{p.paciente_telefone && <span className="flex items-center gap-1 text-gray-400"><Phone size={12} /> {p.paciente_telefone}</span>}</div>
</div>
<div className="flex gap-2">
<button onClick={() => resolver(p)} disabled={busy === p.id}
className="bg-indigo-600 hover:bg-indigo-700 text-white text-[11px] font-black uppercase px-3 py-1.5 rounded-xl flex items-center gap-1 disabled:opacity-50">
{busy === p.id ? <Loader2 size={13} className="animate-spin" /> : <Check size={13} />} Resolvida
</button>
<button onClick={() => recusar(p)} disabled={busy === p.id}
className="bg-gray-100 hover:bg-red-50 text-gray-500 hover:text-red-600 text-[11px] font-black uppercase px-3 py-1.5 rounded-xl flex items-center gap-1">
<Ban size={13} /> Descartar
</button>
</div>
</div>
</div>
) : (
<div key={p.id} className="border border-gray-200 rounded-2xl p-4 bg-white">
<div className="flex items-start justify-between gap-3 flex-wrap">
<div className="space-y-1">