diff --git a/app/manager/dashboard/page.tsx b/app/manager/dashboard/page.tsx index 6e1dd10..df56541 100644 --- a/app/manager/dashboard/page.tsx +++ b/app/manager/dashboard/page.tsx @@ -1,41 +1,105 @@ -import ManagerLayout from "@/components/manager-layout" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Calendar, Clock, User, Plus } from "lucide-react" -import Link from "next/link" +"use client"; + +import ManagerLayout from "@/components/manager-layout"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Calendar, Clock, Plus, User } from "lucide-react"; +import Link from "next/link"; +import React, { useState, useEffect } from "react"; +import { usersService } from "services/usersApi.mjs"; +import { doctorsService } from "services/doctorsApi.mjs"; export default function ManagerDashboard() { + // 🔹 Estados para usuários + const [firstUser, setFirstUser] = useState(null); + const [loadingUser, setLoadingUser] = useState(true); + + // 🔹 Estados para médicos + const [doctors, setDoctors] = useState([]); + const [loadingDoctors, setLoadingDoctors] = useState(true); + + // 🔹 Buscar primeiro usuário + useEffect(() => { + async function fetchFirstUser() { + try { + const data = await usersService.list_roles(); + if (Array.isArray(data) && data.length > 0) { + setFirstUser(data[0]); + } + } catch (error) { + console.error("Erro ao carregar usuário:", error); + } finally { + setLoadingUser(false); + } + } + + fetchFirstUser(); + }, []); + + // 🔹 Buscar 3 primeiros médicos + useEffect(() => { + async function fetchDoctors() { + try { + const data = await doctorsService.list(); // ajuste se seu service tiver outro método + if (Array.isArray(data)) { + setDoctors(data.slice(0, 3)); // pega os 3 primeiros + } + } catch (error) { + console.error("Erro ao carregar médicos:", error); + } finally { + setLoadingDoctors(false); + } + } + + fetchDoctors(); + }, []); + return (
+ {/* Cabeçalho */}

Dashboard

Bem-vindo ao seu portal de consultas médicas

+ {/* Cards principais */}
+ {/* Card 1 */} Relatórios gerenciais -
3
-

2 não lidos, 1 lido

+
0
+

Relatórios disponíveis

+ {/* Card 2 — Gestão de usuários */} Gestão de usuários -
João Marques
-

fez login a 13min

+ {loadingUser ? ( +
Carregando usuário...
+ ) : firstUser ? ( + <> +
{firstUser.full_name || "Sem nome"}
+

+ {firstUser.email || "Sem e-mail cadastrado"} +

+ + ) : ( +
Nenhum usuário encontrado
+ )}
+ {/* Card 3 — Perfil */} Perfil @@ -48,66 +112,79 @@ export default function ManagerDashboard() {
+ {/* Cards secundários */}
+ {/* Card — Ações rápidas */} Ações Rápidas Acesse rapidamente as principais funcionalidades - + - - - - + + + + + + + + {/* Card — Gestão de Médicos */} Gestão de Médicos - Médicos online + Médicos cadastrados recentemente -
-
-
-

Dr. Silva

-

Cardiologia

-
-
-

On-line

-

-
+ {loadingDoctors ? ( +

Carregando médicos...

+ ) : doctors.length === 0 ? ( +

Nenhum médico cadastrado.

+ ) : ( +
+ {doctors.map((doc, index) => ( +
+
+

{doc.full_name || "Sem nome"}

+

+ {doc.specialty || "Sem especialidade"} +

+
+
+

+ {doc.active ? "Ativo" : "Inativo"} +

+
+
+ ))}
-
-
-

Dra. Santos

-

Dermatologia

-
-
-

Off-line

-

Visto as 8:33

-
-
-
+ )}
- ) + ); } diff --git a/app/manager/usuario/novo/page.tsx b/app/manager/usuario/novo/page.tsx index 781f35d..e9f50e5 100644 --- a/app/manager/usuario/novo/page.tsx +++ b/app/manager/usuario/novo/page.tsx @@ -7,208 +7,225 @@ import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Save, Loader2 } from "lucide-react" +import { Save, Loader2 } from "lucide-react" import ManagerLayout from "@/components/manager-layout" +import { usersService } from "services/usersApi.mjs"; -// Mock user service for demonstration. Replace with your actual API service. -const usersService = { - create: async (payload: any) => { - console.log("API Call: Creating user with payload:", payload); - // Simulate network delay - await new Promise(resolve => setTimeout(resolve, 1000)); - // Simulate a success response - return { id: Date.now(), ...payload }; - // To simulate an error, you could throw an error here: - // throw new Error("O e-mail informado já está em uso."); - } -}; - -// Define the structure for our form data interface UserFormData { - email: string; - password: string; - nomeCompleto: string; - telefone: string; - papel: string; // e.g., 'admin', 'gestor', 'medico', etc. + email: string; + password: string; + nomeCompleto: string; + telefone: string; + papel: string; } -// Define the initial state for the form const defaultFormData: UserFormData = { - email: '', - password: '', - nomeCompleto: '', - telefone: '', - papel: '', + email: '', + password: '', + nomeCompleto: '', + telefone: '', + papel: '', }; -// Helper function to remove non-digit characters +// Remove todos os caracteres não numéricos const cleanNumber = (value: string): string => value.replace(/\D/g, ''); -// Helper function to format a phone number +// Definição do requisito mínimo de senha +const MIN_PASSWORD_LENGTH = 8; + + const formatPhone = (value: string): string => { const cleaned = cleanNumber(value).substring(0, 11); - if (cleaned.length > 10) { + + if (cleaned.length === 11) { return cleaned.replace(/(\d{2})(\d{5})(\d{4})/, '($1) $2-$3'); } - return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3'); + + if (cleaned.length === 10) { + return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, '($1) $2-$3'); + } + return cleaned; }; + export default function NovoUsuarioPage() { - const router = useRouter(); - const [formData, setFormData] = useState(defaultFormData); - const [isSaving, setIsSaving] = useState(false); - const [error, setError] = useState(null); + const router = useRouter(); + const [formData, setFormData] = useState(defaultFormData); + const [isSaving, setIsSaving] = useState(false); + const [error, setError] = useState(null); - // Handles changes in form inputs - const handleInputChange = (key: keyof UserFormData, value: string) => { - const updatedValue = key === 'telefone' ? formatPhone(value) : value; - setFormData((prev) => ({ ...prev, [key]: updatedValue })); - }; - - // Handles form submission - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(null); - - // Basic validation - if (!formData.email || !formData.password || !formData.nomeCompleto || !formData.papel) { - setError("Por favor, preencha todos os campos obrigatórios."); - return; - } - - setIsSaving(true); - - // Prepare payload for the API - const payload = { - email: formData.email, - password: formData.password, - full_name: formData.nomeCompleto, - phone: formData.telefone.trim() || null, // Send null if empty - role: formData.papel, + const handleInputChange = (key: keyof UserFormData, value: string) => { + const updatedValue = key === 'telefone' ? formatPhone(value) : value; + setFormData((prev) => ({ ...prev, [key]: updatedValue })); }; - try { - await usersService.create(payload); - // On success, redirect to the main user list page - router.push("/manager/usuario"); - } catch (e: any) { - console.error("Erro ao criar usuário:", e); - setError(e.message || "Ocorreu um erro inesperado. Tente novamente."); - } finally { - setIsSaving(false); - } - }; + // Handles form submission + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); - return ( - -
-
-
-

