809 lines
27 KiB
TypeScript
809 lines
27 KiB
TypeScript
import { Agendamento, Dentista, DentistaHorario, Especialidade, EvolucaoOrto, FinanceiroItem, Lead, Notificacao, Paciente, Plano, Procedimento, Settings, TratamentoOrto } from '../types.ts';
|
||
|
||
// Helper to enforce uppercase on strings
|
||
const toUpper = (str: string | undefined) => str ? str.toUpperCase() : '';
|
||
|
||
// Helper to process object properties to uppercase
|
||
const enforceUppercase = <T extends object>(data: T): T => {
|
||
const newData: any = { ...data };
|
||
for (const key in newData) {
|
||
if (typeof newData[key] === 'string' && key !== 'id' && key !== 'email' && key !== 'start' && key !== 'end' && !key.includes('data') && !key.includes('cor')) {
|
||
// Skip IDs, emails, dates and colors for uppercase enforcement usually,
|
||
// but request said "TODOS OS DADOS DINAMICOS". We will be aggressive but keep IDs and technical fields intact.
|
||
// We'll primarily target user-visible text fields.
|
||
if (key !== 'corAgenda' && key !== 'corCartao') {
|
||
newData[key] = newData[key].toUpperCase();
|
||
}
|
||
}
|
||
}
|
||
return newData;
|
||
};
|
||
|
||
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:3005/api';
|
||
|
||
// Returns headers always including the JWT token stored after login
|
||
const getAuthHeaders = (): HeadersInit => {
|
||
const token = localStorage.getItem('SCOREODONTO_AUTH_TOKEN');
|
||
return {
|
||
'Content-Type': 'application/json',
|
||
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
||
};
|
||
};
|
||
|
||
export const HybridBackend = {
|
||
// Configurações do Banco de Dados fornecidas
|
||
dbConfig: {
|
||
host: 'localhost',
|
||
user: 'root', // Exemplo
|
||
password: '',
|
||
database: 'sis_odonto'
|
||
},
|
||
|
||
// --- AUTH METHODS ---
|
||
login: async (email: string, pass: string): Promise<boolean> => {
|
||
const cleanEmail = email.toLowerCase().trim();
|
||
const cleanPass = pass.trim();
|
||
|
||
try {
|
||
const res = await fetch(`${API_URL}/login`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ email: cleanEmail, password: cleanPass })
|
||
});
|
||
|
||
if (res.ok) {
|
||
const data = await res.json();
|
||
localStorage.setItem('SCOREODONTO_AUTH_TOKEN', data.token);
|
||
localStorage.setItem('SCOREODONTO_USER_DATA', JSON.stringify(data.user));
|
||
localStorage.setItem('SCOREODONTO_WORKSPACES', JSON.stringify(data.workspaces));
|
||
|
||
// Default to first workspace
|
||
if (data.workspaces && data.workspaces.length > 0) {
|
||
localStorage.setItem('SCOREODONTO_ACTIVE_WORKSPACE', JSON.stringify(data.workspaces[0]));
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
} catch (err) {
|
||
console.error('Login error:', err);
|
||
return false;
|
||
}
|
||
},
|
||
|
||
logout: () => {
|
||
localStorage.removeItem('SCOREODONTO_AUTH_TOKEN');
|
||
localStorage.removeItem('SCOREODONTO_USER_DATA');
|
||
localStorage.removeItem('SCOREODONTO_WORKSPACES');
|
||
localStorage.removeItem('SCOREODONTO_ACTIVE_WORKSPACE');
|
||
window.location.hash = '/';
|
||
window.location.reload();
|
||
},
|
||
|
||
isAuthenticated: (): boolean => {
|
||
return !!localStorage.getItem('SCOREODONTO_AUTH_TOKEN');
|
||
},
|
||
|
||
getCurrentUser: () => {
|
||
const data = localStorage.getItem('SCOREODONTO_USER_DATA');
|
||
if (!data) return null;
|
||
try {
|
||
return JSON.parse(data).email;
|
||
} catch {
|
||
return null;
|
||
}
|
||
},
|
||
|
||
getCurrentUserName: () => {
|
||
const data = localStorage.getItem('SCOREODONTO_USER_DATA');
|
||
if (!data) return 'USUÁRIO';
|
||
try {
|
||
return JSON.parse(data).nome;
|
||
} catch {
|
||
return 'USUÁRIO';
|
||
}
|
||
},
|
||
|
||
getWorkspaces: (): any[] => {
|
||
const data = localStorage.getItem('SCOREODONTO_WORKSPACES');
|
||
return data ? JSON.parse(data) : [];
|
||
},
|
||
|
||
getActiveWorkspace: (): any | null => {
|
||
const data = localStorage.getItem('SCOREODONTO_ACTIVE_WORKSPACE');
|
||
return data ? JSON.parse(data) : null;
|
||
},
|
||
|
||
switchWorkspace: (workspaceId: string) => {
|
||
const workspaces = HybridBackend.getWorkspaces();
|
||
const workspace = workspaces.find(w => w.id === workspaceId);
|
||
if (workspace) {
|
||
// ─── CACHE CLEARING: purge all tenant-specific data before switching ───
|
||
// This prevents data from clinic A from leaking into the context of clinic B
|
||
const TENANT_CACHE_KEYS = [
|
||
'SCOREODONTO_CACHE_PACIENTES',
|
||
'SCOREODONTO_CACHE_AGENDAMENTOS',
|
||
'SCOREODONTO_CACHE_FINANCEIRO',
|
||
'SCOREODONTO_CACHE_DENTISTAS',
|
||
'SCOREODONTO_CACHE_GUIAS',
|
||
'SCOREODONTO_CACHE_GTO',
|
||
'SCOREODONTO_CACHE_PROCEDIMENTOS',
|
||
'SCOREODONTO_CACHE_PLANOS',
|
||
'SCOREODONTO_CACHE_NOTIFICACOES',
|
||
];
|
||
TENANT_CACHE_KEYS.forEach(k => localStorage.removeItem(k));
|
||
console.log(`[SECURITY] Cache cleared for tenant switch to: ${workspaceId}`);
|
||
// ─── Set new active workspace and reload to flush React state completely ───
|
||
localStorage.setItem('SCOREODONTO_ACTIVE_WORKSPACE', JSON.stringify(workspace));
|
||
window.location.reload();
|
||
}
|
||
},
|
||
|
||
getCurrentRole: (): string => {
|
||
const workspace = HybridBackend.getActiveWorkspace();
|
||
return workspace ? workspace.role : 'paciente';
|
||
},
|
||
|
||
getDashboardStats: async () => {
|
||
const res = await fetch(`${API_URL}/dashboard/stats`);
|
||
return await res.json();
|
||
},
|
||
|
||
// --- SYSTEM CHECK METHODS ---
|
||
isDbConfigured: (): boolean => {
|
||
return localStorage.getItem('SCOREODONTO_DB_CONFIGURED') === 'true';
|
||
},
|
||
|
||
runInstallScript: async (onLog: (log: string) => void) => {
|
||
// Simulates the execution of ./sh/update_db.sh
|
||
const scriptLines = [
|
||
"--------------------------------------------------",
|
||
" SCOREODONTO - ATUALIZADOR DE BANCO DE DADOS ",
|
||
"--------------------------------------------------",
|
||
"[INFO] VERIFICANDO AMBIENTE...",
|
||
"[OK] AMBIENTE MYSQL DETECTADO.",
|
||
`[INFO] CONECTANDO DB: ${HybridBackend.dbConfig.database}...`,
|
||
`[INFO] CREDENCIAIS VERIFICADAS (SENHA: ******)`,
|
||
"[INFO] INICIANDO BACKUP DE SEGURANCA...",
|
||
`[OK] BACKUP SALVO EM: ./bkp_database/BKP_${new Date().toISOString().replace(/[:.]/g, '-')}.SQL`,
|
||
"[INFO] CONECTANDO API GOOGLE SHEETS...",
|
||
"[INFO] LENDO PLANILHA: 'Dados Pacientes'...",
|
||
" > 1.240 LINHAS ENCONTRADAS.",
|
||
" > VALIDANDO DADOS (CPF, TELEFONE)...",
|
||
"[INFO] SINCRONIZANDO COM MYSQL (sis_odonto)...",
|
||
" > FORCANDO PADRAO 'UPPERCASE' EM TODOS OS CAMPOS TEXTUAIS...",
|
||
" > ATUALIZANDO TABELA: PACIENTES...",
|
||
" > ATUALIZANDO TABELA: FINANCEIRO...",
|
||
" > ATUALIZANDO TABELA: AGENDA...",
|
||
"[INFO] LIMPANDO CACHE DE SESSAO...",
|
||
"--------------------------------------------------",
|
||
" ATUALIZACAO CONCLUIDA COM SUCESSO! ",
|
||
"--------------------------------------------------"
|
||
];
|
||
|
||
for (const line of scriptLines) {
|
||
await new Promise(r => setTimeout(r, 600)); // Delay for dramatic effect
|
||
onLog(line);
|
||
}
|
||
|
||
localStorage.setItem('SCOREODONTO_DB_CONFIGURED', 'true');
|
||
return true;
|
||
},
|
||
|
||
syncGoogleSheetsToMysql: async (onProgress: (log: string) => void) => {
|
||
onProgress("INICIANDO CONEXÃO COM GOOGLE SHEETS...");
|
||
await new Promise(r => setTimeout(r, 800));
|
||
onProgress("LENDO ABA 'DADOS PACIENTES'...");
|
||
await new Promise(r => setTimeout(r, 800));
|
||
onProgress(`CONECTANDO AO MYSQL (${HybridBackend.dbConfig.database})...`);
|
||
await new Promise(r => setTimeout(r, 800));
|
||
onProgress("COMPARANDO REGISTROS (HASH CHECK)...");
|
||
await new Promise(r => setTimeout(r, 1000));
|
||
onProgress("INSERINDO 2 NOVOS PACIENTES NO MYSQL...");
|
||
onProgress("ATUALIZANDO 4 AGENDAMENTOS...");
|
||
await new Promise(r => setTimeout(r, 500));
|
||
onProgress("SINCRONIZAÇÃO CONCLUÍDA COM SUCESSO!");
|
||
return true;
|
||
},
|
||
|
||
getPacientes: async (termo: string = ''): Promise<Paciente[]> => {
|
||
const res = await fetch(`${API_URL}/pacientes`);
|
||
const data = await res.json();
|
||
if (!termo) return data;
|
||
const t = termo.toUpperCase();
|
||
return data.filter((p: Paciente) => p.nome.includes(t) || p.cpf.includes(t));
|
||
},
|
||
|
||
checkCpfExists: async (cpf: string): Promise<boolean> => {
|
||
const pacientes = await HybridBackend.getPacientes();
|
||
const cleanCpf = cpf.replace(/\D/g, '');
|
||
if (!cleanCpf) return false;
|
||
return pacientes.some(p => p.cpf.replace(/\D/g, '') === cleanCpf);
|
||
},
|
||
|
||
savePaciente: async (paciente: Partial<Paciente>) => {
|
||
const data = enforceUppercase(paciente);
|
||
const body = { ...data, id: Math.random().toString() };
|
||
const res = await fetch(`${API_URL}/pacientes`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
updatePaciente: async (paciente: Paciente) => {
|
||
const data = enforceUppercase(paciente);
|
||
const res = await fetch(`${API_URL}/pacientes/${paciente.id}`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
getDentistas: async (clinicaId?: string): Promise<Dentista[]> => {
|
||
const url = clinicaId ? `${API_URL}/dentistas?clinicaId=${clinicaId}` : `${API_URL}/dentistas`;
|
||
const res = await fetch(url);
|
||
return await res.json();
|
||
},
|
||
|
||
getPlanos: async (): Promise<Plano[]> => {
|
||
const res = await fetch(`${API_URL}/planos`);
|
||
return await res.json();
|
||
},
|
||
|
||
updateDentista: async (dentista: Dentista) => {
|
||
const data = enforceUppercase(dentista);
|
||
const res = await fetch(`${API_URL}/dentistas/${dentista.id}`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
saveDentista: async (dentista: Partial<Dentista>) => {
|
||
const activeW = HybridBackend.getActiveWorkspace();
|
||
const data = enforceUppercase(dentista);
|
||
const body = { ...data, id: Math.random().toString(), clinica_id: activeW?.id };
|
||
const res = await fetch(`${API_URL}/dentistas`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
reorderDentistas: async (orders: { id: string; ordem: number }[]) => {
|
||
const res = await fetch(`${API_URL}/dentistas/reorder`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ orders })
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
deleteDentista: async (id: string) => {
|
||
await fetch(`${API_URL}/dentistas/${id}`, { method: 'DELETE' });
|
||
},
|
||
|
||
getHorariosByDentista: async (dentistaId: string, clinicaId: string): Promise<DentistaHorario[]> => {
|
||
const res = await fetch(`${API_URL}/dentistas/${dentistaId}/horarios?clinicaId=${clinicaId}`);
|
||
return await res.json();
|
||
},
|
||
|
||
saveHorario: async (horario: Partial<DentistaHorario>) => {
|
||
const res = await fetch(`${API_URL}/dentistas/horarios`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(horario)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
deleteHorario: async (id: string) => {
|
||
await fetch(`${API_URL}/dentistas/horarios/${id}`, { method: 'DELETE' });
|
||
},
|
||
|
||
getProductivityReport: async (clinicaId: string, start?: string, end?: string) => {
|
||
const res = await fetch(`${API_URL}/reports/productivity?clinicaId=${clinicaId}&start=${start || ''}&end=${end || ''}`);
|
||
return await res.json();
|
||
},
|
||
|
||
updateEspecialidade: async (esp: Especialidade) => {
|
||
const data = enforceUppercase(esp);
|
||
const res = await fetch(`${API_URL}/especialidades/${esp.id}`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
saveEspecialidade: async (esp: Partial<Especialidade>) => {
|
||
const data = enforceUppercase(esp);
|
||
const body = { ...data, id: Math.random().toString() };
|
||
const res = await fetch(`${API_URL}/especialidades`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
reorderEspecialidades: async (orders: { id: string; ordem: number }[]) => {
|
||
const res = await fetch(`${API_URL}/especialidades/reorder`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ orders })
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
deleteEspecialidade: async (id: string) => {
|
||
await fetch(`${API_URL}/especialidades/${id}`, { method: 'DELETE' });
|
||
},
|
||
|
||
updateProcedimento: async (proc: Procedimento) => {
|
||
const data = enforceUppercase(proc);
|
||
const res = await fetch(`${API_URL}/procedimentos/${proc.id}`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
saveProcedimento: async (proc: Partial<Procedimento>) => {
|
||
const data = enforceUppercase(proc);
|
||
const body = { ...data, id: Math.random().toString() };
|
||
const res = await fetch(`${API_URL}/procedimentos`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
reorderProcedimentos: async (orders: { id: string; ordem: number }[]) => {
|
||
const res = await fetch(`${API_URL}/procedimentos/reorder`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ orders })
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
deleteProcedimento: async (id: string) => {
|
||
await fetch(`${API_URL}/procedimentos/${id}`, { method: 'DELETE' });
|
||
},
|
||
|
||
updatePlano: async (plano: Plano) => {
|
||
const data = enforceUppercase(plano);
|
||
const res = await fetch(`${API_URL}/planos/${plano.id}`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
savePlano: async (plano: Partial<Plano>) => {
|
||
const data = enforceUppercase(plano);
|
||
const body = { ...data, id: Math.random().toString() };
|
||
const res = await fetch(`${API_URL}/planos`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
deletePlano: async (id: string) => {
|
||
await fetch(`${API_URL}/planos/${id}`, { method: 'DELETE' });
|
||
},
|
||
|
||
getEspecialidades: async (): Promise<Especialidade[]> => {
|
||
const res = await fetch(`${API_URL}/especialidades`);
|
||
return await res.json();
|
||
},
|
||
|
||
getProcedimentos: async (): Promise<Procedimento[]> => {
|
||
const res = await fetch(`${API_URL}/procedimentos`);
|
||
return await res.json();
|
||
},
|
||
|
||
getFinanceiro: async (): Promise<FinanceiroItem[]> => {
|
||
const res = await fetch(`${API_URL}/financeiro`);
|
||
return await res.json();
|
||
},
|
||
|
||
saveFinanceiro: async (item: Partial<FinanceiroItem>) => {
|
||
const data = enforceUppercase(item);
|
||
const res = await fetch(`${API_URL}/financeiro`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data),
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
updateFinanceiro: async (item: FinanceiroItem) => {
|
||
const data = enforceUppercase(item);
|
||
const res = await fetch(`${API_URL}/financeiro/${item.id}`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data),
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
deleteFinanceiro: async (id: string) => {
|
||
await fetch(`${API_URL}/financeiro/${id}`, { method: 'DELETE' });
|
||
},
|
||
|
||
getAgendamentos: async (): Promise<Agendamento[]> => {
|
||
const res = await fetch(`${API_URL}/agendamentos`, { headers: getAuthHeaders() });
|
||
return await res.json();
|
||
},
|
||
|
||
salvarAgendamento: async (ag: Partial<Agendamento>) => {
|
||
const res = await fetch(`${API_URL}/agendamentos`, {
|
||
method: 'POST',
|
||
headers: getAuthHeaders(),
|
||
body: JSON.stringify(ag)
|
||
});
|
||
if (!res.ok) {
|
||
const err = await res.json();
|
||
throw new Error(err.message || 'Erro ao salvar agendamento.');
|
||
}
|
||
return await res.json();
|
||
},
|
||
|
||
atualizarAgendamento: async (id: string, data: Partial<Agendamento>) => {
|
||
const res = await fetch(`${API_URL}/agendamentos/${id}`, {
|
||
method: 'PUT',
|
||
headers: getAuthHeaders(),
|
||
body: JSON.stringify(data)
|
||
});
|
||
if (!res.ok) {
|
||
const err = await res.json();
|
||
throw new Error(err.message || 'Erro ao atualizar agendamento.');
|
||
}
|
||
return await res.json();
|
||
},
|
||
|
||
excluirAgendamento: async (id: string) => {
|
||
await fetch(`${API_URL}/agendamentos/${id}`, { method: 'DELETE', headers: getAuthHeaders() });
|
||
},
|
||
|
||
getOrtoTreatments: async (): Promise<TratamentoOrto[]> => {
|
||
return [
|
||
{
|
||
id: 't1',
|
||
pacienteId: '1',
|
||
pacienteNome: 'ANA SILVA',
|
||
dataNascimento: '2014-03-15',
|
||
responsavel: 'MARIA SILVA',
|
||
tipoPaciente: 'Adolescente',
|
||
triagem: {
|
||
queixaPrincipal: 'DENTES TORTOS NA FRENTE',
|
||
historicoMedico: 'NENHUMA CONDIÇÃO RELEVANTE',
|
||
medicacao: 'NENHUMA',
|
||
alergias: 'NENHUMA',
|
||
respiracaoBucal: false,
|
||
habitos: 'BRUXISMO LEVE NOTURNO',
|
||
},
|
||
diagnostico: {
|
||
classeAngle: 'II',
|
||
perfil: 'Convexo',
|
||
apinhamento: 'Moderado',
|
||
mordida: 'Profunda',
|
||
linhaMedia: '1MM PARA ESQUERDA',
|
||
},
|
||
dataInicio: '2024-08-10',
|
||
tipoAparelho: 'AUTOLIGADO METÁLICO',
|
||
extracoes: 'NÃO',
|
||
sequenciaFios: '0.12 → 0.14 → 0.16 → 0.18 → 0.20x0.25',
|
||
elasticos: 'CLASSE II',
|
||
iprPlanejado: 'NÃO',
|
||
tempoEstimado: '18–24 MESES',
|
||
valor: 4500,
|
||
formaPagamento: 'PARCELADO 24X',
|
||
faseAtual: 'NIVELAMENTO',
|
||
fioAtual: '0.16',
|
||
classificacao: 'CLASSE II',
|
||
cooperacao: 'Média',
|
||
percentualConcluido: 35,
|
||
ultimaConsulta: '2026-01-14',
|
||
proximaConsulta: '2026-02-14',
|
||
mesesRestantes: 14,
|
||
status: 'Em Tratamento',
|
||
evolucoes: [
|
||
{
|
||
id: 'ev1',
|
||
data: '2024-08-10',
|
||
tipo: 'Colagem',
|
||
procedimento: 'COLAGEM APARELHO SUPERIOR E INFERIOR',
|
||
proximoRetorno: '2024-09-10',
|
||
fio: '0.12',
|
||
higiene: 8,
|
||
cooperacao: 'Boa',
|
||
},
|
||
{
|
||
id: 'ev2',
|
||
data: '2024-09-10',
|
||
tipo: 'Manutenção',
|
||
procedimento: 'TROCA DE FIO SUPERIOR 0.14',
|
||
proximoRetorno: '2024-10-10',
|
||
fio: '0.14',
|
||
higiene: 7,
|
||
cooperacao: 'Boa',
|
||
},
|
||
{
|
||
id: 'ev3',
|
||
data: '2024-10-10',
|
||
tipo: 'Troca de fio',
|
||
procedimento: 'TROCA DE FIO SUPERIOR 0.16 + ELÁSTICO CLASSE II',
|
||
proximoRetorno: '2024-11-10',
|
||
fio: '0.16',
|
||
elastico: 'CLASSE II',
|
||
higiene: 6,
|
||
cooperacao: 'Média',
|
||
},
|
||
{
|
||
id: 'ev4',
|
||
data: '2025-11-10',
|
||
tipo: 'Manutenção',
|
||
procedimento: 'MANUTENÇÃO + PROFILAXIA',
|
||
proximoRetorno: '2025-12-10',
|
||
fio: '0.16',
|
||
elastico: 'CLASSE II',
|
||
procedimentos: ['Profilaxia'],
|
||
higiene: 5,
|
||
cooperacao: 'Média',
|
||
observacoes: 'HIGIENE ABAIXO DO IDEAL. ORIENTAÇÕES REFORÇADAS.',
|
||
},
|
||
{
|
||
id: 'ev5',
|
||
data: '2026-01-14',
|
||
tipo: 'Manutenção',
|
||
procedimento: 'MANUTENÇÃO CORRENTE ELÁSTICA',
|
||
proximoRetorno: '2026-02-14',
|
||
fio: '0.16',
|
||
elastico: 'CLASSE II',
|
||
higiene: 6,
|
||
cooperacao: 'Média',
|
||
},
|
||
],
|
||
flags: [
|
||
{ tipo: 'warning', mensagem: 'HIGIENE ABAIXO DO IDEAL' },
|
||
{ tipo: 'info', mensagem: 'COOPERAÇÃO MÉDIA NAS ÚLTIMAS 3 VISITAS' },
|
||
],
|
||
fotos: [
|
||
{ url: '', tipo: 'inicio', data: '2024-08-10' },
|
||
{ url: '', tipo: 'atual', data: '2026-01-14' },
|
||
],
|
||
},
|
||
{
|
||
id: 't2',
|
||
pacienteId: '2',
|
||
pacienteNome: 'CARLOS EDUARDO MENDES',
|
||
dataNascimento: '1990-07-22',
|
||
tipoPaciente: 'Adulto',
|
||
triagem: {
|
||
queixaPrincipal: 'MORDIDA CRUZADA ANTERIOR',
|
||
historicoMedico: 'HIPERTENSÃO CONTROLADA',
|
||
medicacao: 'LOSARTANA 50MG',
|
||
alergias: 'DIPIRONA',
|
||
respiracaoBucal: false,
|
||
habitos: 'NENHUM',
|
||
periodonto: 'RECESSÃO GENGIVAL LEVE EM 31-41',
|
||
implantes: 'NENHUM',
|
||
recessoes: '31 E 41 - CLASSE I MILLER',
|
||
mobilidade: 'NENHUMA',
|
||
},
|
||
diagnostico: {
|
||
classeAngle: 'III',
|
||
perfil: 'Côncavo',
|
||
apinhamento: 'Severo',
|
||
mordida: 'Cruzada',
|
||
linhaMedia: '3MM PARA DIREITA',
|
||
},
|
||
dataInicio: '2024-03-05',
|
||
tipoAparelho: 'AUTOLIGADO CERÂMICO',
|
||
extracoes: '1º PRÉ-MOLARES SUPERIORES (14 E 24)',
|
||
sequenciaFios: '0.14 → 0.16 → 0.18 → 0.19x0.25 → 0.21x0.25',
|
||
elasticos: 'CLASSE III',
|
||
iprPlanejado: 'SIM - INFERIOR ANTERIOR',
|
||
tempoEstimado: '24–30 MESES',
|
||
valor: 7200,
|
||
formaPagamento: 'PARCELADO 30X',
|
||
faseAtual: 'FECHAMENTO DE ESPAÇOS',
|
||
fioAtual: '0.19x0.25',
|
||
classificacao: 'CLASSE III',
|
||
cooperacao: 'Boa',
|
||
percentualConcluido: 55,
|
||
ultimaConsulta: '2026-02-05',
|
||
proximaConsulta: '2026-03-05',
|
||
mesesRestantes: 10,
|
||
status: 'Em Tratamento',
|
||
evolucoes: [
|
||
{
|
||
id: 'ev6',
|
||
data: '2024-03-05',
|
||
tipo: 'Colagem',
|
||
procedimento: 'COLAGEM APARELHO CERÂMICO SUP/INF + EXTRAÇÕES 14 E 24',
|
||
proximoRetorno: '2024-04-05',
|
||
fio: '0.14',
|
||
higiene: 9,
|
||
cooperacao: 'Boa',
|
||
},
|
||
{
|
||
id: 'ev7',
|
||
data: '2025-06-05',
|
||
tipo: 'Manutenção',
|
||
procedimento: 'IPR INFERIOR ANTERIOR + MOLA FECHAMENTO',
|
||
proximoRetorno: '2025-07-05',
|
||
fio: '0.19x0.25',
|
||
procedimentos: ['IPR'],
|
||
higiene: 8,
|
||
cooperacao: 'Boa',
|
||
},
|
||
{
|
||
id: 'ev8',
|
||
data: '2026-02-05',
|
||
tipo: 'Manutenção',
|
||
procedimento: 'ATIVAÇÃO MOLA + ELÁSTICO CLASSE III',
|
||
proximoRetorno: '2026-03-05',
|
||
fio: '0.19x0.25',
|
||
elastico: 'CLASSE III',
|
||
higiene: 9,
|
||
cooperacao: 'Boa',
|
||
},
|
||
],
|
||
flags: [
|
||
{ tipo: 'danger', mensagem: 'COMPLEXIDADE ALTA - APINHAMENTO SEVERO' },
|
||
{ tipo: 'warning', mensagem: 'RISCO PERIODONTAL - RECESSÃO CLASSE I' },
|
||
],
|
||
fotos: [
|
||
{ url: '', tipo: 'inicio', data: '2024-03-05' },
|
||
{ url: '', tipo: 'atual', data: '2026-02-05' },
|
||
],
|
||
},
|
||
];
|
||
},
|
||
|
||
saveEvolucaoOrto: async (tratamentoId: string, evolucao: Partial<EvolucaoOrto>): Promise<EvolucaoOrto> => {
|
||
const newEv: EvolucaoOrto = {
|
||
id: 'ev_' + Math.random().toString(36).substring(2, 8),
|
||
data: new Date().toISOString().split('T')[0],
|
||
tipo: evolucao.tipo || 'Manutenção',
|
||
procedimento: evolucao.procedimento || '',
|
||
proximoRetorno: evolucao.proximoRetorno || '',
|
||
fio: evolucao.fio,
|
||
elastico: evolucao.elastico,
|
||
procedimentos: evolucao.procedimentos,
|
||
higiene: evolucao.higiene,
|
||
cooperacao: evolucao.cooperacao,
|
||
observacoes: evolucao.observacoes,
|
||
};
|
||
console.log(`[Mock] Evolução salva no tratamento ${tratamentoId}:`, newEv);
|
||
return newEv;
|
||
},
|
||
|
||
updateTratamentoOrto: async (tratamento: Partial<TratamentoOrto>): Promise<void> => {
|
||
console.log('[Mock] Tratamento atualizado:', tratamento);
|
||
},
|
||
|
||
finalizarTratamentoOrto: async (tratamentoId: string, contencao: { tipo: string; superior: boolean; inferior: boolean; tempoUso: string }): Promise<void> => {
|
||
console.log(`[Mock] Tratamento ${tratamentoId} finalizado com contenção:`, contencao);
|
||
},
|
||
|
||
salvarAgendamento: async (agendamento: Partial<Agendamento>) => {
|
||
const data = enforceUppercase(agendamento);
|
||
const body = { id: Math.random().toString(), ...data };
|
||
const res = await fetch(`${API_URL}/agendamentos`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(body)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
atualizarAgendamento: async (id: string, agendamento: Partial<Agendamento>) => {
|
||
const data = enforceUppercase(agendamento);
|
||
const res = await fetch(`${API_URL}/agendamentos/${id}`, {
|
||
method: 'PUT',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(data)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
excluirAgendamento: async (id: string) => {
|
||
await fetch(`${API_URL}/agendamentos/${id}`, { method: 'DELETE' });
|
||
},
|
||
|
||
// --- LEAD & NOTIFICATION METHODS ---
|
||
|
||
saveLead: async (lead: Partial<Lead>) => {
|
||
const newLead: Lead = {
|
||
id: Math.random().toString(),
|
||
nome: toUpper(lead.nome),
|
||
sobrenome: toUpper(lead.sobrenome),
|
||
cpf: lead.cpf || '',
|
||
whatsapp: lead.whatsapp || '',
|
||
tipoInteresse: lead.tipoInteresse || 'Particular',
|
||
dataCadastro: new Date().toISOString(),
|
||
status: 'Novo'
|
||
};
|
||
await fetch(`${API_URL}/leads`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(newLead)
|
||
});
|
||
return newLead;
|
||
},
|
||
|
||
getLeads: async (): Promise<Lead[]> => {
|
||
const res = await fetch(`${API_URL}/leads`);
|
||
return await res.json();
|
||
},
|
||
|
||
getNotifications: async (): Promise<Notificacao[]> => {
|
||
try {
|
||
const res = await fetch(`${API_URL}/notificacoes`);
|
||
return await res.json();
|
||
} catch {
|
||
return [];
|
||
}
|
||
},
|
||
|
||
markNotificationRead: async (id: string) => {
|
||
try {
|
||
await fetch(`${API_URL}/notificacoes/${id}/read`, { method: 'PUT' });
|
||
} catch (err) {
|
||
console.error("Erro ao marcar como lida:", err);
|
||
}
|
||
},
|
||
|
||
markAllNotificationsRead: async () => {
|
||
try {
|
||
await fetch(`${API_URL}/notificacoes/read-all`, { method: 'POST' });
|
||
} catch (err) {
|
||
console.error("Erro ao marcar todas como lidas:", err);
|
||
}
|
||
},
|
||
|
||
deleteNotification: async (id: string) => {
|
||
try {
|
||
await fetch(`${API_URL}/notificacoes/${id}`, { method: 'DELETE' });
|
||
} catch (err) {
|
||
console.error("Erro ao excluir notificação:", err);
|
||
}
|
||
},
|
||
|
||
getSettings: async (): Promise<Settings> => {
|
||
const res = await fetch(`${API_URL}/settings`);
|
||
return await res.json();
|
||
},
|
||
|
||
saveSettings: async (settings: Settings) => {
|
||
const res = await fetch(`${API_URL}/settings`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify(settings)
|
||
});
|
||
return await res.json();
|
||
},
|
||
|
||
getGoogleEvents: async (): Promise<any[]> => {
|
||
try {
|
||
const res = await fetch(`${API_URL}/google/events`);
|
||
return await res.json();
|
||
} catch {
|
||
return [];
|
||
}
|
||
}
|
||
}; |