From af7de1dd0c7b586bc699b416e0a93413e6045eca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?=
<166467972+JoaoGustavo-dev@users.noreply.github.com>
Date: Tue, 23 Sep 2025 01:23:41 -0300
Subject: [PATCH] add login-screen
---
package-lock.json | 51 ++-
package.json | 3 +-
susconecta/app/(main-routes)/layout.tsx | 21 +-
susconecta/app/layout.tsx | 7 +-
susconecta/app/login-admin/page.tsx | 123 ++++++
susconecta/app/login/page.tsx | 123 ++++++
susconecta/app/profissional/page.tsx | 243 +++++++++-
susconecta/components/ProtectedRoute.tsx | 43 ++
susconecta/components/dashboard/header.tsx | 117 +++--
susconecta/components/header.tsx | 8 +-
susconecta/hooks/useAuth.tsx | 109 +++++
susconecta/package-lock.json | 491 +++++++++++++++++++++
12 files changed, 1274 insertions(+), 65 deletions(-)
create mode 100644 susconecta/app/login-admin/page.tsx
create mode 100644 susconecta/app/login/page.tsx
create mode 100644 susconecta/components/ProtectedRoute.tsx
create mode 100644 susconecta/hooks/useAuth.tsx
diff --git a/package-lock.json b/package-lock.json
index 963283f..fe0f642 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,7 +8,8 @@
"@headlessui/react": "^2.2.7",
"@heroicons/react": "^2.2.0",
"date-fns": "^4.1.0",
- "react-big-calendar": "^1.19.4"
+ "react-big-calendar": "^1.19.4",
+ "react-signature-canvas": "^1.1.0-alpha.2"
}
},
"node_modules/@babel/runtime": {
@@ -266,6 +267,12 @@
"csstype": "^3.0.2"
}
},
+ "node_modules/@types/signature_pad": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/@types/signature_pad/-/signature_pad-2.3.6.tgz",
+ "integrity": "sha512-v3j92gCQJoxomHhd+yaG4Vsf8tRS/XbzWKqDv85UsqjMGy4zhokuwKe4b6vhbgncKkh+thF+gpz6+fypTtnFqQ==",
+ "license": "MIT"
+ },
"node_modules/@types/warning": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
@@ -520,6 +527,36 @@
"react-dom": ">=16.3.0"
}
},
+ "node_modules/react-signature-canvas": {
+ "version": "1.1.0-alpha.2",
+ "resolved": "https://registry.npmjs.org/react-signature-canvas/-/react-signature-canvas-1.1.0-alpha.2.tgz",
+ "integrity": "sha512-tKUNk3Gmh04Ug4K8p5g8Is08BFUKvbXxi0PyetQ/f8OgCBzcx4vqNf9+OArY/TdNdfHtswXQNRwZD6tyELjkjQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@babel/runtime": "^7.17.9",
+ "@types/signature_pad": "^2.3.0",
+ "signature_pad": "^2.3.2",
+ "trim-canvas": "^0.1.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/agilgur5"
+ },
+ "peerDependencies": {
+ "@types/prop-types": "^15.7.3",
+ "@types/react": "0.14 - 19",
+ "prop-types": "^15.5.8",
+ "react": "0.14 - 19",
+ "react-dom": "0.14 - 19"
+ },
+ "peerDependenciesMeta": {
+ "@types/prop-types": {
+ "optional": true
+ },
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/scheduler": {
"version": "0.26.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
@@ -527,12 +564,24 @@
"license": "MIT",
"peer": true
},
+ "node_modules/signature_pad": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/signature_pad/-/signature_pad-2.3.2.tgz",
+ "integrity": "sha512-peYXLxOsIY6MES2TrRLDiNg2T++8gGbpP2yaC+6Ohtxr+a2dzoaqWosWDY9sWqTAAk6E/TyQO+LJw9zQwyu5kA==",
+ "license": "MIT"
+ },
"node_modules/tabbable": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
"integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
"license": "MIT"
},
+ "node_modules/trim-canvas": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/trim-canvas/-/trim-canvas-0.1.2.tgz",
+ "integrity": "sha512-nd4Ga3iLFV94mdhW9JFMLpQbHUyCQuhFOD71PEAt1NjtMD5wbZctzhX8c3agHNybMR5zXD1XTGoIEWk995E6pQ==",
+ "license": "Apache-2.0"
+ },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
diff --git a/package.json b/package.json
index e0d27d7..e0ec9ff 100644
--- a/package.json
+++ b/package.json
@@ -3,6 +3,7 @@
"@headlessui/react": "^2.2.7",
"@heroicons/react": "^2.2.0",
"date-fns": "^4.1.0",
- "react-big-calendar": "^1.19.4"
+ "react-big-calendar": "^1.19.4",
+ "react-signature-canvas": "^1.1.0-alpha.2"
}
}
diff --git a/susconecta/app/(main-routes)/layout.tsx b/susconecta/app/(main-routes)/layout.tsx
index 68be841..d0e759f 100644
--- a/susconecta/app/(main-routes)/layout.tsx
+++ b/susconecta/app/(main-routes)/layout.tsx
@@ -1,4 +1,5 @@
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";
@@ -9,14 +10,16 @@ export default function MainRoutesLayout({
children: React.ReactNode;
}) {
return (
-
-
-
-
-
- {children}
-
-
-
+
+
+
+
+
+
+ {children}
+
+
+
+
);
}
diff --git a/susconecta/app/layout.tsx b/susconecta/app/layout.tsx
index f127de6..52ed9d1 100644
--- a/susconecta/app/layout.tsx
+++ b/susconecta/app/layout.tsx
@@ -1,5 +1,6 @@
import type React from "react"
import type { Metadata } from "next"
+import { AuthProvider } from "@/hooks/useAuth"
import "./globals.css"
export const metadata: Metadata = {
@@ -17,7 +18,11 @@ export default function RootLayout({
}) {
return (
- {children}
+
+
+ {children}
+
+
)
}
diff --git a/susconecta/app/login-admin/page.tsx b/susconecta/app/login-admin/page.tsx
new file mode 100644
index 0000000..8570b02
--- /dev/null
+++ b/susconecta/app/login-admin/page.tsx
@@ -0,0 +1,123 @@
+'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'
+
+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('')
+
+ // Simular delay de autenticação
+ await new Promise(resolve => setTimeout(resolve, 1000))
+
+ // Tentar fazer login usando o contexto com tipo administrador
+ const success = login(credentials.email, credentials.password, 'administrador')
+
+ if (success) {
+ // Redirecionar para o dashboard do administrador
+ setTimeout(() => {
+ router.push('/dashboard')
+
+ // Fallback: usar window.location se router.push não funcionar
+ setTimeout(() => {
+ if (window.location.pathname === '/login-admin') {
+ window.location.href = '/dashboard'
+ }
+ }, 100)
+ }, 100)
+ } else {
+ setError('Email ou senha incorretos')
+ }
+
+ 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/susconecta/app/login/page.tsx b/susconecta/app/login/page.tsx
new file mode 100644
index 0000000..b308b85
--- /dev/null
+++ b/susconecta/app/login/page.tsx
@@ -0,0 +1,123 @@
+'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'
+
+export default function LoginPage() {
+ 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('')
+
+ // Simular delay de autenticação
+ await new Promise(resolve => setTimeout(resolve, 1000))
+
+ // Tentar fazer login usando o contexto com tipo profissional
+ const success = login(credentials.email, credentials.password, 'profissional')
+
+ if (success) {
+ // Redirecionar para a página do profissional
+ setTimeout(() => {
+ router.push('/profissional')
+
+ // Fallback: usar window.location se router.push não funcionar
+ setTimeout(() => {
+ if (window.location.pathname === '/login') {
+ window.location.href = '/profissional'
+ }
+ }, 100)
+ }, 100)
+ } else {
+ setError('Email ou senha incorretos')
+ }
+
+ setLoading(false)
+ }
+
+ return (
+
+
+
+
+ Login Profissional de Saúde
+
+
+ Entre com suas credenciais para acessar o sistema
+
+
+
+
+
+ Acesso ao Sistema
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
\ No newline at end of file
diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx
index a796679..1625bce 100644
--- a/susconecta/app/profissional/page.tsx
+++ b/susconecta/app/profissional/page.tsx
@@ -5,6 +5,8 @@ import SignatureCanvas from "react-signature-canvas";
import ReactQuill from "react-quill";
import "react-quill/dist/quill.snow.css";
import Link from "next/link";
+import ProtectedRoute from "@/components/ProtectedRoute";
+import { useAuth } from "@/hooks/useAuth";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
@@ -68,8 +70,24 @@ const colorsByType = {
};
const ProfissionalPage = () => {
+ const { logout, userEmail } = useAuth();
const [activeSection, setActiveSection] = useState('calendario');
const [pacienteSelecionado, setPacienteSelecionado] = useState(null);
+
+ // Estados para o perfil do médico
+ const [isEditingProfile, setIsEditingProfile] = useState(false);
+ const [profileData, setProfileData] = useState({
+ nome: "Dr. Carlos Andrade",
+ email: userEmail || "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",
+ biografia: "Médico cardiologista com mais de 15 anos de experiência em cirurgias cardíacas e tratamentos preventivos."
+ });
+
const [events, setEvents] = useState([
{
@@ -171,6 +189,23 @@ const ProfissionalPage = () => {
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);
+ };
+
const handleDateClick = (arg: any) => {
setSelectedDate(arg.dateStr);
@@ -739,9 +774,166 @@ function LaudoEditor() {
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)
+
+
+ )}
+
+
+
);
@@ -765,20 +957,33 @@ function LaudoEditor() {
};
return (
-
-
-
-
-
-
-
-
-
-
Conta do profissional
-
{medico.nome}
-
{medico.identificacao}
-
-
+
+
+
+
+
+
+
+
+
+
+
+
Conta do profissional
+
{medico.nome}
+
{medico.identificacao}
+ {userEmail && (
+
Logado como: {userEmail}
+ )}
+
+
+
+
{}
@@ -841,6 +1046,7 @@ function LaudoEditor() {
+
Bem-vindo à sua área exclusiva.
@@ -1001,7 +1207,8 @@ function LaudoEditor() {
)}
-
+
+
);
};
diff --git a/susconecta/components/ProtectedRoute.tsx b/susconecta/components/ProtectedRoute.tsx
new file mode 100644
index 0000000..424b870
--- /dev/null
+++ b/susconecta/components/ProtectedRoute.tsx
@@ -0,0 +1,43 @@
+'use client'
+import { useEffect } from 'react'
+import { useRouter } from 'next/navigation'
+import { useAuth } from '@/hooks/useAuth'
+
+interface ProtectedRouteProps {
+ children: React.ReactNode
+ requiredUserType?: string
+}
+
+export default function ProtectedRoute({ children, requiredUserType }: ProtectedRouteProps) {
+ const { isAuthenticated, userType, checkAuth } = useAuth()
+ const router = useRouter()
+
+ useEffect(() => {
+ checkAuth()
+ }, [checkAuth])
+
+ useEffect(() => {
+ if (!isAuthenticated) {
+ console.log('Usuário não autenticado, redirecionando para login...')
+ router.push('/login')
+ } else if (requiredUserType && userType !== requiredUserType) {
+ console.log(`Tipo de usuário incorreto. Esperado: ${requiredUserType}, Atual: ${userType}`)
+ router.push('/login')
+ } else {
+ console.log('Usuário autenticado!')
+ }
+ }, [isAuthenticated, userType, requiredUserType, router])
+
+ if (!isAuthenticated || (requiredUserType && userType !== requiredUserType)) {
+ return (
+
+
+
+
Redirecionando para login...
+
+
+ )
+ }
+
+ return <>{children}>
+}
\ No newline at end of file
diff --git a/susconecta/components/dashboard/header.tsx b/susconecta/components/dashboard/header.tsx
index ff94891..6d5c1b8 100644
--- a/susconecta/components/dashboard/header.tsx
+++ b/susconecta/components/dashboard/header.tsx
@@ -1,20 +1,34 @@
"use client"
-import { Bell, Search } from "lucide-react"
+import { Bell, Search, ChevronDown } from "lucide-react"
+import { useAuth } from "@/hooks/useAuth"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
-import {
- DropdownMenu,
- DropdownMenuContent,
- DropdownMenuItem,
- DropdownMenuLabel,
- DropdownMenuSeparator,
- DropdownMenuTrigger,
-} from "@/components/ui/dropdown-menu"
+import { useState, useEffect, useRef } from "react"
import { SidebarTrigger } from "../ui/sidebar"
export function PagesHeader({ title = "", subtitle = "" }: { title?: string, subtitle?: string }) {
+ const { logout, userEmail, userType } = useAuth();
+ const [dropdownOpen, setDropdownOpen] = useState(false);
+ const dropdownRef = useRef(null);
+
+ // Fechar dropdown quando clicar fora
+ useEffect(() => {
+ function handleClickOutside(event: MouseEvent) {
+ if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
+ setDropdownOpen(false);
+ }
+ }
+
+ if (dropdownOpen) {
+ document.addEventListener('mousedown', handleClickOutside);
+ return () => {
+ document.removeEventListener('mousedown', handleClickOutside);
+ };
+ }
+ }, [dropdownOpen]);
+
return (