Novo Usuário

-

- Preencha os dados para cadastrar um novo usuário no sistema. -

-
- - - -
+ // Basic validation + if (!formData.email || !formData.password || !formData.nomeCompleto || !formData.papel) { + setError("Por favor, preencha todos os campos obrigatórios."); + return; + } -
- - {/* Error Message Display */} - {error && ( -
-

Erro no Cadastro:

-

{error}

-
- )} - -
-
- - handleInputChange("nomeCompleto", e.target.value)} - placeholder="Nome e Sobrenome" - required - /> + // Validação de comprimento mínimo da senha + if (formData.password.length < MIN_PASSWORD_LENGTH) { + setError(`A senha deve ter no mínimo ${MIN_PASSWORD_LENGTH} caracteres.`); + return; + } + + setIsSaving(true); + + // ---------------------------------------------------------------------- + // CORREÇÃO FINAL: Usa o formato de telefone que o mock API comprovadamente aceitou. + // ---------------------------------------------------------------------- + const phoneValue = formData.telefone.trim(); + + // Prepara o payload com os campos obrigatórios + const payload: any = { + email: formData.email, + password: formData.password, + full_name: formData.nomeCompleto, + role: formData.papel, + }; + + // Adiciona o telefone APENAS se estiver preenchido, enviando o formato FORMATADO. + if (phoneValue.length > 0) { + payload.phone = phoneValue; + } + // ---------------------------------------------------------------------- + + try { + await usersService.create_user(payload); + router.push("/manager/usuario"); + } catch (e: any) { + console.error("Erro ao criar usuário:", e); + // Melhorando a mensagem de erro para o usuário final + const apiErrorMsg = e.message?.includes("500") + ? "Erro interno do servidor. Verifique os logs do backend ou tente novamente mais tarde. (Possível problema: E-mail já em uso ou falha de conexão.)" + : e.message || "Ocorreu um erro inesperado. Tente novamente."; + + setError(apiErrorMsg); + } finally { + setIsSaving(false); + } + }; + + return ( + +
+
+
+

