diff --git a/eslint.config.js b/eslint.config.js
index 90b0b03..18c13e3 100644
--- a/eslint.config.js
+++ b/eslint.config.js
@@ -1,19 +1,19 @@
// eslint.config.js
-import globals from "globals";
-import tseslint from "typescript-eslint";
-import eslint from "@eslint/js";
-import nextPlugin from "@next/eslint-plugin-next";
-import unicornPlugin from "eslint-plugin-unicorn";
-import prettierConfig from "eslint-config-prettier";
+import globals from 'globals';
+import tseslint from 'typescript-eslint';
+import eslint from '@eslint/js';
+import nextPlugin from '@next/eslint-plugin-next';
+import unicornPlugin from 'eslint-plugin-unicorn';
+import prettierConfig from 'eslint-config-prettier';
export default [
eslint.configs.recommended,
...tseslint.configs.recommended,
{
- files: ["**/*.{js,mjs,cjs,ts,jsx,tsx}"],
+ files: ['**/*.{js,mjs,cjs,ts,jsx,tsx}'],
plugins: {
- "@next/next": nextPlugin,
- "unicorn": unicornPlugin,
+ '@next/next': nextPlugin,
+ unicorn: unicornPlugin,
},
languageOptions: {
globals: {
@@ -22,14 +22,53 @@ export default [
},
parser: tseslint.parser,
parserOptions: {
- project: "./tsconfig.json",
+ project: './tsconfig.json',
},
},
rules: {
...nextPlugin.configs.recommended.rules,
- ...nextPlugin.configs["core-web-vitals"].rules,
- ...unicornPlugin.configs.recommended.rules,
- }
+ ...nextPlugin.configs['core-web-vitals'].rules,
+ 'unicorn/prevent-abbreviations': 'off',
+ 'unicorn/no-null': 'off',
+ 'unicorn/prefer-string-replace-all': 'off',
+ 'unicorn/prefer-string-slice': 'off',
+ 'unicorn/prefer-number-properties': 'off',
+ 'unicorn/no-array-reduce': 'off',
+ 'unicorn/no-array-for-each': 'off',
+ 'unicorn/prefer-global-this': 'off',
+ 'unicorn/no-useless-undefined': 'off',
+ 'unicorn/explicit-length-check': 'off',
+ 'unicorn/consistent-existence-index-check': 'off',
+ 'unicorn/prefer-ternary': 'off',
+ 'unicorn/numeric-separators-style': 'off',
+ 'unicorn/filename-case': [
+ 'error',
+ {
+ cases: {
+ camelCase: true,
+ pascalCase: true,
+ kebabCase: true,
+ },
+ },
+ ],
+ 'unicorn/prefer-add-event-listener': 'off',
+ 'unicorn/prefer-spread': 'off',
+ 'unicorn/consistent-function-scoping': 'off',
+ 'unicorn/no-document-cookie': 'off',
+ 'unicorn/no-negated-condition': 'off',
+ 'unicorn/prefer-code-point': 'off',
+ 'unicorn/prefer-single-call': 'off',
+ '@typescript-eslint/no-unused-vars': [
+ 'warn',
+ {
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_',
+ caughtErrorsIgnorePattern: '^_',
+ },
+ ],
+ '@typescript-eslint/no-explicit-any': 'off',
+ 'prefer-const': 'off',
+ },
},
prettierConfig,
-];
\ No newline at end of file
+];
diff --git a/package-lock.json b/package-lock.json
index bb4783c..ce0bf32 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -69,6 +69,7 @@
},
"devDependencies": {
"@eslint/js": "^9.36.0",
+ "@next/eslint-plugin-next": "^15.5.4",
"@tailwindcss/postcss": "^4.1.9",
"@types/node": "^22",
"@types/react": "^18",
@@ -79,6 +80,7 @@
"eslint-config-next": "^15.5.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-unicorn": "^61.0.2",
+ "globals": "^16.4.0",
"next": "^15.5.4",
"postcss": "^8.5",
"tailwindcss": "^4.1.9",
@@ -284,6 +286,19 @@
"concat-map": "0.0.1"
}
},
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/@eslint/eslintrc/node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -5399,19 +5414,6 @@
"eslint": ">=9.29.0"
}
},
- "node_modules/eslint-plugin-unicorn/node_modules/globals": {
- "version": "16.4.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
- "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
- "dev": true,
- "license": "MIT",
- "engines": {
- "node": ">=18"
- },
- "funding": {
- "url": "https://github.com/sponsors/sindresorhus"
- }
- },
"node_modules/eslint-scope": {
"version": "8.4.0",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
@@ -5917,9 +5919,9 @@
}
},
"node_modules/globals": {
- "version": "14.0.0",
- "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
- "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "version": "16.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
+ "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
"dev": true,
"license": "MIT",
"engines": {
@@ -9402,21 +9404,6 @@
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
- },
- "node_modules/@next/swc-win32-ia32-msvc": {
- "version": "14.2.16",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.16.tgz",
- "integrity": "sha512-jhPl3nN0oKEshJBNDAo0etGMzv0j3q3VYorTSFqH1o3rwv1MQRdor27u1zhkgsHPNeY1jxcgyx1ZsCkDD1IHgg==",
- "cpu": [
- "ia32"
- ],
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": ">= 10"
- }
}
}
}
diff --git a/package.json b/package.json
index d0eacfd..f6710c0 100644
--- a/package.json
+++ b/package.json
@@ -71,6 +71,7 @@
},
"devDependencies": {
"@eslint/js": "^9.36.0",
+ "@next/eslint-plugin-next": "^15.5.4",
"@tailwindcss/postcss": "^4.1.9",
"@types/node": "^22",
"@types/react": "^18",
@@ -81,6 +82,7 @@
"eslint-config-next": "^15.5.4",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-unicorn": "^61.0.2",
+ "globals": "^16.4.0",
"next": "^15.5.4",
"postcss": "^8.5",
"tailwindcss": "^4.1.9",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6b1edf6..6393a05 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -186,6 +186,9 @@ importers:
'@eslint/js':
specifier: ^9.36.0
version: 9.36.0
+ '@next/eslint-plugin-next':
+ specifier: ^15.5.4
+ version: 15.5.4
'@tailwindcss/postcss':
specifier: ^4.1.9
version: 4.1.13
@@ -216,6 +219,9 @@ importers:
eslint-plugin-unicorn:
specifier: ^61.0.2
version: 61.0.2(eslint@9.36.0(jiti@2.5.1))
+ globals:
+ specifier: ^16.4.0
+ version: 16.4.0
next:
specifier: ^15.5.4
version: 15.5.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
diff --git a/src/app/(main-routes)/calendar/index.css b/src/app/(admin)/calendar/index.css
similarity index 100%
rename from src/app/(main-routes)/calendar/index.css
rename to src/app/(admin)/calendar/index.css
diff --git a/src/app/(main-routes)/calendar/page.tsx b/src/app/(admin)/calendar/page.tsx
similarity index 98%
rename from src/app/(main-routes)/calendar/page.tsx
rename to src/app/(admin)/calendar/page.tsx
index b5a7947..f862ece 100644
--- a/src/app/(main-routes)/calendar/page.tsx
+++ b/src/app/(admin)/calendar/page.tsx
@@ -8,8 +8,6 @@ import dayGridPlugin from "@fullcalendar/daygrid";
import interactionPlugin from "@fullcalendar/interaction";
import timeGridPlugin from "@fullcalendar/timegrid";
import { EventInput } from "@fullcalendar/core/index.js";
-import { Sidebar } from "@/components/dashboard/sidebar";
-import { PagesHeader } from "@/components/dashboard/header";
import { Button } from "@/components/ui/button";
import {
mockAppointments,
diff --git a/src/app/(main-routes)/consultas/page.tsx b/src/app/(admin)/consultas/page.tsx
similarity index 100%
rename from src/app/(main-routes)/consultas/page.tsx
rename to src/app/(admin)/consultas/page.tsx
diff --git a/src/app/(main-routes)/dashboard/page.tsx b/src/app/(admin)/dashboard/page.tsx
similarity index 100%
rename from src/app/(main-routes)/dashboard/page.tsx
rename to src/app/(admin)/dashboard/page.tsx
diff --git a/src/app/(main-routes)/dashboard/relatorios/page.tsx b/src/app/(admin)/dashboard/relatorios/page.tsx
similarity index 100%
rename from src/app/(main-routes)/dashboard/relatorios/page.tsx
rename to src/app/(admin)/dashboard/relatorios/page.tsx
diff --git a/src/app/(main-routes)/doutores/page.tsx b/src/app/(admin)/doutores/page.tsx
similarity index 100%
rename from src/app/(main-routes)/doutores/page.tsx
rename to src/app/(admin)/doutores/page.tsx
diff --git a/src/app/financeiro/page.tsx b/src/app/(admin)/financeiro/page.tsx
similarity index 100%
rename from src/app/financeiro/page.tsx
rename to src/app/(admin)/financeiro/page.tsx
diff --git a/src/app/(main-routes)/layout.tsx b/src/app/(admin)/layout.tsx
similarity index 68%
rename from src/app/(main-routes)/layout.tsx
rename to src/app/(admin)/layout.tsx
index 6269b21..2a72c53 100644
--- a/src/app/(main-routes)/layout.tsx
+++ b/src/app/(admin)/layout.tsx
@@ -1,8 +1,8 @@
import type React from "react";
-import ProtectedRoute from "@/components/ProtectedRoute";
-import { Sidebar } from "@/components/dashboard/sidebar";
-import { SidebarProvider, SidebarTrigger } from "@/components/ui/sidebar";
-import { PagesHeader } from "@/components/dashboard/header";
+import ProtectedRoute from "@/components/layout/ProtectedRoute";
+import { Sidebar } from "@/components/layout/app/Sidebar";
+import { SidebarProvider } from "@/components/ui/sidebar";
+import { PagesHeader } from "@/components/layout/app/Header";
export default function MainRoutesLayout({
children,
diff --git a/src/app/(main-routes)/pacientes/loading.tsx b/src/app/(admin)/pacientes/loading.tsx
similarity index 100%
rename from src/app/(main-routes)/pacientes/loading.tsx
rename to src/app/(admin)/pacientes/loading.tsx
diff --git a/src/app/(main-routes)/pacientes/page.tsx b/src/app/(admin)/pacientes/page.tsx
similarity index 100%
rename from src/app/(main-routes)/pacientes/page.tsx
rename to src/app/(admin)/pacientes/page.tsx
diff --git a/src/app/procedimento/page.tsx b/src/app/(admin)/procedimento/page.tsx
similarity index 100%
rename from src/app/procedimento/page.tsx
rename to src/app/(admin)/procedimento/page.tsx
diff --git a/src/app/sobre/page.tsx b/src/app/(marketing)/sobre/page.tsx
similarity index 69%
rename from src/app/sobre/page.tsx
rename to src/app/(marketing)/sobre/page.tsx
index 4ce80eb..e163efa 100644
--- a/src/app/sobre/page.tsx
+++ b/src/app/(marketing)/sobre/page.tsx
@@ -1,6 +1,6 @@
-import { Header } from "@/components/header"
+import { Header } from "@/components/layout/marketing/Header"
import { AboutSection } from "@/components/about-section"
-import { Footer } from "@/components/footer"
+import { Footer } from "@/components/layout/marketing/Footer"
export default function AboutPage() {
return (
diff --git a/src/app/(paciente)/layout.tsx b/src/app/(paciente)/layout.tsx
new file mode 100644
index 0000000..05a5e0a
--- /dev/null
+++ b/src/app/(paciente)/layout.tsx
@@ -0,0 +1,11 @@
+'use client'
+import type { ReactNode } from 'react'
+import ProtectedRoute from '@/components/layout/ProtectedRoute'
+
+export default function PacienteLayout({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/app/(paciente)/paciente/page.tsx b/src/app/(paciente)/paciente/page.tsx
new file mode 100644
index 0000000..7cd6aff
--- /dev/null
+++ b/src/app/(paciente)/paciente/page.tsx
@@ -0,0 +1,92 @@
+'use client'
+import { useAuth } from '@/hooks/useAuth'
+import { Button } from '@/components/ui/button'
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
+import { User, LogOut, Home } from 'lucide-react'
+import Link from 'next/link'
+
+export default function PacientePage() {
+ const { logout, user } = useAuth()
+
+ const handleLogout = async () => {
+ console.log('[PACIENTE] Iniciando logout...')
+ await logout()
+ }
+
+ 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
+
+
+
+ {/* Informações do Login */}
+
+
+
+ Conectado como:
+
+
+ {user?.email || 'paciente@example.com'}
+
+
+ Tipo de usuário: Paciente
+
+
+
+
+ {/* Botão Voltar ao Início */}
+
+
+ {/* Botão de Logout */}
+
+
+ {/* Informação adicional */}
+
+
+ Em breve, mais funcionalidades estarão disponíveis
+
+
+
+
+
+ )
+}
diff --git a/src/app/(profissional)/layout.tsx b/src/app/(profissional)/layout.tsx
new file mode 100644
index 0000000..4bd26e2
--- /dev/null
+++ b/src/app/(profissional)/layout.tsx
@@ -0,0 +1,11 @@
+'use client'
+import type { ReactNode } from 'react'
+import ProtectedRoute from '@/components/layout/ProtectedRoute'
+
+export default function ProfissionalLayout({ children }: { children: ReactNode }) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/src/app/profissional/page.tsx b/src/app/(profissional)/page.tsx
similarity index 99%
rename from src/app/profissional/page.tsx
rename to src/app/(profissional)/page.tsx
index d81f419..07b158f 100644
--- a/src/app/profissional/page.tsx
+++ b/src/app/(profissional)/page.tsx
@@ -3,7 +3,6 @@
import React, { useState, useRef, useEffect } from "react";
import SignatureCanvas from "react-signature-canvas";
import Link from "next/link";
-import ProtectedRoute from "@/components/ProtectedRoute";
import { useAuth } from "@/hooks/useAuth";
import { buscarPacientes } from "@/lib/api";
import { Button } from "@/components/ui/button";
@@ -3251,8 +3250,7 @@ Nevo melanocítico benigno. Seguimento clínico recomendado.
};
return (
-
-
+
@@ -3510,7 +3508,6 @@ Nevo melanocítico benigno. Seguimento clínico recomendado.
)}
-
);
};
diff --git a/src/app/(profissional)/profissional/page.tsx b/src/app/(profissional)/profissional/page.tsx
new file mode 100644
index 0000000..07b158f
--- /dev/null
+++ b/src/app/(profissional)/profissional/page.tsx
@@ -0,0 +1,3514 @@
+"use client";
+
+import React, { useState, useRef, useEffect } from "react";
+import SignatureCanvas from "react-signature-canvas";
+import Link from "next/link";
+import { useAuth } from "@/hooks/useAuth";
+import { buscarPacientes } from "@/lib/api";
+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 {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"
+import { User, FolderOpen, X, Users, MessageSquare, ClipboardList, Plus, Edit, Trash2, ChevronLeft, ChevronRight, Clock, FileCheck, Upload, Download, Eye, History, Stethoscope, Pill, Activity, Search } from "lucide-react"
+import { Calendar as CalendarIcon, FileText, Settings } from "lucide-react";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+
+
+import dynamic from "next/dynamic";
+import dayGridPlugin from "@fullcalendar/daygrid";
+import timeGridPlugin from "@fullcalendar/timegrid";
+import interactionPlugin from "@fullcalendar/interaction";
+import ptBrLocale from "@fullcalendar/core/locales/pt-br";
+
+const FullCalendar = dynamic(() => import("@fullcalendar/react"), {
+ ssr: false,
+});
+
+const pacientes = [
+ { nome: "Ana Souza", cpf: "123.456.789-00", idade: 42, statusLaudo: "Finalizado" },
+ { nome: "Bruno Lima", cpf: "987.654.321-00", idade: 33, statusLaudo: "Pendente" },
+ { nome: "Carla Menezes", cpf: "111.222.333-44", idade: 67, statusLaudo: "Rascunho" },
+];
+
+const medico = {
+ nome: "Dr. Carlos Andrade",
+ identificacao: "CRM 000000 • Cardiologia e Dermatologia",
+ fotoUrl: "",
+}
+
+
+const colorsByType = {
+ Rotina: "#4dabf7",
+ Cardiologia: "#f76c6c",
+ Otorrino: "#f7b84d",
+ Pediatria: "#6cf78b",
+ Dermatologia: "#9b59b6",
+ Oftalmologia: "#2ecc71"
+};
+
+const ProfissionalPage = () => {
+ const { logout, user } = useAuth();
+ const [activeSection, setActiveSection] = useState('calendario');
+ const [pacienteSelecionado, setPacienteSelecionado] = useState
(null);
+
+ // Estados para edição de laudo
+ const [isEditingLaudoForPatient, setIsEditingLaudoForPatient] = useState(false);
+ const [patientForLaudo, setPatientForLaudo] = useState(null);
+
+ // Estados para o perfil do médico
+ const [isEditingProfile, setIsEditingProfile] = useState(false);
+ const [profileData, setProfileData] = useState({
+ nome: "Dr. Carlos Andrade",
+ email: user?.email || "carlos.andrade@hospital.com",
+ telefone: "(11) 99999-9999",
+ endereco: "Rua das Flores, 123 - Centro",
+ cidade: "São Paulo",
+ cep: "01234-567",
+ crm: "CRM 000000",
+ especialidade: "Cardiologia e Dermatologia",
+ biografia: "Médico especialista em cardiologia e dermatologia com mais de 15 anos de experiência em tratamentos clínicos e cirúrgicos."
+ });
+
+ // Estados para relatórios médicos
+ const [relatorioMedico, setRelatorioMedico] = useState({
+ pacienteNome: "",
+ pacienteCpf: "",
+ pacienteIdade: "",
+ profissionalNome: medico.nome,
+ profissionalCrm: medico.identificacao,
+ motivoRelatorio: "",
+ historicoClinico: "",
+ sinaisSintomas: "",
+ examesRealizados: "",
+ resultadosExames: "",
+ diagnosticos: "",
+ prognostico: "",
+ tratamentosRealizados: "",
+ recomendacoes: "",
+ dataRelatorio: new Date().toISOString().split('T')[0]
+ });
+ const [relatoriosMedicos, setRelatoriosMedicos] = useState([]);
+ const [editandoRelatorio, setEditandoRelatorio] = useState(null);
+
+ // Estados para funcionalidades do prontuário
+ const [consultasRegistradas, setConsultasRegistradas] = useState([]);
+ const [historicoMedico, setHistoricoMedico] = useState([]);
+ const [prescricoesMedicas, setPrescricoesMedicas] = useState([]);
+ const [examesSolicitados, setExamesSolicitados] = useState([]);
+ const [diagnosticos, setDiagnosticos] = useState([]);
+ const [evolucaoQuadro, setEvolucaoQuadro] = useState([]);
+ const [anexos, setAnexos] = useState([]);
+ const [abaProntuarioAtiva, setAbaProntuarioAtiva] = useState('nova-consulta');
+
+ // Estados para campos principais da consulta
+ const [consultaAtual, setConsultaAtual] = useState({
+ dataConsulta: new Date().toISOString().split('T')[0],
+ anamnese: "",
+ exameFisico: "",
+ hipotesesDiagnosticas: "",
+ condutaMedica: "",
+ prescricoes: "",
+ retornoAgendado: "",
+ cid10: ""
+ });
+
+ const [events, setEvents] = useState([
+
+ {
+ id: 1,
+ title: "Ana Souza",
+ type: "Cardiologia",
+ time: "09:00",
+ date: new Date().toISOString().split('T')[0],
+ pacienteId: "123.456.789-00",
+ color: colorsByType.Cardiologia
+ },
+ {
+ id: 2,
+ title: "Bruno Lima",
+ type: "Cardiologia",
+ time: "10:30",
+ date: new Date().toISOString().split('T')[0],
+ pacienteId: "987.654.321-00",
+ color: colorsByType.Cardiologia
+ },
+ {
+ id: 3,
+ title: "Carla Menezes",
+ type: "Dermatologia",
+ time: "14:00",
+ date: new Date().toISOString().split('T')[0],
+ pacienteId: "111.222.333-44",
+ color: colorsByType.Dermatologia
+ }
+ ]);
+ const [editingEvent, setEditingEvent] = useState(null);
+ const [showPopup, setShowPopup] = useState(false);
+ const [showActionModal, setShowActionModal] = useState(false);
+ const [step, setStep] = useState(1);
+ const [newEvent, setNewEvent] = useState({
+ title: "",
+ type: "",
+ time: "",
+ pacienteId: ""
+ });
+ const [selectedDate, setSelectedDate] = useState(null);
+ const [selectedEvent, setSelectedEvent] = useState(null);
+ const [currentCalendarDate, setCurrentCalendarDate] = useState(new Date());
+
+ const handleSave = (event: React.MouseEvent) => {
+ event.preventDefault();
+ console.log("Laudo salvo!");
+ window.scrollTo({ top: 0, behavior: "smooth" });
+ };
+
+ const handleAbrirProntuario = (paciente: any) => {
+ setPacienteSelecionado(paciente);
+
+ const pacienteLaudo = document.getElementById('pacienteLaudo') as HTMLInputElement;
+ if (pacienteLaudo) pacienteLaudo.value = paciente.nome;
+
+ const destinatario = document.getElementById('destinatario') as HTMLInputElement;
+ if (destinatario) destinatario.value = `${paciente.nome} - ${paciente.cpf}`;
+
+ const prontuarioSection = document.getElementById('prontuario-paciente');
+ if (prontuarioSection) {
+ prontuarioSection.scrollIntoView({ behavior: 'smooth' });
+ }
+ };
+
+ const handleFecharProntuario = () => {
+ setPacienteSelecionado(null);
+ };
+
+ const handleEditarLaudo = (paciente: any) => {
+ setPatientForLaudo(paciente);
+ setIsEditingLaudoForPatient(true);
+ setActiveSection('laudos');
+ };
+
+
+ const navigateDate = (direction: 'prev' | 'next') => {
+ const newDate = new Date(currentCalendarDate);
+ newDate.setDate(newDate.getDate() + (direction === 'next' ? 1 : -1));
+ setCurrentCalendarDate(newDate);
+ };
+
+ const goToToday = () => {
+ setCurrentCalendarDate(new Date());
+ };
+
+ const formatDate = (date: Date) => {
+ return date.toLocaleDateString('pt-BR', {
+ weekday: 'long',
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric'
+ });
+ };
+
+ // Filtrar eventos do dia atual
+ const getTodayEvents = () => {
+ const today = currentCalendarDate.toISOString().split('T')[0];
+ return events
+ .filter(event => event.date === today)
+ .sort((a, b) => a.time.localeCompare(b.time));
+ };
+
+ const getStatusColor = (type: string) => {
+ return colorsByType[type as keyof typeof colorsByType] || "#4dabf7";
+ };
+
+ // Funções para o perfil
+ const handleProfileChange = (field: string, value: string) => {
+ setProfileData(prev => ({
+ ...prev,
+ [field]: value
+ }));
+ };
+
+ const handleSaveProfile = () => {
+ setIsEditingProfile(false);
+ alert('Perfil atualizado com sucesso!');
+ };
+
+ const handleCancelEdit = () => {
+ setIsEditingProfile(false);
+ };
+
+ // Funções para relatórios médicos
+ const handleRelatorioChange = (field: string, value: string) => {
+ setRelatorioMedico(prev => ({
+ ...prev,
+ [field]: value
+ }));
+ };
+
+ const handleSalvarRelatorio = () => {
+ if (!relatorioMedico.pacienteNome || !relatorioMedico.motivoRelatorio) {
+ alert('Por favor, preencha pelo menos o nome do paciente e o motivo do relatório.');
+ return;
+ }
+
+ const novoRelatorio = {
+ ...relatorioMedico,
+ id: Date.now(),
+ dataGeracao: new Date().toLocaleString()
+ };
+
+ if (editandoRelatorio) {
+ setRelatoriosMedicos(prev =>
+ prev.map(rel => rel.id === editandoRelatorio.id ? novoRelatorio : rel)
+ );
+ setEditandoRelatorio(null);
+ alert('Relatório médico atualizado com sucesso!');
+ } else {
+ setRelatoriosMedicos(prev => [novoRelatorio, ...prev]);
+ alert('Relatório médico salvo com sucesso!');
+ }
+
+ // Limpar formulário
+ setRelatorioMedico({
+ pacienteNome: "",
+ pacienteCpf: "",
+ pacienteIdade: "",
+ profissionalNome: medico.nome,
+ profissionalCrm: medico.identificacao,
+ motivoRelatorio: "",
+ historicoClinico: "",
+ sinaisSintomas: "",
+ examesRealizados: "",
+ resultadosExames: "",
+ diagnosticos: "",
+ prognostico: "",
+ tratamentosRealizados: "",
+ recomendacoes: "",
+ dataRelatorio: new Date().toISOString().split('T')[0]
+ });
+ };
+
+ const handleEditarRelatorio = (relatorio: any) => {
+ setRelatorioMedico(relatorio);
+ setEditandoRelatorio(relatorio);
+ };
+
+ const handleExcluirRelatorio = (id: number) => {
+ if (confirm('Tem certeza que deseja excluir este relatório médico?')) {
+ setRelatoriosMedicos(prev => prev.filter(rel => rel.id !== id));
+ alert('Relatório médico excluído com sucesso!');
+ }
+ };
+
+ const handleCancelarEdicaoRelatorio = () => {
+ setEditandoRelatorio(null);
+ setRelatorioMedico({
+ pacienteNome: "",
+ pacienteCpf: "",
+ pacienteIdade: "",
+ profissionalNome: medico.nome,
+ profissionalCrm: medico.identificacao,
+ motivoRelatorio: "",
+ historicoClinico: "",
+ sinaisSintomas: "",
+ examesRealizados: "",
+ resultadosExames: "",
+ diagnosticos: "",
+ prognostico: "",
+ tratamentosRealizados: "",
+ recomendacoes: "",
+ dataRelatorio: new Date().toISOString().split('T')[0]
+ });
+ };
+
+
+ const handleDateClick = (arg: any) => {
+ setSelectedDate(arg.dateStr);
+ setNewEvent({ title: "", type: "", time: "", pacienteId: "" });
+ setStep(1);
+ setEditingEvent(null);
+ setShowPopup(true);
+ };
+
+
+ const handleAddEvent = () => {
+ const paciente = pacientes.find(p => p.nome === newEvent.title);
+ const eventToAdd = {
+ id: Date.now(),
+ title: newEvent.title,
+ type: newEvent.type,
+ time: newEvent.time,
+ date: selectedDate || currentCalendarDate.toISOString().split('T')[0],
+ pacienteId: paciente ? paciente.cpf : "",
+ color: colorsByType[newEvent.type as keyof typeof colorsByType] || "#4dabf7"
+ };
+ setEvents((prev) => [...prev, eventToAdd]);
+ setShowPopup(false);
+ };
+
+
+ const handleEditEvent = () => {
+ setEvents((prevEvents) =>
+ prevEvents.map((ev) =>
+ ev.id.toString() === editingEvent.id.toString()
+ ? {
+ ...ev,
+ title: newEvent.title,
+ type: newEvent.type,
+ time: newEvent.time,
+ color: colorsByType[newEvent.type as keyof typeof colorsByType] || "#4dabf7"
+ }
+ : ev
+ )
+ );
+ setEditingEvent(null);
+ setShowPopup(false);
+ setShowActionModal(false);
+ };
+
+
+ const handleNextStep = () => {
+ if (step < 3) setStep(step + 1);
+ else editingEvent ? handleEditEvent() : handleAddEvent();
+ };
+
+
+ const handleEventClick = (clickInfo: any) => {
+ setSelectedEvent(clickInfo.event);
+ setShowActionModal(true);
+ };
+
+
+ const handleDeleteEvent = () => {
+ if (!selectedEvent) return;
+ setEvents((prevEvents) =>
+ prevEvents.filter((ev: any) => ev.id.toString() !== selectedEvent.id.toString())
+ );
+ setShowActionModal(false);
+ };
+
+
+ const handleStartEdit = () => {
+ if (!selectedEvent) return;
+ setEditingEvent(selectedEvent);
+ setNewEvent({
+ title: selectedEvent.title,
+ type: selectedEvent.extendedProps.type,
+ time: selectedEvent.extendedProps.time,
+ pacienteId: selectedEvent.extendedProps.pacienteId || ""
+ });
+ setStep(1);
+ setShowActionModal(false);
+ setShowPopup(true);
+ };
+
+
+ const renderEventContent = (eventInfo: any) => {
+ const bg = eventInfo.event.backgroundColor || eventInfo.event.extendedProps?.color || "#4dabf7";
+
+ return (
+
+ {eventInfo.event.title}
+ •
+ {eventInfo.event.extendedProps.type}
+ •
+ {eventInfo.event.extendedProps.time}
+
+ );
+ };
+
+
+ const renderCalendarioSection = () => {
+ const todayEvents = getTodayEvents();
+
+ return (
+
+
+
Agenda do Dia
+
+
+ {/* Navegação de Data */}
+
+
+
+
+ {formatDate(currentCalendarDate)}
+
+
+
+
+
+ {todayEvents.length} consulta{todayEvents.length !== 1 ? 's' : ''} agendada{todayEvents.length !== 1 ? 's' : ''}
+
+
+
+ {/* Lista de Pacientes do Dia */}
+
+ {todayEvents.length === 0 ? (
+
+
+
Nenhuma consulta agendada para este dia
+
Agenda livre para este dia
+
+ ) : (
+ todayEvents.map((appointment) => {
+ const paciente = pacientes.find(p => p.nome === appointment.title);
+ return (
+
+
+
+
+
+
+
+ {appointment.title}
+
+ {paciente && (
+
+ CPF: {paciente.cpf} • {paciente.idade} anos
+
+ )}
+
+
+
+
+ {appointment.time}
+
+
+
+ {appointment.type}
+
+
+
+
+
+
+ Ver informações do paciente
+
+
+
+
+
+
+
+ );
+ })
+ )}
+
+
+ );
+ };
+
+
+ function PacientesSection({
+ handleAbrirProntuario,
+ setActiveSection,
+ }: {
+ handleAbrirProntuario: (paciente: any) => void;
+ setActiveSection: (section: string) => void;
+ }) {
+ return (
+
+
Gerenciamento de Pacientes
+
+
+
+ {/* Tabela de pacientes padrão */}
+
+
Pacientes Recentes
+
+
+
+ Paciente
+ CPF
+ Idade
+ Status do laudo
+ Ações
+
+
+
+ {pacientes.map((paciente) => (
+
+ {paciente.nome}
+ {paciente.cpf}
+ {paciente.idade}
+ {paciente.statusLaudo}
+
+
+
+
+
+ Ver informações do paciente
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+ );
+ };
+
+
+ const renderProntuarioSection = () => (
+
+
+
Prontuário do Paciente
+
+ {/* Informações do Paciente Selecionado */}
+ {pacienteSelecionado && (
+
+
+
Dados do Paciente
+
+
+
+
+
+
+
+
Nome:
+
{pacienteSelecionado.nome}
+
+
+
CPF:
+
{pacienteSelecionado.cpf}
+
+
+
Idade:
+
{pacienteSelecionado.idade} anos
+
+
+
+ )}
+
+ {/* Seletor de Paciente */}
+ {!pacienteSelecionado && (
+
+
+
+
+
Selecionar Paciente
+
Escolha um paciente para visualizar o prontuário completo
+
+
+
+
+
+
+
+
+ {/* Cards de pacientes para seleção rápida */}
+
+
Ou selecione rapidamente:
+
+ {pacientes.map((paciente) => (
+
setPacienteSelecionado(paciente)}
+ className="border rounded-lg p-4 hover:shadow-md hover:border-primary transition-all cursor-pointer group"
+ >
+
+
+
+
+
+
{paciente.nome}
+
CPF: {paciente.cpf}
+
{paciente.idade} anos
+
+
+
+
+ {paciente.statusLaudo}
+
+
+
+
+ ))}
+
+
+
+ )}
+
+ {/* Tabs de Navegação do Prontuário */}
+ {pacienteSelecionado && (
+
+
+
+ )}
+
+ {/* Conteúdo das Abas */}
+ {pacienteSelecionado && (
+
+ {abaProntuarioAtiva === 'nova-consulta' && renderNovaConsultaTab()}
+ {abaProntuarioAtiva === 'consultas' && renderConsultasTab()}
+ {abaProntuarioAtiva === 'historico' && renderHistoricoTab()}
+ {abaProntuarioAtiva === 'prescricoes' && renderPrescricoesTab()}
+ {abaProntuarioAtiva === 'exames' && renderExamesTab()}
+ {abaProntuarioAtiva === 'diagnosticos' && renderDiagnosticosTab()}
+ {abaProntuarioAtiva === 'evolucao' && renderEvolucaoTab()}
+ {abaProntuarioAtiva === 'anexos' && renderAnexosTab()}
+
+ )}
+
+
+ );
+
+ // Função para alterar campos da consulta atual
+ const handleConsultaChange = (field: string, value: string) => {
+ setConsultaAtual(prev => ({
+ ...prev,
+ [field]: value
+ }));
+ };
+
+ // Função para salvar a consulta
+ const handleSalvarConsulta = () => {
+ if (!consultaAtual.anamnese || !consultaAtual.exameFisico) {
+ alert('Por favor, preencha os campos que são obrigatórios.');
+ return;
+ }
+
+ const novaConsulta = {
+ ...consultaAtual,
+ id: Date.now(),
+ paciente: pacienteSelecionado?.nome,
+ dataCriacao: new Date().toLocaleString(),
+ profissional: medico.nome
+ };
+
+ setConsultasRegistradas(prev => [novaConsulta, ...prev]);
+
+ setConsultaAtual({
+ dataConsulta: new Date().toISOString().split('T')[0],
+ anamnese: "",
+ exameFisico: "",
+ hipotesesDiagnosticas: "",
+ condutaMedica: "",
+ prescricoes: "",
+ retornoAgendado: "",
+ cid10: ""
+ });
+
+ alert('Consulta registrada com sucesso!');
+ };
+
+ // Funções para renderizar cada aba do prontuário
+ const renderNovaConsultaTab = () => (
+
+
+
Registrar Nova Consulta
+
+
+
+
+
+
+
+ {/* Data da Consulta */}
+
+
+ {/* Anamnese */}
+
+
+
+
+ {/* Exame Físico */}
+
+
+
+
+ {/* Hipóteses Diagnósticas */}
+
+
+
+
+ {/* Conduta Médica */}
+
+
+
+
+ {/* Prescrições */}
+
+
+
+
+ {/* Retorno Agendado */}
+
+
+ handleConsultaChange('retornoAgendado', e.target.value)}
+ className="w-full"
+ />
+
+
+ {/* Informações do Registro */}
+
+
+
+
Paciente:
+
{pacienteSelecionado?.nome}
+
+
+
Profissional:
+
{medico.nome}
+
+
+
Data do Registro:
+
{new Date().toLocaleDateString('pt-BR')}
+
+
+
+
+
+ {/* Consultas Anteriores do Paciente */}
+ {consultasRegistradas.length > 0 && (
+
+
Consultas Anteriores
+
+ {consultasRegistradas
+ .filter(consulta => consulta.paciente === pacienteSelecionado?.nome)
+ .slice(0, 3)
+ .map((consulta) => (
+
+
+
+
+ Consulta de {new Date(consulta.dataConsulta).toLocaleDateString('pt-BR')}
+
+
+ Registrada em: {consulta.dataCriacao}
+
+
+ {consulta.cid10 && (
+
+ {consulta.cid10}
+
+ )}
+
+
+
Anamnese: {consulta.anamnese.substring(0, 100)}...
+ {consulta.hipotesesDiagnosticas && (
+
Diagnóstico: {consulta.hipotesesDiagnosticas.substring(0, 80)}...
+ )}
+
+
+ ))}
+
+
+ )}
+
+ );
+
+ const renderConsultasTab = () => (
+
+
+
Registro de Consultas
+
+
+
+
+
+
+
Consulta Cardiológica
+
27/09/2025 - 09:00
+
+
Finalizada
+
+
+
+
Motivo:
+
Dor no peito e falta de ar
+
+
+
Duração:
+
45 minutos
+
+
+
+
Observações:
+
Paciente relatou melhora dos sintomas após início do tratamento. Pressão arterial controlada.
+
+
+
+
+
+
+
Consulta Dermatológica
+
15/09/2025 - 14:30
+
+
Retorno Agendado
+
+
+
+
Motivo:
+
Avaliação de lesão cutânea
+
+
+
Duração:
+
30 minutos
+
+
+
+
+
+ );
+
+ const renderHistoricoTab = () => (
+
+
+
Histórico Médico Completo
+
+
+
+
+
+
Condições Pré-existentes
+
+ - Hipertensão arterial (diagnosticada em 2020)
+ - Diabetes tipo 2 (diagnosticada em 2018)
+ - Histórico familiar de doenças cardiovasculares
+
+
+
+
+
Cirurgias Anteriores
+
+
+ Apendicectomia
+ 15/03/2010
+
+
+ Colecistectomia laparoscópica
+ 22/08/2019
+
+
+
+
+
+
Alergias e Reações Adversas
+
+
+ Alergia
+ Penicilina - reação cutânea
+
+
+ Intolerância
+ Lactose - sintomas gastrintestinais
+
+
+
+
+
+ );
+
+ const renderPrescricoesTab = () => (
+
+
+
Prescrições Médicas
+
+
+
+
+
+
+
+
Prescrição Atual
+
Prescrita em 27/09/2025
+
+
Ativa
+
+
+
+
+
+
Losartana 50mg
+
1 comprimido pela manhã
+
Duração: 30 dias
+
+
+
+
+
+
+
+
Metformina 850mg
+
1 comprimido após café e jantar
+
Duração: 60 dias
+
+
+
+
+
+
+
+
+
+
+
Prescrições Anteriores
+
Histórico de medicamentos
+
+
+
+
+
+
Sinvastatina 20mg
+
Prescrita em 15/08/2025 - Finalizada
+
+
+
+
+
+
+
+ );
+
+ const renderExamesTab = () => (
+
+
+
Exames Solicitados
+
+
+
+
+
+
+
+
+
+
Ecocardiograma
+
Solicitado em 25/09/2025
+
Urgência: Normal
+
+
Pendente
+
+
+
+
+
Hemograma Completo
+
Solicitado em 27/09/2025
+
Urgência: Normal
+
+
Agendado
+
+
+
+
+
+
+
+
Resultados Disponíveis
+
+
+
+
+
+
Glicemia de Jejum
+
Realizado em 20/09/2025
+
Resultado: 95 mg/dL (Normal)
+
+
+
+
+
+
+
+
+
+
+ );
+
+ const renderDiagnosticosTab = () => (
+
+
+
Diagnósticos
+
+
+
+
+
+
Diagnósticos Ativos
+
+
+
+
+
Hipertensão Arterial Sistêmica
+
CID-10: I10
+
Diagnosticado em: 15/03/2020
+
Status: Controlada com medicação
+
+
Ativo
+
+
+
+
+
+
+
Diabetes Mellitus Tipo 2
+
CID-10: E11
+
Diagnosticado em: 10/08/2018
+
Status: Controlada com dieta e medicação
+
+
Ativo
+
+
+
+
+
+
+
Histórico de Diagnósticos
+
+
+
+
Gastrite Aguda
+
CID-10: K29.0 - Resolvido em 2023
+
+
Resolvido
+
+
+
+
+
+ );
+
+ const renderEvolucaoTab = () => (
+
+
+
Evolução do Quadro
+
+
+
+
+
+
+
+
Evolução Recente
+
27/09/2025 - 09:15
+
+
Melhora
+
+
+
Subjetivo: Paciente relatou diminuição significativa da dor no peito e melhora da capacidade respiratória.
+
Objetivo: PA: 130/80 mmHg, FC: 72 bpm, ausculta cardíaca sem alterações.
+
Avaliação: Resposta positiva ao tratamento iniciado, pressão arterial em níveis aceitáveis.
+
Plano: Manter medicação atual, retorno em 30 dias.
+
+
+
+
+
+
+
Evolução Anterior
+
15/09/2025 - 14:45
+
+
Estável
+
+
+
Subjetivo: Paciente apresentou episódios esporádicos de dor torácica leve.
+
Objetivo: Exame físico sem alterações significativas.
+
Plano: Ajuste da medicação e solicitação de exames complementares.
+
+
+
+
+ );
+
+ const renderAnexosTab = () => (
+
+
+
Anexos (Exames, Imagens)
+
+
+
+
+
+
Exames de Imagem
+
+
+
+
+
+
+
+
Radiografia de Tórax
+
20/09/2025
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Laudos e Documentos
+
+
+
+
+
+
+
+
Laudo de Ecocardiograma
+
10/08/2025 - Dr. Carlos Andrade
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Relatório de Consulta Especializada
+
05/09/2025 - Cardiologia
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+
+ const renderLaudosSection = () => (
+
+ {
+ setIsEditingLaudoForPatient(false);
+ setPatientForLaudo(null);
+ }}
+ />
+
+ );
+
+ // --- NOVO SISTEMA DE LAUDOS COMPLETO ---
+ function LaudoManager({ isEditingForPatient, selectedPatientForLaudo, onClosePatientEditor }: { isEditingForPatient?: boolean; selectedPatientForLaudo?: any; onClosePatientEditor?: () => void }) {
+ const [pacientesDisponiveis] = useState([
+ { id: "95170038", nome: "Ana Souza", cpf: "123.456.789-00", idade: 42, sexo: "Feminino" },
+ { id: "93203056", nome: "Bruno Lima", cpf: "987.654.321-00", idade: 33, sexo: "Masculino" },
+ { id: "92953542", nome: "Carla Menezes", cpf: "111.222.333-44", idade: 67, sexo: "Feminino" },
+ ]);
+
+ const [laudos] = useState([
+ {
+ id: "306494942",
+ data: "29/07/2025",
+ prazo: "29/07/2025",
+ paciente: { id: "95170038", nome: "Ana Souza", cpf: "123.456.789-00", idade: 42, sexo: "Feminino" },
+ executante: "Carlos Andrade",
+ exame: "Ecocardiograma",
+ status: "Entregue",
+ urgente: true,
+ especialidade: "Cardiologia",
+ conteudo: `**ECOCARDIOGRAMA TRANSTORÁCICO**
+
+**Dados do Paciente:**
+Nome: Ana Souza
+Idade: 42 anos
+Sexo: Feminino
+
+**Indicação Clínica:**
+Investigação de sopro cardíaco
+
+**Técnica:**
+Ecocardiograma transtorácico bidimensional com Doppler colorido e espectral.
+
+**Resultados:**
+- Átrio esquerdo: dimensões normais
+- Ventrículo esquerdo: função sistólica preservada, FEVE = 65%
+- Valvas cardíacas: sem alterações significativas
+- Pericárdio: sem derrame
+
+**Conclusão:**
+Exame ecocardiográfico dentro dos limites da normalidade.
+
+**CID:** I25.9`,
+ cid: "I25.9",
+ diagnostico: "Exame ecocardiográfico normal",
+ conclusao: "Função cardíaca preservada, sem alterações estruturais significativas."
+ },
+ {
+ id: "306463987",
+ data: "29/07/2025",
+ prazo: "29/07/2025",
+ paciente: { id: "93203056", nome: "Bruno Lima", cpf: "987.654.321-00", idade: 33, sexo: "Masculino" },
+ executante: "Carlos Andrade",
+ exame: "Eletrocardiograma",
+ status: "Entregue",
+ urgente: true,
+ especialidade: "Cardiologia",
+ conteudo: `**ELETROCARDIOGRAMA DE REPOUSO**
+
+**Dados do Paciente:**
+Nome: Bruno Lima
+Idade: 33 anos
+Sexo: Masculino
+
+**Indicação Clínica:**
+Dor precordial atípica
+
+**Técnica:**
+Eletrocardiograma de 12 derivações em repouso.
+
+**Resultados:**
+- Ritmo: sinusal regular
+- Frequência cardíaca: 72 bpm
+- Eixo elétrico: normal
+- Intervalos PR, QRS e QT: dentro dos limites normais
+- Ondas Q patológicas: ausentes
+- Alterações de ST-T: não observadas
+
+**Conclusão:**
+Eletrocardiograma normal.
+
+**CID:** Z01.8`,
+ cid: "Z01.8",
+ diagnostico: "ECG normal",
+ conclusao: "Traçado eletrocardiográfico dentro dos parâmetros de normalidade."
+ },
+ {
+ id: "306452545",
+ data: "29/07/2025",
+ prazo: "29/07/2025",
+ paciente: { id: "92953542", nome: "Carla Menezes", cpf: "111.222.333-44", idade: 67, sexo: "Feminino" },
+ executante: "Carlos Andrade",
+ exame: "Dermatoscopia",
+ status: "Entregue",
+ urgente: true,
+ especialidade: "Dermatologia",
+ conteudo: `**DERMATOSCOPIA DIGITAL**
+
+**Dados do Paciente:**
+Nome: Carla Menezes
+Idade: 67 anos
+Sexo: Feminino
+
+**Indicação Clínica:**
+Avaliação de lesão pigmentada em dorso
+
+**Técnica:**
+Dermatoscopia digital com magnificação de 10x e 20x.
+
+**Localização:**
+Região dorsal, região escapular direita
+
+**Achados Dermatoscópicos:**
+- Lesão melanocítica benigna
+- Padrão reticular típico
+- Bordas regulares e simétricas
+- Pigmentação homogênea
+- Ausência de estruturas atípicas
+
+**Conclusão:**
+Nevo melanocítico benigno. Seguimento clínico recomendado.
+
+**CID:** D22.5`,
+ cid: "D22.5",
+ diagnostico: "Nevo melanocítico benigno",
+ conclusao: "Lesão benigna, recomenda-se acompanhamento dermatológico de rotina."
+ },
+ ]);
+
+ const [activeTab, setActiveTab] = useState("entregue");
+ const [laudoSelecionado, setLaudoSelecionado] = useState(null);
+ const [isViewing, setIsViewing] = useState(false);
+ const [isCreatingNew, setIsCreatingNew] = useState(false);
+
+
+
+
+ return (
+
+ {/* Header */}
+
+
+
+
Gerenciamento de Laudo
+
Nesta seção você pode gerenciar todos os laudos gerados através da integração.
+
+
+
+
+
+ {/* Tabs */}
+
+
+
+
+
+
+
+ {/* Filtros */}
+
+
+
+
+
+
+
+ 01/07/2025
+ -
+ 31/07/2025
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Tabela */}
+
+
+
+
+ Pedido
+ Data
+ Prazo
+ Paciente
+ Executante/Solicitante
+ Exame/Classificação
+ Ação
+
+
+
+ {laudos.map((laudo) => (
+
+
+
+ {laudo.urgente && (
+
+ )}
+
{laudo.id}
+
+
+
+
+
+
+
+
{laudo.prazo}
+
11:48
+
+
+
+
+
+
+ {laudo.paciente.id}
+
+
{laudo.paciente.nome}
+
+
+ {laudo.executante}
+ {laudo.exame || "-"}
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+
+ {/* Visualizador de Laudo */}
+ {isViewing && laudoSelecionado && (
+
setIsViewing(false)} />
+ )}
+
+ {/* Editor para Novo Laudo */}
+ {isCreatingNew && (
+ setIsCreatingNew(false)}
+ isNewLaudo={true}
+ />
+ )}
+
+ {/* Editor para Paciente Específico */}
+ {isEditingForPatient && selectedPatientForLaudo && (
+ {})}
+ isNewLaudo={!selectedPatientForLaudo.conteudo}
+ preSelectedPatient={selectedPatientForLaudo.paciente || selectedPatientForLaudo}
+ />
+ )}
+
+ );
+ }
+
+ // Visualizador de Laudo (somente leitura)
+ function LaudoViewer({ laudo, onClose }: { laudo: any; onClose: () => void }) {
+ return (
+
+
+ {/* Header */}
+
+
+
Visualizar Laudo
+
+ Paciente: {laudo.paciente.nome} | Pedido: {laudo.id} | {laudo.especialidade}
+
+
+
+
+
+ {/* Content */}
+
+
+ {/* Header do Laudo */}
+
+
LAUDO MÉDICO - {laudo.especialidade.toUpperCase()}
+
+ Data: {laudo.data}
+
+
+
+ {/* Dados do Paciente */}
+
+
Dados do Paciente:
+
+
Nome: {laudo.paciente.nome}
+
ID: {laudo.paciente.id}
+
CPF: {laudo.paciente.cpf}
+
Idade: {laudo.paciente.idade} anos
+
Sexo: {laudo.paciente.sexo}
+
CID: {laudo.cid}
+
+
+
+ {/* Conteúdo do Laudo */}
+
+
')
+ }}
+ />
+
+
+ {/* Diagnóstico e Conclusão */}
+ {laudo.diagnostico && (
+
+
Diagnóstico:
+
{laudo.diagnostico}
+
+ )}
+
+ {laudo.conclusao && (
+
+
Conclusão:
+
{laudo.conclusao}
+
+ )}
+
+ {/* Assinatura */}
+
+
+
Dr. Carlos Andrade
+
CRM 000000 - {laudo.especialidade}
+
Data: {laudo.data}
+
+
+
+
+ {/* Footer */}
+
+
+
+ Status: {laudo.status} | Executante: {laudo.executante}
+
+
+
+
+
+
+ );
+ }
+
+ // Editor de Laudo Avançado (para novos laudos)
+ function LaudoEditor({ pacientes, laudo, onClose, isNewLaudo, preSelectedPatient }: { pacientes?: any[]; laudo?: any; onClose: () => void; isNewLaudo?: boolean; preSelectedPatient?: any }) {
+ const [activeTab, setActiveTab] = useState("editor");
+ const [content, setContent] = useState(laudo?.conteudo || "");
+ const [showPreview, setShowPreview] = useState(false);
+ const [pacienteSelecionado, setPacienteSelecionado] = useState
(preSelectedPatient || null);
+ const [campos, setCampos] = useState({
+ cid: laudo?.cid || "",
+ diagnostico: laudo?.diagnostico || "",
+ conclusao: laudo?.conclusao || "",
+ exame: laudo?.exame || "",
+ especialidade: laudo?.especialidade || "",
+ mostrarData: true,
+ mostrarAssinatura: true
+ });
+ const [imagens, setImagens] = useState([]);
+ const [templates] = useState([
+ "Exame normal, sem alterações significativas",
+ "Paciente em acompanhamento ambulatorial",
+ "Recomenda-se retorno em 30 dias",
+ "Alterações compatíveis com processo inflamatório",
+ "Resultado dentro dos parâmetros de normalidade",
+ "Recomendo seguimento com especialista"
+ ]);
+
+ const sigCanvasRef = useRef(null);
+
+ // Carregar dados do laudo existente quando disponível
+ useEffect(() => {
+ if (laudo && !isNewLaudo) {
+ setContent(laudo.conteudo || "");
+ setCampos({
+ cid: laudo.cid || "",
+ diagnostico: laudo.diagnostico || "",
+ conclusao: laudo.conclusao || "",
+ exame: laudo.exame || "",
+ especialidade: laudo.especialidade || "",
+ mostrarData: true,
+ mostrarAssinatura: true
+ });
+ setPacienteSelecionado(laudo.paciente);
+ }
+ }, [laudo, isNewLaudo]);
+
+ const formatText = (type: string) => {
+ const textarea = document.querySelector('textarea') as HTMLTextAreaElement;
+ if (!textarea) return;
+
+ const start = textarea.selectionStart;
+ const end = textarea.selectionEnd;
+ const selectedText = textarea.value.substring(start, end);
+
+ let formattedText = "";
+ switch(type) {
+ case "bold":
+ formattedText = selectedText ? `**${selectedText}**` : "**texto em negrito**";
+ break;
+ case "italic":
+ formattedText = selectedText ? `*${selectedText}*` : "*texto em itálico*";
+ break;
+ case "underline":
+ formattedText = selectedText ? `${selectedText}` : "texto sublinhado";
+ break;
+ case "list":
+ formattedText = selectedText ? `• ${selectedText}` : "• item da lista";
+ break;
+ }
+
+ const newText = textarea.value.substring(0, start) + formattedText + textarea.value.substring(end);
+ setContent(newText);
+ };
+
+ const insertTemplate = (template: string) => {
+ setContent((prev: string) => prev ? `${prev}\n\n${template}` : template);
+ };
+
+ const handleImageUpload = (e: React.ChangeEvent) => {
+ const files = Array.from(e.target.files || []);
+ files.forEach(file => {
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ setImagens(prev => [...prev, {
+ id: Date.now() + Math.random(),
+ name: file.name,
+ url: e.target?.result,
+ type: file.type
+ }]);
+ };
+ reader.readAsDataURL(file);
+ });
+ };
+
+ const processContent = (content: string) => {
+ return content
+ .replace(/\*\*(.*?)\*\*/g, '$1')
+ .replace(/\*(.*?)\*/g, '$1')
+ .replace(/(.*?)<\/u>/g, '$1')
+ .replace(/{{sexo_paciente}}/g, pacienteSelecionado?.sexo || laudo?.paciente?.sexo || '[SEXO]')
+ .replace(/{{diagnostico}}/g, campos.diagnostico || '[DIAGNÓSTICO]')
+ .replace(/{{conclusao}}/g, campos.conclusao || '[CONCLUSÃO]')
+ .replace(/\n/g, '
');
+ };
+
+ return (
+
+
+ {/* Header */}
+
+
+
+
+ {isNewLaudo ? "Novo Laudo Médico" : "Editar Laudo Existente"}
+
+ {isNewLaudo ? (
+
+ Crie um novo laudo selecionando um paciente
+
+ ) : (
+
+ Paciente: {laudo?.paciente?.nome} | Pedido: {laudo?.id} | {laudo?.especialidade}
+
+ )}
+
+
+
+
+ {/* Seleção de Paciente (apenas para novos laudos) */}
+ {isNewLaudo && (
+
+ {!pacienteSelecionado ? (
+
+
+
+
+ ) : (
+
+
+
{pacienteSelecionado.nome}
+
+ CPF: {pacienteSelecionado.cpf} | Idade: {pacienteSelecionado.idade} anos | Sexo: {pacienteSelecionado.sexo}
+
+
+ {!preSelectedPatient && (
+
+ )}
+
+ )}
+
+ )}
+
+
+ {/* Tabs */}
+
+ {isNewLaudo && (
+
+ )}
+
+
+
+
+
+
+ {/* Content */}
+
+ {/* Left Panel */}
+
+ {activeTab === "info" && isNewLaudo && (
+
+ {!pacienteSelecionado ? (
+
+
+
+
Selecione um paciente primeiro
+
+
+ ) : (
+
+
+
Informações do Exame
+
+
+
+
+
+
+
+
+
+ setCampos(prev => ({ ...prev, exame: e.target.value }))}
+ placeholder="Ex: Ecocardiograma, Dermatoscopia, etc."
+ />
+
+
+
+
+
+
Dados do Paciente
+
+
+
+ Nome: {pacienteSelecionado.nome}
+
+
+ ID: {pacienteSelecionado.id}
+
+
+ CPF: {pacienteSelecionado.cpf}
+
+
+ Idade: {pacienteSelecionado.idade} anos
+
+
+ Sexo: {pacienteSelecionado.sexo}
+
+
+
+
+
+
+
+
+
+ )}
+
+ )}
+
+ {activeTab === "editor" && (
+
+ {/* Toolbar */}
+
+
+
+
+
+
+
+
+
+ {/* Templates */}
+
+
Frases rápidas:
+
+ {templates.map((template, idx) => (
+
+ ))}
+
+
+
+
+ {/* Editor */}
+
+
+
+ )}
+
+ {activeTab === "imagens" && (
+
+
+
+
+
+
+
+ {imagens.map((img) => (
+
+ {img.type.startsWith('image/') ? (
+

+ ) : (
+
+
+
+ )}
+
{img.name}
+
+
+ ))}
+
+
+ )}
+
+ {activeTab === "campos" && (
+
+
+
+ setCampos(prev => ({ ...prev, cid: e.target.value }))}
+ placeholder="Ex: M25.5, I10, etc."
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Assinatura Digital */}
+
+
+
+
+
+
+
+
+
+
+ )}
+
+
+ {/* Preview Panel */}
+ {showPreview && (
+
+
+
Pré-visualização do Laudo
+
+
+
+ {/* Header do Laudo */}
+
+
+ LAUDO MÉDICO {campos.especialidade ? `- ${campos.especialidade.toUpperCase()}` : ''}
+
+ {campos.exame && (
+
{campos.exame}
+ )}
+ {campos.mostrarData && (
+
+ Data: {new Date().toLocaleDateString('pt-BR')}
+
+ )}
+
+
+ {/* Dados do Paciente */}
+ {(isNewLaudo ? pacienteSelecionado : laudo?.paciente) && (
+
+
Dados do Paciente:
+ {isNewLaudo && pacienteSelecionado ? (
+ <>
+
Nome: {pacienteSelecionado.nome}
+
ID: {pacienteSelecionado.id}
+
CPF: {pacienteSelecionado.cpf}
+
Idade: {pacienteSelecionado.idade} anos
+
Sexo: {pacienteSelecionado.sexo}
+ {campos.cid &&
CID: {campos.cid}
}
+ >
+ ) : (
+ <>
+
Nome: {laudo?.paciente?.nome}
+
ID: {laudo?.paciente?.id}
+ {campos.cid &&
CID: {campos.cid}
}
+ >
+ )}
+
+ )}
+
+ {/* Conteúdo */}
+
+
+ {/* Imagens */}
+ {imagens.length > 0 && (
+
+
Imagens:
+
+ {imagens.map((img) => (
+

+ ))}
+
+
+ )}
+
+ {/* Assinatura */}
+ {campos.mostrarAssinatura && (
+
+
+
Dr. Carlos Andrade
+
CRM 000000
+
+ )}
+
+
+
+ )}
+
+
+ {/* Footer */}
+
+
+
+ Este editor permite escrever relatórios de forma livre, com formatação de texto rica.
+
+
+
+
+
+
+
+
+
+
+ );
+ }
+
+
+ const renderComunicacaoSection = () => (
+
+
Comunicação com o Paciente
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
03/09/2025
+
+
+
+
Pendente
+
+
+
+
+
+
"Ok, obrigado pelo lembrete!"
+
03/09/2025 14:30
+
+
+
+
+
+
+
+ );
+
+ // Função para renderizar a seção de relatórios médicos
+ const renderRelatoriosMedicosSection = () => (
+
+
+
Relatórios Médicos
+ {editandoRelatorio && (
+
+ )}
+
+
+ {/* Formulário de Relatório Médico */}
+
+
+ {editandoRelatorio ? 'Editar Relatório Médico' : 'Novo Relatório Médico'}
+
+
+
+ {/* Identificação do Profissional */}
+
+
Identificação do Profissional
+
+
+
+ {/* Identificação do Paciente */}
+
+
Identificação do Paciente
+
+
+
+
+
+
+
+ handleRelatorioChange('pacienteCpf', e.target.value)}
+ placeholder="000.000.000-00"
+ />
+
+
+
+ handleRelatorioChange('pacienteIdade', e.target.value)}
+ placeholder="Idade do paciente"
+ />
+
+
+
+
+ {/* Informações do Relatório */}
+
+
Informações do Relatório
+
+
+
+
+
+
+ handleRelatorioChange('dataRelatorio', e.target.value)}
+ />
+
+
+
+
+
+
+
+
+ {/* Sinais, Sintomas e Exames */}
+
+
Sinais, Sintomas e Exames
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {/* Diagnósticos e Prognóstico */}
+
+
Diagnósticos e Prognóstico
+
+
+
+
+
+
+
+
+
+
+ {/* Tratamentos e Recomendações */}
+
+
Tratamentos e Recomendações
+
+
+
+
+
+
+
+
+
+
+ {/* Botões de Ação */}
+
+
+
+
+
+
+
+ {/* Lista de Relatórios Existentes */}
+
+
Relatórios Médicos Salvos
+
+ {relatoriosMedicos.length === 0 ? (
+
+
+
Nenhum relatório médico encontrado
+
Os relatórios salvos aparecerão aqui
+
+ ) : (
+
+ {relatoriosMedicos.map((relatorio) => (
+
+
+
+
{relatorio.pacienteNome}
+
CPF: {relatorio.pacienteCpf} • Idade: {relatorio.pacienteIdade} anos
+
Data do relatório: {new Date(relatorio.dataRelatorio).toLocaleDateString('pt-BR')}
+
Gerado em: {relatorio.dataGeracao}
+
+
+
+
+
+
+
+
+
+
Motivo:
+
{relatorio.motivoRelatorio}
+
+
+ {relatorio.diagnosticos && (
+
+
Diagnóstico(s):
+
{relatorio.diagnosticos}
+
+ )}
+
+ {relatorio.recomendacoes && (
+
+
Recomendações:
+
{relatorio.recomendacoes}
+
+ )}
+
+
+ ))}
+
+ )}
+
+
+ );
+
+ const renderPerfilSection = () => (
+
+
+
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}
+ )}
+
+
+
+
+
{profileData.crm}
+
Este campo não pode ser alterado
+
+
+
+
+ {isEditingProfile ? (
+
handleProfileChange('especialidade', e.target.value)}
+ />
+ ) : (
+
{profileData.especialidade}
+ )}
+
+
+
+ {/* Endereço e Contato */}
+
+
Endereço e Contato
+
+
+
+ {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 ? (
+
+
+
+
+ {/* Foto do Perfil */}
+
+
Foto do Perfil
+
+
+
+ {profileData.nome.split(' ').map(n => n[0]).join('').toUpperCase()}
+
+
+ {isEditingProfile && (
+
+
+
+ Formatos aceitos: JPG, PNG (máx. 2MB)
+
+
+ )}
+
+
+
+ );
+
+
+ const renderActiveSection = () => {
+ switch (activeSection) {
+ case 'calendario':
+ return renderCalendarioSection();
+ case 'pacientes':
+ return ;
+ case 'prontuario':
+ return renderProntuarioSection();
+ case 'laudos':
+ return renderLaudosSection();
+ case 'comunicacao':
+ return renderComunicacaoSection();
+ case 'relatorios-medicos':
+ return renderRelatoriosMedicosSection();
+ case 'perfil':
+ return renderPerfilSection();
+ default:
+ return renderCalendarioSection();
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
Conta do profissional
+
{medico.nome}
+
{medico.identificacao}
+ {user?.email && (
+
Logado como: {user.email}
+ )}
+
+
+
+
+
+
+ {}
+
+
+
+
+
Área do Profissional de Saúde
+
+
+
+ Bem-vindo à sua área exclusiva.
+
+ {renderActiveSection()}
+
+
+
+ {}
+ {showPopup && (
+
+
+
+
+ {step === 1 && (
+ <>
+
Selecionar Paciente
+
+ Data: {selectedDate ? new Date(selectedDate + 'T00:00:00').toLocaleDateString('pt-BR') : 'Não selecionada'}
+
+
+
+
+
+
+ >
+ )}
+
+ {step === 2 && (
+ <>
+
Tipo da Consulta
+
+
+
+
+
+ >
+ )}
+
+ {step === 3 && (
+ <>
+
Horário da Consulta
+
setNewEvent({ ...newEvent, time: e.target.value })}
+ className="mb-4"
+ />
+
+
+
+
+ >
+ )}
+
+
+ )}
+
+ {}
+ {showActionModal && selectedEvent && (
+
+
+
+ Consulta de {selectedEvent.title}
+
+
+ {selectedEvent.extendedProps.type} às {selectedEvent.extendedProps.time}
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default ProfissionalPage;
\ No newline at end of file
diff --git a/src/app/agenda/page.tsx b/src/app/agenda/page.tsx
index fff1a81..ac75b0c 100644
--- a/src/app/agenda/page.tsx
+++ b/src/app/agenda/page.tsx
@@ -56,6 +56,7 @@ export default function NovoAgendamentoPage() {
/>
+
);
}
\ No newline at end of file
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index b7b71f1..4a5ba4d 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,7 +1,7 @@
import type React from "react"
import type { Metadata } from "next"
import { AuthProvider } from "@/hooks/useAuth"
-import { ThemeProvider } from "@/components/theme-provider"
+import { ThemeProvider } from "@/components/layout/ThemeProvider"
import "./globals.css"
export const metadata: Metadata = {
diff --git a/src/app/login-admin/page.tsx b/src/app/login-admin/page.tsx
deleted file mode 100644
index 958c714..0000000
--- a/src/app/login-admin/page.tsx
+++ /dev/null
@@ -1,124 +0,0 @@
-'use client'
-import { useState } from 'react'
-import { useRouter } from 'next/navigation'
-import Link from 'next/link'
-import { useAuth } from '@/hooks/useAuth'
-import { Button } from '@/components/ui/button'
-import { Input } from '@/components/ui/input'
-import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
-import { Alert, AlertDescription } from '@/components/ui/alert'
-import { AuthenticationError } from '@/lib/auth'
-
-export default function LoginAdminPage() {
- const [credentials, setCredentials] = useState({ email: '', password: '' })
- const [error, setError] = useState('')
- const [loading, setLoading] = useState(false)
- const router = useRouter()
- const { login } = useAuth()
-
- const handleLogin = async (e: React.FormEvent) => {
- e.preventDefault()
- setLoading(true)
- setError('')
-
- try {
- // Tentar fazer login usando o contexto com tipo administrador
- const success = await login(credentials.email, credentials.password, 'administrador')
-
- if (success) {
- console.log('[LOGIN-ADMIN] Login bem-sucedido, redirecionando...')
-
- // Redirecionamento direto - solução que funcionou
- window.location.href = '/dashboard'
- }
- } catch (err) {
- console.error('[LOGIN-ADMIN] Erro no login:', err)
-
- if (err instanceof AuthenticationError) {
- setError(err.message)
- } else {
- setError('Erro inesperado. Tente novamente.')
- }
- } finally {
- setLoading(false)
- }
- }
-
- return (
-
-
-
-
- Login Administrador de Clínica
-
-
- Entre com suas credenciais para acessar o sistema administrativo
-
-
-
-
-
- Acesso Administrativo
-
-
-
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/app/login-paciente/page.tsx b/src/app/login-paciente/page.tsx
deleted file mode 100644
index 04f83b3..0000000
--- a/src/app/login-paciente/page.tsx
+++ /dev/null
@@ -1,122 +0,0 @@
-'use client'
-import { useState } from 'react'
-import { useRouter } from 'next/navigation'
-import Link from 'next/link'
-import { useAuth } from '@/hooks/useAuth'
-import { Button } from '@/components/ui/button'
-import { Input } from '@/components/ui/input'
-import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
-import { Alert, AlertDescription } from '@/components/ui/alert'
-import { AuthenticationError } from '@/lib/auth'
-
-export default function LoginPacientePage() {
- const [credentials, setCredentials] = useState({ email: '', password: '' })
- const [error, setError] = useState('')
- const [loading, setLoading] = useState(false)
- const router = useRouter()
- const { login } = useAuth()
-
- const handleLogin = async (e: React.FormEvent) => {
- e.preventDefault()
- setLoading(true)
- setError('')
-
- try {
- // Tentar fazer login usando o contexto com tipo paciente
- const success = await login(credentials.email, credentials.password, 'paciente')
-
- if (success) {
- // Redirecionar para a página do paciente
- router.push('/paciente')
- }
- } catch (err) {
- console.error('[LOGIN-PACIENTE] Erro no login:', err)
-
- if (err instanceof AuthenticationError) {
- setError(err.message)
- } else {
- setError('Erro inesperado. Tente novamente.')
- }
- } finally {
- setLoading(false)
- }
- }
-
- return (
-
-
-
-
- Sou Paciente
-
-
- Acesse sua área pessoal e gerencie suas consultas
-
-
-
-
-
- Entrar como Paciente
-
-
-
-
-
-
-
-
-
-
-
- )
-}
\ No newline at end of file
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
index dba68b1..73a2cd2 100644
--- a/src/app/login/page.tsx
+++ b/src/app/login/page.tsx
@@ -1,39 +1,99 @@
'use client'
-import { useState } from 'react'
-import { useRouter } from 'next/navigation'
+
+import { useEffect, useMemo, useState, type ChangeEvent, type FormEvent } from 'react'
import Link from 'next/link'
+import { useRouter, useSearchParams } from 'next/navigation'
import { useAuth } from '@/hooks/useAuth'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
import { Alert, AlertDescription } from '@/components/ui/alert'
import { AuthenticationError } from '@/lib/auth'
+import { AUTH_STORAGE_KEYS } from '@/types/auth'
+
+type UserRole = 'profissional' | 'paciente' | 'administrador'
+
+const USER_ROLES: readonly UserRole[] = ['profissional', 'paciente', 'administrador'] as const
+
+const roleConfig: Record = {
+ profissional: {
+ title: 'Login Profissional de Saúde',
+ subtitle: 'Entre com suas credenciais para acessar o sistema clínico',
+ redirect: '/profissional',
+ cta: 'Entrar como Profissional'
+ },
+ paciente: {
+ title: 'Sou Paciente',
+ subtitle: 'Acesse sua área pessoal e gerencie suas consultas',
+ redirect: '/paciente',
+ cta: 'Entrar na Minha Área'
+ },
+ administrador: {
+ title: 'Login Administrativo',
+ subtitle: 'Gerencie agendas, pacientes e finanças da clínica',
+ redirect: '/dashboard',
+ cta: 'Entrar no Painel Administrativo'
+ }
+}
+
+const isUserRole = (value: string | null): value is UserRole => {
+ if (!value) return false
+ return USER_ROLES.includes(value as UserRole)
+}
export default function LoginPage() {
const [credentials, setCredentials] = useState({ email: '', password: '' })
const [error, setError] = useState('')
const [loading, setLoading] = useState(false)
- const router = useRouter()
+ const [selectedRole, setSelectedRole] = useState('profissional')
const { login } = useAuth()
+ const router = useRouter()
+ const searchParams = useSearchParams()
- const handleLogin = async (e: React.FormEvent) => {
- e.preventDefault()
+ useEffect(() => {
+ const roleParam = searchParams.get('role')
+ if (isUserRole(roleParam)) {
+ setSelectedRole(roleParam)
+ if (typeof window !== 'undefined') {
+ localStorage.setItem(AUTH_STORAGE_KEYS.USER_TYPE, roleParam)
+ }
+ return
+ }
+
+ if (typeof window !== 'undefined') {
+ const storedRole = localStorage.getItem(AUTH_STORAGE_KEYS.USER_TYPE)
+ if (isUserRole(storedRole)) {
+ setSelectedRole(storedRole)
+ }
+ }
+ }, [searchParams])
+
+ const { title, subtitle, redirect, cta } = useMemo(() => roleConfig[selectedRole], [selectedRole])
+
+ const handleSelectRole = (role: UserRole) => {
+ setSelectedRole(role)
+ if (typeof window !== 'undefined') {
+ localStorage.setItem(AUTH_STORAGE_KEYS.USER_TYPE, role)
+ }
+ router.replace(`/login?role=${role}`, { scroll: false })
+ }
+
+ const handleLogin = async (event: FormEvent) => {
+ event.preventDefault()
setLoading(true)
setError('')
try {
- // Tentar fazer login usando o contexto com tipo profissional
- const success = await login(credentials.email, credentials.password, 'profissional')
-
+ const success = await login(credentials.email, credentials.password, selectedRole)
if (success) {
- console.log('[LOGIN-PROFISSIONAL] Login bem-sucedido, redirecionando...')
-
- // Redirecionamento direto - solução que funcionou
- window.location.href = '/profissional'
+ router.push(redirect)
}
- } catch (err) {
- console.error('[LOGIN-PROFISSIONAL] Erro no login:', err)
-
+ } catch (err: unknown) {
if (err instanceof AuthenticationError) {
setError(err.message)
} else {
@@ -49,18 +109,36 @@ export default function LoginPage() {
- Login Profissional de Saúde
+ {title}
- Entre com suas credenciais para acessar o sistema
+ {subtitle}
-
+
- Acesso ao Sistema
+ Escolha como deseja entrar
+
+ {USER_ROLES.map((role) => {
+ const isActive = role === selectedRole
+ return (
+
+ )
+ })}
+
+