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/paciente/page.tsx b/susconecta/app/paciente/page.tsx index 44449d3..8bd8e24 100644 --- a/susconecta/app/paciente/page.tsx +++ b/susconecta/app/paciente/page.tsx @@ -1,94 +1,750 @@ 'use client' -import { useAuth } from '@/hooks/useAuth' +// import { useAuth } from '@/hooks/useAuth' // removido duplicado + +import { useState } from 'react' +import { useRouter } from 'next/navigation' import { Button } from '@/components/ui/button' +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogFooter } from '@/components/ui/dialog' import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card' -import { User, LogOut, Home } from 'lucide-react' +import { Input } from '@/components/ui/input' +import { Label } from '@/components/ui/label' +import { Textarea } from '@/components/ui/textarea' +import { Avatar, AvatarFallback } from '@/components/ui/avatar' +import { User, LogOut, Calendar, FileText, MessageCircle, UserCog, Home, Clock, FolderOpen, ChevronLeft, ChevronRight, MapPin, Stethoscope } from 'lucide-react' +import { SimpleThemeToggle } from '@/components/simple-theme-toggle' import Link from 'next/link' import ProtectedRoute from '@/components/ProtectedRoute' +import { useAuth } from '@/hooks/useAuth' +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select' +// Simulação de internacionalização básica +const strings = { + dashboard: 'Dashboard', + consultas: 'Consultas', + exames: 'Exames & Laudos', + mensagens: 'Mensagens', + perfil: 'Perfil', + sair: 'Sair', + proximaConsulta: 'Próxima Consulta', + ultimosExames: 'Últimos Exames', + mensagensNaoLidas: 'Mensagens Não Lidas', + agendar: 'Agendar', + reagendar: 'Reagendar', + cancelar: 'Cancelar', + detalhes: 'Detalhes', + adicionarCalendario: 'Adicionar ao calendário', + visualizarLaudo: 'Visualizar Laudo', + download: 'Download', + compartilhar: 'Compartilhar', + inbox: 'Caixa de Entrada', + enviarMensagem: 'Enviar Mensagem', + salvar: 'Salvar', + editarPerfil: 'Editar Perfil', + consentimentos: 'Consentimentos', + notificacoes: 'Preferências de Notificação', + vazio: 'Nenhum dado encontrado.', + erro: 'Ocorreu um erro. Tente novamente.', + carregando: 'Carregando...', + sucesso: 'Salvo com sucesso!', + erroSalvar: 'Erro ao salvar.', +} export default function PacientePage() { const { logout, user } = useAuth() + const [tab, setTab] = useState<'dashboard'|'consultas'|'exames'|'mensagens'|'perfil'>('dashboard') + + // Simulação de loaders, empty states e erro + const [loading, setLoading] = useState(false) + const [error, setError] = useState('') + const [toast, setToast] = useState<{type: 'success'|'error', msg: string}|null>(null) + + // Acessibilidade: foco visível e ordem de tabulação garantidos por padrão nos botões e inputs const handleLogout = async () => { - console.log('[PACIENTE] Iniciando logout...') - await logout() + setLoading(true) + setError('') + try { + await logout() + } catch { + setError(strings.erro) + } finally { + setLoading(false) + } } - return ( - -
- - -
- -
- - Portal do Paciente - -

- Bem-vindo ao seu espaço pessoal -

-
- - - {/* Informações do Paciente */} -
-

- Maria Silva Santos -

-

- CPF: 123.456.789-00 -

-

- Idade: 35 anos -

-
+ // Estado para edição do perfil + const [isEditingProfile, setIsEditingProfile] = useState(false) + const [profileData, setProfileData] = useState({ + nome: "Maria Silva Santos", + email: user?.email || "paciente@example.com", + telefone: "(11) 99999-9999", + endereco: "Rua das Flores, 123", + cidade: "São Paulo", + cep: "01234-567", + biografia: "Paciente desde 2020. Histórico de consultas e exames regulares.", + }) - {/* Informações do Login */} -
-
-

- Conectado como: -

-

- {user?.email || 'paciente@example.com'} -

-

- Tipo de usuário: Paciente -