Novo Usuário

+

+ Preencha os dados para cadastrar um novo usuário no sistema. +

+
+ + + +
+ + + + {/* Error Message Display */} + {error && ( +
+

Erro no Cadastro:

+

{error}

+
+ )} + +
+
+ + handleInputChange("nomeCompleto", e.target.value)} + placeholder="Nome e Sobrenome" + required + /> +
+ +
+
+ + handleInputChange("email", e.target.value)} + placeholder="exemplo@dominio.com" + required + /> +
+
+ + handleInputChange("password", e.target.value)} + placeholder="••••••••" + required + minLength={MIN_PASSWORD_LENGTH} // Adiciona validação HTML + /> + {/* MENSAGEM DE AJUDA PARA SENHA */} +

Mínimo de {MIN_PASSWORD_LENGTH} caracteres.

+
+
+ +
+
+ + handleInputChange("telefone", e.target.value)} + placeholder="(00) 00000-0000" + maxLength={15} + /> +
+
+ + +
+
+
+ + {/* Action Buttons */} +
+ + + + +
+
- -
-
- - handleInputChange("email", e.target.value)} - placeholder="exemplo@dominio.com" - required - /> -
-
- - handleInputChange("password", e.target.value)} - placeholder="••••••••" - required - /> -
-
- -
-
- - handleInputChange("telefone", e.target.value)} - placeholder="(00) 00000-0000" - maxLength={15} - /> -
-
- - -
-
-
- - {/* Action Buttons */} -
- - - - -
- -
- - ); -} \ No newline at end of file + + ); +} diff --git a/app/manager/usuario/page.tsx b/app/manager/usuario/page.tsx index 00c4836..d126112 100644 --- a/app/manager/usuario/page.tsx +++ b/app/manager/usuario/page.tsx @@ -1,12 +1,12 @@ "use client"; -import React, { useEffect, useState, useCallback } from "react" +import React, { useEffect, useState, useCallback } from "react"; import ManagerLayout from "@/components/manager-layout"; -import Link from "next/link" +import Link from "next/link"; import { useRouter } from "next/navigation"; -import { Button } from "@/components/ui/button" -import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select" -import { Plus, Edit, Trash2, Eye, Filter, Loader2 } from "lucide-react" +import { Button } from "@/components/ui/button"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Plus, Edit, Trash2, Eye, Filter, Loader2 } from "lucide-react"; import { AlertDialog, AlertDialogAction, @@ -16,230 +16,294 @@ import { AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, -} from "@/components/ui/alert-dialog" +} from "@/components/ui/alert-dialog"; -// Mock user service for demonstration. Replace with your actual API service. -const usersService = { - list: async (): Promise => { - console.log("API Call: Fetching users..."); - await new Promise(resolve => setTimeout(resolve, 500)); // Simulate network delay - return [ - { id: 1, full_name: 'Alice Admin', email: 'alice.admin@example.com', phone: '(11) 98765-4321', role: 'user' }, +import { usersService } from "services/usersApi.mjs"; - ]; - }, - delete: async (id: number): Promise => { - console.log(`API Call: Deleting user with ID ${id}`); - await new Promise(resolve => setTimeout(resolve, 700)); - // In a real app, you'd handle potential errors here - } -}; - -// Interface for a User object interface User { - id: number; - full_name: string; + user: { + id: string; email: string; - phone: string | null; - role: 'admin' | 'gestor' | 'medico' | 'secretaria' | 'user'; + email_confirmed_at?: string; + created_at?: string; + last_sign_in_at?: string; + }; + profile: { + id?: string; + full_name?: string; + email?: string; + phone?: string | null; + avatar_url?: string; + disabled?: boolean; + created_at?: string; + updated_at?: string; + }; + roles: string[]; + permissions: { + isAdmin?: boolean; + isManager?: boolean; + isDoctor?: boolean; + isSecretary?: boolean; + isAdminOrManager?: boolean; + [key: string]: boolean | undefined; + }; } -// Interface for User Details (can be the same as User for this case) -interface UserDetails extends User {} + +interface FlatUser { + id: string; + user_id: string; + full_name?: string; + email: string; + phone?: string | null; + role: string; +} + + export default function UsersPage() { const router = useRouter(); - const [users, setUsers] = useState([]); + + const [users, setUsers] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [detailsDialogOpen, setDetailsDialogOpen] = useState(false); - const [userDetails, setUserDetails] = useState(null); + const [userDetails, setUserDetails] = useState(null); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [userToDeleteId, setUserToDeleteId] = useState(null); - - const fetchUsers = useCallback(async () => { - setLoading(true); - setError(null); - try { - const data: User[] = await usersService.list(); - setUsers(data || []); - } catch (e: any) { - console.error("Erro ao carregar lista de usuários:", e); - setError("Não foi possível carregar a lista de usuários. Tente novamente."); - setUsers([]); - } finally { - setLoading(false); - } - }, []); + const [selectedRole, setSelectedRole] = useState(""); + const fetchUsers = useCallback(async () => { + setLoading(true); + setError(null); + try { + const data = await usersService.list_roles(); // já retorna o JSON diretamente + console.log("Resposta da API list_roles:", data); + + if (Array.isArray(data)) { + const mappedUsers: FlatUser[] = data.map((item: any) => ({ + id: item.id || (item.user_id ?? ""), // id da linha ou fallback + user_id: item.user_id || item.id || "", // garante que user_id exista + full_name: item.full_name || "—", + email: item.email || "—", + phone: item.phone ?? "—", + role: item.role || "—", + })); + + setUsers(mappedUsers); + } else { + console.warn("Formato inesperado recebido em list_roles:", data); + setUsers([]); + } + } catch (err: any) { + console.error("Erro ao buscar usuários:", err); + setError("Não foi possível carregar os usuários. Tente novamente."); + setUsers([]); + } finally { + setLoading(false); + } +}, []); + + useEffect(() => { fetchUsers(); }, [fetchUsers]); - - const openDetailsDialog = (user: User) => { - setUserDetails(user); - setDetailsDialogOpen(true); - }; - - const handleDelete = async () => { - if (userToDeleteId === null) return; - - setLoading(true); - try { - await usersService.delete(userToDeleteId); - console.log(`Usuário com ID ${userToDeleteId} excluído com sucesso!`); - setDeleteDialogOpen(false); - setUserToDeleteId(null); - await fetchUsers(); // Refresh the list after deletion - } catch (e) { - console.error("Erro ao excluir:", e); - alert("Erro ao excluir usuário."); - } finally { - setLoading(false); - } - }; - - const openDeleteDialog = (userId: number) => { - setUserToDeleteId(userId); - setDeleteDialogOpen(true); - }; - const handleEdit = (userId: number) => { - // Assuming the edit page is at a similar path - router.push(`/manager/usuario/${userId}/editar`); - }; + + + + + const openDetailsDialog = async (flatUser: FlatUser) => { + setDetailsDialogOpen(true); + setUserDetails(null); + + try { + console.log("Buscando detalhes do user_id:", flatUser.user_id); + const fullUserData: User = await usersService.full_data(flatUser.user_id); + setUserDetails(fullUserData); + } catch (err: any) { + console.error("Erro ao buscar detalhes do usuário:", err); + setUserDetails({ + user: { + id: flatUser.user_id, + email: flatUser.email || "", + created_at: "Erro ao Carregar", + last_sign_in_at: "Erro ao Carregar", + }, + profile: { + full_name: flatUser.full_name || "Erro ao Carregar Detalhes", + phone: flatUser.phone || "—", + }, + roles: [], + permissions: {}, + } as any); + } +}; + + + + const filteredUsers = selectedRole && selectedRole !== "all" + ? users.filter((u) => u.role === selectedRole) + : users; + + return ( -
-
-
-

