feat(areas): Fase 2c — UI multi-área no perfil e nas configurações da clínica

- perfil profissional: chips de áreas de atuação (getMyAreas/setMyAreas)
- configurações da clínica: chips de áreas da unidade (getClinicaAreas/setClinicaAreas)
- fix: handleSavePerfil agora envia logradouro/numero (rua/número não salvavam)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
VPS 4 Builder
2026-06-14 07:15:58 +02:00
parent cf86be6f6d
commit 97783c1e34
3 changed files with 60 additions and 4 deletions
+25 -1
View File
@@ -95,11 +95,17 @@ const UnidadePerfilCard: React.FC<{ workspaceId?: string; clinColor: string }> =
const [editing, setEditing] = useState(false);
const [saving, setSaving] = useState(false);
const [f, setF] = useState<any>({});
const [allAreas, setAllAreas] = useState<{ slug: string; nome: string; cor: string }[]>([]);
const [clinAreas, setClinAreas] = useState<string[]>([]);
useEffect(() => { HybridBackend.getAreas().then(setAllAreas); }, []);
const load = async () => {
if (!workspaceId) { setLoading(false); return; }
setLoading(true);
try { const r = await HybridBackend.getClinicaProfile(workspaceId); if (r.success) setF(r.clinica || {}); }
try {
const r = await HybridBackend.getClinicaProfile(workspaceId); if (r.success) setF(r.clinica || {});
setClinAreas(await HybridBackend.getClinicaAreas(workspaceId));
}
catch { /* ignore */ } finally { setLoading(false); }
};
useEffect(() => { load(); }, [workspaceId]);
@@ -126,6 +132,7 @@ const UnidadePerfilCard: React.FC<{ workspaceId?: string; clinColor: string }> =
logradouro: f.logradouro, numero: f.numero, bairro: f.bairro, cidade: f.cidade, estado: f.estado, cep: f.cep,
});
if (!r.success) { toast.error(r.message || 'ERRO AO SALVAR.'); return; }
await HybridBackend.setClinicaAreas(workspaceId, clinAreas);
toast.success('DADOS DA UNIDADE ATUALIZADOS!'); setEditing(false);
} catch { toast.error('ERRO AO SALVAR.'); } finally { setSaving(false); }
};
@@ -172,6 +179,23 @@ const UnidadePerfilCard: React.FC<{ workspaceId?: string; clinColor: string }> =
<div><label className={LABEL_CLS}>Estado</label><input maxLength={2} className={`${FIELD_CLS} uppercase`} value={f.estado || ''} onChange={e => set('estado', e.target.value.toUpperCase())} /></div>
<div><label className={LABEL_CLS}>Cor</label><input type="color" className="w-full h-10 border border-gray-200 rounded-xl px-1" value={f.cor || clinColor} onChange={e => set('cor', e.target.value)} /></div>
</div>
<div>
<label className={LABEL_CLS}>Áreas de atuação da unidade</label>
<div className="flex flex-wrap gap-2 mt-1">
{allAreas.map(a => {
const on = clinAreas.includes(a.slug);
return (
<button key={a.slug} type="button"
onClick={() => setClinAreas(prev => on ? prev.filter(s => s !== a.slug) : [...prev, a.slug])}
className={`text-[11px] font-black uppercase px-3 py-1.5 rounded-full border transition-all ${on ? 'text-white border-transparent' : 'text-gray-500 border-gray-200 bg-white hover:bg-gray-50'}`}
style={on ? { backgroundColor: a.cor } : undefined}>
{a.nome}
</button>
);
})}
</div>
<p className="text-[10px] text-gray-400 mt-1">Uma unidade pode ter mais de uma área (ex.: odontologia + biomedicina).</p>
</div>
<div className="flex gap-2 pt-1">
<button onClick={() => { setEditing(false); load(); }} className="px-4 py-2 border border-gray-200 rounded-xl text-[11px] font-black uppercase hover:bg-gray-50">Cancelar</button>
<button onClick={save} disabled={saving} className="flex-1 py-2 bg-blue-600 text-white rounded-xl text-[11px] font-black uppercase disabled:opacity-50 flex items-center justify-center gap-2">{saving ? <Loader2 size={13} className="animate-spin" /> : <CheckCircle2 size={13} />} Salvar</button>