feat: ViaCEP em todos os forms de endereço + menus por papel + fix preview de plugins
ViaCEP (auto-preenche endereço ao completar 8 dígitos): - novo util services/viacep.ts (buscarCep), reutilizado - ligado em Pacientes, Salas, Profissionais(perfil); Configurações migrada p/ o util - filtro 'CEP (região)' do marketplace mantido como busca (sem auto-fill) Menus por papel: - biomédico: sem Ortodontia, RX e 'Ser Tutor Orto' (não são escopo) - protético: passa a ver Dashboard/Agenda/Tratamentos/Lançar GTO (antes não via nada) - RX/Radiografias: só papéis odonto (dentista + donos/funcionário) - bottom nav mobile ajustado (biomédico/protético) Fix 'Visualizar como': - getActivePlugins é preview-aware (todos os plugins ativos enquanto houver PREVIEW_ROLE), imune ao timing de hydrate/cache; enterPreview/exitPreview simplificados Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+4
-2
@@ -199,7 +199,8 @@ const BOTTOM_TABS: Record<string, Array<{ id: ViewKey; icon: React.ElementType;
|
|||||||
donoconsultorio: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'pacientes', icon: Users, label: 'Pacientes' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'financeiro', icon: DollarSign, label: 'Financeiro' }, { more: true }],
|
donoconsultorio: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'pacientes', icon: Users, label: 'Pacientes' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'financeiro', icon: DollarSign, label: 'Financeiro' }, { more: true }],
|
||||||
funcionario: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'pacientes', icon: Users, label: 'Pacientes' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'financeiro', icon: DollarSign, label: 'Financeiro' }, { more: true }],
|
funcionario: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'pacientes', icon: Users, label: 'Pacientes' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'financeiro', icon: DollarSign, label: 'Financeiro' }, { more: true }],
|
||||||
dentista: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'ortodontia', icon: Activity, label: 'Orto' }, { more: true }],
|
dentista: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'ortodontia', icon: Activity, label: 'Orto' }, { more: true }],
|
||||||
biomedico: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'ortodontia', icon: Activity, label: 'Orto' }, { more: true }],
|
biomedico: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'tratamentos', icon: ClipboardList, label: 'Tratam.' }, { more: true }],
|
||||||
|
protetico: [{ id: 'dashboard', icon: LayoutDashboard, label: 'Início' }, { id: 'agenda', icon: CalendarIcon, label: 'Agenda' }, { id: 'lancar-gto', icon: ClipboardList, label: 'GTO' }, { more: true }],
|
||||||
paciente: [{ id: 'tratamentos', icon: ClipboardList, label: 'Tratamentos' }, { id: 'notificacoes', icon: Bell, label: 'Notif.' }, { id: 'configuracoes', icon: Settings, label: 'Config.' }],
|
paciente: [{ id: 'tratamentos', icon: ClipboardList, label: 'Tratamentos' }, { id: 'notificacoes', icon: Bell, label: 'Notif.' }, { id: 'configuracoes', icon: Settings, label: 'Config.' }],
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -271,7 +272,8 @@ const App: React.FC = () => {
|
|||||||
const permissions: Record<string, ViewKey[]> = {
|
const permissions: Record<string, ViewKey[]> = {
|
||||||
paciente: ['tratamentos', 'notificacoes', 'configuracoes'],
|
paciente: ['tratamentos', 'notificacoes', 'configuracoes'],
|
||||||
dentista: ['dashboard', 'agenda', 'ortodontia', 'tratamentos', 'lancar-gto', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'rx-radiografias', 'salas', 'marketplace-profissionais', 'candidatura-tutor', 'tutoria-painel', 'diretorio-profissionais'],
|
dentista: ['dashboard', 'agenda', 'ortodontia', 'tratamentos', 'lancar-gto', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'rx-radiografias', 'salas', 'marketplace-profissionais', 'candidatura-tutor', 'tutoria-painel', 'diretorio-profissionais'],
|
||||||
biomedico: ['dashboard', 'agenda', 'ortodontia', 'tratamentos', 'lancar-gto', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'rx-radiografias', 'salas', 'marketplace-profissionais', 'diretorio-profissionais'],
|
biomedico: ['dashboard', 'agenda', 'tratamentos', 'lancar-gto', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'salas', 'marketplace-profissionais', 'diretorio-profissionais'],
|
||||||
|
protetico: ['dashboard', 'agenda', 'tratamentos', 'lancar-gto', 'notificacoes', 'clinicas', 'configuracoes', 'plugins', 'salas', 'marketplace-profissionais', 'diretorio-profissionais'],
|
||||||
funcionario: ['dashboard', 'leads', 'pacientes', 'agenda', 'financeiro', 'tratamentos', 'lancar-gto', 'relatorios', 'notificacoes', 'clinicas', 'configuracoes', 'contratos', 'plugins', 'rx-radiografias', 'salas', 'marketplace-profissionais', 'candidatura-tutor', 'diretorio-profissionais'],
|
funcionario: ['dashboard', 'leads', 'pacientes', 'agenda', 'financeiro', 'tratamentos', 'lancar-gto', 'relatorios', 'notificacoes', 'clinicas', 'configuracoes', 'contratos', 'plugins', 'rx-radiografias', 'salas', 'marketplace-profissionais', 'candidatura-tutor', 'diretorio-profissionais'],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -26,8 +26,9 @@ export const Sidebar: React.FC<SidebarProps> = ({ activeTab, setActiveTab }) =>
|
|||||||
|
|
||||||
const pluginMenuItems: Array<{ id: string; label: string; icon: any; color: string }> = [];
|
const pluginMenuItems: Array<{ id: string; label: string; icon: any; color: string }> = [];
|
||||||
if (activePluginIds.includes('rx-scoreodonto')) {
|
if (activePluginIds.includes('rx-scoreodonto')) {
|
||||||
// Visualização operacional de radiografias: papéis de clínica (não o superadmin).
|
// Radiografias são ODONTOLÓGICAS: só papéis odonto (dentista + donos/funcionário).
|
||||||
if (currentRole !== 'superadmin') {
|
// Biomédico e protético NÃO veem RX.
|
||||||
|
if (['dentista', 'funcionario', 'admin', 'donoclinica', 'donoconsultorio'].includes(currentRole)) {
|
||||||
pluginMenuItems.push({ id: 'rx-radiografias', label: 'RX / RADIOGRAFIAS', icon: ScanLine, color: '#7c3aed' });
|
pluginMenuItems.push({ id: 'rx-radiografias', label: 'RX / RADIOGRAFIAS', icon: ScanLine, color: '#7c3aed' });
|
||||||
}
|
}
|
||||||
// DISPOSITIVOS RX (config + PCs): admin/donos da clínica e superadmin (gestão global).
|
// DISPOSITIVOS RX (config + PCs): admin/donos da clínica e superadmin (gestão global).
|
||||||
@@ -106,7 +107,12 @@ export const Sidebar: React.FC<SidebarProps> = ({ activeTab, setActiveTab }) =>
|
|||||||
|
|
||||||
if (['admin', 'donoclinica', 'donoconsultorio'].includes(currentRole)) return true;
|
if (['admin', 'donoclinica', 'donoconsultorio'].includes(currentRole)) return true;
|
||||||
if (currentRole === 'paciente') return ['tratamentos', 'notificacoes', 'configuracoes'].includes(item.id);
|
if (currentRole === 'paciente') return ['tratamentos', 'notificacoes', 'configuracoes'].includes(item.id);
|
||||||
if (currentRole === 'dentista' || currentRole === 'biomedico') return ['dashboard', 'tratamentos', 'agenda', 'ortodontia', 'notificacoes', 'clinicas', 'configuracoes', 'candidatura-tutor', 'diretorio-profissionais'].includes(item.id);
|
// Dentista: clínico odonto completo (ortodontia, tutoria orto).
|
||||||
|
if (currentRole === 'dentista') return ['dashboard', 'tratamentos', 'agenda', 'ortodontia', 'notificacoes', 'clinicas', 'configuracoes', 'candidatura-tutor', 'diretorio-profissionais'].includes(item.id);
|
||||||
|
// Biomédico(a): SEM itens odontológicos (ortodontia, RX, tutoria orto).
|
||||||
|
if (currentRole === 'biomedico') return ['dashboard', 'agenda', 'tratamentos', 'notificacoes', 'clinicas', 'configuracoes', 'diretorio-profissionais'].includes(item.id);
|
||||||
|
// Protético: laboratório — agenda própria + GTO; sem ortodontia/RX/pacientes clínicos.
|
||||||
|
if (currentRole === 'protetico') return ['dashboard', 'agenda', 'tratamentos', 'lancar-gto', 'notificacoes', 'clinicas', 'configuracoes', 'diretorio-profissionais'].includes(item.id);
|
||||||
if (currentRole === 'funcionario') return ['dashboard', 'leads', 'pacientes', 'tratamentos', 'lancar-gto', 'agenda', 'financeiro', 'relatorios', 'notificacoes', 'clinicas', 'configuracoes', 'contratos', 'diretorio-profissionais'].includes(item.id);
|
if (currentRole === 'funcionario') return ['dashboard', 'leads', 'pacientes', 'tratamentos', 'lancar-gto', 'agenda', 'financeiro', 'relatorios', 'notificacoes', 'clinicas', 'configuracoes', 'contratos', 'diretorio-profissionais'].includes(item.id);
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -259,12 +259,6 @@ export const HybridBackend = {
|
|||||||
getPreviewRole: (): string | null => localStorage.getItem('SCOREODONTO_PREVIEW_ROLE'),
|
getPreviewRole: (): string | null => localStorage.getItem('SCOREODONTO_PREVIEW_ROLE'),
|
||||||
|
|
||||||
enterPreview: (role: string) => {
|
enterPreview: (role: string) => {
|
||||||
// Na primeira entrada do preview, guarda os plugins reais do superadmin p/ restaurar no exit.
|
|
||||||
// (não sobrescreve o backup se já estiver previewando e só trocando de papel)
|
|
||||||
if (!localStorage.getItem('SCOREODONTO_PREVIEW_ROLE')) {
|
|
||||||
const prevPlugins = localStorage.getItem('SCOREODONTO_ACTIVE_PLUGINS');
|
|
||||||
localStorage.setItem('SCOREODONTO_PREVIEW_PREV_PLUGINS', prevPlugins === null ? '__none__' : prevPlugins);
|
|
||||||
}
|
|
||||||
const synthetic = {
|
const synthetic = {
|
||||||
id: '00000000-0000-0000-0000-000000000000', // UUID inexistente → consultas retornam vazio
|
id: '00000000-0000-0000-0000-000000000000', // UUID inexistente → consultas retornam vazio
|
||||||
nome: `VISUALIZAÇÃO: ${role.toUpperCase()}`,
|
nome: `VISUALIZAÇÃO: ${role.toUpperCase()}`,
|
||||||
@@ -275,8 +269,8 @@ export const HybridBackend = {
|
|||||||
localStorage.setItem('SCOREODONTO_PREVIEW_ROLE', role);
|
localStorage.setItem('SCOREODONTO_PREVIEW_ROLE', role);
|
||||||
localStorage.setItem('SCOREODONTO_ACTIVE_WORKSPACE', JSON.stringify(synthetic));
|
localStorage.setItem('SCOREODONTO_ACTIVE_WORKSPACE', JSON.stringify(synthetic));
|
||||||
localStorage.setItem('SCOREODONTO_WORKSPACES', JSON.stringify([synthetic]));
|
localStorage.setItem('SCOREODONTO_WORKSPACES', JSON.stringify([synthetic]));
|
||||||
// Preview ativa os plugins para o superadmin ver/testar as telas (RX, Salas, Profissionais).
|
// Os plugins ativos no preview vêm de getActivePlugins() (preview-aware): todos
|
||||||
localStorage.setItem('SCOREODONTO_ACTIVE_PLUGINS', JSON.stringify(['rx-scoreodonto', 'locacao-salas', 'profissionais-marketplace']));
|
// ficam ativos enquanto SCOREODONTO_PREVIEW_ROLE existir. Nada a mutar aqui.
|
||||||
const path = role === 'paciente' ? '/tratamentos' : '/dashboard';
|
const path = role === 'paciente' ? '/tratamentos' : '/dashboard';
|
||||||
window.location.assign(path);
|
window.location.assign(path);
|
||||||
},
|
},
|
||||||
@@ -285,11 +279,6 @@ export const HybridBackend = {
|
|||||||
localStorage.removeItem('SCOREODONTO_PREVIEW_ROLE');
|
localStorage.removeItem('SCOREODONTO_PREVIEW_ROLE');
|
||||||
localStorage.removeItem('SCOREODONTO_ACTIVE_WORKSPACE');
|
localStorage.removeItem('SCOREODONTO_ACTIVE_WORKSPACE');
|
||||||
localStorage.removeItem('SCOREODONTO_WORKSPACES');
|
localStorage.removeItem('SCOREODONTO_WORKSPACES');
|
||||||
// Restaura os plugins que o superadmin tinha antes do preview.
|
|
||||||
const prev = localStorage.getItem('SCOREODONTO_PREVIEW_PREV_PLUGINS');
|
|
||||||
if (prev === null || prev === '__none__') localStorage.removeItem('SCOREODONTO_ACTIVE_PLUGINS');
|
|
||||||
else localStorage.setItem('SCOREODONTO_ACTIVE_PLUGINS', prev);
|
|
||||||
localStorage.removeItem('SCOREODONTO_PREVIEW_PREV_PLUGINS');
|
|
||||||
window.location.assign('/superadmin/overview');
|
window.location.assign('/superadmin/overview');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// Busca de endereço por CEP via ViaCEP. Retorna os campos em MAIÚSCULAS
|
||||||
|
// (padrão de endereço do sistema). Retorna null se o CEP for inválido,
|
||||||
|
// não existir ou a API falhar — o chamador trata o feedback ao usuário.
|
||||||
|
export interface EnderecoCep {
|
||||||
|
logradouro: string;
|
||||||
|
bairro: string;
|
||||||
|
cidade: string;
|
||||||
|
estado: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function buscarCep(cep: string): Promise<EnderecoCep | null> {
|
||||||
|
const digits = (cep || '').replace(/\D/g, '');
|
||||||
|
if (digits.length !== 8) return null;
|
||||||
|
try {
|
||||||
|
const r = await fetch(`https://viacep.com.br/ws/${digits}/json/`);
|
||||||
|
const d = await r.json();
|
||||||
|
if (d.erro) return null;
|
||||||
|
return {
|
||||||
|
logradouro: (d.logradouro || '').toUpperCase(),
|
||||||
|
bairro: (d.bairro || '').toUpperCase(),
|
||||||
|
cidade: (d.localidade || '').toUpperCase(),
|
||||||
|
estado: (d.uf || '').toUpperCase(),
|
||||||
|
};
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,28 +5,12 @@ import { GoogleConnectButton } from '../components/GoogleConnectButton.tsx';
|
|||||||
import { useToast } from '../contexts/ToastContext.tsx';
|
import { useToast } from '../contexts/ToastContext.tsx';
|
||||||
import { ChangePasswordModal } from '../components/ChangePasswordModal.tsx';
|
import { ChangePasswordModal } from '../components/ChangePasswordModal.tsx';
|
||||||
import { APP_VERSION } from '../constants.ts';
|
import { APP_VERSION } from '../constants.ts';
|
||||||
|
import { buscarCep } from '../services/viacep.ts';
|
||||||
|
|
||||||
const FIELD_CLS = 'w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium text-gray-800 outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-100 transition-all';
|
const FIELD_CLS = 'w-full border border-gray-200 rounded-xl px-3 py-2.5 text-sm font-medium text-gray-800 outline-none focus:border-blue-500 focus:ring-2 focus:ring-blue-100 transition-all';
|
||||||
const LABEL_CLS = 'text-[10px] font-black text-gray-400 uppercase tracking-wider block mb-1';
|
const LABEL_CLS = 'text-[10px] font-black text-gray-400 uppercase tracking-wider block mb-1';
|
||||||
const TIPO_LABEL: Record<string, string> = { pessoal: 'Pessoal', consultorio: 'Consultório', clinica: 'Clínica' };
|
const TIPO_LABEL: Record<string, string> = { pessoal: 'Pessoal', consultorio: 'Consultório', clinica: 'Clínica' };
|
||||||
|
|
||||||
// Busca de endereço por CEP (ViaCEP) — retorna tudo em MAIÚSCULAS.
|
|
||||||
async function buscarCep(cep: string): Promise<{ logradouro: string; bairro: string; cidade: string; estado: string } | null> {
|
|
||||||
const digits = (cep || '').replace(/\D/g, '');
|
|
||||||
if (digits.length !== 8) return null;
|
|
||||||
try {
|
|
||||||
const r = await fetch(`https://viacep.com.br/ws/${digits}/json/`);
|
|
||||||
const d = await r.json();
|
|
||||||
if (d.erro) return null;
|
|
||||||
return {
|
|
||||||
logradouro: (d.logradouro || '').toUpperCase(),
|
|
||||||
bairro: (d.bairro || '').toUpperCase(),
|
|
||||||
cidade: (d.localidade || '').toUpperCase(),
|
|
||||||
estado: (d.uf || '').toUpperCase(),
|
|
||||||
};
|
|
||||||
} catch { return null; }
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Card: Meu Perfil (Sistema) ---
|
// --- Card: Meu Perfil (Sistema) ---
|
||||||
const MeuPerfilCard: React.FC<{ clinColor: string; role: string }> = ({ clinColor, role }) => {
|
const MeuPerfilCard: React.FC<{ clinColor: string; role: string }> = ({ clinColor, role }) => {
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { HybridBackend } from '../services/backend.ts';
|
|||||||
import { Paciente, Dentista, Agendamento, Plano } from '../types.ts';
|
import { Paciente, Dentista, Agendamento, Plano } from '../types.ts';
|
||||||
import { useHybridBackend } from '../hooks/useHybridBackend.ts';
|
import { useHybridBackend } from '../hooks/useHybridBackend.ts';
|
||||||
import { useToast } from '../contexts/ToastContext.tsx';
|
import { useToast } from '../contexts/ToastContext.tsx';
|
||||||
|
import { buscarCep } from '../services/viacep.ts';
|
||||||
|
|
||||||
const RELACOES_COMUNS = ['MÃE', 'PAI', 'FILHO', 'FILHA', 'IRMÃO', 'IRMÃ', 'CÔNJUGE', 'AVÔ', 'AVÓ', 'NETO', 'NETA', 'TIO', 'TIA', 'SOBRINHO', 'SOBRINHA', 'PRIMO', 'PRIMA', 'PADRASTO', 'MADRASTA', 'ENTEADO', 'ENTEADA'];
|
const RELACOES_COMUNS = ['MÃE', 'PAI', 'FILHO', 'FILHA', 'IRMÃO', 'IRMÃ', 'CÔNJUGE', 'AVÔ', 'AVÓ', 'NETO', 'NETA', 'TIO', 'TIA', 'SOBRINHO', 'SOBRINHA', 'PRIMO', 'PRIMA', 'PADRASTO', 'MADRASTA', 'ENTEADO', 'ENTEADA'];
|
||||||
|
|
||||||
@@ -637,7 +638,15 @@ const EditarPacienteModal: React.FC<{ patient: Paciente; onClose: () => void; on
|
|||||||
<div className="grid grid-cols-3 gap-3">
|
<div className="grid grid-cols-3 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-[10px] font-black text-gray-500 uppercase tracking-wider mb-1">CEP</label>
|
<label className="block text-[10px] font-black text-gray-500 uppercase tracking-wider mb-1">CEP</label>
|
||||||
<input value={form.cep || ''} onChange={e => setForm(f => ({ ...f, cep: e.target.value }))} className={inp} placeholder="00000-000" maxLength={9} />
|
<input value={form.cep || ''} onChange={async e => {
|
||||||
|
const v = e.target.value;
|
||||||
|
setForm(f => ({ ...f, cep: v }));
|
||||||
|
if (v.replace(/\D/g, '').length === 8) {
|
||||||
|
const r = await buscarCep(v);
|
||||||
|
if (r) { setForm(f => ({ ...f, logradouro: r.logradouro, bairro: r.bairro, cidade: r.cidade, estado: r.estado })); toast.success('ENDEREÇO PREENCHIDO PELO CEP!'); }
|
||||||
|
else toast.error('CEP NÃO ENCONTRADO.');
|
||||||
|
}
|
||||||
|
}} className={inp} placeholder="00000-000" maxLength={9} />
|
||||||
</div>
|
</div>
|
||||||
<div className="col-span-2">
|
<div className="col-span-2">
|
||||||
<label className="block text-[10px] font-black text-gray-500 uppercase tracking-wider mb-1">Logradouro</label>
|
<label className="block text-[10px] font-black text-gray-500 uppercase tracking-wider mb-1">Logradouro</label>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
import { PageHeader } from '../../components/PageHeader.tsx';
|
import { PageHeader } from '../../components/PageHeader.tsx';
|
||||||
import { useToast } from '../../contexts/ToastContext.tsx';
|
import { useToast } from '../../contexts/ToastContext.tsx';
|
||||||
import { HybridBackend } from '../../services/backend.ts';
|
import { HybridBackend } from '../../services/backend.ts';
|
||||||
|
import { buscarCep } from '../../services/viacep.ts';
|
||||||
|
|
||||||
const API_URL = (window as any).__API_URL__ || '';
|
const API_URL = (window as any).__API_URL__ || '';
|
||||||
const UF_LIST = ['', '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 UF_LIST = ['', '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'];
|
||||||
@@ -385,7 +386,15 @@ export const ProfissionaisPlugin: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className={labelCls}>CEP</label>
|
<label className={labelCls}>CEP</label>
|
||||||
<input className={inputCls} value={perfil.cep || ''} onChange={e => setP('cep', e.target.value)} placeholder="79000-000" />
|
<input className={inputCls} value={perfil.cep || ''} onChange={async e => {
|
||||||
|
const v = e.target.value;
|
||||||
|
setP('cep', v);
|
||||||
|
if (v.replace(/\D/g, '').length === 8) {
|
||||||
|
const r = await buscarCep(v);
|
||||||
|
if (r) { setPerfil((prev: any) => ({ ...prev, bairro: r.bairro, cidade: r.cidade, estado: r.estado })); toast.success('LOCALIZAÇÃO PREENCHIDA PELO CEP!'); }
|
||||||
|
else toast.error('CEP NÃO ENCONTRADO.');
|
||||||
|
}
|
||||||
|
}} placeholder="79000-000" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className={labelCls}>Especialidade principal</label>
|
<label className={labelCls}>Especialidade principal</label>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import {
|
|||||||
import { PageHeader } from '../../components/PageHeader.tsx';
|
import { PageHeader } from '../../components/PageHeader.tsx';
|
||||||
import { useToast } from '../../contexts/ToastContext.tsx';
|
import { useToast } from '../../contexts/ToastContext.tsx';
|
||||||
import { HybridBackend } from '../../services/backend.ts';
|
import { HybridBackend } from '../../services/backend.ts';
|
||||||
|
import { buscarCep } from '../../services/viacep.ts';
|
||||||
|
|
||||||
const API_URL = (window as any).__API_URL__ || '';
|
const API_URL = (window as any).__API_URL__ || '';
|
||||||
const UF_LIST = ['', '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 UF_LIST = ['', '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'];
|
||||||
@@ -171,7 +172,15 @@ const SalaFormModal: React.FC<{ sala: Sala | null; onClose: () => void; onSaved:
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className={labelCls}>CEP</label>
|
<label className={labelCls}>CEP</label>
|
||||||
<input className={inputCls} value={f.cep} onChange={e => set('cep', e.target.value)} placeholder="79000-000" />
|
<input className={inputCls} value={f.cep} onChange={async e => {
|
||||||
|
const v = e.target.value;
|
||||||
|
set('cep', v);
|
||||||
|
if (v.replace(/\D/g, '').length === 8) {
|
||||||
|
const r = await buscarCep(v);
|
||||||
|
if (r) { setF(p => ({ ...p, endereco: r.logradouro, bairro: r.bairro, cidade: r.cidade, estado: r.estado })); toast.success('ENDEREÇO PREENCHIDO PELO CEP!'); }
|
||||||
|
else toast.error('CEP NÃO ENCONTRADO.');
|
||||||
|
}
|
||||||
|
}} placeholder="79000-000" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className={labelCls}>Cidade *</label>
|
<label className={labelCls}>Cidade *</label>
|
||||||
|
|||||||
@@ -1,9 +1,15 @@
|
|||||||
export const ACTIVE_PLUGINS_KEY = 'SCOREODONTO_ACTIVE_PLUGINS';
|
export const ACTIVE_PLUGINS_KEY = 'SCOREODONTO_ACTIVE_PLUGINS';
|
||||||
export const PLUGIN_CONFIG_KEY = 'SCOREODONTO_PLUGIN_CONFIG';
|
export const PLUGIN_CONFIG_KEY = 'SCOREODONTO_PLUGIN_CONFIG';
|
||||||
|
|
||||||
|
// "Visualizar como" (superadmin) ativa todos os plugins para teste, sem depender
|
||||||
|
// do cache em localStorage (que é reconciliado/limpo em outros fluxos).
|
||||||
|
const PREVIEW_PLUGINS = ['rx-scoreodonto', 'locacao-salas', 'profissionais-marketplace'];
|
||||||
|
|
||||||
export const getActivePlugins = (): string[] => {
|
export const getActivePlugins = (): string[] => {
|
||||||
try { return JSON.parse(localStorage.getItem(ACTIVE_PLUGINS_KEY) || '[]'); }
|
try {
|
||||||
catch { return []; }
|
if (localStorage.getItem('SCOREODONTO_PREVIEW_ROLE')) return [...PREVIEW_PLUGINS];
|
||||||
|
return JSON.parse(localStorage.getItem(ACTIVE_PLUGINS_KEY) || '[]');
|
||||||
|
} catch { return []; }
|
||||||
};
|
};
|
||||||
|
|
||||||
export const isPluginActive = (id: string): boolean => getActivePlugins().includes(id);
|
export const isPluginActive = (id: string): boolean => getActivePlugins().includes(id);
|
||||||
|
|||||||
Reference in New Issue
Block a user