Usuários Cadastrados

-

Gerencie todos os usuários do sistema.

-
- - - -
- - {/* Filters Section */} -
- - -
- - {/* Users Table */} -
- {loading ? ( -
- - Carregando usuários... -
- ) : error ? ( -
- {error} -
- ) : users.length === 0 ? ( -
- Nenhum usuário cadastrado. Adicione um novo. -
- ) : ( -
- - - - - - - - - - - - {users.map((user) => ( - - - - - - - - ))} - -
Nome CompletoE-mailTelefonePapelAções
{user.full_name}{user.email}{user.phone || "N/A"}{user.role} -
- - - -
-
+
+
+
+

Usuários Cadastrados

+

Gerencie todos os usuários e seus papéis no sistema.

- )} -
- - {/* Delete Confirmation Dialog */} - - - - Confirma a exclusão? - - Esta ação é irreversível e excluirá permanentemente o registro deste usuário. - - - - Cancelar - - {loading ? : null} - Excluir - - - - + + + +
- {/* User Details Dialog */} - - - - {userDetails?.full_name} - - {userDetails && ( + +
+ + + +
+ + +
+ {loading ? ( +
+ + Carregando usuários... +
+ ) : error ? ( +
{error}
+ ) : filteredUsers.length === 0 ? ( +
+ Nenhum usuário encontrado.{" "} + + Adicione um novo + + . +
+ ) : ( +
+ + + + + + + + + + + + + {filteredUsers.map((user) => ( + + + + + + + + + + ))} + +
IDNomeE-mailTelefonePapelAções
{user.id}{user.full_name || "—"}{user.email || "—"}{user.phone || "—"}{user.role || "—"} +
+ + +
+
+
+ )} +
+ + + + + + + {userDetails?.profile?.full_name || userDetails?.user?.email || "Detalhes do Usuário"} + + + + {!userDetails ? ( +
+ + Buscando dados completos... +
+ ) : (
-
-
E-mail: {userDetails.email}
-
Telefone: {userDetails.phone || 'Não informado'}
-
Papel: {userDetails.role}
+ +
ID: {userDetails.user.id}
+
E-mail: {userDetails.user.email}
+
Email confirmado em: {userDetails.user.email_confirmed_at || "—"}
+
Último login: {userDetails.user.last_sign_in_at || "—"}
+
Criado em: {userDetails.user.created_at || "—"}
+ + +
Nome completo: {userDetails.profile.full_name || "—"}
+
Telefone: {userDetails.profile.phone || "—"}
+ {userDetails.profile.avatar_url && ( +
Avatar:
+ )} +
Conta desativada: {userDetails.profile.disabled ? "Sim" : "Não"}
+
Profile criado em: {userDetails.profile.created_at || "—"}
+
Profile atualizado em: {userDetails.profile.updated_at || "—"}
+ + +
+ Roles: +
    + {userDetails.roles.map((role, idx) =>
  • {role}
  • )} +
+
+ +
+ Permissões: +
    + {Object.entries(userDetails.permissions).map(([key, value]) => ( +
  • {key}: {value ? "Sim" : "Não"}
  • + ))} +
)} - - - - Fechar - - - -
+ + +
+
+ + Fechar + +
+
+
); } \ No newline at end of file diff --git a/app/secretary/dashboard/page.tsx b/app/secretary/dashboard/page.tsx index 7171aa8..e37141c 100644 --- a/app/secretary/dashboard/page.tsx +++ b/app/secretary/dashboard/page.tsx @@ -1,41 +1,207 @@ -import SecretaryLayout from "@/components/secretary-layout" -import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card" -import { Button } from "@/components/ui/button" -import { Calendar, Clock, User, Plus } from "lucide-react" -import Link from "next/link" +"use client"; + +import SecretaryLayout from "@/components/secretary-layout"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { Button } from "@/components/ui/button"; +import { Calendar, Clock, User, Plus } from "lucide-react"; +import Link from "next/link"; +import React, { useState, useEffect } from "react"; +import { patientsService } from "@/services/patientsApi.mjs"; +import { appointmentsService } from "@/services/appointmentsApi.mjs"; export default function SecretaryDashboard() { + // Estados + const [patients, setPatients] = useState([]); + const [loadingPatients, setLoadingPatients] = useState(true); + + const [firstConfirmed, setFirstConfirmed] = useState(null); + const [nextAgendada, setNextAgendada] = useState(null); + const [loadingAppointments, setLoadingAppointments] = useState(true); + + // 🔹 Buscar pacientes + useEffect(() => { + async function fetchPatients() { + try { + const data = await patientsService.list(); + if (Array.isArray(data)) { + setPatients(data.slice(0, 3)); + } + } catch (error) { + console.error("Erro ao carregar pacientes:", error); + } finally { + setLoadingPatients(false); + } + } + fetchPatients(); + }, []); + + // 🔹 Buscar consultas (confirmadas + 1ª do mês) + useEffect(() => { + async function fetchAppointments() { + try { + const hoje = new Date(); + const inicioMes = new Date(hoje.getFullYear(), hoje.getMonth(), 1); + const fimMes = new Date(hoje.getFullYear(), hoje.getMonth() + 1, 0); + + // Mesmo parâmetro de ordenação da página /secretary/appointments + const queryParams = "order=scheduled_at.desc"; + const data = await appointmentsService.search_appointment(queryParams); + + if (!Array.isArray(data) || data.length === 0) { + setFirstConfirmed(null); + setNextAgendada(null); + return; + } + + // 🩵 1️⃣ Consultas confirmadas (para o card “Próxima Consulta Confirmada”) + const confirmadas = data.filter((apt: any) => { + const dataConsulta = new Date(apt.scheduled_at || apt.date); + return apt.status === "confirmed" && dataConsulta >= hoje; + }); + + confirmadas.sort( + (a: any, b: any) => + new Date(a.scheduled_at || a.date).getTime() - + new Date(b.scheduled_at || b.date).getTime() + ); + + setFirstConfirmed(confirmadas[0] || null); + + // 💙 2️⃣ Consultas deste mês — pegar sempre a 1ª (mais próxima) + const consultasMes = data.filter((apt: any) => { + const dataConsulta = new Date(apt.scheduled_at); + return dataConsulta >= inicioMes && dataConsulta <= fimMes; + }); + + if (consultasMes.length > 0) { + consultasMes.sort( + (a: any, b: any) => + new Date(a.scheduled_at).getTime() - + new Date(b.scheduled_at).getTime() + ); + setNextAgendada(consultasMes[0]); + } else { + setNextAgendada(null); + } + } catch (error) { + console.error("Erro ao carregar consultas:", error); + } finally { + setLoadingAppointments(false); + } + } + + fetchAppointments(); + }, []); + return (
+ {/* Cabeçalho */}

