From a994a70d903d3e072accfa2690c30dff054951da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Mon, 6 Oct 2025 23:24:57 -0300 Subject: [PATCH 1/8] atualizing-api.ts --- susconecta/lib/api.ts | 467 +++++++++++++++++++++++++++++++++++++++++- 1 file changed, 466 insertions(+), 1 deletion(-) diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index c009b0f..bfb5d42 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -1,5 +1,8 @@ // lib/api.ts +import { ENV_CONFIG } from '@/lib/env-config'; +import { API_KEY } from '@/lib/config'; + export type ApiOk = { success?: boolean; data: T; @@ -194,7 +197,32 @@ async function parse(res: Response): Promise { console.error("[API ERROR]", res.url, res.status, json); const code = (json && (json.error?.code || json.code)) ?? res.status; const msg = (json && (json.error?.message || json.message)) ?? res.statusText; - throw new Error(`${code}: ${msg}`); + + // Mensagens amigáveis para erros comuns + let friendlyMessage = `${code}: ${msg}`; + + // Erro de CPF duplicado + if (code === '23505' && msg.includes('patients_cpf_key')) { + friendlyMessage = 'Já existe um paciente cadastrado com este CPF. Por favor, verifique se o paciente já está registrado no sistema ou use um CPF diferente.'; + } + // Erro de email duplicado (paciente) + else if (code === '23505' && msg.includes('patients_email_key')) { + friendlyMessage = 'Já existe um paciente cadastrado com este email. Por favor, use um email diferente.'; + } + // Erro de CRM duplicado (médico) + else if (code === '23505' && msg.includes('doctors_crm')) { + friendlyMessage = 'Já existe um médico cadastrado com este CRM. Por favor, verifique se o médico já está registrado no sistema.'; + } + // Erro de email duplicado (médico) + else if (code === '23505' && msg.includes('doctors_email_key')) { + friendlyMessage = 'Já existe um médico cadastrado com este email. Por favor, use um email diferente.'; + } + // Outros erros de constraint unique + else if (code === '23505') { + friendlyMessage = 'Registro duplicado: já existe um cadastro com essas informações no sistema.'; + } + + throw new Error(friendlyMessage); } return (json?.data ?? json) as T; @@ -552,6 +580,432 @@ export async function excluirMedico(id: string | number): Promise { await parse(res); } +// ===== USUÁRIOS ===== +export type UserRole = { + id: string; + user_id: string; + role: string; + created_at: string; +}; + +export async function listarUserRoles(): Promise { + const url = `https://mock.apidog.com/m1/1053378-0-default/rest/v1/user_roles`; + const res = await fetch(url, { + method: "GET", + headers: baseHeaders(), + }); + return await parse(res); +} + +export type User = { + id: string; + email: string; + email_confirmed_at: string; + created_at: string; + last_sign_in_at: string; +}; + +export type CurrentUser = { + id: string; + email: string; + email_confirmed_at: string; + created_at: string; + last_sign_in_at: string; +}; + +export type Profile = { + id: string; + full_name: string; + email: string; + phone: string; + avatar_url: string; + disabled: boolean; + created_at: string; + updated_at: string; +}; + +export type ProfileInput = Partial>; + +export type Permissions = { + isAdmin: boolean; + isManager: boolean; + isDoctor: boolean; + isSecretary: boolean; + isAdminOrManager: boolean; +}; + +export type UserInfo = { + user: User; + profile: Profile; + roles: string[]; + permissions: Permissions; +}; + +export async function getCurrentUser(): Promise { + const url = `https://mock.apidog.com/m1/1053378-0-default/auth/v1/user`; + const res = await fetch(url, { + method: "GET", + headers: baseHeaders(), + }); + return await parse(res); +} + +export async function getUserInfo(): Promise { + const url = `https://mock.apidog.com/m1/1053378-0-default/functions/v1/user-info`; + const res = await fetch(url, { + method: "GET", + headers: baseHeaders(), + }); + return await parse(res); +} + +export type CreateUserInput = { + email: string; + full_name: string; + phone: string; + role: string; + password?: string; +}; + +export type CreatedUser = { + id: string; + email: string; + full_name: string; + phone: string; + role: string; +}; + +export type CreateUserResponse = { + success: boolean; + user: CreatedUser; + password?: string; +}; + +export type CreateUserWithPasswordResponse = { + success: boolean; + user: CreatedUser; + email: string; + password: string; +}; + +// Função para gerar senha aleatória (formato: senhaXXX!) +export function gerarSenhaAleatoria(): string { + const num1 = Math.floor(Math.random() * 10); + const num2 = Math.floor(Math.random() * 10); + const num3 = Math.floor(Math.random() * 10); + return `senha${num1}${num2}${num3}!`; +} + +export async function criarUsuario(input: CreateUserInput): Promise { + const url = `https://mock.apidog.com/m1/1053378-0-default/functions/v1/create-user`; + const res = await fetch(url, { + method: "POST", + headers: { ...baseHeaders(), "Content-Type": "application/json" }, + body: JSON.stringify(input), + }); + return await parse(res); +} + +// ============================================ +// CRIAÇÃO DE USUÁRIOS NO SUPABASE AUTH +// Vínculo com pacientes/médicos por EMAIL +// ============================================ + +// Criar usuário para MÉDICO no Supabase Auth (sistema de autenticação) +export async function criarUsuarioMedico(medico: { + email: string; + full_name: string; + phone_mobile: string; +}): Promise { + + const senha = gerarSenhaAleatoria(); + + console.log('🏥 [CRIAR MÉDICO] Iniciando criação no Supabase Auth...'); + console.log('📧 Email:', medico.email); + console.log('👤 Nome:', medico.full_name); + console.log('📱 Telefone:', medico.phone_mobile); + console.log('🔑 Senha gerada:', senha); + + // Endpoint do Supabase Auth (mesmo que auth.ts usa) + const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`; + + const payload = { + email: medico.email, + password: senha, + data: { + userType: 'profissional', // Para login em /login -> /profissional + full_name: medico.full_name, + phone: medico.phone_mobile, + } + }; + + console.log('📤 [CRIAR MÉDICO] Enviando para:', signupUrl); + + try { + const response = await fetch(signupUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "apikey": API_KEY, + }, + body: JSON.stringify(payload), + }); + + console.log('📋 [CRIAR MÉDICO] Status da resposta:', response.status, response.statusText); + + if (!response.ok) { + const errorText = await response.text(); + console.error('❌ [CRIAR MÉDICO] Erro na resposta:', errorText); + + // Tenta parsear o erro para pegar mensagem específica + let errorMsg = `Erro ao criar usuário (${response.status})`; + try { + const errorData = JSON.parse(errorText); + errorMsg = errorData.msg || errorData.message || errorData.error_description || errorMsg; + + // Mensagens amigáveis para erros comuns + if (errorMsg.includes('already registered') || errorMsg.includes('already exists')) { + errorMsg = 'Este email já está cadastrado no sistema'; + } else if (errorMsg.includes('invalid email')) { + errorMsg = 'Formato de email inválido'; + } else if (errorMsg.includes('weak password')) { + errorMsg = 'Senha muito fraca'; + } + } catch (e) { + // Se não conseguir parsear, usa mensagem genérica + } + + throw new Error(errorMsg); + } + + const responseData = await response.json(); + console.log('✅ [CRIAR MÉDICO] Usuário criado com sucesso no Supabase Auth!'); + console.log('🆔 User ID:', responseData.user?.id || responseData.id); + + // 🔧 AUTO-CONFIRMAR EMAIL: Fazer login automático logo após criar usuário + // Isso força o Supabase a confirmar o email automaticamente + if (responseData.user?.email_confirmed_at === null || !responseData.user?.email_confirmed_at) { + console.warn('⚠️ [CRIAR MÉDICO] Email NÃO confirmado - tentando auto-confirmar via login...'); + + try { + const loginUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/token?grant_type=password`; + console.log('🔧 [AUTO-CONFIRMAR] Fazendo login automático para confirmar email...'); + + const loginResponse = await fetch(loginUrl, { + method: 'POST', + headers: { + 'apikey': ENV_CONFIG.SUPABASE_ANON_KEY, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: medico.email, + password: senha, + }), + }); + + if (loginResponse.ok) { + const loginData = await loginResponse.json(); + console.log('✅ [AUTO-CONFIRMAR] Login automático realizado com sucesso!'); + console.log('📦 [AUTO-CONFIRMAR] Email confirmado:', loginData.user?.email_confirmed_at ? 'SIM ✅' : 'NÃO ❌'); + + // Atualizar responseData com dados do login (que tem email confirmado) + if (loginData.user) { + responseData.user = loginData.user; + } + } else { + const errorText = await loginResponse.text(); + console.error('❌ [AUTO-CONFIRMAR] Falha no login automático:', loginResponse.status, errorText); + console.warn('⚠️ [AUTO-CONFIRMAR] Usuário pode não conseguir fazer login imediatamente!'); + } + } catch (confirmError) { + console.error('❌ [AUTO-CONFIRMAR] Erro ao tentar fazer login automático:', confirmError); + console.warn('⚠️ [AUTO-CONFIRMAR] Continuando sem confirmação automática...'); + } + } else { + console.log('✅ [CRIAR MÉDICO] Email confirmado automaticamente!'); + } + + // Log bem visível com as credenciais para teste + console.log('🔐🔐🔐 ========================================'); + console.log('🔐 CREDENCIAIS DO MÉDICO CRIADO:'); + console.log('🔐 Email:', medico.email); + console.log('🔐 Senha:', senha); + console.log('🔐 Pode fazer login?', responseData.user?.email_confirmed_at ? 'SIM ✅' : 'NÃO ❌ (precisa confirmar email)'); + console.log('🔐 ========================================'); + + return { + success: true, + user: responseData.user || responseData, + email: medico.email, + password: senha, + }; + + } catch (error: any) { + console.error('❌ [CRIAR MÉDICO] Erro ao criar usuário:', error); + throw error; + } +} + +// Criar usuário para PACIENTE no Supabase Auth (sistema de autenticação) +export async function criarUsuarioPaciente(paciente: { + email: string; + full_name: string; + phone_mobile: string; +}): Promise { + + const senha = gerarSenhaAleatoria(); + + console.log('🏥 [CRIAR PACIENTE] Iniciando criação no Supabase Auth...'); + console.log('📧 Email:', paciente.email); + console.log('👤 Nome:', paciente.full_name); + console.log('📱 Telefone:', paciente.phone_mobile); + console.log('🔑 Senha gerada:', senha); + + // Endpoint do Supabase Auth (mesmo que auth.ts usa) + const signupUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/signup`; + + const payload = { + email: paciente.email, + password: senha, + data: { + userType: 'paciente', // Para login em /login-paciente -> /paciente + full_name: paciente.full_name, + phone: paciente.phone_mobile, + } + }; + + console.log('📤 [CRIAR PACIENTE] Enviando para:', signupUrl); + + try { + const response = await fetch(signupUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Accept": "application/json", + "apikey": API_KEY, + }, + body: JSON.stringify(payload), + }); + + console.log('📋 [CRIAR PACIENTE] Status da resposta:', response.status, response.statusText); + + if (!response.ok) { + const errorText = await response.text(); + console.error('❌ [CRIAR PACIENTE] Erro na resposta:', errorText); + + // Tenta parsear o erro para pegar mensagem específica + let errorMsg = `Erro ao criar usuário (${response.status})`; + try { + const errorData = JSON.parse(errorText); + errorMsg = errorData.msg || errorData.message || errorData.error_description || errorMsg; + + // Mensagens amigáveis para erros comuns + if (errorMsg.includes('already registered') || errorMsg.includes('already exists')) { + errorMsg = 'Este email já está cadastrado no sistema'; + } else if (errorMsg.includes('invalid email')) { + errorMsg = 'Formato de email inválido'; + } else if (errorMsg.includes('weak password')) { + errorMsg = 'Senha muito fraca'; + } + } catch (e) { + // Se não conseguir parsear, usa mensagem genérica + } + + throw new Error(errorMsg); + } + + const responseData = await response.json(); + console.log('✅ [CRIAR PACIENTE] Usuário criado com sucesso no Supabase Auth!'); + console.log('🆔 User ID:', responseData.user?.id || responseData.id); + console.log('📦 [CRIAR PACIENTE] Resposta completa do Supabase:', JSON.stringify(responseData, null, 2)); + + // VERIFICAÇÃO CRÍTICA: O usuário foi realmente criado? + if (!responseData.user && !responseData.id) { + console.error('⚠️⚠️⚠️ AVISO: Supabase retornou sucesso mas SEM user ID!'); + console.error('Isso pode significar que o usuário NÃO foi criado de verdade!'); + } + + const userId = responseData.user?.id || responseData.id; + + // 🔧 AUTO-CONFIRMAR EMAIL: Fazer login automático logo após criar usuário + // Isso força o Supabase a confirmar o email automaticamente + if (responseData.user?.email_confirmed_at === null || !responseData.user?.email_confirmed_at) { + console.warn('⚠️ [CRIAR PACIENTE] Email NÃO confirmado - tentando auto-confirmar via login...'); + + try { + const loginUrl = `${ENV_CONFIG.SUPABASE_URL}/auth/v1/token?grant_type=password`; + console.log('🔧 [AUTO-CONFIRMAR] Fazendo login automático para confirmar email...'); + + const loginResponse = await fetch(loginUrl, { + method: 'POST', + headers: { + 'apikey': ENV_CONFIG.SUPABASE_ANON_KEY, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + email: paciente.email, + password: senha, + }), + }); + + console.log('🔍 [AUTO-CONFIRMAR] Status do login automático:', loginResponse.status); + + if (loginResponse.ok) { + const loginData = await loginResponse.json(); + console.log('✅ [AUTO-CONFIRMAR] Login automático realizado com sucesso!'); + console.log('📦 [AUTO-CONFIRMAR] Dados completos do login:', JSON.stringify(loginData, undefined, 2)); + console.log('📧 [AUTO-CONFIRMAR] Email confirmado:', loginData.user?.email_confirmed_at ? 'SIM ✅' : 'NÃO ❌'); + console.log('👤 [AUTO-CONFIRMAR] UserType no metadata:', loginData.user?.user_metadata?.userType); + console.log('🎯 [AUTO-CONFIRMAR] Email verified:', loginData.user?.user_metadata?.email_verified); + + // Atualizar responseData com dados do login (que tem email confirmado) + if (loginData.user) { + responseData.user = loginData.user; + } + } else { + const errorText = await loginResponse.text(); + console.error('❌ [AUTO-CONFIRMAR] Falha no login automático:', loginResponse.status, errorText); + console.warn('⚠️ [AUTO-CONFIRMAR] Usuário pode não conseguir fazer login imediatamente!'); + + // Tentar parsear o erro para entender melhor + try { + const errorData = JSON.parse(errorText); + console.error('📋 [AUTO-CONFIRMAR] Detalhes do erro:', errorData); + } catch (e) { + console.error('📋 [AUTO-CONFIRMAR] Erro não é JSON:', errorText); + } + } + } catch (confirmError) { + console.error('❌ [AUTO-CONFIRMAR] Erro ao tentar fazer login automático:', confirmError); + console.warn('⚠️ [AUTO-CONFIRMAR] Continuando sem confirmação automática...'); + } + } else { + console.log('✅ [CRIAR PACIENTE] Email confirmado automaticamente!'); + } + + // Log bem visível com as credenciais para teste + console.log('🔐🔐🔐 ========================================'); + console.log('🔐 CREDENCIAIS DO PACIENTE CRIADO:'); + console.log('🔐 Email:', paciente.email); + console.log('🔐 Senha:', senha); + console.log('🔐 UserType:', 'paciente'); + console.log('🔐 Pode fazer login?', responseData.user?.email_confirmed_at ? 'SIM ✅' : 'NÃO ❌ (precisa confirmar email)'); + console.log('🔐 ========================================'); + + return { + success: true, + user: responseData.user || responseData, + email: paciente.email, + password: senha, + }; + + } catch (error: any) { + console.error('❌ [CRIAR PACIENTE] Erro ao criar usuário:', error); + throw error; + } +} + // ===== CEP (usado nos formulários) ===== export async function buscarCepAPI(cep: string): Promise<{ logradouro?: string; @@ -588,3 +1042,14 @@ export async function adicionarAnexoMedico(_id: string | number, _file: File): P export async function removerAnexoMedico(_id: string | number, _anexoId: string | number): Promise {} export async function uploadFotoMedico(_id: string | number, _file: File): Promise<{ foto_url?: string; thumbnail_url?: string }> { return {}; } export async function removerFotoMedico(_id: string | number): Promise {} + +// ===== PERFIS DE USUÁRIOS ===== +export async function listarPerfis(): Promise { + const url = `https://mock.apidog.com/m1/1053378-0-default/rest/v1/profiles`; + const res = await fetch(url, { + method: "GET", + headers: baseHeaders(), + }); + return await parse(res); +} + -- 2.47.2 From 40bf3e0e6f6b250fb0eeebc1c1218f41f4fd4d1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Tue, 7 Oct 2025 00:16:39 -0300 Subject: [PATCH 2/8] fix-report-page --- .../dashboard/relatorios/page.tsx | 311 ++++++++++++++---- susconecta/app/profissional/page.tsx | 18 +- susconecta/components/dashboard/header.tsx | 8 +- 3 files changed, 262 insertions(+), 75 deletions(-) diff --git a/susconecta/app/(main-routes)/dashboard/relatorios/page.tsx b/susconecta/app/(main-routes)/dashboard/relatorios/page.tsx index 808ca1f..473cbde 100644 --- a/susconecta/app/(main-routes)/dashboard/relatorios/page.tsx +++ b/susconecta/app/(main-routes)/dashboard/relatorios/page.tsx @@ -1,86 +1,263 @@ + "use client"; - import { Button } from "@/components/ui/button"; -import { FileDown } from "lucide-react"; +import { FileDown, BarChart2, Users, DollarSign, TrendingUp, UserCheck, CalendarCheck, ThumbsUp, User, Briefcase } from "lucide-react"; import jsPDF from "jspdf"; -import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts"; +import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer, LineChart, Line, PieChart, Pie, Cell } from "recharts"; + +// Dados fictícios para demonstração +const metricas = [ + { label: "Atendimentos", value: 1240, icon: }, + { label: "Absenteísmo", value: "7,2%", icon: }, + { label: "Satisfação", value: "92%", icon: }, + { label: "Faturamento (Mês)", value: "R$ 45.000", icon: }, + { label: "No-show", value: "5,1%", icon: }, +]; + +const consultasPorPeriodo = [ + { periodo: "Jan", consultas: 210 }, + { periodo: "Fev", consultas: 180 }, + { periodo: "Mar", consultas: 250 }, + { periodo: "Abr", consultas: 230 }, + { periodo: "Mai", consultas: 270 }, + { periodo: "Jun", consultas: 220 }, +]; + +const faturamentoMensal = [ + { mes: "Jan", valor: 35000 }, + { mes: "Fev", valor: 29000 }, + { mes: "Mar", valor: 42000 }, + { mes: "Abr", valor: 38000 }, + { mes: "Mai", valor: 45000 }, + { mes: "Jun", valor: 41000 }, +]; + +const taxaNoShow = [ + { mes: "Jan", noShow: 6.2 }, + { mes: "Fev", noShow: 5.8 }, + { mes: "Mar", noShow: 4.9 }, + { mes: "Abr", noShow: 5.5 }, + { mes: "Mai", noShow: 5.1 }, + { mes: "Jun", noShow: 4.7 }, +]; + +const pacientesMaisAtendidos = [ + { nome: "Ana Souza", consultas: 18 }, + { nome: "Bruno Lima", consultas: 15 }, + { nome: "Carla Menezes", consultas: 13 }, + { nome: "Diego Alves", consultas: 12 }, + { nome: "Fernanda Dias", consultas: 11 }, +]; + +const medicosMaisProdutivos = [ + { nome: "Dr. Carlos Andrade", consultas: 62 }, + { nome: "Dra. Paula Silva", consultas: 58 }, + { nome: "Dr. João Pedro", consultas: 54 }, + { nome: "Dra. Marina Costa", consultas: 51 }, +]; + +const convenios = [ + { nome: "Unimed", valor: 18000 }, + { nome: "Bradesco", valor: 12000 }, + { nome: "SulAmérica", valor: 9000 }, + { nome: "Particular", valor: 15000 }, +]; + +const performancePorMedico = [ + { nome: "Dr. Carlos Andrade", consultas: 62, absenteismo: 4.8 }, + { nome: "Dra. Paula Silva", consultas: 58, absenteismo: 6.1 }, + { nome: "Dr. João Pedro", consultas: 54, absenteismo: 7.5 }, + { nome: "Dra. Marina Costa", consultas: 51, absenteismo: 5.2 }, +]; + +const COLORS = ["#10b981", "#6366f1", "#f59e42", "#ef4444"]; + +function exportPDF(title: string, content: string) { + const doc = new jsPDF(); + doc.text(title, 10, 10); + doc.text(content, 10, 20); + doc.save(`${title.toLowerCase().replace(/ /g, '-')}.pdf`); +} export default function RelatoriosPage() { - // Dados fictícios para o gráfico financeiro - const financeiro = [ - { mes: "Jan", faturamento: 35000, despesas: 12000 }, - { mes: "Fev", faturamento: 29000, despesas: 15000 }, - { mes: "Mar", faturamento: 42000, despesas: 18000 }, - { mes: "Abr", faturamento: 38000, despesas: 14000 }, - { mes: "Mai", faturamento: 45000, despesas: 20000 }, - { mes: "Jun", faturamento: 41000, despesas: 17000 }, - ]; - // ============================ - // PASSO 3 - Funções de exportar - // ============================ - const exportConsultasPDF = () => { - const doc = new jsPDF(); - doc.text("Relatório de Consultas", 10, 10); - doc.text("Resumo das consultas realizadas.", 10, 20); - doc.save("relatorio-consultas.pdf"); - }; - - const exportPacientesPDF = () => { - const doc = new jsPDF(); - doc.text("Relatório de Pacientes", 10, 10); - doc.text("Informações gerais dos pacientes cadastrados.", 10, 20); - doc.save("relatorio-pacientes.pdf"); - }; - - const exportFinanceiroPDF = () => { - const doc = new jsPDF(); - doc.text("Relatório Financeiro", 10, 10); - doc.text("Receitas e despesas da clínica.", 10, 20); - doc.save("relatorio-financeiro.pdf"); - }; - return (
-