+ const handleProfileChange = (field: string, value: string) => { + setProfileData(prev => ({ ...prev, [field]: value })) + } + const handleSaveProfile = () => { + setIsEditingProfile(false) + setToast({ type: 'success', msg: strings.sucesso }) + } + const handleCancelEdit = () => { + setIsEditingProfile(false) + } + function DashboardCards() { + return ( +
+ + + {strings.proximaConsulta} + 12/10/2025 + + + + {strings.ultimosExames} + 2 + + + + {strings.mensagensNaoLidas} + 1 + +
+ ) + } + + // Consultas fictícias + const [currentDate, setCurrentDate] = useState(new Date()) + const consultasFicticias = [ + { + id: 1, + medico: "Dr. Carlos Andrade", + especialidade: "Cardiologia", + local: "Clínica Coração Feliz", + data: new Date().toISOString().split('T')[0], + hora: "09:00", + status: "Confirmada" + }, + { + id: 2, + medico: "Dra. Fernanda Lima", + especialidade: "Dermatologia", + local: "Clínica Pele Viva", + data: new Date().toISOString().split('T')[0], + hora: "14:30", + status: "Pendente" + }, + { + id: 3, + medico: "Dr. João Silva", + especialidade: "Ortopedia", + local: "Hospital Ortopédico", + data: (() => { let d = new Date(); d.setDate(d.getDate()+1); return d.toISOString().split('T')[0] })(), + hora: "11:00", + status: "Cancelada" + }, + ]; + + function formatDatePt(date: Date) { + return date.toLocaleDateString('pt-BR', { weekday: 'long', day: 'numeric', month: 'long', year: 'numeric' }); + } + + function navigateDate(direction: 'prev' | 'next') { + const newDate = new Date(currentDate); + newDate.setDate(newDate.getDate() + (direction === 'next' ? 1 : -1)); + setCurrentDate(newDate); + } + function goToToday() { + setCurrentDate(new Date()); + } + + const todayStr = currentDate.toISOString().split('T')[0]; + const consultasDoDia = consultasFicticias.filter(c => c.data === todayStr); + + function Consultas() { + const router = useRouter() + const [tipoConsulta, setTipoConsulta] = useState<'teleconsulta' | 'presencial'>('teleconsulta') + const [especialidade, setEspecialidade] = useState('cardiologia') + const [localizacao, setLocalizacao] = useState('') + const [mostrarAgendadas, setMostrarAgendadas] = useState(false) + const hoverPrimaryClass = "transition duration-200 hover:bg-[#2563eb] hover:text-white focus-visible:ring-2 focus-visible:ring-[#2563eb]/60 active:scale-[0.97]" + const activeToggleClass = "w-full transition duration-200 focus-visible:ring-2 focus-visible:ring-[#2563eb]/60 active:scale-[0.97] bg-[#2563eb] text-white hover:bg-[#2563eb] hover:text-white" + const inactiveToggleClass = "w-full transition duration-200 bg-slate-50 text-[#2563eb] border border-[#2563eb]/30 hover:bg-slate-100 hover:text-[#2563eb] dark:bg-white/5 dark:text-white dark:hover:bg-white/10 dark:border-white/20" + const hoverPrimaryIconClass = "rounded-xl bg-white text-[#1e293b] border border-black/10 shadow-[0_2px_8px_rgba(0,0,0,0.03)] transition duration-200 hover:bg-[#2563eb] hover:text-white focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-[#2563eb] dark:bg-slate-800 dark:text-slate-100 dark:border-white/10 dark:shadow-none dark:hover:bg-[#2563eb] dark:hover:text-white" + const today = new Date(); today.setHours(0, 0, 0, 0); + const selectedDate = new Date(currentDate); selectedDate.setHours(0, 0, 0, 0); + const isSelectedDateToday = selectedDate.getTime() === today.getTime() + + const handlePesquisar = () => { + const params = new URLSearchParams({ + tipo: tipoConsulta, + especialidade, + local: localizacao + }) + router.push(`/resultados?${params.toString()}`) + } + + return ( +
+
+
+

Agende sua próxima consulta

+

Escolha o formato ideal, selecione a especialidade e encontre o profissional perfeito para você.

+
+ +
+
+ +
+ +
- {/* Botão Voltar ao Início */} -
+ + +
+ +
+ +
+
+ + setMostrarAgendadas(open)}> + + + Consultas agendadas + Gerencie suas consultas confirmadas, pendentes ou canceladas. + + +
+
+ + {formatDatePt(currentDate)} + + {isSelectedDateToday && ( + + )} +
+
+ {consultasDoDia.length} consulta{consultasDoDia.length !== 1 ? 's' : ''} agendada{consultasDoDia.length !== 1 ? 's' : ''} +
+
+ +
+ {consultasDoDia.length === 0 ? ( +
+ +

Nenhuma consulta agendada para este dia

+

Use a busca para marcar 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 ? ( +