feature/consultations #17

Merged
Jonasbomfim merged 5 commits from feature/consultations into develop 2025-09-25 13:07:02 +00:00
3 changed files with 199 additions and 19 deletions
Showing only changes of commit f14643fa6a - Show all commits

View File

@ -21,6 +21,14 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogFooter,
} from "@/components/ui/dialog";
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
@ -36,6 +44,7 @@ import {
TableRow, TableRow,
} from "@/components/ui/table"; } from "@/components/ui/table";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { import {
Select, Select,
SelectContent, SelectContent,
@ -49,6 +58,7 @@ import { CalendarRegistrationForm } from "@/components/forms/calendar-registrati
// --- Helper Functions --- // --- Helper Functions ---
const formatDate = (date: string | Date) => { const formatDate = (date: string | Date) => {
if (!date) return "";
return new Date(date).toLocaleDateString("pt-BR", { return new Date(date).toLocaleDateString("pt-BR", {
day: "2-digit", day: "2-digit",
month: "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 --- // --- Main Page Component ---
export default function ConsultasPage() { export default function ConsultasPage() {
const [appointments, setAppointments] = useState(mockAppointments); const [appointments, setAppointments] = useState(mockAppointments);
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [editingAppointment, setEditingAppointment] = useState<any | null>(null); 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 // Converte o objeto da consulta para o formato esperado pelo formulário
const mapAppointmentToFormData = (appointment: any) => { const mapAppointmentToFormData = (appointment: any) => {
@ -105,6 +119,10 @@ export default function ConsultasPage() {
setShowForm(true); setShowForm(true);
}; };
const handleView = (appointment: any) => {
setViewingAppointment(appointment);
};
const handleCancel = () => { const handleCancel = () => {
setEditingAppointment(null); setEditingAppointment(null);
setShowForm(false); setShowForm(false);
@ -133,7 +151,7 @@ export default function ConsultasPage() {
if (showForm && editingAppointment) { if (showForm && editingAppointment) {
return ( 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"> <div className="flex items-center gap-4">
<Button type="button" variant="ghost" size="icon" onClick={handleCancel}> <Button type="button" variant="ghost" size="icon" onClick={handleCancel}>
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
@ -150,12 +168,13 @@ export default function ConsultasPage() {
} }
return ( 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"> <div className="flex items-center justify-between gap-4 flex-wrap">
<h1 className="text-lg font-semibold md:text-2xl"> <div>
Gerenciamento de Consultas <h1 className="text-2xl font-bold">Gerenciamento de Consultas</h1>
</h1> <p className="text-muted-foreground">Visualize, filtre e gerencie todas as consultas da clínica.</p>
<div className="ml-auto flex items-center gap-2"> </div>
<div className="flex items-center gap-2">
<Link href="/agenda"> <Link href="/agenda">
<Button size="sm" className="h-8 gap-1"> <Button size="sm" className="h-8 gap-1">
<PlusCircle className="h-3.5 w-3.5" /> <PlusCircle className="h-3.5 w-3.5" />
@ -204,9 +223,7 @@ export default function ConsultasPage() {
<TableHead>Médico</TableHead> <TableHead>Médico</TableHead>
<TableHead>Status</TableHead> <TableHead>Status</TableHead>
<TableHead>Data e Hora</TableHead> <TableHead>Data e Hora</TableHead>
<TableHead> <TableHead>Ações</TableHead>
<span className="sr-only">Ações</span>
</TableHead>
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
@ -249,9 +266,7 @@ export default function ConsultasPage() {
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem <DropdownMenuItem
onClick={() => onClick={() => handleView(appointment)}
alert(`Visualizando: ${appointment.patient}`)
}
> >
<Eye className="mr-2 h-4 w-4" /> <Eye className="mr-2 h-4 w-4" />
Ver Ver
@ -277,6 +292,77 @@ export default function ConsultasPage() {
</Table> </Table>
</CardContent> </CardContent>
</Card> </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> </div>
); );
} }

View File

@ -5,6 +5,8 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; 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 { MoreHorizontal, Plus, Search, Edit, Trash2, ArrowLeft, Eye } from "lucide-react";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form"; import { DoctorRegistrationForm } from "@/components/forms/doctor-registration-form";
@ -18,6 +20,7 @@ export default function DoutoresPage() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null); const [editingId, setEditingId] = useState<string | null>(null);
const [viewingDoctor, setViewingDoctor] = useState<Medico | null>(null);
// Carrega da API // Carrega da API
async function load() { async function load() {
@ -55,6 +58,10 @@ export default function DoutoresPage() {
setShowForm(true); setShowForm(true);
} }
function handleView(doctor: Medico) {
setViewingDoctor(doctor);
}
// Excluir via API e recarregar // Excluir via API e recarregar
async function handleDelete(id: string) { async function handleDelete(id: string) {
if (!confirm("Excluir este médico?")) return; if (!confirm("Excluir este médico?")) return;
@ -70,7 +77,7 @@ export default function DoutoresPage() {
if (showForm) { if (showForm) {
return ( return (
<div className="space-y-6"> <div className="space-y-6 p-6">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<Button variant="ghost" size="icon" onClick={() => setShowForm(false)}> <Button variant="ghost" size="icon" onClick={() => setShowForm(false)}>
<ArrowLeft className="h-4 w-4" /> <ArrowLeft className="h-4 w-4" />
@ -90,7 +97,7 @@ export default function DoutoresPage() {
} }
return ( 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 className="flex items-center justify-between gap-4 flex-wrap">
<div> <div>
<h1 className="text-2xl font-bold">Médicos</h1> <h1 className="text-2xl font-bold">Médicos</h1>
@ -155,7 +162,7 @@ export default function DoutoresPage() {
</button> </button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => alert(JSON.stringify(doctor, null, 2))}> <DropdownMenuItem onClick={() => handleView(doctor)}>
<Eye className="mr-2 h-4 w-4" /> <Eye className="mr-2 h-4 w-4" />
Ver Ver
</DropdownMenuItem> </DropdownMenuItem>
@ -182,6 +189,47 @@ export default function DoutoresPage() {
</TableBody> </TableBody>
</Table> </Table>
</div> </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"> <div className="text-sm text-muted-foreground">
Mostrando {filtered.length} de {doctors.length} Mostrando {filtered.length} de {doctors.length}
</div> </div>

View File

@ -6,6 +6,8 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; 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 { MoreHorizontal, Plus, Search, Eye, Edit, Trash2, ArrowLeft } from "lucide-react";
import { Paciente, Endereco, listarPacientes, buscarPacientePorId, excluirPaciente } from "@/lib/api"; import { Paciente, Endereco, listarPacientes, buscarPacientePorId, excluirPaciente } from "@/lib/api";
@ -47,6 +49,7 @@ export default function PacientesPage() {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [editingId, setEditingId] = useState<string | null>(null); const [editingId, setEditingId] = useState<string | null>(null);
const [viewingPatient, setViewingPatient] = useState<Paciente | null>(null);
async function loadAll() { async function loadAll() {
try { try {
@ -88,6 +91,10 @@ export default function PacientesPage() {
setShowForm(true); setShowForm(true);
} }
function handleView(patient: Paciente) {
setViewingPatient(patient);
}
async function handleDelete(id: string) { async function handleDelete(id: string) {
if (!confirm("Excluir este paciente?")) return; if (!confirm("Excluir este paciente?")) return;
try { try {
@ -161,7 +168,6 @@ export default function PacientesPage() {
return ( return (
<div className="space-y-6 p-6"> <div className="space-y-6 p-6">
{}
<div className="flex items-center justify-between gap-4 flex-wrap"> <div className="flex items-center justify-between gap-4 flex-wrap">
<div> <div>
<h1 className="text-2xl font-bold">Pacientes</h1> <h1 className="text-2xl font-bold">Pacientes</h1>
@ -217,7 +223,7 @@ export default function PacientesPage() {
</button> </button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => alert(JSON.stringify(p, null, 2))}> <DropdownMenuItem onClick={() => handleView(p)}>
<Eye className="mr-2 h-4 w-4" /> <Eye className="mr-2 h-4 w-4" />
Ver Ver
</DropdownMenuItem> </DropdownMenuItem>
@ -245,6 +251,46 @@ export default function PacientesPage() {
</Table> </Table>
</div> </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 className="text-sm text-muted-foreground">Mostrando {filtered.length} de {patients.length}</div>
</div> </div>
); );