feat: Implementa sistema completo de modo escuro

- Adiciona ThemeProvider com next-themes para controle de tema
- Implementa componente SimpleThemeToggle com ícones sol/lua
- Configura CSS variables completas para light/dark modes no globals.css
- Padroniza todas as páginas de autenticação (login, login-admin, login-paciente)
- Padroniza todos os módulos principais (dashboard, pacientes, doutores, consultas, calendar, configuração)
- Padroniza completamente área profissional com todas as seções:
  * Calendário e agendamentos
  * Busca e gestão de pacientes
  * Prontuários médicos completos
  * Comunicação e relatórios
  * Seções de exames (solicitados, resultados, diagnósticos, prescrições, evolução, anexos)
- Atualiza componentes UI (input, select, textarea) com bordas visíveis
- Implementa suporte dark mode em tooltips, badges de status e mensagens
- Garante acessibilidade e consistência visual em ambos os modos
- Mantém funcionalidades existentes sem breaking changes

Todos os elementos agora respondem adequadamente ao toggle de tema,
proporcionando experiência de usuário consistente e acessível.
This commit is contained in:
Jonas Francisco 2025-10-03 02:22:40 -03:00
parent b8369dd248
commit 47967eb37f
18 changed files with 332 additions and 216 deletions

View File

@ -85,7 +85,7 @@ export default function AgendamentoPage() {
}; };
return ( return (
<div className="flex flex-row"> <div className="flex flex-row bg-background">
<div className="flex w-full flex-col"> <div className="flex w-full flex-col">
<div className="flex w-full flex-col gap-10 p-6"> <div className="flex w-full flex-col gap-10 p-6">
<div className="flex flex-row justify-between items-center"> <div className="flex flex-row justify-between items-center">
@ -122,7 +122,7 @@ export default function AgendamentoPage() {
<div className="flex flex-row"> <div className="flex flex-row">
<Button <Button
variant={"outline"} variant={"outline"}
className="bg-gray-100 hover:bg-gray-200 hover:text-foreground rounded-l-[100px] rounded-r-[0px]" className="bg-muted hover:bg-muted/80 hover:text-foreground rounded-l-[100px] rounded-r-[0px]"
onClick={() => setActiveTab("calendar")} onClick={() => setActiveTab("calendar")}
> >
Calendário Calendário
@ -130,7 +130,7 @@ export default function AgendamentoPage() {
<Button <Button
variant={"outline"} variant={"outline"}
className="bg-gray-100 hover:bg-gray-200 hover:text-foreground rounded-r-[100px] rounded-l-[0px]" className="bg-muted hover:bg-muted/80 hover:text-foreground rounded-r-[100px] rounded-l-[0px]"
onClick={() => setActiveTab("espera")} onClick={() => setActiveTab("espera")}
> >
Lista de espera Lista de espera

View File

@ -45,12 +45,12 @@ export default function ConfiguracaoPage() {
] ]
return ( return (
<div className="p-6 space-y-6"> <div className="p-6 space-y-6 bg-background">
{/* título */} {/* título */}
<h1 className="text-2xl font-bold">Configurações</h1> <h1 className="text-2xl font-bold text-foreground">Configurações</h1>
{/* introdução */} {/* introdução */}
<p className="text-gray-600"> <p className="text-muted-foreground">
Ajuste os principais parâmetros do sistema. Escolha uma das seções abaixo Ajuste os principais parâmetros do sistema. Escolha uma das seções abaixo
para configurar horários, mensagens, notificações internas, permissões de usuários para configurar horários, mensagens, notificações internas, permissões de usuários
e regras de segurança da clínica. e regras de segurança da clínica.
@ -66,7 +66,7 @@ export default function ConfiguracaoPage() {
<CardTitle>{item.title}</CardTitle> <CardTitle>{item.title}</CardTitle>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-sm text-gray-600">{item.desc}</p> <p className="text-sm text-muted-foreground">{item.desc}</p>
</CardContent> </CardContent>
</Card> </Card>
</Link> </Link>

View File

@ -145,7 +145,7 @@ export default function ConsultasPage() {
if (showForm && editingAppointment) { if (showForm && editingAppointment) {
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 p-6 bg-background">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button type="button" variant="ghost" size="icon" onClick={handleCancel}> <Button type="button" variant="ghost" size="icon" onClick={handleCancel}>
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
@ -162,7 +162,7 @@ export default function ConsultasPage() {
} }
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 p-6 bg-background">
<div className="flex items-center justify-between gap-4 flex-wrap"> <div className="flex items-center justify-between gap-4 flex-wrap">
<div> <div>
<h1 className="text-2xl font-bold">Gerenciamento de Consultas</h1> <h1 className="text-2xl font-bold">Gerenciamento de Consultas</h1>

View File

@ -1,7 +1,7 @@
export default function DashboardPage() { export default function DashboardPage() {
return ( return (
<> <>
<div className="space-y-6 p-6"> <div className="space-y-6 p-6 bg-background">
<div> <div>
<h1 className="text-2xl font-bold text-foreground">Dashboard</h1> <h1 className="text-2xl font-bold text-foreground">Dashboard</h1>
<p className="text-muted-foreground"> <p className="text-muted-foreground">

View File

@ -287,7 +287,7 @@ export default function DoutoresPage() {
if (showForm) { if (showForm) {
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 p-6 bg-background">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button variant="ghost" size="icon" onClick={() => setShowForm(false)}> <Button variant="ghost" size="icon" onClick={() => setShowForm(false)}>
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
@ -307,7 +307,7 @@ export default function DoutoresPage() {
} }
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 p-6 bg-background">
<div className="flex items-center justify-between gap-4 flex-wrap"> <div className="flex items-center justify-between gap-4 flex-wrap">
<div> <div>
<h1 className="text-2xl font-bold">Médicos</h1> <h1 className="text-2xl font-bold">Médicos</h1>

View File

@ -166,7 +166,7 @@ export default function PacientesPage() {
if (showForm) { if (showForm) {
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 p-6 bg-background">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button variant="ghost" onClick={() => setShowForm(false)}> <Button variant="ghost" onClick={() => setShowForm(false)}>
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
@ -186,7 +186,7 @@ export default function PacientesPage() {
} }
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 p-6 bg-background">
<div className="flex items-center justify-between gap-4 flex-wrap"> <div className="flex items-center justify-between gap-4 flex-wrap">
<div> <div>
<h1 className="text-2xl font-bold">Pacientes</h1> <h1 className="text-2xl font-bold">Pacientes</h1>

View File

@ -39,6 +39,41 @@
--sidebar-ring: var(--color-blue-500); --sidebar-ring: var(--color-blue-500);
} }
.dark {
--background: #0f172a;
--foreground: #cbd5e1;
--card: #1e293b;
--card-foreground: #e2e8f0;
--popover: #1e293b;
--popover-foreground: #cbd5e1;
--primary: var(--color-blue-500);
--primary-foreground: #ffffff;
--secondary: #334155;
--secondary-foreground: #cbd5e1;
--muted: #334155;
--muted-foreground: #94a3b8;
--accent: #0891b2;
--accent-foreground: #ffffff;
--destructive: #dc2626;
--destructive-foreground: #ffffff;
--border: #334155;
--input: #334155;
--ring: var(--color-blue-500);
--chart-1: #0891b2;
--chart-2: #0f766e;
--chart-3: #f59e0b;
--chart-4: #dc2626;
--chart-5: #94a3b8;
--sidebar: #1e293b;
--sidebar-foreground: #cbd5e1;
--sidebar-primary: var(--color-blue-500);
--sidebar-primary-foreground: #ffffff;
--sidebar-accent: var(--color-blue-500);
--sidebar-accent-foreground: #ffffff;
--sidebar-border: #334155;
--sidebar-ring: var(--color-blue-500);
}
@theme inline { @theme inline {
--font-sans: var(--font-geist-sans); --font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono); --font-mono: var(--font-geist-mono);

View File

@ -1,6 +1,7 @@
import type React from "react" import type React from "react"
import type { Metadata } from "next" import type { Metadata } from "next"
import { AuthProvider } from "@/hooks/useAuth" import { AuthProvider } from "@/hooks/useAuth"
import { ThemeProvider } from "@/components/theme-provider"
import "./globals.css" import "./globals.css"
export const metadata: Metadata = { export const metadata: Metadata = {
@ -17,11 +18,13 @@ export default function RootLayout({
children: React.ReactNode children: React.ReactNode
}) { }) {
return ( return (
<html lang="pt-BR" className="antialiased"> <html lang="pt-BR" className="antialiased" suppressHydrationWarning>
<body style={{ fontFamily: "var(--font-geist-sans)" }}> <body style={{ fontFamily: "var(--font-geist-sans)" }}>
<AuthProvider> <ThemeProvider attribute="class" defaultTheme="light" enableSystem={false}>
{children} <AuthProvider>
</AuthProvider> {children}
</AuthProvider>
</ThemeProvider>
</body> </body>
</html> </html>
) )

View File

@ -45,13 +45,13 @@ export default function LoginAdminPage() {
} }
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8"> <div className="min-h-screen flex items-center justify-center bg-background py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8"> <div className="max-w-md w-full space-y-8">
<div className="text-center"> <div className="text-center">
<h2 className="mt-6 text-3xl font-extrabold text-gray-900"> <h2 className="mt-6 text-3xl font-extrabold text-foreground">
Login Administrador de Clínica Login Administrador de Clínica
</h2> </h2>
<p className="mt-2 text-sm text-gray-600"> <p className="mt-2 text-sm text-muted-foreground">
Entre com suas credenciais para acessar o sistema administrativo Entre com suas credenciais para acessar o sistema administrativo
</p> </p>
</div> </div>
@ -63,7 +63,7 @@ export default function LoginAdminPage() {
<CardContent> <CardContent>
<form onSubmit={handleLogin} className="space-y-6"> <form onSubmit={handleLogin} className="space-y-6">
<div> <div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700"> <label htmlFor="email" className="block text-sm font-medium text-foreground">
Email Email
</label> </label>
<Input <Input
@ -79,7 +79,7 @@ export default function LoginAdminPage() {
</div> </div>
<div> <div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700"> <label htmlFor="password" className="block text-sm font-medium text-foreground">
Senha Senha
</label> </label>
<Input <Input

View File

@ -43,13 +43,13 @@ export default function LoginPacientePage() {
} }
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8"> <div className="min-h-screen flex items-center justify-center bg-background py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8"> <div className="max-w-md w-full space-y-8">
<div className="text-center"> <div className="text-center">
<h2 className="mt-6 text-3xl font-extrabold text-gray-900"> <h2 className="mt-6 text-3xl font-extrabold text-foreground">
Portal do Paciente Portal do Paciente
</h2> </h2>
<p className="mt-2 text-sm text-gray-600"> <p className="mt-2 text-sm text-muted-foreground">
Acesse sua área pessoal e gerencie suas consultas Acesse sua área pessoal e gerencie suas consultas
</p> </p>
</div> </div>
@ -61,7 +61,7 @@ export default function LoginPacientePage() {
<CardContent> <CardContent>
<form onSubmit={handleLogin} className="space-y-6"> <form onSubmit={handleLogin} className="space-y-6">
<div> <div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700"> <label htmlFor="email" className="block text-sm font-medium text-foreground">
Email Email
</label> </label>
<Input <Input
@ -77,7 +77,7 @@ export default function LoginPacientePage() {
</div> </div>
<div> <div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700"> <label htmlFor="password" className="block text-sm font-medium text-foreground">
Senha Senha
</label> </label>
<Input <Input

View File

@ -45,13 +45,13 @@ export default function LoginPage() {
} }
return ( return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8"> <div className="min-h-screen flex items-center justify-center bg-background py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8"> <div className="max-w-md w-full space-y-8">
<div className="text-center"> <div className="text-center">
<h2 className="mt-6 text-3xl font-extrabold text-gray-900"> <h2 className="mt-6 text-3xl font-extrabold text-foreground">
Login Profissional de Saúde Login Profissional de Saúde
</h2> </h2>
<p className="mt-2 text-sm text-gray-600"> <p className="mt-2 text-sm text-muted-foreground">
Entre com suas credenciais para acessar o sistema Entre com suas credenciais para acessar o sistema
</p> </p>
</div> </div>
@ -63,7 +63,7 @@ export default function LoginPage() {
<CardContent> <CardContent>
<form onSubmit={handleLogin} className="space-y-6"> <form onSubmit={handleLogin} className="space-y-6">
<div> <div>
<label htmlFor="email" className="block text-sm font-medium text-gray-700"> <label htmlFor="email" className="block text-sm font-medium text-foreground">
Email Email
</label> </label>
<Input <Input
@ -79,7 +79,7 @@ export default function LoginPage() {
</div> </div>
<div> <div>
<label htmlFor="password" className="block text-sm font-medium text-gray-700"> <label htmlFor="password" className="block text-sm font-medium text-foreground">
Senha Senha
</label> </label>
<Input <Input

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ import Link from "next/link";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Menu, X } from "lucide-react"; import { Menu, X } from "lucide-react";
import { usePathname } from "next/navigation"; import { usePathname } from "next/navigation";
import { SimpleThemeToggle } from "@/components/simple-theme-toggle";
export function Header() { export function Header() {
const [isMenuOpen, setIsMenuOpen] = useState(false); const [isMenuOpen, setIsMenuOpen] = useState(false);
@ -43,6 +44,7 @@ export function Header() {
{} {}
<div className="hidden md:flex items-center space-x-3"> <div className="hidden md:flex items-center space-x-3">
<SimpleThemeToggle />
<Button <Button
variant="outline" variant="outline"
className="text-primary border-primary hover:bg-primary hover:text-primary-foreground bg-transparent" className="text-primary border-primary hover:bg-primary hover:text-primary-foreground bg-transparent"
@ -92,6 +94,7 @@ export function Header() {
Sobre Sobre
</Link> </Link>
<div className="flex flex-col space-y-2 pt-4"> <div className="flex flex-col space-y-2 pt-4">
<SimpleThemeToggle />
<Button <Button
variant="outline" variant="outline"
className="text-primary border-primary hover:bg-primary hover:text-primary-foreground bg-transparent" className="text-primary border-primary hover:bg-primary hover:text-primary-foreground bg-transparent"

View File

@ -0,0 +1,22 @@
"use client"
import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
export function SimpleThemeToggle() {
const { theme, setTheme } = useTheme()
const toggleTheme = () => {
setTheme(theme === "dark" ? "light" : "dark")
}
return (
<Button variant="outline" size="icon" onClick={toggleTheme}>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Alternar tema</span>
</Button>
)
}

View File

@ -0,0 +1,37 @@
"use client"
import * as React from "react"
import { Moon, Sun } from "lucide-react"
import { useTheme } from "next-themes"
import { Button } from "@/components/ui/button"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
export function ThemeToggle() {
const { setTheme } = useTheme()
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Alternar tema</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
Claro
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
Escuro
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
)
}

View File

@ -8,9 +8,11 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
type={type} type={type}
data-slot="input" data-slot="input"
className={cn( className={cn(
"file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input flex h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 flex h-9 w-full min-w-0 rounded-md bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]", "border-2 border-gray-300 dark:border-gray-600",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", "focus-visible:border-primary focus-visible:ring-primary/20 focus-visible:ring-2",
"hover:border-gray-400 dark:hover:border-gray-500",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive aria-invalid:border-2",
className className
)} )}
{...props} {...props}

View File

@ -37,7 +37,11 @@ function SelectTrigger({
data-slot="select-trigger" data-slot="select-trigger"
data-size={size} data-size={size}
className={cn( className={cn(
"border-input data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md border bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "data-[placeholder]:text-muted-foreground [&_svg:not([class*='text-'])]:text-muted-foreground dark:bg-input/30 dark:hover:bg-input/50 flex w-fit items-center justify-between gap-2 rounded-md bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
"border-2 border-gray-300 dark:border-gray-600",
"focus-visible:border-primary focus-visible:ring-primary/20 focus-visible:ring-2",
"hover:border-gray-400 dark:hover:border-gray-500",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive aria-invalid:border-2",
className className
)} )}
{...props} {...props}

View File

@ -7,7 +7,11 @@ function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
<textarea <textarea
data-slot="textarea" data-slot="textarea"
className={cn( className={cn(
"border-input placeholder:text-muted-foreground focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md border bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "placeholder:text-muted-foreground dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-md bg-transparent px-3 py-2 text-base shadow-xs transition-[color,box-shadow] outline-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
"border-2 border-gray-300 dark:border-gray-600",
"focus-visible:border-primary focus-visible:ring-primary/20 focus-visible:ring-2",
"hover:border-gray-400 dark:hover:border-gray-500",
"aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive aria-invalid:border-2",
className className
)} )}
{...props} {...props}