Relatórios

+

Dashboard Executivo de Relatórios

-
- {/* Card Consultas */} -
-

Relatório de Consultas

-

Resumo das consultas realizadas.

- {/* PASSO 4 - Botão chama a função */} - + {/* Métricas principais */} +
+ {metricas.map((m) => ( +
+ {m.icon} + {m.value} + {m.label} +
+ ))} +
+ + {/* Gráficos e Relatórios */} +
+ {/* Consultas realizadas por período */} +
+
+

Consultas por Período

+ +
+ + + + + + + + +
- {/* Card Pacientes */} -
-

Relatório de Pacientes

-

Informações gerais dos pacientes cadastrados.

- -
- - {/* Card Financeiro com gráfico */} -
-

Relatório Financeiro

- - + {/* Faturamento mensal/anual */} +
+
+

Faturamento Mensal

+ +
+ + - - - - + + - +
+
+ +
+ {/* Taxa de no-show */} +
+
+

Taxa de No-show

+ +
+ + + + + + + + + +
+ + {/* Indicadores de satisfação */} +
+
+

Satisfação dos Pacientes

+ +
+
+ 92% + Índice de satisfação geral +
+
+
+ +
+ {/* Pacientes mais atendidos */} +
+
+

Pacientes Mais Atendidos

+ +
+ + + + + + + + + {pacientesMaisAtendidos.map((p) => ( + + + + + ))} + +
PacienteConsultas
{p.nome}{p.consultas}
+
+ + {/* Médicos mais produtivos */} +
+
+