Dashboard

Bem-vindo ao seu portal de consultas médicas

+ {/* Cards principais */}
+ {/* Próxima Consulta Confirmada */} - Próxima Consulta + + Próxima Consulta Confirmada + -
15 Jan
-

Dr. Silva - 14:30

+ {loadingAppointments ? ( +
+ Carregando próxima consulta... +
+ ) : firstConfirmed ? ( + <> +
+ {new Date( + firstConfirmed.scheduled_at || firstConfirmed.date + ).toLocaleDateString("pt-BR")} +
+

+ {firstConfirmed.doctor_name + ? `Dr(a). ${firstConfirmed.doctor_name}` + : "Médico não informado"}{" "} + -{" "} + {new Date( + firstConfirmed.scheduled_at + ).toLocaleTimeString("pt-BR", { + hour: "2-digit", + minute: "2-digit", + })} +

+ + ) : ( +
+ Nenhuma consulta confirmada encontrada +
+ )}
+ {/* Consultas Este Mês */} - Consultas Este Mês + + Consultas Este Mês + -
3
-

2 realizadas, 1 agendada

+ {loadingAppointments ? ( +
+ Carregando consultas... +
+ ) : nextAgendada ? ( + <> +
+ {new Date( + nextAgendada.scheduled_at + ).toLocaleDateString("pt-BR", { + day: "2-digit", + month: "2-digit", + year: "numeric", + })}{" "} + às{" "} + {new Date( + nextAgendada.scheduled_at + ).toLocaleTimeString("pt-BR", { + hour: "2-digit", + minute: "2-digit", + })} +
+

+ {nextAgendada.doctor_name + ? `Dr(a). ${nextAgendada.doctor_name}` + : "Médico não informado"} +

+

+ {nextAgendada.patient_name + ? `Paciente: ${nextAgendada.patient_name}` + : ""} +

+ + ) : ( +
+ Nenhuma consulta agendada neste mês +
+ )}
+ {/* Perfil */} Perfil @@ -48,11 +214,15 @@ export default function SecretaryDashboard() {
+ {/* Cards Secundários */}
+ {/* Ações rápidas */} Ações Rápidas - Acesse rapidamente as principais funcionalidades + + Acesse rapidamente as principais funcionalidades + @@ -62,52 +232,73 @@ export default function SecretaryDashboard() { - - - + {/* Pacientes */} - Próximas Consultas - Suas consultas agendadas + Pacientes + + Últimos pacientes cadastrados + -
-
-
-

Dr. Silva

-

Cardiologia

-
-
-

15 Jan

-

14:30

-
+ {loadingPatients ? ( +

+ Carregando pacientes... +

+ ) : patients.length === 0 ? ( +

+ Nenhum paciente cadastrado. +

+ ) : ( +
+ {patients.map((patient, index) => ( +
+
+

+ {patient.full_name || "Sem nome"} +

+

+ {patient.phone_mobile || + patient.phone1 || + "Sem telefone"} +

+
+
+

+ {patient.convenio || "Particular"} +

+
+
+ ))}
-
-
-

Dra. Santos

-

Dermatologia

-
-
-

22 Jan

-

10:00

-
-
-
+ )}
- ) + ); } diff --git a/components/LoginForm.tsx b/components/LoginForm.tsx index 1176948..fe94bb2 100644 --- a/components/LoginForm.tsx +++ b/components/LoginForm.tsx @@ -8,17 +8,20 @@ import Link from "next/link" import { cn } from "@/lib/utils" // Nossos serviços de API centralizados -import { loginWithEmailAndPassword, api } from "@/services/api"; +import { loginWithEmailAndPassword, api } from "@/services/api.mjs"; -// Componentes Shadcn UI -import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Label } from "@/components/ui/label" -import { Card, CardContent } from "@/components/ui/card" -import { useToast } from "@/hooks/use-toast" -// Ícones -import { Eye, EyeOff, Loader2, Mail, Lock } from "lucide-react" +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; + + + +import { useToast } from "@/hooks/use-toast"; + + +import { Eye, EyeOff, Mail, Lock, Loader2, UserCheck, Stethoscope, IdCard, Receipt } from "lucide-react"; interface LoginFormProps { children?: React.ReactNode diff --git a/components/doctor-layout.tsx b/components/doctor-layout.tsx index c9134ba..2d4375d 100644 --- a/components/doctor-layout.tsx +++ b/components/doctor-layout.tsx @@ -135,7 +135,7 @@ export default function DoctorLayout({ children }: PatientLayoutProps) {
- MidConnecta + MidConnecta
)} +
+
+ +
+ + {isMobileMenuOpen && (
)} -
-
+
+
- Hospital System + Hospital System