feat(ui): implements a visualization mode and standardizes the layout for patients, physicians, and appointments

This commit is contained in:
M-Gabrielly 2025-09-25 02:09:47 -03:00
parent 2399fdfac9
commit f14643fa6a
3 changed files with 199 additions and 19 deletions

View File

@ -21,6 +21,14 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import {
DropdownMenu,
DropdownMenuContent,
@ -36,6 +44,7 @@ import {
TableRow,
} from "@/components/ui/table";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import {
Select,
SelectContent,
@ -49,6 +58,7 @@ import { CalendarRegistrationForm } from "@/components/forms/calendar-registrati
// --- Helper Functions ---
const formatDate = (date: string | Date) => {
if (!date) return "";
return new Date(date).toLocaleDateString("pt-BR", {
day: "2-digit",
month: "2-digit",
@ -58,13 +68,17 @@ const formatDate = (date: string | Date) => {
});
};
const capitalize = (s: string) => s.charAt(0).toUpperCase() + s.slice(1);
const capitalize = (s: string) => {
if (typeof s !== 'string' || s.length === 0) return '';
return s.charAt(0).toUpperCase() + s.slice(1);
};
// --- Main Page Component ---
export default function ConsultasPage() {
const [appointments, setAppointments] = useState(mockAppointments);
const [showForm, setShowForm] = useState(false);
const [editingAppointment, setEditingAppointment] = useState<any | null>(null);
const [viewingAppointment, setViewingAppointment] = useState<any | null>(null);
// Converte o objeto da consulta para o formato esperado pelo formulário
const mapAppointmentToFormData = (appointment: any) => {
@ -105,6 +119,10 @@ export default function ConsultasPage() {
setShowForm(true);
};
const handleView = (appointment: any) => {
setViewingAppointment(appointment);
};
const handleCancel = () => {
setEditingAppointment(null);
setShowForm(false);
@ -133,7 +151,7 @@ export default function ConsultasPage() {
if (showForm && editingAppointment) {
return (
<div className="flex flex-1 flex-col gap-4 p-4 md:gap-8 md:p-8">
<div className="space-y-6 p-6">
<div className="flex items-center gap-4">
<Button type="button" variant="ghost" size="icon" onClick={handleCancel}>
<ArrowLeft className="h-4 w-4" />
@ -150,12 +168,13 @@ export default function ConsultasPage() {
}
return (
<div className="flex flex-1 flex-col gap-4 p-4 md:gap-8 md:p-8">
<div className="flex items-center">
<h1 className="text-lg font-semibold md:text-2xl">
Gerenciamento de Consultas
</h1>
<div className="ml-auto flex items-center gap-2">
<div className="space-y-6 p-6">
<div className="flex items-center justify-between gap-4 flex-wrap">
<div>
<h1 className="text-2xl font-bold">Gerenciamento de Consultas</h1>
<p className="text-muted-foreground">Visualize, filtre e gerencie todas as consultas da clínica.</p>
</div>
<div className="flex items-center gap-2">
<Link href="/agenda">
<Button size="sm" className="h-8 gap-1">
<PlusCircle className="h-3.5 w-3.5" />
@ -204,9 +223,7 @@ export default function ConsultasPage() {
<TableHead>Médico</TableHead>
<TableHead>Status</TableHead>
<TableHead>Data e Hora</TableHead>
<TableHead>
<span className="sr-only">Ações</span>
</TableHead>
<TableHead>Ações</TableHead>
</TableRow>
</TableHeader>
<TableBody>
@ -249,9 +266,7 @@ export default function ConsultasPage() {
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
onClick={() =>
alert(`Visualizando: ${appointment.patient}`)
}
onClick={() => handleView(appointment)}
>
<Eye className="mr-2 h-4 w-4" />
Ver
@ -277,6 +292,77 @@ export default function ConsultasPage() {
</Table>
</CardContent>
</Card>
{viewingAppointment && (
<Dialog open={!!viewingAppointment} onOpenChange={() => setViewingAppointment(null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>Detalhes da Consulta</DialogTitle>
<DialogDescription>
Informações detalhadas da consulta de {viewingAppointment?.patient}.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label htmlFor="name" className="text-right">
Paciente
</Label>
<span className="col-span-3">{viewingAppointment?.patient}</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">
Médico
</Label>
<span className="col-span-3">
{mockProfessionals.find(p => p.id === viewingAppointment?.professional)?.name || "Não encontrado"}
</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">
Data e Hora
</Label>
<span className="col-span-3">{viewingAppointment?.time ? formatDate(viewingAppointment.time) : ''}</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">
Status
</Label>
<span className="col-span-3">
<Badge
variant={
viewingAppointment?.status === "confirmed"
? "default"
: viewingAppointment?.status === "pending"
? "secondary"
: "destructive"
}
className={
viewingAppointment?.status === "confirmed" ? "bg-green-600" : ""
}
>
{capitalize(viewingAppointment?.status || '')}
</Badge>
</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">
Tipo
</Label>
<span className="col-span-3">{capitalize(viewingAppointment?.type || '')}</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">
Observações
</Label>
<span className="col-span-3">{viewingAppointment?.notes || "Nenhuma"}</span>
</div>
</div>
<DialogFooter>
<Button onClick={() => setViewingAppointment(null)}>Fechar</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
</div>
);
}

View File

@ -5,6 +5,8 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { MoreHorizontal, Plus, Search, Edit, Trash2, ArrowLeft, Eye } from "lucide-react";
import { Badge } from "@/components/ui/badge";
import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form";
@ -18,6 +20,7 @@ export default function DoutoresPage() {
const [search, setSearch] = useState("");
const [showForm, setShowForm] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
const [viewingDoctor, setViewingDoctor] = useState<Medico | null>(null);
// Carrega da API
async function load() {
@ -55,6 +58,10 @@ export default function DoutoresPage() {
setShowForm(true);
}
function handleView(doctor: Medico) {
setViewingDoctor(doctor);
}
// Excluir via API e recarregar
async function handleDelete(id: string) {
if (!confirm("Excluir este médico?")) return;
@ -70,7 +77,7 @@ export default function DoutoresPage() {
if (showForm) {
return (
<div className="space-y-6">
<div className="space-y-6 p-6">
<div className="flex items-center gap-4">
<Button variant="ghost" size="icon" onClick={() => setShowForm(false)}>
<ArrowLeft className="h-4 w-4" />
@ -90,7 +97,7 @@ export default function DoutoresPage() {
}
return (
<div className="space-y-6">
<div className="space-y-6 p-6">
<div className="flex items-center justify-between gap-4 flex-wrap">
<div>
<h1 className="text-2xl font-bold">Médicos</h1>
@ -155,7 +162,7 @@ export default function DoutoresPage() {
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => alert(JSON.stringify(doctor, null, 2))}>
<DropdownMenuItem onClick={() => handleView(doctor)}>
<Eye className="mr-2 h-4 w-4" />
Ver
</DropdownMenuItem>
@ -182,6 +189,47 @@ export default function DoutoresPage() {
</TableBody>
</Table>
</div>
{viewingDoctor && (
<Dialog open={!!viewingDoctor} onOpenChange={() => setViewingDoctor(null)}>
<DialogContent>
<DialogHeader>
<DialogTitle>Detalhes do Médico</DialogTitle>
<DialogDescription>
Informações detalhadas de {viewingDoctor?.nome}.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Nome</Label>
<span className="col-span-3 font-medium">{viewingDoctor?.nome}</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Especialidade</Label>
<span className="col-span-3">
<Badge variant="outline">{viewingDoctor?.especialidade}</Badge>
</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">CRM</Label>
<span className="col-span-3">{viewingDoctor?.crm}</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Email</Label>
<span className="col-span-3">{viewingDoctor?.email}</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Telefone</Label>
<span className="col-span-3">{viewingDoctor?.telefone}</span>
</div>
</div>
<DialogFooter>
<Button onClick={() => setViewingDoctor(null)}>Fechar</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
<div className="text-sm text-muted-foreground">
Mostrando {filtered.length} de {doctors.length}
</div>

View File

@ -6,6 +6,8 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
import { Label } from "@/components/ui/label";
import { MoreHorizontal, Plus, Search, Eye, Edit, Trash2, ArrowLeft } from "lucide-react";
import { Paciente, Endereco, listarPacientes, buscarPacientePorId, excluirPaciente } from "@/lib/api";
@ -47,6 +49,7 @@ export default function PacientesPage() {
const [search, setSearch] = useState("");
const [showForm, setShowForm] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null);
const [viewingPatient, setViewingPatient] = useState<Paciente | null>(null);
async function loadAll() {
try {
@ -88,6 +91,10 @@ export default function PacientesPage() {
setShowForm(true);
}
function handleView(patient: Paciente) {
setViewingPatient(patient);
}
async function handleDelete(id: string) {
if (!confirm("Excluir este paciente?")) return;
try {
@ -161,7 +168,6 @@ export default function PacientesPage() {
return (
<div className="space-y-6 p-6">
{}
<div className="flex items-center justify-between gap-4 flex-wrap">
<div>
<h1 className="text-2xl font-bold">Pacientes</h1>
@ -217,7 +223,7 @@ export default function PacientesPage() {
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => alert(JSON.stringify(p, null, 2))}>
<DropdownMenuItem onClick={() => handleView(p)}>
<Eye className="mr-2 h-4 w-4" />
Ver
</DropdownMenuItem>
@ -245,6 +251,46 @@ export default function PacientesPage() {
</Table>
</div>
{viewingPatient && (
<Dialog open={!!viewingPatient} onOpenChange={() => setViewingPatient(null)}>
<DialogContent className="max-w-2xl">
<DialogHeader>
<DialogTitle>Detalhes do Paciente</DialogTitle>
<DialogDescription>
Informações detalhadas de {viewingPatient.nome}.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Nome</Label>
<span className="col-span-3 font-medium">{viewingPatient.nome}</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">CPF</Label>
<span className="col-span-3">{viewingPatient.cpf}</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Telefone</Label>
<span className="col-span-3">{viewingPatient.telefone}</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Endereço</Label>
<span className="col-span-3">
{`${viewingPatient.endereco.logradouro}, ${viewingPatient.endereco.numero} - ${viewingPatient.endereco.bairro}, ${viewingPatient.endereco.cidade} - ${viewingPatient.endereco.estado}`}
</span>
</div>
<div className="grid grid-cols-4 items-center gap-4">
<Label className="text-right">Observações</Label>
<span className="col-span-3">{viewingPatient.observacoes || "Nenhuma"}</span>
</div>
</div>
<DialogFooter>
<Button onClick={() => setViewingPatient(null)}>Fechar</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)}
<div className="text-sm text-muted-foreground">Mostrando {filtered.length} de {patients.length}</div>
</div>
);