Médicos Mais Produtivos

+ +
+ + + + + + + + + {medicosMaisProdutivos.map((m) => ( + + + + + ))} + +
MédicoConsultas
{m.nome}{m.consultas}
+
+
+ +
+ {/* Análise de convênios */} +
+
+

Análise de Convênios

+ +
+ + + + {convenios.map((entry, index) => ( + + ))} + + + + + +
+ + {/* Performance por médico */} +
+
+

Performance por Médico

+ +
+ + + + + + + + + + {performancePorMedico.map((m) => ( + + + + + + ))} + +
MédicoConsultasAbsenteísmo (%)
{m.nome}{m.consultas}{m.absenteismo}
diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx index d81f419..7976db9 100644 --- a/susconecta/app/profissional/page.tsx +++ b/susconecta/app/profissional/page.tsx @@ -10,6 +10,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; +import { SimpleThemeToggle } from "@/components/simple-theme-toggle"; import { Table, TableBody, @@ -3270,13 +3271,16 @@ Nevo melanocítico benigno. Seguimento clínico recomendado. )}
- +
+ + +
diff --git a/susconecta/components/dashboard/header.tsx b/susconecta/components/dashboard/header.tsx index 5e23a67..0872b66 100644 --- a/susconecta/components/dashboard/header.tsx +++ b/susconecta/components/dashboard/header.tsx @@ -7,6 +7,7 @@ import { Input } from "@/components/ui/input" import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar" import { useState, useEffect, useRef } from "react" import { SidebarTrigger } from "../ui/sidebar" +import { SimpleThemeToggle } from "@/components/simple-theme-toggle"; export function PagesHeader({ title = "", subtitle = "" }: { title?: string, subtitle?: string }) { const { logout, user } = useAuth(); @@ -44,7 +45,12 @@ export function PagesHeader({ title = "", subtitle = "" }: { title?: string, sub - + + {/* Avatar Dropdown Simples */}
+

