c8443a146a
1) 403 ao "desligar a IA no chat": guardAreaAccess tratava TODO /sec/* como config da
Secretária (exige can_secretaria). Mas /sec/handoff é assumir/soltar o CHAT = INBOX.
Reclassificado p/ can_inbox — a recep (can_inbox, sem can_secretaria) volta a assumir
o chat. /sec/agent|nodes|slots e /secretaria/* seguem exigindo can_secretaria.
(session-power global segue can_secretaria — o dono delega se quiser.)
2) Duplicação de horário (parte 2): o DONO agora tem, no modal, o card + botão p/
configurar o "Horário da clínica" (clinicas_horarios) em Horários de Funcionamento
(aba clínica). Completude do dono passa a exigir esse horário. Backend:
comunicacaoCompleto(p, {ehDentista,jornadaOk,ehDono,horarioClinicaOk}); GET devolve
horario_clinica_configurado. Verificado: dono da Consultt sem horário → gate exige.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
340 lines
23 KiB
TypeScript
340 lines
23 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { X, Plus, Trash2, MapPin, Phone, Clock, CalendarClock, Save, Loader2, MessageSquare, AlertTriangle, CheckCircle2 } from 'lucide-react';
|
|
import { useToast } from '../contexts/ToastContext.tsx';
|
|
import { buscarCep } from '../services/viacep.ts';
|
|
import { HorariosConfig } from './HorariosConfig.tsx';
|
|
|
|
// Comunicação interna: perfil de contato de não-pacientes (dentistas/funcionários).
|
|
// Números de WhatsApp múltiplos (cada um com descrição, situações e janelas de horário
|
|
// permitido p/ a Secretária enviar msg — proteção contra mensagem fora do expediente),
|
|
// endereço e disponibilidade de agenda (abrir/fechar + jornada).
|
|
|
|
const API_URL = (window as any).__API_URL__ || '';
|
|
const COLOR = '#0d9488';
|
|
const UF = ['', 'AC', 'AL', 'AM', 'AP', 'BA', 'CE', 'DF', 'ES', 'GO', 'MA', 'MG', 'MS', 'MT', 'PA', 'PB', 'PE', 'PI', 'PR', 'RJ', 'RN', 'RO', 'RR', 'RS', 'SC', 'SE', 'SP', 'TO'];
|
|
const DIAS = [{ v: 0, l: 'Dom' }, { v: 1, l: 'Seg' }, { v: 2, l: 'Ter' }, { v: 3, l: 'Qua' }, { v: 4, l: 'Qui' }, { v: 5, l: 'Sex' }, { v: 6, l: 'Sáb' }];
|
|
|
|
const api = (path: string, opts: RequestInit = {}) =>
|
|
fetch(`${API_URL}${path}`, {
|
|
...opts,
|
|
headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${localStorage.getItem('SCOREODONTO_AUTH_TOKEN')}` },
|
|
});
|
|
|
|
interface Janela { dias: number[]; inicio: string; fim: string }
|
|
interface Numero { numero: string; descricao: string; situacoes: string; janelas: Janela[] }
|
|
interface Endereco { cep?: string; logradouro?: string; numero?: string; complemento?: string; bairro?: string; cidade?: string; estado?: string }
|
|
interface Agenda { aberta?: boolean; horarios?: Janela[] }
|
|
|
|
const input = 'w-full px-3 py-2.5 bg-gray-50 border border-gray-200 rounded-xl text-sm font-medium text-gray-700 outline-none focus:border-teal-400';
|
|
const label = 'text-[10px] font-black text-gray-400 uppercase tracking-widest block mb-1';
|
|
|
|
// ── Editor de janelas de horário (dias + faixa) — reutilizado p/ contato e agenda ──
|
|
const JanelaEditor: React.FC<{ janelas: Janela[]; onChange: (j: Janela[]) => void; addLabel: string }> = ({ janelas, onChange, addLabel }) => {
|
|
const upd = (i: number, patch: Partial<Janela>) => onChange(janelas.map((j, k) => (k === i ? { ...j, ...patch } : j)));
|
|
const toggleDia = (i: number, d: number) => {
|
|
const j = janelas[i]; const has = j.dias.includes(d);
|
|
upd(i, { dias: has ? j.dias.filter(x => x !== d) : [...j.dias, d].sort((a, b) => a - b) });
|
|
};
|
|
return (
|
|
<div className="space-y-2">
|
|
{janelas.map((j, i) => (
|
|
<div key={i} className="rounded-xl border border-gray-200 p-3 bg-white space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<div className="flex flex-wrap gap-1">
|
|
{DIAS.map(d => {
|
|
const on = j.dias.includes(d.v);
|
|
return (
|
|
<button key={d.v} type="button" onClick={() => toggleDia(i, d.v)}
|
|
className={`w-8 h-8 rounded-lg text-[10px] font-black transition-all ${on ? 'text-white' : 'bg-gray-100 text-gray-400 hover:bg-gray-200'}`}
|
|
style={on ? { backgroundColor: COLOR } : {}}>{d.l}</button>
|
|
);
|
|
})}
|
|
</div>
|
|
<button type="button" onClick={() => onChange(janelas.filter((_, k) => k !== i))} className="p-1.5 text-gray-300 hover:text-red-500"><Trash2 size={15} /></button>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-[10px] font-black text-gray-400 uppercase">Das</span>
|
|
<input type="time" value={j.inicio} onChange={e => upd(i, { inicio: e.target.value })} className="px-2 py-1.5 bg-gray-50 border border-gray-200 rounded-lg text-sm font-bold text-gray-700 outline-none focus:border-teal-400" />
|
|
<span className="text-[10px] font-black text-gray-400 uppercase">até</span>
|
|
<input type="time" value={j.fim} onChange={e => upd(i, { fim: e.target.value })} className="px-2 py-1.5 bg-gray-50 border border-gray-200 rounded-lg text-sm font-bold text-gray-700 outline-none focus:border-teal-400" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
<button type="button" onClick={() => onChange([...janelas, { dias: [1, 2, 3, 4, 5], inicio: '08:00', fim: '18:00' }])}
|
|
className="w-full py-2 rounded-xl border border-dashed border-gray-300 text-[11px] font-black uppercase text-gray-400 hover:border-teal-400 hover:text-teal-600 flex items-center justify-center gap-1.5">
|
|
<Plus size={13} /> {addLabel}
|
|
</button>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export const ComunicacaoInternaModal: React.FC<{
|
|
usuarioId?: string; clinicaId?: string; nome?: string; forced?: boolean;
|
|
onClose: () => void; onSaved?: (completo: boolean) => void; onSnooze?: () => void;
|
|
}> = ({ usuarioId = 'me', clinicaId, nome, forced, onClose, onSaved, onSnooze }) => {
|
|
const toast = useToast();
|
|
const [loading, setLoading] = useState(true);
|
|
const [saving, setSaving] = useState(false);
|
|
const [endereco, setEndereco] = useState<Endereco>({});
|
|
const [numeros, setNumeros] = useState<Numero[]>([]);
|
|
const [enderecoDaClinica, setEnderecoDaClinica] = useState(false);
|
|
const [isDentista, setIsDentista] = useState(false); // dentista precisa da jornada
|
|
const [isDono, setIsDono] = useState(false); // dono precisa do horário da clínica
|
|
const [jornadaOk, setJornadaOk] = useState(false); // jornada real (dentistas_horarios) configurada
|
|
const [horarioClinicaOk, setHorarioClinicaOk] = useState(false); // clinicas_horarios configurado
|
|
const [horariosTab, setHorariosTab] = useState<'dentistas' | 'clinica' | null>(null); // abre "Horários de Funcionamento"
|
|
|
|
const load = useCallback(async () => {
|
|
setLoading(true);
|
|
try {
|
|
const qs = clinicaId ? `?clinicaId=${encodeURIComponent(clinicaId)}` : '';
|
|
const r = await api(`/api/comunicacao/${usuarioId}${qs}`);
|
|
const d = await r.json();
|
|
setEndereco(d.endereco || {});
|
|
setNumeros(Array.isArray(d.numeros) ? d.numeros : []);
|
|
setEnderecoDaClinica(!!d.endereco_da_clinica);
|
|
setIsDentista((d.role || '') === 'dentista');
|
|
setIsDono(['donoclinica', 'donoconsultorio'].includes(d.role || ''));
|
|
setJornadaOk(!!d.jornada_configurada);
|
|
setHorarioClinicaOk(!!d.horario_clinica_configurado);
|
|
} catch { /* mantém vazio */ } finally { setLoading(false); }
|
|
}, [usuarioId, clinicaId]);
|
|
useEffect(() => { load(); }, [load]);
|
|
|
|
const setEnd = (k: keyof Endereco, v: string) => setEndereco(p => ({ ...p, [k]: v }));
|
|
const onCep = async () => {
|
|
const cep = (endereco.cep || '').replace(/\D/g, '');
|
|
if (cep.length !== 8) return;
|
|
const e = await buscarCep(cep);
|
|
if (e) setEndereco(p => ({ ...p, logradouro: e.logradouro || p.logradouro, bairro: e.bairro || p.bairro, cidade: e.cidade || p.cidade, estado: e.estado || p.estado }));
|
|
};
|
|
|
|
const updNum = (i: number, patch: Partial<Numero>) => setNumeros(ns => ns.map((n, k) => (k === i ? { ...n, ...patch } : n)));
|
|
const addNum = () => setNumeros(ns => [...ns, { numero: '', descricao: '', situacoes: '', janelas: [{ dias: [1, 2, 3, 4, 5], inicio: '08:00', fim: '18:00' }] }]);
|
|
|
|
// Notificação de número compartilhado: ao sair do campo, verifica se o número já é de
|
|
// outra conta/papel da clínica (a mesma pessoa pode ser dono E dentista, por ex.).
|
|
const [avisos, setAvisos] = useState<Record<number, { nome: string; role: string }[]>>({});
|
|
const roleLabel = (r: string) => (({ dentista: 'dentista', donoclinica: 'dono', donoconsultorio: 'dono', funcionario: 'funcionário', admin: 'admin' } as Record<string, string>)[r] || r || 'conta');
|
|
const checkNumero = async (i: number, numero: string) => {
|
|
const tel = (numero || '').replace(/\D/g, '');
|
|
if (tel.length < 10 || !clinicaId) { setAvisos(a => ({ ...a, [i]: [] })); return; }
|
|
try {
|
|
const r = await api(`/api/comunicacao/numero-lookup?clinicaId=${encodeURIComponent(clinicaId)}&numero=${encodeURIComponent(tel)}`);
|
|
const d = await r.json();
|
|
setAvisos(a => ({ ...a, [i]: Array.isArray(d.encontrados) ? d.encontrados : [] }));
|
|
} catch { /* silencioso */ }
|
|
};
|
|
|
|
// Completude (espelha o backend) — orienta o preenchimento e destrava o gate.
|
|
const enderecoOk = !!(endereco.cidade && endereco.estado && (endereco.cep || endereco.logradouro));
|
|
const numerosOk = numeros.length >= 1 && numeros.every(n => (n.numero || '').replace(/\D/g, '').length >= 10 && (n.descricao || '').trim() && (n.janelas?.length ?? 0) >= 1);
|
|
const jornadaFeita = !isDentista || jornadaOk; // dentista: jornada real configurada
|
|
const clinicaFeita = !isDono || horarioClinicaOk; // dono: horário da clínica configurado
|
|
const completo = enderecoOk && numerosOk && jornadaFeita && clinicaFeita;
|
|
|
|
const salvar = async () => {
|
|
setSaving(true);
|
|
try {
|
|
const r = await api(`/api/comunicacao/${usuarioId}`, { method: 'PUT', body: JSON.stringify({ clinicaId, endereco, numeros }) });
|
|
const d = await r.json();
|
|
if (!r.ok) throw new Error(d.error || 'Erro ao salvar.');
|
|
toast.success('DADOS SALVOS!');
|
|
onSaved?.(!!d.completo);
|
|
if (!forced || d.completo) onClose();
|
|
else toast.error('AINDA FALTAM CAMPOS OBRIGATÓRIOS PARA LIBERAR O SISTEMA.');
|
|
} catch (e: any) { toast.error(String(e.message).toUpperCase()); }
|
|
finally { setSaving(false); }
|
|
};
|
|
|
|
return (
|
|
<>
|
|
<div className="fixed inset-0 z-[70] bg-white flex flex-col">
|
|
{/* Header */}
|
|
<div className="px-5 sm:px-8 py-4 border-b border-gray-100 flex items-center justify-between shrink-0">
|
|
<div className="flex items-center gap-3">
|
|
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={{ backgroundColor: `${COLOR}18`, color: COLOR }}><MessageSquare size={20} /></div>
|
|
<div>
|
|
<h2 className="text-sm sm:text-base font-black text-gray-900 uppercase">Comunicação interna{nome ? ` · ${nome}` : ''}</h2>
|
|
<p className="text-[10px] font-bold uppercase tracking-widest" style={{ color: completo ? '#16a34a' : COLOR }}>
|
|
{completo ? 'Perfil completo' : 'Preencha para liberar o sistema'}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
{!forced && <button onClick={onClose} className="p-2 hover:bg-gray-100 rounded-xl text-gray-500"><X size={20} /></button>}
|
|
</div>
|
|
|
|
{loading ? (
|
|
<div className="flex-1 flex items-center justify-center text-gray-400"><Loader2 className="animate-spin" size={28} /></div>
|
|
) : (
|
|
<>
|
|
{/* Conteúdo */}
|
|
<div className="flex-1 overflow-y-auto px-5 sm:px-8 py-6">
|
|
<div className="max-w-3xl mx-auto space-y-8">
|
|
|
|
{forced && (
|
|
<div className="rounded-2xl bg-amber-50 border border-amber-200 p-4 flex gap-3">
|
|
<AlertTriangle size={18} className="text-amber-500 shrink-0 mt-0.5" />
|
|
<p className="text-xs text-amber-700 font-medium leading-relaxed">
|
|
Precisamos destes dados para o sistema funcionar corretamente e para a <b>Secretária IA</b> só contatar você
|
|
nos horários certos. Mensagens fora do expediente podem gerar problemas — por isso as <b>janelas de horário</b> são obrigatórias.
|
|
</p>
|
|
</div>
|
|
)}
|
|
|
|
{/* ── Endereço ── */}
|
|
<section>
|
|
<h3 className="flex items-center gap-2 text-xs font-black text-gray-700 uppercase tracking-wide mb-1"><MapPin size={15} style={{ color: COLOR }} /> Endereço</h3>
|
|
{enderecoDaClinica && <p className="text-[11px] text-teal-600 font-bold mb-3">Puxamos o endereço da clínica — é só conferir (ou ajustar, se o seu for diferente).</p>}
|
|
<div className="grid grid-cols-1 sm:grid-cols-6 gap-3">
|
|
<div className="sm:col-span-2">
|
|
<label className={label}>CEP</label>
|
|
<input className={input} value={endereco.cep || ''} onChange={e => setEnd('cep', e.target.value)} onBlur={onCep} placeholder="00000-000" />
|
|
</div>
|
|
<div className="sm:col-span-4">
|
|
<label className={label}>Logradouro</label>
|
|
<input className={input} value={endereco.logradouro || ''} onChange={e => setEnd('logradouro', e.target.value)} placeholder="Rua / Avenida" />
|
|
</div>
|
|
<div className="sm:col-span-1">
|
|
<label className={label}>Número</label>
|
|
<input className={input} value={endereco.numero || ''} onChange={e => setEnd('numero', e.target.value)} />
|
|
</div>
|
|
<div className="sm:col-span-2">
|
|
<label className={label}>Complemento</label>
|
|
<input className={input} value={endereco.complemento || ''} onChange={e => setEnd('complemento', e.target.value)} />
|
|
</div>
|
|
<div className="sm:col-span-3">
|
|
<label className={label}>Bairro</label>
|
|
<input className={input} value={endereco.bairro || ''} onChange={e => setEnd('bairro', e.target.value)} />
|
|
</div>
|
|
<div className="sm:col-span-4">
|
|
<label className={label}>Cidade</label>
|
|
<input className={input} value={endereco.cidade || ''} onChange={e => setEnd('cidade', e.target.value)} />
|
|
</div>
|
|
<div className="sm:col-span-2">
|
|
<label className={label}>UF</label>
|
|
<select className={input} value={endereco.estado || ''} onChange={e => setEnd('estado', e.target.value)}>
|
|
{UF.map(u => <option key={u} value={u}>{u || '—'}</option>)}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* ── Telefones / WhatsApp ── */}
|
|
<section>
|
|
<h3 className="flex items-center gap-2 text-xs font-black text-gray-700 uppercase tracking-wide mb-1"><Phone size={15} style={{ color: COLOR }} /> Números de WhatsApp</h3>
|
|
<p className="text-[11px] text-gray-400 font-medium mb-3">Cadastre todos os seus números. Para cada um, descreva quando ele pode ser usado e em quais horários a Secretária pode enviar mensagem.</p>
|
|
<div className="space-y-4">
|
|
{numeros.map((n, i) => (
|
|
<div key={i} className="rounded-2xl border border-gray-200 p-4 space-y-3">
|
|
<div className="flex items-start gap-3">
|
|
<div className="flex-1 grid grid-cols-1 sm:grid-cols-2 gap-3">
|
|
<div>
|
|
<label className={label}>Número</label>
|
|
<input className={input} value={n.numero} onChange={e => updNum(i, { numero: e.target.value })} onBlur={e => checkNumero(i, e.target.value)} placeholder="(67) 99999-9999" />
|
|
</div>
|
|
<div>
|
|
<label className={label}>Descrição</label>
|
|
<input className={input} value={n.descricao} onChange={e => updNum(i, { descricao: e.target.value })} placeholder="Ex.: WhatsApp de trabalho" />
|
|
</div>
|
|
</div>
|
|
<button type="button" onClick={() => setNumeros(ns => ns.filter((_, k) => k !== i))} className="p-2 text-gray-300 hover:text-red-500 mt-5"><Trash2 size={16} /></button>
|
|
</div>
|
|
{(avisos[i]?.length ?? 0) > 0 && (
|
|
<div className="rounded-xl bg-amber-50 border border-amber-200 p-2.5 text-[11px] text-amber-700 font-medium flex gap-2">
|
|
<AlertTriangle size={14} className="shrink-0 mt-0.5 text-amber-500" />
|
|
<span>Este número já está cadastrado por <b>{avisos[i].map(x => `${x.nome} (${roleLabel(x.role)})`).join(', ')}</b>. Pode compartilhar o mesmo número — descreva abaixo as situações deste papel; a Secretária identifica pelo assunto quem deve responder.</span>
|
|
</div>
|
|
)}
|
|
<div>
|
|
<label className={label}>Situações em que a Secretária pode mandar msg neste número</label>
|
|
<textarea className={input} rows={2} value={n.situacoes} onChange={e => updNum(i, { situacoes: e.target.value })} placeholder="Ex.: só confirmações de encaixe e avisos urgentes; nunca assuntos pessoais." />
|
|
</div>
|
|
<div>
|
|
<label className="text-[10px] font-black text-gray-400 uppercase tracking-widest flex items-center gap-1.5 mb-1"><Clock size={12} /> Horários permitidos para envio</label>
|
|
<JanelaEditor janelas={n.janelas || []} onChange={j => updNum(i, { janelas: j })} addLabel="Adicionar janela de horário" />
|
|
</div>
|
|
</div>
|
|
))}
|
|
<button type="button" onClick={addNum} className="w-full py-3 rounded-2xl border border-dashed border-gray-300 text-xs font-black uppercase text-gray-400 hover:border-teal-400 hover:text-teal-600 flex items-center justify-center gap-2">
|
|
<Plus size={15} /> Adicionar número
|
|
</button>
|
|
</div>
|
|
</section>
|
|
|
|
{/* ── Agenda de atendimento — fonte única: "Horários de Funcionamento" ── */}
|
|
{isDentista && (
|
|
<section>
|
|
<h3 className="flex items-center gap-2 text-xs font-black text-gray-700 uppercase tracking-wide mb-1"><CalendarClock size={15} style={{ color: COLOR }} /> Agenda de atendimento</h3>
|
|
<p className="text-[11px] text-gray-400 font-medium mb-3">São os horários em que a Secretária pode marcar consultas para você. Ficam em "Horários de Funcionamento" (a mesma jornada que a agenda usa).</p>
|
|
{jornadaOk ? (
|
|
<div className="rounded-xl bg-green-50 border border-green-200 p-3.5 flex items-center justify-between">
|
|
<span className="flex items-center gap-2 text-xs font-black text-green-700 uppercase"><CheckCircle2 size={15} /> Jornada configurada</span>
|
|
<button type="button" onClick={() => setHorariosTab('dentistas')} className="text-[10px] font-black uppercase text-teal-600 hover:text-teal-700">Editar</button>
|
|
</div>
|
|
) : (
|
|
<div className="rounded-xl bg-amber-50 border border-amber-200 p-3.5">
|
|
<p className="text-[11px] text-amber-700 font-medium mb-2 flex gap-2"><AlertTriangle size={14} className="shrink-0 mt-0.5 text-amber-500" /> Você ainda não configurou sua agenda de atendimento. Sem ela, a Secretária não consegue marcar consultas com você.</p>
|
|
<button type="button" onClick={() => setHorariosTab('dentistas')} className="w-full py-2.5 rounded-xl text-[11px] font-black uppercase text-white hover:opacity-90" style={{ backgroundColor: COLOR }}>Configurar minha agenda</button>
|
|
</div>
|
|
)}
|
|
</section>
|
|
)}
|
|
|
|
{/* ── Horário de funcionamento da clínica — só para o DONO ── */}
|
|
{isDono && (
|
|
<section>
|
|
<h3 className="flex items-center gap-2 text-xs font-black text-gray-700 uppercase tracking-wide mb-1"><CalendarClock size={15} style={{ color: COLOR }} /> Horário da clínica</h3>
|
|
<p className="text-[11px] text-gray-400 font-medium mb-3">É o horário de funcionamento que a Secretária usa para atender e marcar consultas. Fica em "Horários de Funcionamento".</p>
|
|
{horarioClinicaOk ? (
|
|
<div className="rounded-xl bg-green-50 border border-green-200 p-3.5 flex items-center justify-between">
|
|
<span className="flex items-center gap-2 text-xs font-black text-green-700 uppercase"><CheckCircle2 size={15} /> Horário da clínica configurado</span>
|
|
<button type="button" onClick={() => setHorariosTab('clinica')} className="text-[10px] font-black uppercase text-teal-600 hover:text-teal-700">Editar</button>
|
|
</div>
|
|
) : (
|
|
<div className="rounded-xl bg-amber-50 border border-amber-200 p-3.5">
|
|
<p className="text-[11px] text-amber-700 font-medium mb-2 flex gap-2"><AlertTriangle size={14} className="shrink-0 mt-0.5 text-amber-500" /> A clínica ainda não tem horário de funcionamento. Sem ele, a Secretária não consegue atender nem marcar consultas.</p>
|
|
<button type="button" onClick={() => setHorariosTab('clinica')} className="w-full py-2.5 rounded-xl text-[11px] font-black uppercase text-white hover:opacity-90" style={{ backgroundColor: COLOR }}>Configurar horário da clínica</button>
|
|
</div>
|
|
)}
|
|
</section>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="px-5 sm:px-8 py-4 border-t border-gray-100 shrink-0">
|
|
<div className="max-w-3xl mx-auto flex items-center gap-3">
|
|
<div className="flex-1 flex flex-wrap gap-x-3 gap-y-1 text-[10px] font-black uppercase tracking-wide">
|
|
<span className={enderecoOk ? 'text-green-600' : 'text-gray-300'}>{enderecoOk ? '✓' : '○'} Endereço</span>
|
|
<span className={numerosOk ? 'text-green-600' : 'text-gray-300'}>{numerosOk ? '✓' : '○'} Números</span>
|
|
{isDentista && <span className={jornadaFeita ? 'text-green-600' : 'text-gray-300'}>{jornadaFeita ? '✓' : '○'} Agenda</span>}
|
|
{isDono && <span className={clinicaFeita ? 'text-green-600' : 'text-gray-300'}>{clinicaFeita ? '✓' : '○'} Clínica</span>}
|
|
</div>
|
|
{!forced && onSnooze && (
|
|
<button onClick={onSnooze} className="px-4 py-3 rounded-xl text-xs font-black uppercase text-gray-500 border border-gray-200 hover:bg-gray-50">
|
|
Lembrar mais tarde
|
|
</button>
|
|
)}
|
|
<button onClick={salvar} disabled={saving}
|
|
className="px-6 py-3 text-white rounded-xl text-xs font-black uppercase hover:opacity-90 disabled:opacity-50 flex items-center justify-center gap-2" style={{ backgroundColor: completo ? '#16a34a' : COLOR }}>
|
|
{saving ? <Loader2 size={15} className="animate-spin" /> : completo ? <CheckCircle2 size={15} /> : <Save size={15} />}
|
|
{forced ? (completo ? 'Salvar e liberar' : 'Salvar progresso') : 'Salvar'}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{/* "Horários de Funcionamento" por cima — fonte única (dentistas_horarios / clinicas_horarios). */}
|
|
{horariosTab && (
|
|
<HorariosConfig isOpen clinicaId={clinicaId} initialTab={horariosTab} soDentista={horariosTab === 'dentistas' && usuarioId === 'me'}
|
|
onClose={() => { setHorariosTab(null); load(); }} />
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default ComunicacaoInternaModal;
|