feat(comunicacao-interna): notificação de número compartilhado + agenda de contatos
Opção (b) — mesma pessoa/número servindo a papéis diferentes, roteando por situação: - GET /api/comunicacao/numero-lookup: ao digitar um número, avisa se ele já é de outra conta/papel da clínica (ex.: número na conta dentista aparece ao editar a de dono). Modal mostra banner "pode compartilhar — descreva as situações; a Secretária roteia". - Bridge GET /contatos: agenda de contatos internos (nome, papel, situações de cada número, janela atual) p/ a Secretária rotear por assunto. Não expõe o número cru. Verificado E2E: lookup como dono acha o nº na conta dentista; /contatos lista papel+situações. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -106,6 +106,20 @@ export const ComunicacaoInternaModal: React.FC<{
|
||||
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);
|
||||
@@ -209,7 +223,7 @@ export const ComunicacaoInternaModal: React.FC<{
|
||||
<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 })} placeholder="(67) 99999-9999" />
|
||||
<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>
|
||||
@@ -218,6 +232,12 @@ export const ComunicacaoInternaModal: React.FC<{
|
||||
</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." />
|
||||
|
||||
Reference in New Issue
Block a user