{formatDatePt(todayStr)}

+ + +
+
+ {consultasDoDia.length} consulta{consultasDoDia.length !== 1 ? 's' : ''} agendada{consultasDoDia.length !== 1 ? 's' : ''} +
+
+ {/* Lista de Consultas do Dia */} +
+ {consultasDoDia.length === 0 ? ( +
+ +

Nenhuma consulta agendada para este dia

+

Você pode agendar uma nova consulta

+ +
+ ) : ( + consultasDoDia.map(consulta => ( +
+
+
+
+
+
+ + {consulta.medico} +
+
+ {consulta.especialidade} • {consulta.local} +
+
+
+
+ + {consulta.hora} +
+
+
{consulta.status}
+
+
+ + {consulta.status !== 'Cancelada' && } + {consulta.status !== 'Cancelada' && } +
+
+
+ )) + )} +
+ + ) + } + + // Exames e laudos fictícios + const examesFicticios = [ + { + id: 1, + nome: "Hemograma Completo", + data: "2025-09-20", + status: "Disponível", + prontuario: "Paciente apresenta hemograma dentro dos padrões de normalidade. Sem alterações significativas.", + }, + { + id: 2, + nome: "Raio-X de Tórax", + data: "2025-08-10", + status: "Disponível", + prontuario: "Exame radiológico sem evidências de lesões pulmonares. Estruturas cardíacas normais.", + }, + { + id: 3, + nome: "Eletrocardiograma", + data: "2025-07-05", + status: "Disponível", + prontuario: "Ritmo sinusal, sem arritmias. Exame dentro da normalidade.", + }, + ]; + + const laudosFicticios = [ + { + id: 1, + nome: "Laudo Hemograma Completo", + data: "2025-09-21", + status: "Assinado", + laudo: "Hemoglobina, hematócrito, leucócitos e plaquetas dentro dos valores de referência. Sem anemias ou infecções detectadas.", + }, + { + id: 2, + nome: "Laudo Raio-X de Tórax", + data: "2025-08-11", + status: "Assinado", + laudo: "Radiografia sem alterações. Parênquima pulmonar preservado. Ausência de derrame pleural.", + }, + { + id: 3, + nome: "Laudo Eletrocardiograma", + data: "2025-07-06", + status: "Assinado", + laudo: "ECG normal. Não há sinais de isquemia ou sobrecarga.", + }, + ]; + + const [exameSelecionado, setExameSelecionado] = useState(null) + const [laudoSelecionado, setLaudoSelecionado] = useState(null) + + function ExamesLaudos() { + return ( +
+

