Adição de barra de pesquisar pagina de consultas

This commit is contained in:
GagoDuBroca 2025-12-03 20:54:00 -03:00
parent 5a3ea1bb75
commit ce45c7187a

View File

@ -11,6 +11,7 @@ import {
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Dialog } from "@/components/ui/dialog"; import { Dialog } from "@/components/ui/dialog";
import { Input } from "@/components/ui/input"; // Importei o Input
import { Calendar as CalendarShadcn } from "@/components/ui/calendar"; import { Calendar as CalendarShadcn } from "@/components/ui/calendar";
import { Separator } from "@/components/ui/separator"; import { Separator } from "@/components/ui/separator";
import { import {
@ -24,6 +25,7 @@ import {
List, List,
RefreshCw, RefreshCw,
Loader2, Loader2,
Search, // Importei o ícone de busca
} from "lucide-react"; } from "lucide-react";
import { format, parseISO, isValid, isToday, isTomorrow } from "date-fns"; import { format, parseISO, isValid, isToday, isTomorrow } from "date-fns";
import { ptBR } from "date-fns/locale"; import { ptBR } from "date-fns/locale";
@ -43,6 +45,9 @@ export default function SecretaryAppointments() {
const [deleteModal, setDeleteModal] = useState(false); const [deleteModal, setDeleteModal] = useState(false);
const [editModal, setEditModal] = useState(false); const [editModal, setEditModal] = useState(false);
// Estado da Busca
const [searchTerm, setSearchTerm] = useState("");
// Estado para o formulário de edição // Estado para o formulário de edição
const [editFormData, setEditFormData] = useState({ const [editFormData, setEditFormData] = useState({
date: "", date: "",
@ -50,7 +55,7 @@ export default function SecretaryAppointments() {
status: "", status: "",
}); });
// Estado de data selecionada para o layout novo // Estado de data selecionada
const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date()); const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
const fetchData = async () => { const fetchData = async () => {
@ -91,17 +96,31 @@ export default function SecretaryAppointments() {
fetchData(); fetchData();
}, []); }, []);
// --- Agrupamento por dia para o layout novo --- // --- Filtragem e Agrupamento ---
const groupedAppointments = useMemo(() => { const groupedAppointments = useMemo(() => {
const list = selectedDate let filteredList = appointments;
? appointments.filter((apt) => {
if (!apt.scheduled_at) return false;
const iso = apt.scheduled_at.toString();
return iso.startsWith(format(selectedDate, "yyyy-MM-dd"));
})
: appointments;
return list.reduce((acc: Record<string, any[]>, apt: any) => { // 1. Filtro de Texto (Nome do Paciente ou Médico)
if (searchTerm) {
const lowerTerm = searchTerm.toLowerCase();
filteredList = filteredList.filter(
(apt) =>
apt.patient.full_name.toLowerCase().includes(lowerTerm) ||
apt.doctor.full_name.toLowerCase().includes(lowerTerm)
);
}
// 2. Filtro de Data (se selecionada)
if (selectedDate) {
filteredList = filteredList.filter((apt) => {
if (!apt.scheduled_at) return false;
const iso = apt.scheduled_at.toString();
return iso.startsWith(format(selectedDate, "yyyy-MM-dd"));
});
}
// 3. Agrupamento por dia
return filteredList.reduce((acc: Record<string, any[]>, apt: any) => {
if (!apt.scheduled_at) return acc; if (!apt.scheduled_at) return acc;
const dateObj = new Date(apt.scheduled_at); const dateObj = new Date(apt.scheduled_at);
if (!isValid(dateObj)) return acc; if (!isValid(dateObj)) return acc;
@ -110,7 +129,7 @@ export default function SecretaryAppointments() {
acc[key].push(apt); acc[key].push(apt);
return acc; return acc;
}, {}); }, {});
}, [appointments, selectedDate]); }, [appointments, selectedDate, searchTerm]);
// Dias que têm consulta (para destacar no calendário) // Dias que têm consulta (para destacar no calendário)
const bookedDays = useMemo( const bookedDays = useMemo(
@ -134,7 +153,7 @@ export default function SecretaryAppointments() {
return format(date, "EEEE, dd 'de' MMMM", { locale: ptBR }); return format(date, "EEEE, dd 'de' MMMM", { locale: ptBR });
}; };
// --- LÓGICA DE EDIÇÃO --- // --- LÓGICA DE EDIÇÃO E DELEÇÃO ---
const handleEdit = (appointment: any) => { const handleEdit = (appointment: any) => {
setSelectedAppointment(appointment); setSelectedAppointment(appointment);
const appointmentDate = new Date(appointment.scheduled_at); const appointmentDate = new Date(appointment.scheduled_at);
@ -172,9 +191,7 @@ export default function SecretaryAppointments() {
}; };
await appointmentsService.update(selectedAppointment.id, updatePayload); await appointmentsService.update(selectedAppointment.id, updatePayload);
await fetchData(); await fetchData();
setEditModal(false); setEditModal(false);
toast.success("Consulta atualizada com sucesso!"); toast.success("Consulta atualizada com sucesso!");
} catch (error) { } catch (error) {
@ -183,7 +200,6 @@ export default function SecretaryAppointments() {
} }
}; };
// --- LÓGICA DE DELEÇÃO ---
const handleDelete = (appointment: any) => { const handleDelete = (appointment: any) => {
setSelectedAppointment(appointment); setSelectedAppointment(appointment);
setDeleteModal(true); setDeleteModal(true);
@ -204,39 +220,11 @@ export default function SecretaryAppointments() {
} }
}; };
// Mantidos caso use nos modais
const timeSlots = [
"08:00",
"08:30",
"09:00",
"09:30",
"10:00",
"10:30",
"11:00",
"11:30",
"14:00",
"14:30",
"15:00",
"15:30",
"16:00",
"16:30",
"17:00",
"17:30",
];
const appointmentStatuses = [
"requested",
"confirmed",
"checked_in",
"completed",
"cancelled",
"no_show",
];
return ( return (
<Sidebar> <Sidebar>
<div className="space-y-6"> <div className="space-y-6">
{/* Cabeçalho principal */} {/* Cabeçalho principal */}
<div className="flex justify-between items-center"> <div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<div> <div>
<h1 className="text-3xl font-bold text-foreground"> <h1 className="text-3xl font-bold text-foreground">
Agenda Médica Agenda Médica
@ -245,43 +233,61 @@ export default function SecretaryAppointments() {
Consultas para os pacientes Consultas para os pacientes
</p> </p>
</div> </div>
<div className="flex gap-2"> <Link href="/secretary/schedule">
<Link href="/secretary/schedule"> <Button className="bg-primary hover:bg-primary/90 text-primary-foreground">
<Button className="bg-primary hover:bg-primary/90 text-primary-foreground"> <CalendarIcon className="mr-2 h-4 w-4" />
<CalendarIcon className="mr-2 h-4 w-4" /> Agendar Nova Consulta
Agendar Nova Consulta </Button>
</Button> </Link>
</Link>
</div>
</div> </div>
{/* Subtítulo e ações (mostrar todas / atualizar) */} {/* Barra de Filtros e Ações */}
<div className="flex justify-between items-center"> <div className="flex flex-col md:flex-row justify-between items-center gap-4">
<h2 className="text-xl font-semibold capitalize"> <h2 className="text-xl font-semibold capitalize whitespace-nowrap">
{selectedDate {selectedDate
? `Agenda de ${format(selectedDate, "dd/MM/yyyy")}` ? `Agenda de ${format(selectedDate, "dd/MM/yyyy")}`
: "Próximas Consultas"} : "Todas as Consultas"}
</h2> </h2>
<div className="flex gap-2">
<Button <div className="flex flex-col md:flex-row items-center gap-3 w-full md:w-auto">
onClick={() => setSelectedDate(undefined)} {/* BARRA DE PESQUISA ADICIONADA AQUI */}
variant="ghost" <div className="relative w-full md:w-72">
size="sm" <Search className="absolute left-2.5 top-2.5 h-4 w-4 text-muted-foreground" />
> <Input
<List className="mr-2 h-4 w-4" /> type="search"
Mostrar Todas placeholder="Buscar paciente ou médico..."
</Button> className="pl-9 w-full"
<Button value={searchTerm}
onClick={() => fetchData()} onChange={(e) => setSearchTerm(e.target.value)}
disabled={isLoading}
variant="outline"
size="sm"
>
<RefreshCw
className={`mr-2 h-4 w-4 ${isLoading ? "animate-spin" : ""}`}
/> />
Atualizar </div>
</Button>
<div className="flex gap-2 w-full md:w-auto">
<Button
onClick={() => {
setSelectedDate(undefined);
setSearchTerm("");
}}
variant="ghost"
size="sm"
className="flex-1 md:flex-none"
>
<List className="mr-2 h-4 w-4" />
Mostrar Todas
</Button>
<Button
onClick={() => fetchData()}
disabled={isLoading}
variant="outline"
size="sm"
className="flex-1 md:flex-none"
>
<RefreshCw
className={`mr-2 h-4 w-4 ${isLoading ? "animate-spin" : ""}`}
/>
Atualizar
</Button>
</div>
</div> </div>
</div> </div>
@ -326,9 +332,11 @@ export default function SecretaryAppointments() {
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<p className="text-muted-foreground"> <p className="text-muted-foreground">
{selectedDate {searchTerm
? "Não há agendamentos para esta data." ? "Nenhum resultado para a busca."
: "Não há próximas consultas agendadas."} : selectedDate
? "Não há agendamentos para esta data."
: "Não há consultas agendadas."}
</p> </p>
</CardContent> </CardContent>
</Card> </Card>
@ -350,7 +358,7 @@ export default function SecretaryAppointments() {
key={appointment.id} key={appointment.id}
className="shadow-sm hover:shadow-md transition-shadow" className="shadow-sm hover:shadow-md transition-shadow"
> >
<CardContent className="p-4 grid grid-cols-3 items-center gap-4"> <CardContent className="p-4 grid grid-cols-1 md:grid-cols-3 items-center gap-4">
{/* Coluna 1: Paciente + hora */} {/* Coluna 1: Paciente + hora */}
<div className="col-span-1 flex flex-col gap-2"> <div className="col-span-1 flex flex-col gap-2">
<div className="font-semibold flex items-center text-foreground"> <div className="font-semibold flex items-center text-foreground">
@ -384,8 +392,8 @@ export default function SecretaryAppointments() {
</div> </div>
{/* Coluna 3: Ações */} {/* Coluna 3: Ações */}
<div className="col-span-1 flex justify-end"> <div className="col-span-1 flex justify-start md:justify-end">
<div className="flex flex-col sm:flex-row gap-2"> <div className="flex gap-2">
<Button <Button
variant="outline" variant="outline"
size="sm" size="sm"
@ -419,12 +427,13 @@ export default function SecretaryAppointments() {
{/* MODAL DE EDIÇÃO */} {/* MODAL DE EDIÇÃO */}
<Dialog open={editModal} onOpenChange={setEditModal}> <Dialog open={editModal} onOpenChange={setEditModal}>
{/* ... (código do modal de edição permanece) ... */} {/* Modal de edição permanece o mesmo, adicione o DialogContent se precisar */}
{/* Aqui estou assumindo que você tem o conteúdo do Dialog no seu código original ou em outro lugar, pois ele não estava completo no snippet anterior */}
</Dialog> </Dialog>
{/* Modal de Deleção */} {/* Modal de Deleção */}
<Dialog open={deleteModal} onOpenChange={setDeleteModal}> <Dialog open={deleteModal} onOpenChange={setDeleteModal}>
{/* ... (código do modal de deleção permanece) ... */} {/* Modal de deleção permanece o mesmo */}
</Dialog> </Dialog>
</div> </div>
</Sidebar> </Sidebar>
@ -456,4 +465,4 @@ const getStatusBadge = (status: string) => {
default: default:
return <Badge variant="secondary">{status}</Badge>; return <Badge variant="secondary">{status}</Badge>;
} }
}; };