@ -324,7 +324,7 @@ export default function AvailabilityPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6 flex-1 overflow-y-auto p-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-gray-900">Definir Disponibilidade</h1>
|
<h1 className="text-2xl font-bold text-gray-900">Definir Disponibilidade</h1>
|
||||||
@ -416,11 +416,12 @@ export default function AvailabilityPage() {
|
|||||||
|
|
||||||
{/* **AJUSTE DE RESPONSIVIDADE: BOTÕES DE AÇÃO** */}
|
{/* **AJUSTE DE RESPONSIVIDADE: BOTÕES DE AÇÃO** */}
|
||||||
{/* Alinha à direita em telas maiores e empilha (com o botão primário no final) em telas menores */}
|
{/* Alinha à direita em telas maiores e empilha (com o botão primário no final) em telas menores */}
|
||||||
|
{/* Alteração aqui: Adicionado w-full aos Links e Buttons para ocuparem a largura total em telas pequenas */}
|
||||||
<div className="flex flex-col-reverse sm:flex-row sm:justify-between gap-4">
|
<div className="flex flex-col-reverse sm:flex-row sm:justify-between gap-4">
|
||||||
<Link href="/doctor/disponibilidade/excecoes" className="w-full sm:w-auto">
|
<Link href="/doctor/disponibilidade/excecoes" className="w-full sm:w-auto">
|
||||||
<Button variant="default" className="w-full sm:w-auto">Adicionar Exceção</Button>
|
<Button variant="default" className="w-full sm:w-auto">Adicionar Exceção</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex gap-4 w-full sm:w-auto">
|
<div className="flex flex-col sm:flex-row gap-4 w-full sm:w-auto"> {/* Ajustado para empilhar os botões Cancelar e Salvar em telas pequenas */}
|
||||||
<Link href="/doctor/dashboard" className="w-full sm:w-auto">
|
<Link href="/doctor/dashboard" className="w-full sm:w-auto">
|
||||||
<Button variant="outline" className="w-full sm:w-auto">Cancelar</Button>
|
<Button variant="outline" className="w-full sm:w-auto">Cancelar</Button>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// app/doctor/pacientes/page.tsx (assumindo a localização)
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState, useCallback } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
@ -12,340 +11,405 @@ import { Button } from "@/components/ui/button";
|
|||||||
import Sidebar from "@/components/Sidebar";
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
interface Paciente {
|
interface Paciente {
|
||||||
id: string;
|
id: string;
|
||||||
nome: string;
|
nome: string;
|
||||||
telefone: string;
|
telefone: string;
|
||||||
cidade: string;
|
cidade: string;
|
||||||
estado: string;
|
estado: string;
|
||||||
ultimoAtendimento?: string;
|
ultimoAtendimento?: string;
|
||||||
proximoAtendimento?: string;
|
proximoAtendimento?: string;
|
||||||
email?: string;
|
email?: string;
|
||||||
birth_date?: string;
|
birth_date?: string;
|
||||||
cpf?: string;
|
cpf?: string;
|
||||||
blood_type?: string;
|
blood_type?: string;
|
||||||
weight_kg?: number;
|
weight_kg?: number;
|
||||||
height_m?: number;
|
height_m?: number;
|
||||||
street?: string;
|
street?: string;
|
||||||
number?: string;
|
number?: string;
|
||||||
complement?: string;
|
complement?: string;
|
||||||
neighborhood?: string;
|
neighborhood?: string;
|
||||||
cep?: string;
|
cep?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function PacientesPage() {
|
export default function PacientesPage() {
|
||||||
const [pacientes, setPacientes] = useState<Paciente[]>([]);
|
const [pacientes, setPacientes] = useState<Paciente[]>([]);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [selectedPatient, setSelectedPatient] = useState<Paciente | null>(null);
|
const [selectedPatient, setSelectedPatient] = useState<Paciente | null>(null);
|
||||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
|
||||||
// --- Lógica de Paginação INÍCIO ---
|
// --- Lógica de Paginação INÍCIO ---
|
||||||
const [itemsPerPage, setItemsPerPage] = useState(5);
|
const [itemsPerPage, setItemsPerPage] = useState(5);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
const totalPages = Math.ceil(pacientes.length / itemsPerPage);
|
const totalPages = Math.ceil(pacientes.length / itemsPerPage);
|
||||||
|
|
||||||
const indexOfLastItem = currentPage * itemsPerPage;
|
const indexOfLastItem = currentPage * itemsPerPage;
|
||||||
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
||||||
const currentItems = pacientes.slice(indexOfFirstItem, indexOfLastItem);
|
const currentItems = pacientes.slice(indexOfFirstItem, indexOfLastItem);
|
||||||
|
|
||||||
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
|
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
|
||||||
|
|
||||||
// Funções de Navegação
|
// Funções de Navegação
|
||||||
const goToPrevPage = () => {
|
const goToPrevPage = () => {
|
||||||
setCurrentPage((prev) => Math.max(1, prev - 1));
|
setCurrentPage((prev) => Math.max(1, prev - 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
const goToNextPage = () => {
|
const goToNextPage = () => {
|
||||||
setCurrentPage((prev) => Math.min(totalPages, prev + 1));
|
setCurrentPage((prev) => Math.min(totalPages, prev + 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lógica para gerar os números das páginas visíveis (máximo de 5)
|
|
||||||
const getVisiblePageNumbers = (totalPages: number, currentPage: number) => {
|
|
||||||
const pages: number[] = [];
|
|
||||||
const maxVisiblePages = 5;
|
|
||||||
const halfRange = Math.floor(maxVisiblePages / 2);
|
|
||||||
let startPage = Math.max(1, currentPage - halfRange);
|
|
||||||
let endPage = Math.min(totalPages, currentPage + halfRange);
|
|
||||||
|
|
||||||
if (endPage - startPage + 1 < maxVisiblePages) {
|
// Lógica para gerar os números das páginas visíveis (máximo de 5)
|
||||||
if (endPage === totalPages) {
|
const getVisiblePageNumbers = (totalPages: number, currentPage: number) => {
|
||||||
startPage = Math.max(1, totalPages - maxVisiblePages + 1);
|
const pages: number[] = [];
|
||||||
}
|
const maxVisiblePages = 5;
|
||||||
if (startPage === 1) {
|
const halfRange = Math.floor(maxVisiblePages / 2);
|
||||||
endPage = Math.min(totalPages, maxVisiblePages);
|
let startPage = Math.max(1, currentPage - halfRange);
|
||||||
}
|
let endPage = Math.min(totalPages, currentPage + halfRange);
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = startPage; i <= endPage; i++) {
|
|
||||||
pages.push(i);
|
|
||||||
}
|
|
||||||
return pages;
|
|
||||||
};
|
|
||||||
|
|
||||||
const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage);
|
|
||||||
|
|
||||||
// Lógica para mudar itens por página, resetando para a página 1
|
if (endPage - startPage + 1 < maxVisiblePages) {
|
||||||
const handleItemsPerPageChange = (value: string) => {
|
if (endPage === totalPages) {
|
||||||
setItemsPerPage(Number(value));
|
startPage = Math.max(1, totalPages - maxVisiblePages + 1);
|
||||||
setCurrentPage(1);
|
}
|
||||||
};
|
if (startPage === 1) {
|
||||||
// --- Lógica de Paginação FIM ---
|
endPage = Math.min(totalPages, maxVisiblePages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = startPage; i <= endPage; i++) {
|
||||||
|
pages.push(i);
|
||||||
|
}
|
||||||
|
return pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage);
|
||||||
|
|
||||||
|
// Lógica para mudar itens por página, resetando para a página 1
|
||||||
|
const handleItemsPerPageChange = (value: string) => {
|
||||||
|
setItemsPerPage(Number(value));
|
||||||
|
setCurrentPage(1);
|
||||||
|
};
|
||||||
|
// --- Lógica de Paginação FIM ---
|
||||||
|
|
||||||
|
|
||||||
const handleOpenModal = (patient: Paciente) => {
|
const handleOpenModal = (patient: Paciente) => {
|
||||||
setSelectedPatient(patient);
|
setSelectedPatient(patient);
|
||||||
setIsModalOpen(true);
|
setIsModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
const handleCloseModal = () => {
|
||||||
setSelectedPatient(null);
|
setSelectedPatient(null);
|
||||||
setIsModalOpen(false);
|
setIsModalOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const formatDate = (dateString: string | null | undefined) => {
|
const formatDate = (dateString: string | null | undefined) => {
|
||||||
if (!dateString) return "N/A";
|
if (!dateString) return "N/A";
|
||||||
try {
|
try {
|
||||||
const date = new Date(dateString);
|
const date = new Date(dateString);
|
||||||
return new Intl.DateTimeFormat("pt-BR").format(date);
|
return new Intl.DateTimeFormat("pt-BR").format(date);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return dateString; // Retorna o string original se o formato for inválido
|
return dateString; // Retorna o string original se o formato for inválido
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchPacientes = useCallback(async () => {
|
const fetchPacientes = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
const json = await api.get("/rest/v1/patients");
|
const json = await api.get("/rest/v1/patients");
|
||||||
const items = Array.isArray(json)
|
const items = Array.isArray(json)
|
||||||
? json
|
? json
|
||||||
: Array.isArray(json?.data)
|
: Array.isArray(json?.data)
|
||||||
? json.data
|
? json.data
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const mapped: Paciente[] = items.map((p: any) => ({
|
const mapped: Paciente[] = items.map((p: any) => ({
|
||||||
id: String(p.id ?? ""),
|
id: String(p.id ?? ""),
|
||||||
nome: p.full_name ?? "—",
|
nome: p.full_name ?? "—",
|
||||||
telefone: p.phone_mobile ?? "N/A",
|
telefone: p.phone_mobile ?? "N/A",
|
||||||
cidade: p.city ?? "N/A",
|
cidade: p.city ?? "N/A",
|
||||||
estado: p.state ?? "N/A",
|
estado: p.state ?? "N/A",
|
||||||
ultimoAtendimento: formatDate(p.created_at),
|
ultimoAtendimento: formatDate(p.created_at),
|
||||||
proximoAtendimento: "N/A", // Necessita de lógica de agendamento real
|
proximoAtendimento: "N/A", // Necessita de lógica de agendamento real
|
||||||
email: p.email ?? "N/A",
|
email: p.email ?? "N/A",
|
||||||
birth_date: p.birth_date ?? "N/A",
|
birth_date: p.birth_date ?? "N/A",
|
||||||
cpf: p.cpf ?? "N/A",
|
cpf: p.cpf ?? "N/A",
|
||||||
blood_type: p.blood_type ?? "N/A",
|
blood_type: p.blood_type ?? "N/A",
|
||||||
weight_kg: p.weight_kg ?? 0,
|
weight_kg: p.weight_kg ?? 0,
|
||||||
height_m: p.height_m ?? 0,
|
height_m: p.height_m ?? 0,
|
||||||
street: p.street ?? "N/A",
|
street: p.street ?? "N/A",
|
||||||
number: p.number ?? "N/A",
|
number: p.number ?? "N/A",
|
||||||
complement: p.complement ?? "N/A",
|
complement: p.complement ?? "N/A",
|
||||||
neighborhood: p.neighborhood ?? "N/A",
|
neighborhood: p.neighborhood ?? "N/A",
|
||||||
cep: p.cep ?? "N/A",
|
cep: p.cep ?? "N/A",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
setPacientes(mapped);
|
setPacientes(mapped);
|
||||||
setCurrentPage(1); // Resetar a página ao carregar novos dados
|
setCurrentPage(1); // Resetar a página ao carregar novos dados
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error("Erro ao carregar pacientes:", e);
|
console.error("Erro ao carregar pacientes:", e);
|
||||||
setError(e?.message || "Erro ao carregar pacientes");
|
setError(e?.message || "Erro ao carregar pacientes");
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchPacientes();
|
fetchPacientes();
|
||||||
}, [fetchPacientes]);
|
}, [fetchPacientes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
||||||
{/* Cabeçalho */}
|
{/* Cabeçalho */}
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3"> {/* Ajustado para flex-col em telas pequenas */}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-foreground">Pacientes</h1>
|
<h1 className="text-2xl font-bold text-foreground">Pacientes</h1>
|
||||||
<p className="text-muted-foreground text-sm sm:text-base">
|
<p className="text-muted-foreground text-sm sm:text-base">
|
||||||
Lista de pacientes vinculados
|
Lista de pacientes vinculados
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{/* Adicione um seletor de itens por página ao lado de um botão de 'Novo Paciente' se aplicável */}
|
{/* Controles de filtro e novo paciente */}
|
||||||
<div className="flex gap-3">
|
{/* Alterado para que o Select e o Link ocupem a largura total em telas pequenas e fiquem lado a lado em telas maiores */}
|
||||||
<Select
|
<div className="flex flex-wrap gap-3 mt-4 sm:mt-0 w-full sm:w-auto justify-start sm:justify-end">
|
||||||
onValueChange={handleItemsPerPageChange}
|
<Select
|
||||||
defaultValue={String(itemsPerPage)}
|
onValueChange={handleItemsPerPageChange}
|
||||||
>
|
defaultValue={String(itemsPerPage)}
|
||||||
<SelectTrigger className="w-[140px]">
|
>
|
||||||
<SelectValue placeholder="Itens por pág." />
|
<SelectTrigger className="w-full sm:w-[140px]">
|
||||||
</SelectTrigger>
|
<SelectValue placeholder="Itens por pág." />
|
||||||
<SelectContent>
|
</SelectTrigger>
|
||||||
<SelectItem value="5">5 por página</SelectItem>
|
<SelectContent>
|
||||||
<SelectItem value="10">10 por página</SelectItem>
|
<SelectItem value="5">5 por página</SelectItem>
|
||||||
<SelectItem value="20">20 por página</SelectItem>
|
<SelectItem value="10">10 por página</SelectItem>
|
||||||
</SelectContent>
|
<SelectItem value="20">20 por página</SelectItem>
|
||||||
</Select>
|
</SelectContent>
|
||||||
<Link href="/doctor/pacientes/novo">
|
</Select>
|
||||||
<Button variant="default" className="bg-green-600 hover:bg-green-700">
|
<Link href="/doctor/pacientes/novo" className="w-full sm:w-auto">
|
||||||
Novo Paciente
|
<Button variant="default" className="bg-green-600 hover:bg-green-700 w-full sm:w-auto">
|
||||||
</Button>
|
Novo Paciente
|
||||||
</Link>
|
</Button>
|
||||||
</div>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<div className="bg-card rounded-lg border border-border overflow-hidden shadow-md">
|
<div className="bg-card rounded-lg border border-border overflow-hidden shadow-md">
|
||||||
<div className="overflow-x-auto">
|
{/* Tabela para Telas Médias e Grandes */}
|
||||||
<table className="min-w-[600px] w-full">
|
<div className="overflow-x-auto hidden md:block"> {/* Esconde em telas pequenas */}
|
||||||
<thead className="bg-muted border-b border-border">
|
<table className="min-w-[600px] w-full">
|
||||||
<tr>
|
<thead className="bg-muted border-b border-border">
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground">Nome</th>
|
<tr>
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden md:table-cell">
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground">Nome</th>
|
||||||
Telefone
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground">
|
||||||
</th>
|
Telefone
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden lg:table-cell">
|
</th>
|
||||||
Cidade
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden lg:table-cell">
|
||||||
</th>
|
Cidade
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden lg:table-cell">
|
</th>
|
||||||
Estado
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden lg:table-cell">
|
||||||
</th>
|
Estado
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden xl:table-cell">
|
</th>
|
||||||
Último atendimento
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden xl:table-cell">
|
||||||
</th>
|
Último atendimento
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden xl:table-cell">
|
</th>
|
||||||
Próximo atendimento
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground hidden xl:table-cell">
|
||||||
</th>
|
Próximo atendimento
|
||||||
<th className="text-left p-3 sm:p-4 font-medium text-foreground">Ações</th>
|
</th>
|
||||||
</tr>
|
<th className="text-left p-3 sm:p-4 font-medium text-foreground">Ações</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{loading ? (
|
<tbody>
|
||||||
<tr>
|
{loading ? (
|
||||||
<td colSpan={7} className="p-6 text-muted-foreground text-center">
|
<tr>
|
||||||
<Loader2 className="w-6 h-6 animate-spin mx-auto text-primary" />
|
<td colSpan={7} className="p-6 text-muted-foreground text-center">
|
||||||
Carregando pacientes...
|
<Loader2 className="w-6 h-6 animate-spin mx-auto text-primary" />
|
||||||
</td>
|
Carregando pacientes...
|
||||||
</tr>
|
</td>
|
||||||
) : error ? (
|
</tr>
|
||||||
<tr>
|
) : error ? (
|
||||||
<td colSpan={7} className="p-6 text-red-600 text-center">{`Erro: ${error}`}</td>
|
<tr>
|
||||||
</tr>
|
<td colSpan={7} className="p-6 text-red-600 text-center">{`Erro: ${error}`}</td>
|
||||||
) : pacientes.length === 0 ? (
|
</tr>
|
||||||
<tr>
|
) : pacientes.length === 0 ? (
|
||||||
<td colSpan={7} className="p-8 text-center text-muted-foreground">
|
<tr>
|
||||||
Nenhum paciente encontrado
|
<td colSpan={7} className="p-8 text-center text-muted-foreground">
|
||||||
</td>
|
Nenhum paciente encontrado
|
||||||
</tr>
|
</td>
|
||||||
) : (
|
</tr>
|
||||||
currentItems.map((p) => (
|
) : (
|
||||||
<tr
|
currentItems.map((p) => (
|
||||||
key={p.id}
|
<tr
|
||||||
className="border-b border-border hover:bg-accent/40 transition-colors"
|
key={p.id}
|
||||||
>
|
className="border-b border-border hover:bg-accent/40 transition-colors"
|
||||||
<td className="p-3 sm:p-4">{p.nome}</td>
|
>
|
||||||
<td className="p-3 sm:p-4 text-muted-foreground hidden md:table-cell">
|
<td className="p-3 sm:p-4">{p.nome}</td>
|
||||||
{p.telefone}
|
<td className="p-3 sm:p-4 text-muted-foreground">
|
||||||
</td>
|
{p.telefone}
|
||||||
<td className="p-3 sm:p-4 text-muted-foreground hidden lg:table-cell">
|
</td>
|
||||||
{p.cidade}
|
<td className="p-3 sm:p-4 text-muted-foreground hidden lg:table-cell">
|
||||||
</td>
|
{p.cidade}
|
||||||
<td className="p-3 sm:p-4 text-muted-foreground hidden lg:table-cell">
|
</td>
|
||||||
{p.estado}
|
<td className="p-3 sm:p-4 text-muted-foreground hidden lg:table-cell">
|
||||||
</td>
|
{p.estado}
|
||||||
<td className="p-3 sm:p-4 text-muted-foreground hidden xl:table-cell">
|
</td>
|
||||||
{p.ultimoAtendimento}
|
<td className="p-3 sm:p-4 text-muted-foreground hidden xl:table-cell">
|
||||||
</td>
|
{p.ultimoAtendimento}
|
||||||
<td className="p-3 sm:p-4 text-muted-foreground hidden xl:table-cell">
|
</td>
|
||||||
{p.proximoAtendimento}
|
<td className="p-3 sm:p-4 text-muted-foreground hidden xl:table-cell">
|
||||||
</td>
|
{p.proximoAtendimento}
|
||||||
<td className="p-3 sm:p-4">
|
</td>
|
||||||
<DropdownMenu>
|
<td className="p-3 sm:p-4">
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenu>
|
||||||
<button className="text-primary hover:underline text-sm sm:text-base">
|
<DropdownMenuTrigger asChild>
|
||||||
Ações
|
<button className="text-primary hover:underline text-sm sm:text-base">
|
||||||
</button>
|
Ações
|
||||||
</DropdownMenuTrigger>
|
</button>
|
||||||
<DropdownMenuContent align="end">
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuItem onClick={() => handleOpenModal(p)}>
|
<DropdownMenuContent align="end">
|
||||||
<Eye className="w-4 h-4 mr-2" />
|
<DropdownMenuItem onClick={() => handleOpenModal(p)}>
|
||||||
Ver detalhes
|
<Eye className="w-4 h-4 mr-2" />
|
||||||
</DropdownMenuItem>
|
Ver detalhes
|
||||||
<DropdownMenuItem asChild>
|
</DropdownMenuItem>
|
||||||
<Link href={`/doctor/pacientes/${p.id}/laudos`}>
|
<DropdownMenuItem asChild>
|
||||||
<Edit className="w-4 h-4 mr-2" />
|
<Link href={`/doctor/pacientes/${p.id}/laudos`}>
|
||||||
Laudos
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
</Link>
|
Laudos
|
||||||
</DropdownMenuItem>
|
</Link>
|
||||||
<DropdownMenuItem onClick={() => alert(`Agenda para paciente ID: ${p.id}`)}>
|
</DropdownMenuItem>
|
||||||
<Calendar className="w-4 h-4 mr-2" />
|
<DropdownMenuItem onClick={() => alert(`Agenda para paciente ID: ${p.id}`)}>
|
||||||
Ver agenda
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
</DropdownMenuItem>
|
Ver agenda
|
||||||
<DropdownMenuItem
|
</DropdownMenuItem>
|
||||||
onClick={() => {
|
<DropdownMenuItem
|
||||||
// Simulação de exclusão (A exclusão real deve ser feita via API)
|
onClick={() => {
|
||||||
const newPacientes = pacientes.filter((pac) => pac.id !== p.id);
|
const newPacientes = pacientes.filter((pac) => pac.id !== p.id);
|
||||||
setPacientes(newPacientes);
|
setPacientes(newPacientes);
|
||||||
alert(`Paciente ID: ${p.id} excluído`);
|
alert(`Paciente ID: ${p.id} excluído`);
|
||||||
// Necessário chamar a API de exclusão aqui
|
}}
|
||||||
}}
|
className="text-red-600 focus:bg-red-50 focus:text-red-600"
|
||||||
className="text-red-600 focus:bg-red-50 focus:text-red-600"
|
>
|
||||||
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
|
Excluir
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Layout em Cards/Lista para Telas Pequenas */}
|
||||||
|
<div className="md:hidden divide-y divide-border"> {/* Visível apenas em telas pequenas */}
|
||||||
|
{loading ? (
|
||||||
|
<div className="p-6 text-muted-foreground text-center">
|
||||||
|
<Loader2 className="w-6 h-6 animate-spin mx-auto text-primary" />
|
||||||
|
Carregando pacientes...
|
||||||
|
</div>
|
||||||
|
) : error ? (
|
||||||
|
<div className="p-6 text-red-600 text-center">{`Erro: ${error}`}</div>
|
||||||
|
) : pacientes.length === 0 ? (
|
||||||
|
<div className="p-8 text-center text-muted-foreground">
|
||||||
|
Nenhum paciente encontrado
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
currentItems.map((p) => (
|
||||||
|
<div key={p.id} className="flex items-center justify-between p-4 hover:bg-accent/40 transition-colors">
|
||||||
|
<div className="flex-1 min-w-0 pr-4"> {/* Adicionado padding à direita */}
|
||||||
|
<div className="text-base font-semibold text-foreground break-words"> {/* Aumentado a fonte e break-words para evitar corte do nome */}
|
||||||
|
{p.nome || "—"}
|
||||||
|
</div>
|
||||||
|
{/* Removido o 'truncate' e adicionado 'break-words' no telefone */}
|
||||||
|
<div className="text-sm text-muted-foreground break-words">
|
||||||
|
Telefone: **{p.telefone || "N/A"}**
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-4 flex-shrink-0">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" size="icon">
|
||||||
|
<Eye className="w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => handleOpenModal(p)}>
|
||||||
|
<Eye className="w-4 h-4 mr-2" />
|
||||||
|
Ver detalhes
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<Link href={`/doctor/pacientes/${p.id}/laudos`}>
|
||||||
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
|
Laudos
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => alert(`Agenda para paciente ID: ${p.id}`)}>
|
||||||
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
|
Ver agenda
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
const newPacientes = pacientes.filter((pac) => pac.id !== p.id);
|
||||||
|
setPacientes(newPacientes);
|
||||||
|
alert(`Paciente ID: ${p.id} excluído`);
|
||||||
|
}}
|
||||||
|
className="text-red-600 focus:bg-red-50 focus:text-red-600"
|
||||||
|
>
|
||||||
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
|
Excluir
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Paginação */}
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div className="flex flex-wrap justify-center items-center gap-2 border-t border-border p-4 bg-muted/40">
|
||||||
|
|
||||||
|
{/* Botão Anterior */}
|
||||||
|
<button
|
||||||
|
onClick={goToPrevPage}
|
||||||
|
disabled={currentPage === 1}
|
||||||
|
className="flex items-center px-4 py-2 rounded-md font-medium transition-colors text-sm bg-secondary text-secondary-foreground hover:bg-secondary/80 disabled:opacity-50 disabled:cursor-not-allowed border border-border"
|
||||||
>
|
>
|
||||||
<Trash2 className="w-4 h-4 mr-2" />
|
{"< Anterior"}
|
||||||
Excluir
|
</button>
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenu>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))
|
|
||||||
)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Paginação ATUALIZADA */}
|
{/* Números das Páginas */}
|
||||||
{totalPages > 1 && (
|
{visiblePageNumbers.map((number) => (
|
||||||
<div className="flex flex-wrap justify-center items-center gap-2 border-t border-border p-4 bg-muted/40">
|
<button
|
||||||
|
key={number}
|
||||||
{/* Botão Anterior */}
|
onClick={() => paginate(number)}
|
||||||
<button
|
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm border border-border ${
|
||||||
onClick={goToPrevPage}
|
currentPage === number
|
||||||
disabled={currentPage === 1}
|
? "bg-green-600 text-primary-foreground shadow-md border-green-600"
|
||||||
className="flex items-center px-4 py-2 rounded-md font-medium transition-colors text-sm bg-secondary text-secondary-foreground hover:bg-secondary/80 disabled:opacity-50 disabled:cursor-not-allowed border border-border"
|
: "bg-secondary text-secondary-foreground hover:bg-secondary/80"
|
||||||
>
|
}`}
|
||||||
{"< Anterior"}
|
>
|
||||||
</button>
|
{number}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
|
||||||
{/* Números das Páginas */}
|
{/* Botão Próximo */}
|
||||||
{visiblePageNumbers.map((number) => (
|
<button
|
||||||
<button
|
onClick={goToNextPage}
|
||||||
key={number}
|
disabled={currentPage === totalPages}
|
||||||
onClick={() => paginate(number)}
|
className="flex items-center px-4 py-2 rounded-md font-medium transition-colors text-sm bg-secondary text-secondary-foreground hover:bg-secondary/80 disabled:opacity-50 disabled:cursor-not-allowed border border-border"
|
||||||
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm border border-border ${
|
>
|
||||||
currentPage === number
|
{"Próximo >"}
|
||||||
? "bg-green-600 text-primary-foreground shadow-md border-green-600"
|
</button>
|
||||||
: "bg-secondary text-secondary-foreground hover:bg-secondary/80"
|
|
||||||
}`}
|
</div>
|
||||||
>
|
)}
|
||||||
{number}
|
</div>
|
||||||
</button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Botão Próximo */}
|
|
||||||
<button
|
|
||||||
onClick={goToNextPage}
|
|
||||||
disabled={currentPage === totalPages}
|
|
||||||
className="flex items-center px-4 py-2 rounded-md font-medium transition-colors text-sm bg-secondary text-secondary-foreground hover:bg-secondary/80 disabled:opacity-50 disabled:cursor-not-allowed border border-border"
|
|
||||||
>
|
|
||||||
{"Próximo >"}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
{/* Fim da Paginação ATUALIZADA */}
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<PatientDetailsModal
|
<PatientDetailsModal
|
||||||
patient={selectedPatient}
|
patient={selectedPatient}
|
||||||
|
|||||||
@ -134,7 +134,6 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* PAINEL DIREITO: A Imagem e Branding */}
|
{/* PAINEL DIREITO: A Imagem e Branding */}
|
||||||
<div className="hidden lg:block relative">
|
<div className="hidden lg:block relative">
|
||||||
{/* Usamos o componente <Image> para otimização e performance */}
|
{/* Usamos o componente <Image> para otimização e performance */}
|
||||||
|
|||||||
@ -24,7 +24,6 @@ interface Doctor {
|
|||||||
status?: string;
|
status?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
interface DoctorDetails {
|
interface DoctorDetails {
|
||||||
nome: string;
|
nome: string;
|
||||||
crm: string;
|
crm: string;
|
||||||
@ -32,11 +31,11 @@ interface DoctorDetails {
|
|||||||
contato: {
|
contato: {
|
||||||
celular?: string;
|
celular?: string;
|
||||||
telefone1?: string;
|
telefone1?: string;
|
||||||
}
|
};
|
||||||
endereco: {
|
endereco: {
|
||||||
cidade?: string;
|
cidade?: string;
|
||||||
estado?: string;
|
estado?: string;
|
||||||
}
|
};
|
||||||
convenio?: string;
|
convenio?: string;
|
||||||
vip?: boolean;
|
vip?: boolean;
|
||||||
status?: string;
|
status?: string;
|
||||||
@ -71,7 +70,7 @@ export default function DoctorsPage() {
|
|||||||
const data: Doctor[] = await doctorsService.list();
|
const data: Doctor[] = await doctorsService.list();
|
||||||
const dataWithStatus = data.map((doc, index) => ({
|
const dataWithStatus = data.map((doc, index) => ({
|
||||||
...doc,
|
...doc,
|
||||||
status: index % 3 === 0 ? "Inativo" : index % 2 === 0 ? "Férias" : "Ativo"
|
status: index % 3 === 0 ? "Inativo" : index % 2 === 0 ? "Férias" : "Ativo",
|
||||||
}));
|
}));
|
||||||
setDoctors(dataWithStatus || []);
|
setDoctors(dataWithStatus || []);
|
||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
@ -84,12 +83,10 @@ export default function DoctorsPage() {
|
|||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchDoctors();
|
fetchDoctors();
|
||||||
}, [fetchDoctors]);
|
}, [fetchDoctors]);
|
||||||
|
|
||||||
|
|
||||||
const openDetailsDialog = async (doctor: Doctor) => {
|
const openDetailsDialog = async (doctor: Doctor) => {
|
||||||
setDetailsDialogOpen(true);
|
setDetailsDialogOpen(true);
|
||||||
setDoctorDetails({
|
setDoctorDetails({
|
||||||
@ -106,7 +103,6 @@ export default function DoctorsPage() {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleDelete = async () => {
|
const handleDelete = async () => {
|
||||||
if (doctorToDeleteId === null) return;
|
if (doctorToDeleteId === null) return;
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@ -129,11 +125,11 @@ export default function DoctorsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const uniqueSpecialties = useMemo(() => {
|
const uniqueSpecialties = useMemo(() => {
|
||||||
const specialties = doctors.map(doctor => doctor.specialty).filter(Boolean);
|
const specialties = doctors.map((doctor) => doctor.specialty).filter(Boolean);
|
||||||
return [...new Set(specialties)];
|
return [...new Set(specialties)];
|
||||||
}, [doctors]);
|
}, [doctors]);
|
||||||
|
|
||||||
const filteredDoctors = doctors.filter(doctor => {
|
const filteredDoctors = doctors.filter((doctor) => {
|
||||||
const specialtyMatch = specialtyFilter === "all" || doctor.specialty === specialtyFilter;
|
const specialtyMatch = specialtyFilter === "all" || doctor.specialty === specialtyFilter;
|
||||||
const statusMatch = statusFilter === "all" || doctor.status === statusFilter;
|
const statusMatch = statusFilter === "all" || doctor.status === statusFilter;
|
||||||
return specialtyMatch && statusMatch;
|
return specialtyMatch && statusMatch;
|
||||||
@ -182,11 +178,9 @@ export default function DoctorsPage() {
|
|||||||
setCurrentPage(1);
|
setCurrentPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
||||||
|
|
||||||
{/* Cabeçalho */}
|
{/* Cabeçalho */}
|
||||||
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
<div className="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
|
||||||
<div>
|
<div>
|
||||||
@ -195,29 +189,26 @@ export default function DoctorsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* Filtros e Itens por Página */}
|
{/* Filtros e Itens por Página */}
|
||||||
<div className="flex flex-wrap items-center gap-3 bg-white p-3 sm:p-4 rounded-lg border border-gray-200">
|
<div className="flex flex-wrap items-center gap-3 bg-white p-3 sm:p-4 rounded-lg border border-gray-200">
|
||||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||||
<span className="text-sm font-medium text-foreground">
|
<span className="text-sm font-medium text-foreground">Especialidade</span>
|
||||||
Especialidade
|
|
||||||
</span>
|
|
||||||
<Select value={specialtyFilter} onValueChange={setSpecialtyFilter}>
|
<Select value={specialtyFilter} onValueChange={setSpecialtyFilter}>
|
||||||
<SelectTrigger className="w-[160px] sm:w-[180px]">
|
<SelectTrigger className="w-[160px] sm:w-[180px]">
|
||||||
<SelectValue placeholder="Especialidade" />
|
<SelectValue placeholder="Especialidade" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="all">Todas</SelectItem>
|
<SelectItem value="all">Todas</SelectItem>
|
||||||
{uniqueSpecialties.map(specialty => (
|
{uniqueSpecialties.map((specialty) => (
|
||||||
<SelectItem key={specialty} value={specialty}>{specialty}</SelectItem>
|
<SelectItem key={specialty} value={specialty}>
|
||||||
|
{specialty}
|
||||||
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||||
<span className="text-sm font-medium text-foreground">
|
<span className="text-sm font-medium text-foreground">Status</span>
|
||||||
Status
|
|
||||||
</span>
|
|
||||||
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
<Select value={statusFilter} onValueChange={setStatusFilter}>
|
||||||
<SelectTrigger className="w-[160px] sm:w-[180px]">
|
<SelectTrigger className="w-[160px] sm:w-[180px]">
|
||||||
<SelectValue placeholder="Status" />
|
<SelectValue placeholder="Status" />
|
||||||
@ -231,12 +222,8 @@ export default function DoctorsPage() {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||||
<span className="text-sm font-medium text-foreground">
|
<span className="text-sm font-medium text-foreground">Itens por página</span>
|
||||||
Itens por página
|
<Select onValueChange={handleItemsPerPageChange} defaultValue={String(itemsPerPage)}>
|
||||||
</span>
|
|
||||||
<Select
|
|
||||||
onValueChange={handleItemsPerPageChange}
|
|
||||||
defaultValue={String(itemsPerPage)}>
|
|
||||||
<SelectTrigger className="w-[140px]">
|
<SelectTrigger className="w-[140px]">
|
||||||
<SelectValue placeholder="Itens por pág." />
|
<SelectValue placeholder="Itens por pág." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@ -253,9 +240,8 @@ export default function DoctorsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Tabela de Médicos (Visível em Telas Médias e Maiores) */}
|
||||||
{/* Tabela de Médicos */}
|
<div className="bg-white rounded-lg border border-gray-200 shadow-md overflow-hidden hidden md:block">
|
||||||
<div className="bg-white rounded-lg border border-gray-200 shadow-md overflow-hidden">
|
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-8 text-center text-gray-500">
|
<div className="p-8 text-center text-gray-500">
|
||||||
<Loader2 className="w-8 h-8 animate-spin mx-auto mb-3 text-green-600" />
|
<Loader2 className="w-8 h-8 animate-spin mx-auto mb-3 text-green-600" />
|
||||||
@ -278,8 +264,8 @@ export default function DoctorsPage() {
|
|||||||
<th className="text-left p-2 md:p-4 font-medium text-gray-700">Nome</th>
|
<th className="text-left p-2 md:p-4 font-medium text-gray-700">Nome</th>
|
||||||
<th className="text-left p-2 md:p-4 font-medium text-gray-700">CRM</th>
|
<th className="text-left p-2 md:p-4 font-medium text-gray-700">CRM</th>
|
||||||
<th className="text-left p-2 md:p-4 font-medium text-gray-700">Especialidade</th>
|
<th className="text-left p-2 md:p-4 font-medium text-gray-700">Especialidade</th>
|
||||||
<th className="text-left p-2 md:p-4 font-medium text-gray-700">Status</th>
|
<th className="text-left p-2 md:p-4 font-medium text-gray-700 hidden lg:table-cell">Status</th>
|
||||||
<th className="text-left p-2 md:p-4 font-medium text-gray-700">Cidade/Estado</th>
|
<th className="text-left p-2 md:p-4 font-medium text-gray-700 hidden xl:table-cell">Cidade/Estado</th>
|
||||||
<th className="text-right p-4 font-medium text-gray-700">Ações</th>
|
<th className="text-right p-4 font-medium text-gray-700">Ações</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -296,7 +282,6 @@ export default function DoctorsPage() {
|
|||||||
: "N/A"}
|
: "N/A"}
|
||||||
</td>
|
</td>
|
||||||
<td className="px-4 py-3 text-right">
|
<td className="px-4 py-3 text-right">
|
||||||
{/* ===== INÍCIO DA ALTERAÇÃO ===== */}
|
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<div className="text-blue-600 cursor-pointer inline-block">Ações</div>
|
<div className="text-blue-600 cursor-pointer inline-block">Ações</div>
|
||||||
@ -322,7 +307,6 @@ export default function DoctorsPage() {
|
|||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
{/* ===== FIM DA ALTERAÇÃO ===== */}
|
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
))}
|
))}
|
||||||
@ -332,6 +316,61 @@ export default function DoctorsPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Cards de Médicos (Visível Apenas em Telas Pequenas) */}
|
||||||
|
<div className="bg-white rounded-lg border border-gray-200 shadow-md p-4 block md:hidden">
|
||||||
|
{loading ? (
|
||||||
|
<div className="p-8 text-center text-gray-500">
|
||||||
|
<Loader2 className="w-8 h-8 animate-spin mx-auto mb-3 text-green-600" />
|
||||||
|
Carregando médicos...
|
||||||
|
</div>
|
||||||
|
) : error ? (
|
||||||
|
<div className="p-8 text-center text-red-600">{error}</div>
|
||||||
|
) : filteredDoctors.length === 0 ? (
|
||||||
|
<div className="p-8 text-center text-gray-500">
|
||||||
|
{doctors.length === 0
|
||||||
|
? <>Nenhum médico cadastrado. <Link href="/manager/home/novo" className="text-green-600 hover:underline">Adicione um novo</Link>.</>
|
||||||
|
: "Nenhum médico encontrado com os filtros aplicados."
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{currentItems.map((doctor) => (
|
||||||
|
<div key={doctor.id} className="bg-white-50 rounded-lg p-4 flex justify-between items-center border border-white-200">
|
||||||
|
<div>
|
||||||
|
<div className="font-semibold text-gray-900">{doctor.full_name}</div>
|
||||||
|
<div className="text-sm text-gray-600">{doctor.specialty}</div>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<div className="text-blue-600 cursor-pointer inline-block">Ações</div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => openDetailsDialog(doctor)}>
|
||||||
|
<Eye className="mr-2 h-4 w-4" />
|
||||||
|
Ver detalhes
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem asChild>
|
||||||
|
<Link href={`/manager/home/${doctor.id}/editar`}>
|
||||||
|
<Edit className="mr-2 h-4 w-4" />
|
||||||
|
Editar
|
||||||
|
</Link>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
<Calendar className="mr-2 h-4 w-4" />
|
||||||
|
Marcar consulta
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem className="text-red-600" onClick={() => openDeleteDialog(doctor.id)}>
|
||||||
|
<Trash2 className="mr-2 h-4 w-4" />
|
||||||
|
Excluir
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Paginação */}
|
{/* Paginação */}
|
||||||
{totalPages > 1 && (
|
{totalPages > 1 && (
|
||||||
<div className="flex flex-wrap justify-center items-center gap-2 mt-4 p-4 bg-white rounded-lg border border-gray-200 shadow-md">
|
<div className="flex flex-wrap justify-center items-center gap-2 mt-4 p-4 bg-white rounded-lg border border-gray-200 shadow-md">
|
||||||
@ -347,10 +386,11 @@ export default function DoctorsPage() {
|
|||||||
<button
|
<button
|
||||||
key={number}
|
key={number}
|
||||||
onClick={() => paginate(number)}
|
onClick={() => paginate(number)}
|
||||||
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm border border-gray-300 ${currentPage === number
|
className={`px-4 py-2 rounded-md font-medium transition-colors text-sm border border-gray-300 ${
|
||||||
? "bg-green-600 text-white shadow-md border-green-600"
|
currentPage === number
|
||||||
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
|
? "bg-green-600 text-white shadow-md border-green-600"
|
||||||
}`}
|
: "bg-gray-100 text-gray-700 hover:bg-gray-200"
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
{number}
|
{number}
|
||||||
</button>
|
</button>
|
||||||
@ -371,9 +411,7 @@ export default function DoctorsPage() {
|
|||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Confirma a exclusão?</AlertDialogTitle>
|
<AlertDialogTitle>Confirma a exclusão?</AlertDialogTitle>
|
||||||
<AlertDialogDescription>
|
<AlertDialogDescription>Esta ação é irreversível e excluirá permanentemente o registro deste médico.</AlertDialogDescription>
|
||||||
Esta ação é irreversível e excluirá permanentemente o registro deste médico.
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
<AlertDialogCancel disabled={loading}>Cancelar</AlertDialogCancel>
|
<AlertDialogCancel disabled={loading}>Cancelar</AlertDialogCancel>
|
||||||
@ -394,25 +432,41 @@ export default function DoctorsPage() {
|
|||||||
<div className="space-y-3 text-left">
|
<div className="space-y-3 text-left">
|
||||||
<h3 className="font-semibold mt-2">Informações Principais</h3>
|
<h3 className="font-semibold mt-2">Informações Principais</h3>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-y-2 gap-x-4 text-sm">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-y-2 gap-x-4 text-sm">
|
||||||
<div><strong>CRM:</strong> {doctorDetails.crm}</div>
|
<div>
|
||||||
<div><strong>Especialidade:</strong> {doctorDetails.especialidade}</div>
|
<strong>CRM:</strong> {doctorDetails.crm}
|
||||||
<div><strong>Celular:</strong> {doctorDetails.contato.celular || 'N/A'}</div>
|
</div>
|
||||||
<div><strong>Localização:</strong> {`${doctorDetails.endereco.cidade || 'N/A'}/${doctorDetails.endereco.estado || 'N/A'}`}</div>
|
<div>
|
||||||
|
<strong>Especialidade:</strong> {doctorDetails.especialidade}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Celular:</strong> {doctorDetails.contato.celular || "N/A"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Localização:</strong> {`${doctorDetails.endereco.cidade || "N/A"}/${doctorDetails.endereco.estado || "N/A"}`}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 className="font-semibold mt-4">Atendimento e Convênio</h3>
|
<h3 className="font-semibold mt-4">Atendimento e Convênio</h3>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-y-2 gap-x-4 text-sm">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-y-2 gap-x-4 text-sm">
|
||||||
<div><strong>Convênio:</strong> {doctorDetails.convenio || 'N/A'}</div>
|
<div>
|
||||||
<div><strong>VIP:</strong> {doctorDetails.vip ? "Sim" : "Não"}</div>
|
<strong>Convênio:</strong> {doctorDetails.convenio || "N/A"}
|
||||||
<div><strong>Status:</strong> {doctorDetails.status || 'N/A'}</div>
|
</div>
|
||||||
<div><strong>Último atendimento:</strong> {doctorDetails.ultimo_atendimento || 'N/A'}</div>
|
<div>
|
||||||
<div><strong>Próximo atendimento:</strong> {doctorDetails.proximo_atendimento || 'N/A'}</div>
|
<strong>VIP:</strong> {doctorDetails.vip ? "Sim" : "Não"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Status:</strong> {doctorDetails.status || "N/A"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Último atendimento:</strong> {doctorDetails.ultimo_atendimento || "N/A"}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Próximo atendimento:</strong> {doctorDetails.proximo_atendimento || "N/A"}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{doctorDetails === null && !loading && (
|
{doctorDetails === null && !loading && <div className="text-red-600">Detalhes não disponíveis.</div>}
|
||||||
<div className="text-red-600">Detalhes não disponíveis.</div>
|
|
||||||
)}
|
|
||||||
</AlertDialogDescription>
|
</AlertDialogDescription>
|
||||||
</AlertDialogHeader>
|
</AlertDialogHeader>
|
||||||
<AlertDialogFooter>
|
<AlertDialogFooter>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useCallback } from "react";
|
import { useState, useEffect, useCallback } from "react";
|
||||||
@ -156,6 +155,7 @@ export default function PacientesPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bloco de Filtros (Responsividade APLICADA) */}
|
{/* Bloco de Filtros (Responsividade APLICADA) */}
|
||||||
|
{/* Adicionado flex-wrap para permitir que os itens quebrem para a linha de baixo */}
|
||||||
<div className="flex flex-wrap items-center gap-4 bg-card p-4 rounded-lg border border-border">
|
<div className="flex flex-wrap items-center gap-4 bg-card p-4 rounded-lg border border-border">
|
||||||
<Filter className="w-5 h-5 text-gray-400" />
|
<Filter className="w-5 h-5 text-gray-400" />
|
||||||
|
|
||||||
@ -165,14 +165,15 @@ export default function PacientesPage() {
|
|||||||
placeholder="Buscar por nome ou telefone..."
|
placeholder="Buscar por nome ou telefone..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full sm:flex-grow sm:min-w-[150px] p-2 border rounded-md text-sm"
|
// w-full no mobile, depois flex-grow para ocupar o espaço disponível
|
||||||
|
className="w-full sm:flex-grow sm:max-w-[300px] p-2 border rounded-md text-sm"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Convênio - Ocupa metade da linha no mobile */}
|
{/* Convênio - Ocupa a largura total em telas pequenas, depois se ajusta */}
|
||||||
<div className="flex items-center gap-2 w-[calc(50%-8px)] sm:w-auto sm:flex-grow sm:max-w-[200px]">
|
<div className="flex items-center gap-2 w-full sm:w-auto sm:flex-grow sm:max-w-[200px]">
|
||||||
<span className="text-sm font-medium text-foreground whitespace-nowrap hidden md:block">Convênio</span>
|
<span className="text-sm font-medium text-foreground whitespace-nowrap hidden md:block">Convênio</span>
|
||||||
<Select value={convenioFilter} onValueChange={setConvenioFilter}>
|
<Select value={convenioFilter} onValueChange={setConvenioFilter}>
|
||||||
<SelectTrigger className="w-full sm:w-40">
|
<SelectTrigger className="w-full sm:w-40"> {/* w-full para mobile, w-40 para sm+ */}
|
||||||
<SelectValue placeholder="Convênio" />
|
<SelectValue placeholder="Convênio" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -185,11 +186,11 @@ export default function PacientesPage() {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* VIP - Ocupa a outra metade da linha no mobile */}
|
{/* VIP - Ocupa a largura total em telas pequenas, depois se ajusta */}
|
||||||
<div className="flex items-center gap-2 w-[calc(50%-8px)] sm:w-auto sm:flex-grow sm:max-w-[150px]">
|
<div className="flex items-center gap-2 w-full sm:w-auto sm:flex-grow sm:max-w-[150px]">
|
||||||
<span className="text-sm font-medium text-foreground whitespace-nowrap hidden md:block">VIP</span>
|
<span className="text-sm font-medium text-foreground whitespace-nowrap hidden md:block">VIP</span>
|
||||||
<Select value={vipFilter} onValueChange={setVipFilter}>
|
<Select value={vipFilter} onValueChange={setVipFilter}>
|
||||||
<SelectTrigger className="w-full sm:w-32">
|
<SelectTrigger className="w-full sm:w-32"> {/* w-full para mobile, w-32 para sm+ */}
|
||||||
<SelectValue placeholder="VIP" />
|
<SelectValue placeholder="VIP" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -200,16 +201,17 @@ export default function PacientesPage() {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Aniversariantes - Vai para a linha de baixo no mobile, ocupando 100% */}
|
{/* Aniversariantes - Ocupa 100% no mobile, e se alinha à direita no md+ */}
|
||||||
<Button variant="outline" className="w-full md:w-auto md:ml-auto">
|
<Button variant="outline" className="w-full md:w-auto md:ml-auto">
|
||||||
<Calendar className="w-4 h-4 mr-2" />
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
Aniversariantes
|
Aniversariantes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabela (Responsividade APLICADA) */}
|
{/* --- SEÇÃO DE TABELA (VISÍVEL EM TELAS MAIORES OU IGUAIS A MD) --- */}
|
||||||
<div className="bg-white rounded-lg border border-gray-200 shadow-md">
|
{/* Garantir que a tabela se esconda em telas menores e apareça em MD+ */}
|
||||||
<div className="overflow-x-auto">
|
<div className="bg-white rounded-lg border border-gray-200 shadow-md hidden md:block">
|
||||||
|
<div className="overflow-x-auto"> {/* Permite rolagem horizontal se a tabela for muito larga */}
|
||||||
{error ? (
|
{error ? (
|
||||||
<div className="p-6 text-red-600">{`Erro ao carregar pacientes: ${error}`}</div>
|
<div className="p-6 text-red-600">{`Erro ao carregar pacientes: ${error}`}</div>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
@ -217,18 +219,14 @@ export default function PacientesPage() {
|
|||||||
<Loader2 className="w-6 h-6 mr-2 animate-spin text-green-600" /> Carregando pacientes...
|
<Loader2 className="w-6 h-6 mr-2 animate-spin text-green-600" /> Carregando pacientes...
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// min-w ajustado para responsividade
|
<table className="w-full min-w-[650px]"> {/* min-w para evitar que a tabela se contraia demais */}
|
||||||
<table className="w-full min-w-[650px] md:min-w-[900px]">
|
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50 border-b border-gray-200">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[20%]">Nome</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[20%]">Nome</th>
|
||||||
{/* Coluna oculta em telas muito pequenas */}
|
{/* Ajustes de visibilidade de colunas para diferentes breakpoints */}
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden sm:table-cell">Telefone</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden sm:table-cell">Telefone</th>
|
||||||
{/* Coluna oculta em telas pequenas e muito pequenas */}
|
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden md:table-cell">Cidade / Estado</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden md:table-cell">Cidade / Estado</th>
|
||||||
{/* Coluna oculta em telas muito pequenas */}
|
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden sm:table-cell">Convênio</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden sm:table-cell">Convênio</th>
|
||||||
{/* Colunas ocultas em telas médias, pequenas e muito pequenas */}
|
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden lg:table-cell">Último atendimento</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden lg:table-cell">Último atendimento</th>
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden lg:table-cell">Próximo atendimento</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden lg:table-cell">Próximo atendimento</th>
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[5%]">Ações</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[5%]">Ações</th>
|
||||||
@ -257,7 +255,6 @@ export default function PacientesPage() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{/* Aplicação das classes de visibilidade */}
|
|
||||||
<td className="p-4 text-gray-600 hidden sm:table-cell">{patient.telefone}</td>
|
<td className="p-4 text-gray-600 hidden sm:table-cell">{patient.telefone}</td>
|
||||||
<td className="p-4 text-gray-600 hidden md:table-cell">{`${patient.cidade} / ${patient.estado}`}</td>
|
<td className="p-4 text-gray-600 hidden md:table-cell">{`${patient.cidade} / ${patient.estado}`}</td>
|
||||||
<td className="p-4 text-gray-600 hidden sm:table-cell">{patient.convenio}</td>
|
<td className="p-4 text-gray-600 hidden sm:table-cell">{patient.convenio}</td>
|
||||||
@ -300,53 +297,109 @@ export default function PacientesPage() {
|
|||||||
</table>
|
</table>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Paginação */}
|
{/* --- SEÇÃO DE CARDS (VISÍVEL APENAS EM TELAS MENORES QUE MD) --- */}
|
||||||
{totalPages > 1 && !loading && (
|
{/* Garantir que os cards apareçam em telas menores e se escondam em MD+ */}
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center p-4 border-t border-gray-200">
|
<div className="bg-white rounded-lg border border-gray-200 shadow-md p-4 block md:hidden">
|
||||||
{/* Renderização dos botões de número de página (Limitando a 5) */}
|
{error ? (
|
||||||
<div className="flex space-x-2"> {/* Increased space-x for more separation */}
|
<div className="p-6 text-red-600">{`Erro ao carregar pacientes: ${error}`}</div>
|
||||||
{/* Botão Anterior */}
|
) : loading ? (
|
||||||
<Button
|
<div className="p-6 text-center text-gray-500 flex items-center justify-center">
|
||||||
onClick={() => setPage((prev) => Math.max(1, prev - 1))}
|
<Loader2 className="w-6 h-6 mr-2 animate-spin text-green-600" /> Carregando pacientes...
|
||||||
disabled={page === 1}
|
</div>
|
||||||
variant="outline"
|
) : filteredPatients.length === 0 ? (
|
||||||
size="lg" // Changed to "lg" for larger buttons
|
<div className="p-8 text-center text-gray-500">
|
||||||
>
|
{allPatients.length === 0 ? "Nenhum paciente cadastrado" : "Nenhum paciente encontrado com os filtros aplicados"}
|
||||||
< Anterior
|
</div>
|
||||||
</Button>
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{currentPatients.map((patient) => (
|
||||||
|
<div key={patient.id} className="bg-gray-50 rounded-lg p-4 flex flex-col sm:flex-row justify-between items-start sm:items-center border border-gray-200">
|
||||||
|
<div className="flex-grow mb-2 sm:mb-0">
|
||||||
|
<div className="font-semibold text-lg text-gray-900 flex items-center">
|
||||||
|
{patient.nome}
|
||||||
|
{patient.vip && (
|
||||||
|
<span className="ml-2 px-2 py-0.5 text-xs font-semibold text-purple-600 bg-purple-100 rounded-full">VIP</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-600">Telefone: {patient.telefone}</div>
|
||||||
|
<div className="text-sm text-gray-600">Convênio: {patient.convenio}</div>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<div className="w-full"><Button variant="outline" className="w-full">Ações</Button></div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>
|
||||||
|
<Eye className="w-4 h-4 mr-2" />
|
||||||
|
Ver detalhes
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
{Array.from({ length: totalPages }, (_, index) => index + 1)
|
<DropdownMenuItem asChild>
|
||||||
.slice(Math.max(0, page - 3), Math.min(totalPages, page + 2))
|
<Link href={`/secretary/pacientes/${patient.id}/editar`} className="flex items-center w-full">
|
||||||
.map((pageNumber) => (
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
<Button
|
Editar
|
||||||
key={pageNumber}
|
</Link>
|
||||||
onClick={() => setPage(pageNumber)}
|
</DropdownMenuItem>
|
||||||
variant={pageNumber === page ? "default" : "outline"}
|
|
||||||
size="lg" // Changed to "lg" for larger buttons
|
|
||||||
className={pageNumber === page ? "bg-green-600 hover:bg-green-700 text-white" : "text-gray-700"}
|
|
||||||
>
|
|
||||||
{pageNumber}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Botão Próximo */}
|
<DropdownMenuItem>
|
||||||
<Button
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
|
Marcar consulta
|
||||||
disabled={page === totalPages}
|
</DropdownMenuItem>
|
||||||
variant="outline"
|
<DropdownMenuItem className="text-red-600" onClick={() => openDeleteDialog(String(patient.id))}>
|
||||||
size="lg" // Changed to "lg" for larger buttons
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
>
|
Excluir
|
||||||
Próximo >
|
</DropdownMenuItem>
|
||||||
</Button>
|
</DropdownMenuContent>
|
||||||
</div>
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Paginação */}
|
||||||
|
{totalPages > 1 && !loading && (
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-center p-4 border-t border-gray-200">
|
||||||
|
<div className="flex space-x-2 flex-wrap justify-center"> {/* Adicionado flex-wrap e justify-center para botões da paginação */}
|
||||||
|
<Button
|
||||||
|
onClick={() => setPage((prev) => Math.max(1, prev - 1))}
|
||||||
|
disabled={page === 1}
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
< Anterior
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{Array.from({ length: totalPages }, (_, index) => index + 1)
|
||||||
|
.slice(Math.max(0, page - 3), Math.min(totalPages, page + 2))
|
||||||
|
.map((pageNumber) => (
|
||||||
|
<Button
|
||||||
|
key={pageNumber}
|
||||||
|
onClick={() => setPage(pageNumber)}
|
||||||
|
variant={pageNumber === page ? "default" : "outline"}
|
||||||
|
size="lg"
|
||||||
|
className={pageNumber === page ? "bg-green-600 hover:bg-green-700 text-white" : "text-gray-700"}
|
||||||
|
>
|
||||||
|
{pageNumber}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
|
||||||
|
disabled={page === totalPages}
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
Próximo >
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* AlertDialogs (Permanecem os mesmos) */}
|
{/* AlertDialogs (Permanecem os mesmos) */}
|
||||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
{/* ... (AlertDialog de Exclusão) ... */}
|
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
|
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
|
||||||
@ -362,7 +415,6 @@ export default function PacientesPage() {
|
|||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
<AlertDialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}>
|
<AlertDialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}>
|
||||||
{/* ... (AlertDialog de Detalhes) ... */}
|
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Detalhes do Paciente</AlertDialogTitle>
|
<AlertDialogTitle>Detalhes do Paciente</AlertDialogTitle>
|
||||||
@ -376,7 +428,7 @@ export default function PacientesPage() {
|
|||||||
<div className="text-red-600 p-4">{patientDetails.error}</div>
|
<div className="text-red-600 p-4">{patientDetails.error}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-4 py-4">
|
<div className="grid gap-4 py-4">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Nome Completo</p>
|
<p className="font-semibold">Nome Completo</p>
|
||||||
<p>{patientDetails.full_name}</p>
|
<p>{patientDetails.full_name}</p>
|
||||||
@ -412,7 +464,7 @@ export default function PacientesPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="border-t pt-4 mt-4">
|
<div className="border-t pt-4 mt-4">
|
||||||
<h3 className="font-semibold mb-2">Endereço</h3>
|
<h3 className="font-semibold mb-2">Endereço</h3>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Rua</p>
|
<p className="font-semibold">Rua</p>
|
||||||
<p>{`${patientDetails.street}, ${patientDetails.number}`}</p>
|
<p>{`${patientDetails.street}, ${patientDetails.number}`}</p>
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// app/manager/usuario/page.tsx
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState, useCallback } from "react";
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
@ -35,17 +34,15 @@ export default function UsersPage() {
|
|||||||
const [userDetails, setUserDetails] = useState<UserInfoResponse | null>(
|
const [userDetails, setUserDetails] = useState<UserInfoResponse | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
// Ajuste 1: Definir 'all' como valor inicial para garantir que todos os usuários sejam exibidos por padrão.
|
|
||||||
const [selectedRole, setSelectedRole] = useState<string>("all");
|
const [selectedRole, setSelectedRole] = useState<string>("all");
|
||||||
|
|
||||||
// --- Lógica de Paginação INÍCIO ---
|
// --- Lógica de Paginação INÍCIO ---
|
||||||
const [itemsPerPage, setItemsPerPage] = useState(10);
|
const [itemsPerPage, setItemsPerPage] = useState(10);
|
||||||
const [currentPage, setCurrentPage] = useState(1);
|
const [currentPage, setCurrentPage] = useState(1);
|
||||||
|
|
||||||
// Lógica para mudar itens por página, resetando para a página 1
|
|
||||||
const handleItemsPerPageChange = (value: string) => {
|
const handleItemsPerPageChange = (value: string) => {
|
||||||
setItemsPerPage(Number(value));
|
setItemsPerPage(Number(value));
|
||||||
setCurrentPage(1); // Resetar para a primeira página
|
setCurrentPage(1);
|
||||||
};
|
};
|
||||||
// --- Lógica de Paginação FIM ---
|
// --- Lógica de Paginação FIM ---
|
||||||
|
|
||||||
@ -81,8 +78,7 @@ export default function UsersPage() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setUsers(mapped);
|
setUsers(mapped);
|
||||||
setCurrentPage(1); // Resetar a página após carregar
|
setCurrentPage(1);
|
||||||
console.log("[fetchUsers] mapped count:", mapped.length);
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Erro ao buscar usuários:", err);
|
console.error("Erro ao buscar usuários:", err);
|
||||||
setError("Não foi possível carregar os usuários. Veja console.");
|
setError("Não foi possível carregar os usuários. Veja console.");
|
||||||
@ -109,9 +105,7 @@ export default function UsersPage() {
|
|||||||
setUserDetails(null);
|
setUserDetails(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log("[openDetailsDialog] user_id:", flatUser.user_id);
|
|
||||||
const data = await usersService.full_data(flatUser.user_id);
|
const data = await usersService.full_data(flatUser.user_id);
|
||||||
console.log("[openDetailsDialog] full_data returned:", data);
|
|
||||||
setUserDetails(data);
|
setUserDetails(data);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Erro ao carregar detalhes:", err);
|
console.error("Erro ao carregar detalhes:", err);
|
||||||
@ -124,23 +118,19 @@ export default function UsersPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 1. Filtragem
|
|
||||||
const filteredUsers =
|
const filteredUsers =
|
||||||
selectedRole && selectedRole !== "all"
|
selectedRole && selectedRole !== "all"
|
||||||
? users.filter((u) => u.role === selectedRole)
|
? users.filter((u) => u.role === selectedRole)
|
||||||
: users;
|
: users;
|
||||||
|
|
||||||
// 2. Paginação (aplicada sobre a lista filtrada)
|
|
||||||
const indexOfLastItem = currentPage * itemsPerPage;
|
const indexOfLastItem = currentPage * itemsPerPage;
|
||||||
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
const indexOfFirstItem = indexOfLastItem - itemsPerPage;
|
||||||
const currentItems = filteredUsers.slice(indexOfFirstItem, indexOfLastItem);
|
const currentItems = filteredUsers.slice(indexOfFirstItem, indexOfLastItem);
|
||||||
|
|
||||||
// Função para mudar de página
|
|
||||||
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
|
const paginate = (pageNumber: number) => setCurrentPage(pageNumber);
|
||||||
|
|
||||||
const totalPages = Math.ceil(filteredUsers.length / itemsPerPage);
|
const totalPages = Math.ceil(filteredUsers.length / itemsPerPage);
|
||||||
|
|
||||||
// --- Funções e Lógica de Navegação ADICIONADAS ---
|
|
||||||
const goToPrevPage = () => {
|
const goToPrevPage = () => {
|
||||||
setCurrentPage((prev) => Math.max(1, prev - 1));
|
setCurrentPage((prev) => Math.max(1, prev - 1));
|
||||||
};
|
};
|
||||||
@ -149,15 +139,13 @@ export default function UsersPage() {
|
|||||||
setCurrentPage((prev) => Math.min(totalPages, prev + 1));
|
setCurrentPage((prev) => Math.min(totalPages, prev + 1));
|
||||||
};
|
};
|
||||||
|
|
||||||
// Lógica para gerar os números das páginas visíveis
|
|
||||||
const getVisiblePageNumbers = (totalPages: number, currentPage: number) => {
|
const getVisiblePageNumbers = (totalPages: number, currentPage: number) => {
|
||||||
const pages: number[] = [];
|
const pages: number[] = [];
|
||||||
const maxVisiblePages = 5; // Número máximo de botões de página a serem exibidos (ex: 2, 3, 4, 5, 6)
|
const maxVisiblePages = 5;
|
||||||
const halfRange = Math.floor(maxVisiblePages / 2);
|
const halfRange = Math.floor(maxVisiblePages / 2);
|
||||||
let startPage = Math.max(1, currentPage - halfRange);
|
let startPage = Math.max(1, currentPage - halfRange);
|
||||||
let endPage = Math.min(totalPages, currentPage + halfRange);
|
let endPage = Math.min(totalPages, currentPage + halfRange);
|
||||||
|
|
||||||
// Ajusta para manter o número fixo de botões quando nos limites
|
|
||||||
if (endPage - startPage + 1 < maxVisiblePages) {
|
if (endPage - startPage + 1 < maxVisiblePages) {
|
||||||
if (endPage === totalPages) {
|
if (endPage === totalPages) {
|
||||||
startPage = Math.max(1, totalPages - maxVisiblePages + 1);
|
startPage = Math.max(1, totalPages - maxVisiblePages + 1);
|
||||||
@ -174,8 +162,6 @@ export default function UsersPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage);
|
const visiblePageNumbers = getVisiblePageNumbers(totalPages, currentPage);
|
||||||
// --- Fim das Funções e Lógica de Navegação ADICIONADAS ---
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
@ -199,17 +185,17 @@ export default function UsersPage() {
|
|||||||
|
|
||||||
{/* Select de Filtro por Papel - Ajustado para resetar a página */}
|
{/* Select de Filtro por Papel - Ajustado para resetar a página */}
|
||||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||||
<span className="text-sm font-medium text-foreground">
|
<span className="text-sm font-medium text-foreground whitespace-nowrap">
|
||||||
Filtrar por papel
|
Filtrar por papel
|
||||||
</span>
|
</span>
|
||||||
<Select
|
<Select
|
||||||
onValueChange={(value) => {
|
onValueChange={(value) => {
|
||||||
setSelectedRole(value);
|
setSelectedRole(value);
|
||||||
setCurrentPage(1); // Resetar para a primeira página ao mudar o filtro
|
setCurrentPage(1);
|
||||||
}}
|
}}
|
||||||
value={selectedRole}>
|
value={selectedRole}>
|
||||||
|
|
||||||
<SelectTrigger className="w-[180px]">
|
<SelectTrigger className="w-full sm:w-[180px]"> {/* w-full para mobile, w-[180px] para sm+ */}
|
||||||
<SelectValue placeholder="Filtrar por papel" />
|
<SelectValue placeholder="Filtrar por papel" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -225,14 +211,14 @@ export default function UsersPage() {
|
|||||||
|
|
||||||
{/* Select de Itens por Página */}
|
{/* Select de Itens por Página */}
|
||||||
<div className="flex items-center gap-2 w-full md:w-auto">
|
<div className="flex items-center gap-2 w-full md:w-auto">
|
||||||
<span className="text-sm font-medium text-foreground">
|
<span className="text-sm font-medium text-foreground whitespace-nowrap">
|
||||||
Itens por página
|
Itens por página
|
||||||
</span>
|
</span>
|
||||||
<Select
|
<Select
|
||||||
onValueChange={handleItemsPerPageChange}
|
onValueChange={handleItemsPerPageChange}
|
||||||
defaultValue={String(itemsPerPage)}
|
defaultValue={String(itemsPerPage)}
|
||||||
>
|
>
|
||||||
<SelectTrigger className="w-[140px]">
|
<SelectTrigger className="w-full sm:w-[140px]"> {/* w-full para mobile, w-[140px] para sm+ */}
|
||||||
<SelectValue placeholder="Itens por pág." />
|
<SelectValue placeholder="Itens por pág." />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -249,7 +235,7 @@ export default function UsersPage() {
|
|||||||
</div>
|
</div>
|
||||||
{/* Fim do Filtro e Itens por Página */}
|
{/* Fim do Filtro e Itens por Página */}
|
||||||
|
|
||||||
{/* Tabela */}
|
{/* Tabela/Lista */}
|
||||||
<div className="bg-white rounded-lg border border-gray-200 shadow-md overflow-x-auto">
|
<div className="bg-white rounded-lg border border-gray-200 shadow-md overflow-x-auto">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<div className="p-8 text-center text-gray-500">
|
<div className="p-8 text-center text-gray-500">
|
||||||
@ -264,10 +250,10 @@ export default function UsersPage() {
|
|||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<table className="min-w-full divide-y divide-gray-200">
|
{/* Tabela para Telas Médias e Grandes */}
|
||||||
<thead className="bg-gray-50 hidden md:table-header-group">
|
<table className="min-w-full divide-y divide-gray-200 hidden md:table">
|
||||||
|
<thead className="bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">ID</th>
|
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nome</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Nome</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">E-mail</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">E-mail</th>
|
||||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Telefone</th>
|
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Telefone</th>
|
||||||
@ -276,15 +262,8 @@ export default function UsersPage() {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody className="bg-white divide-y divide-gray-200">
|
<tbody className="bg-white divide-y divide-gray-200">
|
||||||
{/* Usando currentItems para a paginação */}
|
|
||||||
{currentItems.map((u) => (
|
{currentItems.map((u) => (
|
||||||
<tr
|
<tr key={u.id} className="hover:bg-gray-50">
|
||||||
key={u.id}
|
|
||||||
className="flex flex-col md:table-row md:flex-row border-b md:border-0 hover:bg-gray-50"
|
|
||||||
>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-500 break-all md:whitespace-nowrap">
|
|
||||||
{u.id}
|
|
||||||
</td>
|
|
||||||
<td className="px-6 py-4 text-sm text-gray-900">
|
<td className="px-6 py-4 text-sm text-gray-900">
|
||||||
{u.full_name}
|
{u.full_name}
|
||||||
</td>
|
</td>
|
||||||
@ -312,7 +291,33 @@ export default function UsersPage() {
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
{/* Paginação ATUALIZADA */}
|
{/* Layout em Cards/Lista para Telas Pequenas */}
|
||||||
|
<div className="md:hidden divide-y divide-gray-200">
|
||||||
|
{currentItems.map((u) => (
|
||||||
|
<div key={u.id} className="flex items-center justify-between p-4 hover:bg-gray-50">
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<div className="text-sm font-medium text-gray-900 truncate">
|
||||||
|
{u.full_name || "—"}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-500 capitalize">
|
||||||
|
{u.role || "—"}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="ml-4 flex-shrink-0">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="icon"
|
||||||
|
onClick={() => openDetailsDialog(u)}
|
||||||
|
title="Visualizar"
|
||||||
|
>
|
||||||
|
<Eye className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Paginação */}
|
||||||
{totalPages > 1 && (
|
{totalPages > 1 && (
|
||||||
<div className="flex flex-wrap justify-center items-center gap-2 mt-4 p-4 border-t border-gray-200">
|
<div className="flex flex-wrap justify-center items-center gap-2 mt-4 p-4 border-t border-gray-200">
|
||||||
|
|
||||||
@ -350,7 +355,6 @@ export default function UsersPage() {
|
|||||||
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{/* Fim da Paginação ATUALIZADA */}
|
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@ -387,7 +391,6 @@ export default function UsersPage() {
|
|||||||
<strong>Roles:</strong>{" "}
|
<strong>Roles:</strong>{" "}
|
||||||
{userDetails.roles?.join(", ")}
|
{userDetails.roles?.join(", ")}
|
||||||
</div>
|
</div>
|
||||||
{/* Melhoria na visualização das permissões no modal */}
|
|
||||||
<div className="pt-2">
|
<div className="pt-2">
|
||||||
<strong className="block mb-1">Permissões:</strong>
|
<strong className="block mb-1">Permissões:</strong>
|
||||||
<ul className="list-disc list-inside space-y-0.5 text-sm">
|
<ul className="list-disc list-inside space-y-0.5 text-sm">
|
||||||
|
|||||||
210
app/page.tsx
@ -1,112 +1,184 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import Link from "next/link"
|
import Link from "next/link";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { useState } from "react";
|
||||||
|
|
||||||
export default function InicialPage() {
|
export default function InicialPage() {
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex flex-col bg-background">
|
<div className="min-h-screen flex flex-col bg-background">
|
||||||
{}
|
{/* Barra superior de informações */}
|
||||||
<div className="bg-primary text-primary-foreground text-sm py-2 px-6 flex justify-between">
|
<div className="bg-primary text-primary-foreground text-sm py-2 px-4 md:px-6 flex justify-between items-center">
|
||||||
<span> Horário: 08h00 - 21h00</span>
|
<span className="hidden sm:inline">Horário: 08h00 - 21h00</span>
|
||||||
<span> Email: contato@mediconnect.com</span>
|
<span>Email: contato@mediconnect.com</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{}
|
{/* Header principal - Com Logo REAL */}
|
||||||
<header className="bg-card shadow-md py-4 px-6 flex justify-between items-center">
|
<header className="bg-card shadow-md py-4 px-4 md:px-6 flex justify-between items-center relative">
|
||||||
<h1 className="text-2xl font-bold text-primary">MediConnect</h1>
|
{/* Agrupamento do Logo e Nome do Site */}
|
||||||
<nav className="flex space-x-6 text-muted-foreground font-medium">
|
<a href="#home" className="flex items-center space-x-1 cursor-pointer">
|
||||||
<Link href="/cadastro" className="hover:text-primary"> Home</Link>
|
{/* 1. IMAGEM/LOGO REAL: Referenciando o arquivo placeholder-logo.png na pasta public */}
|
||||||
<a href="#about" className="hover:text-primary">Sobre</a>
|
<img
|
||||||
<a href="#departments" className="hover:text-primary">Departamentos</a>
|
src="/android-chrome-512x512.png" // O caminho se inicia a partir da pasta 'public'
|
||||||
<a href="#doctors" className="hover:text-primary">Médicos</a>
|
alt="Logo MediConnect"
|
||||||
<a href="#contact" className="hover:text-primary">Contato</a>
|
className="w-14 h-14 object-contain" // ALTERADO: Aumentado para w-14 h-14
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* 2. NOME DO SITE */}
|
||||||
|
<h1 className="text-2xl font-bold text-primary">MediConnect</h1>
|
||||||
|
</a>
|
||||||
|
|
||||||
|
{/* Botão do menu hambúrguer para telas menores */}
|
||||||
|
<div className="md:hidden flex items-center space-x-4">
|
||||||
|
{/* O botão de login agora estará sempre aqui, fora do menu */}
|
||||||
|
<Link href="/login">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
className="rounded-full px-4 py-2 text-sm border-2 transition cursor-pointer"
|
||||||
|
>
|
||||||
|
Login
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
<button
|
||||||
|
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||||
|
className="text-primary-foreground focus:outline-none"
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
className="w-6 h-6 text-primary"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
{isMenuOpen ? (
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
></path>
|
||||||
|
) : (
|
||||||
|
<path
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
strokeWidth="2"
|
||||||
|
d="M4 6h16M4 12h16M4 18h16"
|
||||||
|
></path>
|
||||||
|
)}
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Navegação principal */}
|
||||||
|
<nav
|
||||||
|
className={`${
|
||||||
|
isMenuOpen ? "block" : "hidden"
|
||||||
|
} absolute top-[76px] left-0 w-full bg-card shadow-md py-4 md:relative md:top-auto md:left-auto md:w-auto md:block md:bg-transparent md:shadow-none z-10`}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-6 text-muted-foreground font-medium items-center">
|
||||||
|
<Link href="#home" className="hover:text-primary">
|
||||||
|
Home
|
||||||
|
</Link>
|
||||||
|
<a href="#about" className="hover:text-primary">
|
||||||
|
Sobre
|
||||||
|
</a>
|
||||||
|
<a href="#departments" className="hover:text-primary">
|
||||||
|
Departamentos
|
||||||
|
</a>
|
||||||
|
<a href="#doctors" className="hover:text-primary">
|
||||||
|
Médicos
|
||||||
|
</a>
|
||||||
|
<a href="#contact" className="hover:text-primary">
|
||||||
|
Contato
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</nav>
|
</nav>
|
||||||
<div className="flex space-x-4">
|
|
||||||
{}
|
{/* Botão de Login para telas maiores (md e acima) */}
|
||||||
<Link href="/login">
|
<div className="hidden md:flex space-x-4">
|
||||||
<Button
|
<Link href="/login">
|
||||||
variant="outline"
|
<Button
|
||||||
className="rounded-full px-6 py-2 border-2 transition cursor-pointer"
|
variant="outline"
|
||||||
>
|
className="rounded-full px-6 py-2 border-2 transition cursor-pointer"
|
||||||
Login
|
>
|
||||||
</Button>
|
Login
|
||||||
</Link>
|
</Button>
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
{}
|
{/* Seção principal de destaque */}
|
||||||
<section className="flex flex-col md:flex-row items-center justify-between px-10 md:px-20 py-16 bg-background">
|
<section className="flex flex-col md:flex-row items-center justify-between px-6 md:px-10 lg:px-20 py-16 bg-background text-center md:text-left">
|
||||||
<div className="max-w-lg">
|
<div className="max-w-lg mx-auto md:mx-0">
|
||||||
<h2 className="text-muted-foreground uppercase text-sm">Bem-vindo à Saúde Digital</h2>
|
<h2 className="text-muted-foreground uppercase text-sm">
|
||||||
<h1 className="text-4xl font-extrabold text-foreground leading-tight mt-2">
|
Bem-vindo à Saúde Digital
|
||||||
|
</h2>
|
||||||
|
<h1 className="text-3xl sm:text-4xl lg:text-5xl font-extrabold text-foreground leading-tight mt-2">
|
||||||
Soluções Médicas <br /> & Cuidados com a Saúde
|
Soluções Médicas <br /> & Cuidados com a Saúde
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground mt-4">
|
<p className="text-muted-foreground mt-4 text-sm sm:text-base">
|
||||||
Excelência em saúde há mais de 25 anos. Atendimento médicio com qualidade,segurança e carinho.
|
Excelência em saúde há mais de 25 anos. Atendimento médico com
|
||||||
|
qualidade, segurança e carinho.
|
||||||
</p>
|
</p>
|
||||||
<div className="mt-6 flex space-x-4">
|
<div className="mt-6 flex flex-col sm:flex-row space-y-4 sm:space-y-0 sm:space-x-4 justify-center md:justify-start">
|
||||||
<Button>
|
<Button>Nossos Serviços</Button>
|
||||||
Nossos Serviços
|
<Button variant="secondary">Saiba Mais</Button>
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
>
|
|
||||||
Saiba Mais
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-10 md:mt-0">
|
<div className="mt-10 md:mt-0 flex justify-center">
|
||||||
<img
|
<img
|
||||||
src="https://t4.ftcdn.net/jpg/03/20/52/31/360_F_320523164_tx7Rdd7I2XDTvvKfz2oRuRpKOPE5z0ni.jpg"
|
src="https://t4.ftcdn.net/jpg/03/20/52/31/360_F_320523164_tx7Rdd7I2XDTvvKfz2oRuRpKOPE5z0ni.jpg"
|
||||||
alt="Médico"
|
alt="Médico"
|
||||||
className="w-80"
|
className="w-60 sm:w-80 lg:w-96 h-auto object-cover rounded-lg shadow-lg"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{}
|
{/* Seção de serviços */}
|
||||||
<section className="py-16 px-10 md:px-20 bg-card">
|
<section className="py-16 px-6 md:px-10 lg:px-20 bg-card">
|
||||||
<h2 className="text-center text-3xl font-bold text-foreground">Cuidados completos para a sua saúde</h2>
|
<h2 className="text-center text-2xl sm:text-3xl font-bold text-foreground">
|
||||||
<p className="text-center text-muted-foreground mt-2">Serviços médicos que oferecemos</p>
|
Cuidados completos para a sua saúde
|
||||||
|
</h2>
|
||||||
|
<p className="text-center text-muted-foreground mt-2 text-sm sm:text-base">
|
||||||
|
Serviços médicos que oferecemos
|
||||||
|
</p>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-3 gap-8 mt-10">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-8 mt-10 max-w-5xl mx-auto">
|
||||||
<div className="p-6 bg-background rounded-xl shadow hover:shadow-lg transition">
|
<div className="p-6 bg-background rounded-xl shadow hover:shadow-lg transition">
|
||||||
<h3 className="text-xl font-semibold text-primary">Clínica Geral</h3>
|
<h3 className="text-xl font-semibold text-primary">
|
||||||
<p className="text-muted-foreground mt-2">
|
Clínica Geral
|
||||||
Seu primeiro passo para o cuidado. Atendimento focado na prevenção e no diagnóstico inicial.
|
</h3>
|
||||||
|
<p className="text-muted-foreground mt-2 text-sm">
|
||||||
|
Seu primeiro passo para o cuidado. Atendimento focado na prevenção
|
||||||
|
e no diagnóstico inicial.
|
||||||
</p>
|
</p>
|
||||||
<Button className="mt-4">
|
<Button className="mt-4 w-full">Agendar</Button>
|
||||||
Agendar
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6 bg-background rounded-xl shadow hover:shadow-lg transition">
|
<div className="p-6 bg-background rounded-xl shadow hover:shadow-lg transition">
|
||||||
<h3 className="text-xl font-semibold text-primary">Pediatria</h3>
|
<h3 className="text-xl font-semibold text-primary">Pediatria</h3>
|
||||||
<p className="text-muted-foreground mt-2">
|
<p className="text-muted-foreground mt-2 text-sm">
|
||||||
Cuidado gentil e especializado para garantir a saúde e o desenvolvimeto de crianças e adolescentes.
|
Cuidado gentil e especializado para garantir a saúde e o
|
||||||
|
desenvolvimento de crianças e adolescentes.
|
||||||
</p>
|
</p>
|
||||||
<Button className="mt-4">
|
<Button className="mt-4 w-full">Agendar</Button>
|
||||||
Agendar
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="p-6 bg-background rounded-xl shadow hover:shadow-lg transition">
|
<div className="p-6 bg-background rounded-xl shadow hover:shadow-lg transition">
|
||||||
<h3 className="text-xl font-semibold text-primary">Exames</h3>
|
<h3 className="text-xl font-semibold text-primary">Exames</h3>
|
||||||
<p className="text-muted-foreground mt-2">
|
<p className="text-muted-foreground mt-2 text-sm">
|
||||||
Resultados rápidos e precisos em exames laboratoriais e de imagem essenciais para seu diagnóstico.
|
Resultados rápidos e precisos em exames laboratoriais e de imagem
|
||||||
|
essenciais para seu diagnóstico.
|
||||||
</p>
|
</p>
|
||||||
<Button className="mt-4">
|
<Button className="mt-4 w-full">Agendar</Button>
|
||||||
Agendar
|
|
||||||
</Button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
{}
|
{/* Footer */}
|
||||||
<footer className="bg-primary text-primary-foreground py-6 text-center">
|
<footer className="bg-primary text-primary-foreground py-6 text-center text-sm">
|
||||||
<p>© 2025 MediConnect</p>
|
<p>© 2025 MediConnect</p>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -80,6 +80,12 @@ export default function EditarPacientePage() {
|
|||||||
heightM?: string;
|
heightM?: string;
|
||||||
bmi?: string;
|
bmi?: string;
|
||||||
bloodType?: string;
|
bloodType?: string;
|
||||||
|
// Adicionei os campos do convênio para o tipo FormData
|
||||||
|
convenio?: string;
|
||||||
|
plano?: string;
|
||||||
|
numeroMatricula?: string;
|
||||||
|
validadeCarteira?: string;
|
||||||
|
alergias?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@ -132,6 +138,11 @@ export default function EditarPacientePage() {
|
|||||||
heightM: "",
|
heightM: "",
|
||||||
bmi: "",
|
bmi: "",
|
||||||
bloodType: "",
|
bloodType: "",
|
||||||
|
convenio: "",
|
||||||
|
plano: "",
|
||||||
|
numeroMatricula: "",
|
||||||
|
validadeCarteira: "",
|
||||||
|
alergias: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isGuiaConvenio, setIsGuiaConvenio] = useState(false);
|
const [isGuiaConvenio, setIsGuiaConvenio] = useState(false);
|
||||||
@ -140,7 +151,7 @@ export default function EditarPacientePage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function fetchPatient() {
|
async function fetchPatient() {
|
||||||
try {
|
try {
|
||||||
const res = await patientsService.getById(patientId);
|
const res = await patientsService.getById(patientId);
|
||||||
// Map API snake_case/nested to local camelCase form
|
// Map API snake_case/nested to local camelCase form
|
||||||
setFormData({
|
setFormData({
|
||||||
id: res[0]?.id ?? "",
|
id: res[0]?.id ?? "",
|
||||||
@ -191,6 +202,12 @@ export default function EditarPacientePage() {
|
|||||||
heightM: res[0]?.height_m ? String(res[0].height_m) : "",
|
heightM: res[0]?.height_m ? String(res[0].height_m) : "",
|
||||||
bmi: res[0]?.bmi ? String(res[0].bmi) : "",
|
bmi: res[0]?.bmi ? String(res[0].bmi) : "",
|
||||||
bloodType: res[0]?.blood_type ?? "",
|
bloodType: res[0]?.blood_type ?? "",
|
||||||
|
// Os campos de convênio e alergias não vêm da API, então os deixamos vazios ou com valores padrão
|
||||||
|
convenio: "",
|
||||||
|
plano: "",
|
||||||
|
numeroMatricula: "",
|
||||||
|
validadeCarteira: "",
|
||||||
|
alergias: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
@ -237,8 +254,8 @@ export default function EditarPacientePage() {
|
|||||||
router.push("/secretary/pacientes");
|
router.push("/secretary/pacientes");
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("Erro ao atualizar paciente:", err);
|
console.error("Erro ao atualizar paciente:", err);
|
||||||
toast({
|
toast({
|
||||||
title: "Erro",
|
title: "Erro",
|
||||||
description: err?.message || "Não foi possível atualizar o paciente",
|
description: err?.message || "Não foi possível atualizar o paciente",
|
||||||
variant: "destructive"
|
variant: "destructive"
|
||||||
});
|
});
|
||||||
@ -247,66 +264,45 @@ export default function EditarPacientePage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
{/* O espaçamento foi reduzido aqui: de `p-4 sm:p-6 lg:p-8` para `p-2 sm:p-4 lg:p-6` */}
|
||||||
<div className="flex items-center gap-4">
|
<div className="space-y-6 p-2 sm:p-4 lg:p-6 max-w-10xl mx-auto"> {/* Alterado padding responsivo */}
|
||||||
<Link href="/secretary/pacientes">
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
|
||||||
<Button variant="ghost" size="sm">
|
<div className="flex items-center gap-4">
|
||||||
<ArrowLeft className="w-4 h-4 mr-2" />
|
<Link href="/secretary/pacientes">
|
||||||
Voltar
|
<Button variant="ghost" size="sm">
|
||||||
</Button>
|
<ArrowLeft className="w-4 h-4 mr-2" />
|
||||||
</Link>
|
Voltar
|
||||||
<div>
|
</Button>
|
||||||
<h1 className="text-2xl font-bold text-gray-900">Editar Paciente</h1>
|
</Link>
|
||||||
<p className="text-gray-600">Atualize as informações do paciente</p>
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900">Editar Paciente</h1>
|
||||||
|
<p className="text-gray-600">Atualize as informações do paciente</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Anexos Section */}
|
{/* Anexos Section - Movido para fora do cabeçalho para melhor organização e responsividade */}
|
||||||
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
|
||||||
<h2 className="text-lg font-semibold text-gray-900 mb-6">Anexos</h2>
|
|
||||||
<div className="flex items-center gap-3 mb-4">
|
|
||||||
<input ref={anexoInputRef} type="file" className="hidden" />
|
|
||||||
<Button type="button" variant="outline" disabled={isUploadingAnexo}>
|
|
||||||
<Paperclip className="w-4 h-4 mr-2" /> {isUploadingAnexo ? "Enviando..." : "Adicionar anexo"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
{anexos.length === 0 ? (
|
|
||||||
<p className="text-sm text-gray-500">Nenhum anexo encontrado.</p>
|
|
||||||
) : (
|
|
||||||
<ul className="divide-y">
|
|
||||||
{anexos.map((a) => (
|
|
||||||
<li key={a.id} className="flex items-center justify-between py-2">
|
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
|
||||||
<Paperclip className="w-4 h-4 text-gray-500 shrink-0" />
|
|
||||||
<span className="text-sm text-gray-800 truncate">{a.nome || a.filename || `Anexo ${a.id}`}</span>
|
|
||||||
</div>
|
|
||||||
<Button type="button" variant="ghost" className="text-red-600">
|
|
||||||
<Trash2 className="w-4 h-4 mr-1" /> Remover
|
|
||||||
</Button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<form onSubmit={handleSubmit} className="space-y-8">
|
<form onSubmit={handleSubmit} className="space-y-8">
|
||||||
|
{/* Dados Pessoais Section */}
|
||||||
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
<div className="bg-white rounded-lg border border-gray-200 p-6">
|
||||||
<h2 className="text-lg font-semibold text-gray-900 mb-6">Dados Pessoais</h2>
|
<h2 className="text-lg font-semibold text-gray-900 mb-6">Dados Pessoais</h2>
|
||||||
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||||
{/* Photo upload */}
|
{/* Photo upload */}
|
||||||
<div className="space-y-2">
|
<div className="space-y-2 col-span-1 md:col-span-2 lg:col-span-1">
|
||||||
<Label>Foto do paciente</Label>
|
<Label>Foto do paciente</Label>
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex flex-col sm:flex-row items-center gap-4">
|
||||||
<div className="w-20 h-20 rounded-full bg-gray-100 overflow-hidden flex items-center justify-center">
|
<div className="w-20 h-20 rounded-full bg-gray-100 overflow-hidden flex items-center justify-center">
|
||||||
{photoUrl ? (
|
{photoUrl ? (
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
// eslint-disable-next-line @next/next/no-img-element
|
||||||
<img src={photoUrl} alt="Foto do paciente" className="w-full h-full object-cover" />
|
<img src={photoUrl} alt="Foto do paciente" className="w-full h-full object-cover" />
|
||||||
) : (
|
) : (
|
||||||
<span className="text-gray-400 text-sm">Sem foto</span>
|
<span className="text-gray-400 text-sm text-center">Sem foto</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<div className="flex gap-2">
|
<div className="flex flex-col sm:flex-row gap-2 mt-2 sm:mt-0">
|
||||||
<input ref={fileInputRef} type="file" accept="image/*" className="hidden" />
|
<input ref={fileInputRef} type="file" accept="image/*" className="hidden" />
|
||||||
<Button type="button" variant="outline" disabled={isUploadingPhoto}>
|
<Button type="button" variant="outline" disabled={isUploadingPhoto}>
|
||||||
{isUploadingPhoto ? "Enviando..." : "Enviar foto"}
|
{isUploadingPhoto ? "Enviando..." : "Enviar foto"}
|
||||||
@ -336,7 +332,7 @@ export default function EditarPacientePage() {
|
|||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label>Sexo *</Label>
|
<Label>Sexo *</Label>
|
||||||
<div className="flex gap-4">
|
<div className="flex flex-col sm:flex-row gap-4">
|
||||||
<div className="flex items-center space-x-2">
|
<div className="flex items-center space-x-2">
|
||||||
<input type="radio" id="Masculino" name="sexo" value="Masculino" checked={formData.sexo === "Masculino"} onChange={(e) => handleInputChange("sexo", e.target.value)} className="w-4 h-4 text-blue-600" />
|
<input type="radio" id="Masculino" name="sexo" value="Masculino" checked={formData.sexo === "Masculino"} onChange={(e) => handleInputChange("sexo", e.target.value)} className="w-4 h-4 text-blue-600" />
|
||||||
<Label htmlFor="Masculino">Masculino</Label>
|
<Label htmlFor="Masculino">Masculino</Label>
|
||||||
@ -403,7 +399,7 @@ export default function EditarPacientePage() {
|
|||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="profissao">Profissão</Label>
|
<Label htmlFor="profissao">Profissão</Label>
|
||||||
<Input id="profissao" value={formData.profession} onChange={(e) => handleInputChange("profession", e.target.value)} />
|
<Input id="profissao" value={formData.profession} onChange={(e) => handleInputChange("profissao", e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@ -477,12 +473,12 @@ export default function EditarPacientePage() {
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="email">E-mail *</Label>
|
<Label htmlFor="email">E-mail *</Label>
|
||||||
<Input id="email" type="email" value={formData.email} onChange={(e) => handleInputChange("email", e.target.value)} required/>
|
<Input id="email" type="email" value={formData.email} onChange={(e) => handleInputChange("email", e.target.value)} required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="celular">Celular *</Label>
|
<Label htmlFor="celular">Celular *</Label>
|
||||||
<Input id="celular" value={formData.phoneMobile} onChange={(e) => handleInputChange("phoneMobile", e.target.value)} placeholder="(00) 00000-0000" required/>
|
<Input id="celular" value={formData.phoneMobile} onChange={(e) => handleInputChange("phoneMobile", e.target.value)} placeholder="(00) 00000-0000" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@ -614,7 +610,7 @@ export default function EditarPacientePage() {
|
|||||||
|
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<Label htmlFor="alergias">Alergias</Label>
|
<Label htmlFor="alergias">Alergias</Label>
|
||||||
<Textarea id="alergias" onChange={(e) => handleInputChange("alergias", e.target.value)} placeholder="Ex: AAS, Dipirona, etc." className="mt-2" />
|
<Textarea id="alergias" value={formData.alergias} onChange={(e) => handleInputChange("alergias", e.target.value)} placeholder="Ex: AAS, Dipirona, etc." className="mt-2" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -625,7 +621,7 @@ export default function EditarPacientePage() {
|
|||||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="convenio">Convênio</Label>
|
<Label htmlFor="convenio">Convênio</Label>
|
||||||
<Select onValueChange={(value) => handleInputChange("convenio", value)}>
|
<Select value={formData.convenio} onValueChange={(value) => handleInputChange("convenio", value)}>
|
||||||
<SelectTrigger>
|
<SelectTrigger>
|
||||||
<SelectValue placeholder="Selecione" />
|
<SelectValue placeholder="Selecione" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@ -641,17 +637,17 @@ export default function EditarPacientePage() {
|
|||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="plano">Plano</Label>
|
<Label htmlFor="plano">Plano</Label>
|
||||||
<Input id="plano" onChange={(e) => handleInputChange("plano", e.target.value)} />
|
<Input id="plano" value={formData.plano} onChange={(e) => handleInputChange("plano", e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="numeroMatricula">Nº de matrícula</Label>
|
<Label htmlFor="numeroMatricula">Nº de matrícula</Label>
|
||||||
<Input id="numeroMatricula" onChange={(e) => handleInputChange("numeroMatricula", e.target.value)} />
|
<Input id="numeroMatricula" value={formData.numeroMatricula} onChange={(e) => handleInputChange("numeroMatricula", e.target.value)} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="validadeCarteira">Validade da Carteira</Label>
|
<Label htmlFor="validadeCarteira">Validade da Carteira</Label>
|
||||||
<Input id="validadeCarteira" type="date" onChange={(e) => handleInputChange("validadeCarteira", e.target.value)} disabled={validadeIndeterminada} />
|
<Input id="validadeCarteira" type="date" value={formData.validadeCarteira} onChange={(e) => handleInputChange("validadeCarteira", e.target.value)} disabled={validadeIndeterminada} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -663,13 +659,13 @@ export default function EditarPacientePage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex justify-end gap-4">
|
<div className="flex flex-col sm:flex-row justify-end gap-4">
|
||||||
<Link href="/secretary/pacientes">
|
<Link href="/secretary/pacientes">
|
||||||
<Button type="button" variant="outline">
|
<Button type="button" variant="outline" className="w-full sm:w-auto">
|
||||||
Cancelar
|
Cancelar
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Button type="submit" className="bg-blue-600 hover:bg-blue-700">
|
<Button type="submit" className="bg-blue-600 hover:bg-blue-700 w-full sm:w-auto">
|
||||||
<Save className="w-4 h-4 mr-2" />
|
<Save className="w-4 h-4 mr-2" />
|
||||||
Salvar Alterações
|
Salvar Alterações
|
||||||
</Button>
|
</Button>
|
||||||
@ -678,4 +674,4 @@ export default function EditarPacientePage() {
|
|||||||
</div>
|
</div>
|
||||||
</Sidebar>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -93,18 +93,22 @@ export default function NovoUsuarioPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
<div className="w-full h-full p-4 md:p-8 flex justify-center items-start">
|
{/* Container principal com padding responsivo e centralização */}
|
||||||
<div className="w-full max-w-screen-lg space-y-8">
|
<div className="w-full h-full p-4 md:p-8 lg:p-12 flex justify-center items-start">
|
||||||
<div className="flex items-center justify-between border-b pb-4">
|
{/* Conteúdo do formulário com largura máxima para telas maiores */}
|
||||||
|
<div className="w-full max-w-screen-md lg:max-w-screen-lg space-y-8">
|
||||||
|
{/* Cabeçalho da página */}
|
||||||
|
<div className="flex flex-col sm:flex-row sm:items-center sm:justify-between border-b pb-4 gap-4"> {/* Ajustado para empilhar em telas pequenas */}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-extrabold text-gray-900">Novo Usuário</h1>
|
<h1 className="text-2xl sm:text-3xl font-extrabold text-gray-900">Novo Usuário</h1> {/* Tamanho de texto responsivo */}
|
||||||
<p className="text-md text-gray-500">Preencha os dados para cadastrar um novo usuário no sistema.</p>
|
<p className="text-sm sm:text-md text-gray-500">Preencha os dados para cadastrar um novo usuário no sistema.</p> {/* Tamanho de texto responsivo */}
|
||||||
</div>
|
</div>
|
||||||
<Link href="/manager/usuario">
|
<Link href="/manager/usuario">
|
||||||
<Button variant="outline">Cancelar</Button>
|
<Button variant="outline" className="w-full sm:w-auto">Cancelar</Button> {/* Botão ocupa largura total em telas pequenas */}
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Formulário */}
|
||||||
<form onSubmit={handleSubmit} className="space-y-6 bg-white p-6 md:p-10 border rounded-xl shadow-lg">
|
<form onSubmit={handleSubmit} className="space-y-6 bg-white p-6 md:p-10 border rounded-xl shadow-lg">
|
||||||
{error && (
|
{error && (
|
||||||
<div className="p-4 bg-red-50 text-red-700 rounded-lg border border-red-300">
|
<div className="p-4 bg-red-50 text-red-700 rounded-lg border border-red-300">
|
||||||
@ -113,8 +117,9 @@ export default function NovoUsuarioPage() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Campos do formulário em grid responsivo */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div className="space-y-2 md:col-span-2">
|
<div className="space-y-2 md:col-span-2"> {/* Nome Completo ocupa 2 colunas em telas maiores */}
|
||||||
<Label htmlFor="nomeCompleto">Nome Completo *</Label>
|
<Label htmlFor="nomeCompleto">Nome Completo *</Label>
|
||||||
<Input id="nomeCompleto" value={formData.nomeCompleto} onChange={(e) => handleInputChange("nomeCompleto", e.target.value)} placeholder="Nome e Sobrenome" required />
|
<Input id="nomeCompleto" value={formData.nomeCompleto} onChange={(e) => handleInputChange("nomeCompleto", e.target.value)} placeholder="Nome e Sobrenome" required />
|
||||||
</div>
|
</div>
|
||||||
@ -124,7 +129,10 @@ export default function NovoUsuarioPage() {
|
|||||||
<Input id="email" type="email" value={formData.email} onChange={(e) => handleInputChange("email", e.target.value)} placeholder="exemplo@dominio.com" required />
|
<Input id="email" type="email" value={formData.email} onChange={(e) => handleInputChange("email", e.target.value)} placeholder="exemplo@dominio.com" required />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* O seletor de Papel (Função) foi removido */}
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="telefone">Telefone</Label>
|
||||||
|
<Input id="telefone" value={formData.telefone} onChange={(e) => handleInputChange("telefone", e.target.value)} placeholder="(00) 00000-0000" maxLength={15} />
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="senha">Senha *</Label>
|
<Label htmlFor="senha">Senha *</Label>
|
||||||
@ -136,25 +144,21 @@ export default function NovoUsuarioPage() {
|
|||||||
<Input id="confirmarSenha" type="password" value={formData.confirmarSenha} onChange={(e) => handleInputChange("confirmarSenha", e.target.value)} placeholder="Repita a senha" required />
|
<Input id="confirmarSenha" type="password" value={formData.confirmarSenha} onChange={(e) => handleInputChange("confirmarSenha", e.target.value)} placeholder="Repita a senha" required />
|
||||||
{formData.senha && formData.confirmarSenha && formData.senha !== formData.confirmarSenha && <p className="text-xs text-red-500">As senhas não coincidem.</p>}
|
{formData.senha && formData.confirmarSenha && formData.senha !== formData.confirmarSenha && <p className="text-xs text-red-500">As senhas não coincidem.</p>}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2 md:col-span-2"> {/* CPF ocupa 2 colunas em telas maiores */}
|
||||||
<Label htmlFor="telefone">Telefone</Label>
|
<Label htmlFor="cpf">CPF *</Label>
|
||||||
<Input id="telefone" value={formData.telefone} onChange={(e) => handleInputChange("telefone", e.target.value)} placeholder="(00) 00000-0000" maxLength={15} />
|
<Input id="cpf" value={formData.cpf} onChange={(e) => handleInputChange("cpf", e.target.value)} placeholder="xxx.xxx.xxx-xx" required />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
{/* Botões de ação */}
|
||||||
<Label htmlFor="cpf">Cpf *</Label>
|
<div className="flex flex-col sm:flex-row justify-end gap-4 pt-6 border-t mt-6"> {/* Botões empilhados em telas pequenas */}
|
||||||
<Input id="cpf" value={formData.cpf} onChange={(e) => handleInputChange("cpf", e.target.value)} placeholder="xxx.xxx.xxx-xx" required />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex justify-end gap-4 pt-6 border-t mt-6">
|
|
||||||
<Link href="/manager/usuario">
|
<Link href="/manager/usuario">
|
||||||
<Button type="button" variant="outline" disabled={isSaving}>
|
<Button type="button" variant="outline" disabled={isSaving} className="w-full sm:w-auto">
|
||||||
Cancelar
|
Cancelar
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Button type="submit" className="bg-green-600 hover:bg-green-700" disabled={isSaving}>
|
<Button type="submit" className="bg-green-600 hover:bg-green-700 w-full sm:w-auto" disabled={isSaving}>
|
||||||
{isSaving ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <Save className="w-4 h-4 mr-2" />}
|
{isSaving ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <Save className="w-4 h-4 mr-2" />}
|
||||||
{isSaving ? "Salvando..." : "Salvar Usuário"}
|
{isSaving ? "Salvando..." : "Salvar Usuário"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -164,7 +164,7 @@ export default function PacientesPage() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bloco de Filtros (Responsividade APLICADA) */}
|
{/* Bloco de Filtros (Responsividade APLICADA) */}
|
||||||
<div className="flex flex-wrap items-center gap-4 bg-card p-4 rounded-lg border border-border">
|
<div className="flex flex-wrap items-center gap-4 bg-card p-4 rounded-lg border border-border">
|
||||||
<Filter className="w-5 h-5 text-gray-400" />
|
<Filter className="w-5 h-5 text-gray-400" />
|
||||||
|
|
||||||
{/* Busca - Ocupa 100% no mobile, depois cresce */}
|
{/* Busca - Ocupa 100% no mobile, depois cresce */}
|
||||||
@ -173,14 +173,15 @@ export default function PacientesPage() {
|
|||||||
placeholder="Buscar por nome ou telefone..."
|
placeholder="Buscar por nome ou telefone..."
|
||||||
value={searchTerm}
|
value={searchTerm}
|
||||||
onChange={(e) => setSearchTerm(e.target.value)}
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
className="w-full sm:flex-grow sm:min-w-[150px] p-2 border rounded-md text-sm"
|
// w-full no mobile, depois flex-grow para ocupar o espaço disponível
|
||||||
|
className="w-full sm:flex-grow sm:max-w-[300px] p-2 border rounded-md text-sm"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Convênio - Ocupa metade da linha no mobile */}
|
{/* Convênio - Ocupa a largura total em telas pequenas, depois se ajusta */}
|
||||||
<div className="flex items-center gap-2 w-[calc(50%-8px)] sm:w-auto sm:flex-grow sm:max-w-[200px]">
|
<div className="flex items-center gap-2 w-full sm:w-auto sm:flex-grow sm:max-w-[200px]">
|
||||||
<span className="text-sm font-medium text-foreground whitespace-nowrap hidden md:block">Convênio</span>
|
<span className="text-sm font-medium text-foreground whitespace-nowrap hidden md:block">Convênio</span>
|
||||||
<Select value={convenioFilter} onValueChange={setConvenioFilter}>
|
<Select value={convenioFilter} onValueChange={setConvenioFilter}>
|
||||||
<SelectTrigger className="w-full sm:w-40">
|
<SelectTrigger className="w-full sm:w-40"> {/* w-full para mobile, w-40 para sm+ */}
|
||||||
<SelectValue placeholder="Convênio" />
|
<SelectValue placeholder="Convênio" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -193,11 +194,11 @@ export default function PacientesPage() {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* VIP - Ocupa a outra metade da linha no mobile */}
|
{/* VIP - Ocupa a largura total em telas pequenas, depois se ajusta */}
|
||||||
<div className="flex items-center gap-2 w-[calc(50%-8px)] sm:w-auto sm:flex-grow sm:max-w-[150px]">
|
<div className="flex items-center gap-2 w-full sm:w-auto sm:flex-grow sm:max-w-[150px]">
|
||||||
<span className="text-sm font-medium text-foreground whitespace-nowrap hidden md:block">VIP</span>
|
<span className="text-sm font-medium text-foreground whitespace-nowrap hidden md:block">VIP</span>
|
||||||
<Select value={vipFilter} onValueChange={setVipFilter}>
|
<Select value={vipFilter} onValueChange={setVipFilter}>
|
||||||
<SelectTrigger className="w-full sm:w-32">
|
<SelectTrigger className="w-full sm:w-32"> {/* w-full para mobile, w-32 para sm+ */}
|
||||||
<SelectValue placeholder="VIP" />
|
<SelectValue placeholder="VIP" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
@ -208,16 +209,17 @@ export default function PacientesPage() {
|
|||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Aniversariantes - Vai para a linha de baixo no mobile, ocupando 100% */}
|
{/* Aniversariantes - Ocupa 100% no mobile, e se alinha à direita no md+ */}
|
||||||
<Button variant="outline" className="w-full md:w-auto md:ml-auto">
|
<Button variant="outline" className="w-full md:w-auto md:ml-auto">
|
||||||
<Calendar className="w-4 h-4 mr-2" />
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
Aniversariantes
|
Aniversariantes
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Tabela (Responsividade APLICADA) */}
|
{/* --- SEÇÃO DE TABELA (VISÍVEL EM TELAS MAIORES OU IGUAIS A MD) --- */}
|
||||||
<div className="bg-white rounded-lg border border-gray-200 shadow-md">
|
{/* Garantir que a tabela se esconda em telas menores e apareça em MD+ */}
|
||||||
<div className="overflow-x-auto">
|
<div className="bg-white rounded-lg border border-gray-200 shadow-md hidden md:block">
|
||||||
|
<div className="overflow-x-auto"> {/* Permite rolagem horizontal se a tabela for muito larga */}
|
||||||
{error ? (
|
{error ? (
|
||||||
<div className="p-6 text-red-600">{`Erro ao carregar pacientes: ${error}`}</div>
|
<div className="p-6 text-red-600">{`Erro ao carregar pacientes: ${error}`}</div>
|
||||||
) : loading ? (
|
) : loading ? (
|
||||||
@ -225,18 +227,14 @@ export default function PacientesPage() {
|
|||||||
<Loader2 className="w-6 h-6 mr-2 animate-spin text-green-600" /> Carregando pacientes...
|
<Loader2 className="w-6 h-6 mr-2 animate-spin text-green-600" /> Carregando pacientes...
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
// min-w ajustado para responsividade
|
<table className="w-full min-w-[650px]"> {/* min-w para evitar que a tabela se contraia demais */}
|
||||||
<table className="w-full min-w-[650px] md:min-w-[900px]">
|
|
||||||
<thead className="bg-gray-50 border-b border-gray-200">
|
<thead className="bg-gray-50 border-b border-gray-200">
|
||||||
<tr>
|
<tr>
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[20%]">Nome</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[20%]">Nome</th>
|
||||||
{/* Coluna oculta em telas muito pequenas */}
|
{/* Ajustes de visibilidade de colunas para diferentes breakpoints */}
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden sm:table-cell">Telefone</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden sm:table-cell">Telefone</th>
|
||||||
{/* Coluna oculta em telas pequenas e muito pequenas */}
|
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden md:table-cell">Cidade / Estado</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden md:table-cell">Cidade / Estado</th>
|
||||||
{/* Coluna oculta em telas muito pequenas */}
|
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden sm:table-cell">Convênio</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden sm:table-cell">Convênio</th>
|
||||||
{/* Colunas ocultas em telas médias, pequenas e muito pequenas */}
|
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden lg:table-cell">Último atendimento</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden lg:table-cell">Último atendimento</th>
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden lg:table-cell">Próximo atendimento</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[15%] hidden lg:table-cell">Próximo atendimento</th>
|
||||||
<th className="text-left p-4 font-medium text-gray-700 w-[5%]">Ações</th>
|
<th className="text-left p-4 font-medium text-gray-700 w-[5%]">Ações</th>
|
||||||
@ -265,7 +263,6 @@ export default function PacientesPage() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
{/* Aplicação das classes de visibilidade */}
|
|
||||||
<td className="p-4 text-gray-600 hidden sm:table-cell">{patient.telefone}</td>
|
<td className="p-4 text-gray-600 hidden sm:table-cell">{patient.telefone}</td>
|
||||||
<td className="p-4 text-gray-600 hidden md:table-cell">{`${patient.cidade} / ${patient.estado}`}</td>
|
<td className="p-4 text-gray-600 hidden md:table-cell">{`${patient.cidade} / ${patient.estado}`}</td>
|
||||||
<td className="p-4 text-gray-600 hidden sm:table-cell">{patient.convenio}</td>
|
<td className="p-4 text-gray-600 hidden sm:table-cell">{patient.convenio}</td>
|
||||||
@ -308,53 +305,109 @@ export default function PacientesPage() {
|
|||||||
</table>
|
</table>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Paginação */}
|
{/* --- SEÇÃO DE CARDS (VISÍVEL APENAS EM TELAS MENORES QUE MD) --- */}
|
||||||
{totalPages > 1 && !loading && (
|
{/* Garantir que os cards apareçam em telas menores e se escondam em MD+ */}
|
||||||
<div className="flex flex-col sm:flex-row items-center justify-center p-4 border-t border-gray-200">
|
<div className="bg-white rounded-lg border border-gray-200 shadow-md p-4 block md:hidden">
|
||||||
{/* Renderização dos botões de número de página (Limitando a 5) */}
|
{error ? (
|
||||||
<div className="flex space-x-2"> {/* Increased space-x for more separation */}
|
<div className="p-6 text-red-600">{`Erro ao carregar pacientes: ${error}`}</div>
|
||||||
{/* Botão Anterior */}
|
) : loading ? (
|
||||||
<Button
|
<div className="p-6 text-center text-gray-500 flex items-center justify-center">
|
||||||
onClick={() => setPage((prev) => Math.max(1, prev - 1))}
|
<Loader2 className="w-6 h-6 mr-2 animate-spin text-green-600" /> Carregando pacientes...
|
||||||
disabled={page === 1}
|
</div>
|
||||||
variant="outline"
|
) : filteredPatients.length === 0 ? (
|
||||||
size="lg" // Changed to "lg" for larger buttons
|
<div className="p-8 text-center text-gray-500">
|
||||||
>
|
{allPatients.length === 0 ? "Nenhum paciente cadastrado" : "Nenhum paciente encontrado com os filtros aplicados"}
|
||||||
< Anterior
|
</div>
|
||||||
</Button>
|
) : (
|
||||||
|
<div className="space-y-4">
|
||||||
|
{currentPatients.map((patient) => (
|
||||||
|
<div key={patient.id} className="bg-gray-50 rounded-lg p-4 flex flex-col sm:flex-row justify-between items-start sm:items-center border border-gray-200">
|
||||||
|
<div className="flex-grow mb-2 sm:mb-0">
|
||||||
|
<div className="font-semibold text-lg text-gray-900 flex items-center">
|
||||||
|
{patient.nome}
|
||||||
|
{patient.vip && (
|
||||||
|
<span className="ml-2 px-2 py-0.5 text-xs font-semibold text-purple-600 bg-purple-100 rounded-full">VIP</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="text-sm text-gray-600">Telefone: {patient.telefone}</div>
|
||||||
|
<div className="text-sm text-gray-600">Convênio: {patient.convenio}</div>
|
||||||
|
</div>
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<div className="w-full"><Button variant="outline" className="w-full">Ações</Button></div>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => openDetailsDialog(String(patient.id))}>
|
||||||
|
<Eye className="w-4 h-4 mr-2" />
|
||||||
|
Ver detalhes
|
||||||
|
</DropdownMenuItem>
|
||||||
|
|
||||||
{Array.from({ length: totalPages }, (_, index) => index + 1)
|
<DropdownMenuItem asChild>
|
||||||
.slice(Math.max(0, page - 3), Math.min(totalPages, page + 2))
|
<Link href={`/secretary/pacientes/${patient.id}/editar`} className="flex items-center w-full">
|
||||||
.map((pageNumber) => (
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
<Button
|
Editar
|
||||||
key={pageNumber}
|
</Link>
|
||||||
onClick={() => setPage(pageNumber)}
|
</DropdownMenuItem>
|
||||||
variant={pageNumber === page ? "default" : "outline"}
|
|
||||||
size="lg" // Changed to "lg" for larger buttons
|
|
||||||
className={pageNumber === page ? "bg-green-600 hover:bg-green-700 text-white" : "text-gray-700"}
|
|
||||||
>
|
|
||||||
{pageNumber}
|
|
||||||
</Button>
|
|
||||||
))}
|
|
||||||
|
|
||||||
{/* Botão Próximo */}
|
<DropdownMenuItem>
|
||||||
<Button
|
<Calendar className="w-4 h-4 mr-2" />
|
||||||
onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
|
Marcar consulta
|
||||||
disabled={page === totalPages}
|
</DropdownMenuItem>
|
||||||
variant="outline"
|
<DropdownMenuItem className="text-red-600" onClick={() => openDeleteDialog(String(patient.id))}>
|
||||||
size="lg" // Changed to "lg" for larger buttons
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
>
|
Excluir
|
||||||
Próximo >
|
</DropdownMenuItem>
|
||||||
</Button>
|
</DropdownMenuContent>
|
||||||
</div>
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Paginação */}
|
||||||
|
{totalPages > 1 && !loading && (
|
||||||
|
<div className="flex flex-col sm:flex-row items-center justify-center p-4 border-t border-gray-200">
|
||||||
|
<div className="flex space-x-2 flex-wrap justify-center"> {/* Adicionado flex-wrap e justify-center para botões da paginação */}
|
||||||
|
<Button
|
||||||
|
onClick={() => setPage((prev) => Math.max(1, prev - 1))}
|
||||||
|
disabled={page === 1}
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
< Anterior
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{Array.from({ length: totalPages }, (_, index) => index + 1)
|
||||||
|
.slice(Math.max(0, page - 3), Math.min(totalPages, page + 2))
|
||||||
|
.map((pageNumber) => (
|
||||||
|
<Button
|
||||||
|
key={pageNumber}
|
||||||
|
onClick={() => setPage(pageNumber)}
|
||||||
|
variant={pageNumber === page ? "default" : "outline"}
|
||||||
|
size="lg"
|
||||||
|
className={pageNumber === page ? "bg-green-600 hover:bg-green-700 text-white" : "text-gray-700"}
|
||||||
|
>
|
||||||
|
{pageNumber}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={() => setPage((prev) => Math.min(totalPages, prev + 1))}
|
||||||
|
disabled={page === totalPages}
|
||||||
|
variant="outline"
|
||||||
|
size="lg"
|
||||||
|
>
|
||||||
|
Próximo >
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* AlertDialogs (Permanecem os mesmos) */}
|
{/* AlertDialogs (Permanecem os mesmos) */}
|
||||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
{/* ... (AlertDialog de Exclusão) ... */}
|
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
|
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
|
||||||
@ -370,7 +423,6 @@ export default function PacientesPage() {
|
|||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
|
|
||||||
<AlertDialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}>
|
<AlertDialog open={detailsDialogOpen} onOpenChange={setDetailsDialogOpen}>
|
||||||
{/* ... (AlertDialog de Detalhes) ... */}
|
|
||||||
<AlertDialogContent>
|
<AlertDialogContent>
|
||||||
<AlertDialogHeader>
|
<AlertDialogHeader>
|
||||||
<AlertDialogTitle>Detalhes do Paciente</AlertDialogTitle>
|
<AlertDialogTitle>Detalhes do Paciente</AlertDialogTitle>
|
||||||
@ -384,7 +436,7 @@ export default function PacientesPage() {
|
|||||||
<div className="text-red-600 p-4">{patientDetails.error}</div>
|
<div className="text-red-600 p-4">{patientDetails.error}</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="grid gap-4 py-4">
|
<div className="grid gap-4 py-4">
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Nome Completo</p>
|
<p className="font-semibold">Nome Completo</p>
|
||||||
<p>{patientDetails.full_name}</p>
|
<p>{patientDetails.full_name}</p>
|
||||||
@ -420,7 +472,7 @@ export default function PacientesPage() {
|
|||||||
</div>
|
</div>
|
||||||
<div className="border-t pt-4 mt-4">
|
<div className="border-t pt-4 mt-4">
|
||||||
<h3 className="font-semibold mb-2">Endereço</h3>
|
<h3 className="font-semibold mb-2">Endereço</h3>
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-semibold">Rua</p>
|
<p className="font-semibold">Rua</p>
|
||||||
<p>{`${patientDetails.street}, ${patientDetails.number}`}</p>
|
<p>{`${patientDetails.street}, ${patientDetails.number}`}</p>
|
||||||
|
|||||||
BIN
public/Logo MedConnect.png
Normal file
|
After Width: | Height: | Size: 45 KiB |
BIN
public/android-chrome-192x192.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
public/android-chrome-512x512.png
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/apple-touch-icon.png
Normal file
|
After Width: | Height: | Size: 8.9 KiB |
BIN
public/favicon-16x16.png
Normal file
|
After Width: | Height: | Size: 303 B |
BIN
public/favicon-32x32.png
Normal file
|
After Width: | Height: | Size: 670 B |
BIN
public/favicon.ico
Normal file
|
After Width: | Height: | Size: 15 KiB |