Exames

+
+

Meus Exames

+
+ {examesFicticios.map(exame => ( +
+
+
{exame.nome}
+
Data: {new Date(exame.data).toLocaleDateString('pt-BR')}
+
+
+ + +
+
+ ))} +
+
+

Laudos

+
+

Meus Laudos

+
+ {laudosFicticios.map(laudo => ( +
+
+
{laudo.nome}
+
Data: {new Date(laudo.data).toLocaleDateString('pt-BR')}
+
+
+ + +
+
+ ))} +
+
+ + {/* Modal Prontuário Exame */} + !open && setExameSelecionado(null)}> + + + Prontuário do Exame + + {exameSelecionado && ( + <> +
{exameSelecionado.nome}
+
Data: {new Date(exameSelecionado.data).toLocaleDateString('pt-BR')}
+
{exameSelecionado.prontuario}
+ + )} +
+
+ + + +
+
+ + {/* Modal Visualizar Laudo */} + !open && setLaudoSelecionado(null)}> + + + Laudo Médico + + {laudoSelecionado && ( + <> +
{laudoSelecionado.nome}
+
Data: {new Date(laudoSelecionado.data).toLocaleDateString('pt-BR')}
+
{laudoSelecionado.laudo}
+ + )} +
+
+ + + +
+
+
+ ) + } + + // Mensagens fictícias recebidas do médico + const mensagensFicticias = [ + { + id: 1, + medico: "Dr. Carlos Andrade", + data: "2025-10-06T15:30:00", + conteudo: "Olá Maria, seu exame de hemograma está normal. Parabéns por manter seus exames em dia!", + lida: false + }, + { + id: 2, + medico: "Dra. Fernanda Lima", + data: "2025-09-21T10:15:00", + conteudo: "Maria, seu laudo de Raio-X já está disponível no sistema. Qualquer dúvida, estou à disposição.", + lida: true + }, + { + id: 3, + medico: "Dr. João Silva", + data: "2025-08-12T09:00:00", + conteudo: "Bom dia! Lembre-se de agendar seu retorno para acompanhamento da ortopedia.", + lida: true + }, + ]; + + function Mensagens() { + return ( +
+

Mensagens Recebidas

+
+ {mensagensFicticias.length === 0 ? ( +
+ +

Nenhuma mensagem recebida

+

Você ainda não recebeu mensagens dos seus médicos.

+
+ ) : ( + mensagensFicticias.map(msg => ( +
+
+
+ + {msg.medico} + {!msg.lida && Nova} +
+
{new Date(msg.data).toLocaleString('pt-BR')}
+
{msg.conteudo}
+
+
+ )) + )} +
+
+ ) + } + + function Perfil() { + return ( +
+
+

Meu Perfil

+ {!isEditingProfile ? ( + + ) : ( +
+ + +
+ )} +
+
+ {/* Informações Pessoais */} +
+

Informações Pessoais

+
+ +

{profileData.nome}

+ Este campo não pode ser alterado +
+
+ + {isEditingProfile ? ( + handleProfileChange('email', e.target.value)} /> + ) : ( +

{profileData.email}

+ )} +
+
+ + {isEditingProfile ? ( + handleProfileChange('telefone', e.target.value)} /> + ) : ( +

{profileData.telefone}

+ )} +
+
+ {/* Endereço e Contato */} +
+

Endereço

+
+ + {isEditingProfile ? ( + handleProfileChange('endereco', e.target.value)} /> + ) : ( +

{profileData.endereco}

+ )} +
+
+ + {isEditingProfile ? ( + handleProfileChange('cidade', e.target.value)} /> + ) : ( +

{profileData.cidade}

+ )} +
+
+ + {isEditingProfile ? ( + handleProfileChange('cep', e.target.value)} /> + ) : ( +

{profileData.cep}

+ )} +
+
+ + {isEditingProfile ? ( +