419 lines
15 KiB
TypeScript
419 lines
15 KiB
TypeScript
import React, { useEffect, useState } from "react";
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardHeader,
|
|
CardTitle,
|
|
CardDescription,
|
|
} from "./MetricCard";
|
|
import { Button } from "@/components/ui/button";
|
|
import { Input } from "@/components/ui/input";
|
|
import { Label } from "@/components/ui/label";
|
|
import {
|
|
Select,
|
|
SelectContent,
|
|
SelectItem,
|
|
SelectTrigger,
|
|
SelectValue,
|
|
} from "@/components/ui/select";
|
|
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
import { Calendar } from "@/components/ui/calendar";
|
|
import { Textarea } from "@/components/ui/textarea";
|
|
import {
|
|
Dialog,
|
|
DialogContent,
|
|
DialogDescription,
|
|
DialogFooter,
|
|
DialogHeader,
|
|
DialogTitle,
|
|
} from "@/components/ui/dialog";
|
|
import { ENDPOINTS } from "../services/endpoints";
|
|
import api from "../services/api";
|
|
|
|
// Adapte conforme o seu projeto
|
|
const months = [
|
|
"Janeiro",
|
|
"Fevereiro",
|
|
"Março",
|
|
"Abril",
|
|
"Maio",
|
|
"Junho",
|
|
"Julho",
|
|
"Agosto",
|
|
"Setembro",
|
|
"Outubro",
|
|
"Novembro",
|
|
"Dezembro",
|
|
];
|
|
const currentYear = new Date().getFullYear();
|
|
const years = Array.from({ length: 10 }, (_, i) => currentYear - 2 + i);
|
|
|
|
export default function BookAppointment() {
|
|
const [doctors, setDoctors] = useState<any[]>([]);
|
|
const [searchTerm, setSearchTerm] = useState("");
|
|
const [selectedSpecialty, setSelectedSpecialty] = useState("all");
|
|
const [selectedDate, setSelectedDate] = useState<Date | undefined>(
|
|
new Date()
|
|
);
|
|
const [currentMonth, setCurrentMonth] = useState(new Date());
|
|
const [selectedDoctor, setSelectedDoctor] = useState<any | null>(null);
|
|
const [selectedTime, setSelectedTime] = useState("");
|
|
const [appointmentType, setAppointmentType] = useState<
|
|
"presential" | "online"
|
|
>("presential");
|
|
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
|
|
const [reason, setReason] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// Busca médicos da API
|
|
api
|
|
.get(ENDPOINTS.DOCTORS)
|
|
.then((res) => setDoctors(res.data))
|
|
.catch(() => setDoctors([]));
|
|
}, []);
|
|
|
|
const filteredDoctors = doctors.filter((doctor) => {
|
|
const matchesSearch =
|
|
doctor.name?.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
|
doctor.specialty?.toLowerCase().includes(searchTerm.toLowerCase());
|
|
const matchesSpecialty =
|
|
selectedSpecialty === "all" || doctor.specialty === selectedSpecialty;
|
|
return matchesSearch && matchesSpecialty;
|
|
});
|
|
|
|
const handleBookAppointment = () => {
|
|
if (selectedDoctor && selectedTime) {
|
|
setShowConfirmDialog(true);
|
|
}
|
|
};
|
|
|
|
const confirmAppointment = async () => {
|
|
if (!selectedDoctor || !selectedTime || !selectedDate) return;
|
|
setLoading(true);
|
|
try {
|
|
await api.post(ENDPOINTS.APPOINTMENTS, {
|
|
doctor_id: selectedDoctor.id,
|
|
date: selectedDate.toISOString().split("T")[0],
|
|
time: selectedTime,
|
|
type: appointmentType,
|
|
reason,
|
|
});
|
|
alert("Agendamento realizado com sucesso!");
|
|
setShowConfirmDialog(false);
|
|
setSelectedDoctor(null);
|
|
setSelectedTime("");
|
|
setReason("");
|
|
} catch (e) {
|
|
alert("Erro ao agendar consulta");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const handleMonthChange = (month: string) => {
|
|
const newDate = new Date(currentMonth.getFullYear(), Number(month));
|
|
setCurrentMonth(newDate);
|
|
};
|
|
const handleYearChange = (year: string) => {
|
|
const newDate = new Date(Number(year), currentMonth.getMonth());
|
|
setCurrentMonth(newDate);
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
<div>
|
|
<h1>Agendar Consulta</h1>
|
|
<p className="text-muted-foreground">
|
|
Escolha um médico e horário disponível
|
|
</p>
|
|
</div>
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Buscar Médicos</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div className="space-y-2">
|
|
<Label>Buscar por nome ou especialidade</Label>
|
|
<Input
|
|
placeholder="Ex: Cardiologia, Dr. Silva..."
|
|
value={searchTerm}
|
|
onChange={(e) => setSearchTerm(e.target.value)}
|
|
/>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>Especialidade</Label>
|
|
<Select
|
|
value={selectedSpecialty}
|
|
onValueChange={setSelectedSpecialty}
|
|
>
|
|
<SelectTrigger>
|
|
<SelectValue placeholder="Todas as especialidades" />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value="all">Todas as especialidades</SelectItem>
|
|
{/* Adapte para especialidades reais */}
|
|
<SelectItem value="Cardiologia">Cardiologia</SelectItem>
|
|
<SelectItem value="Dermatologia">Dermatologia</SelectItem>
|
|
<SelectItem value="Ortopedia">Ortopedia</SelectItem>
|
|
<SelectItem value="Pediatria">Pediatria</SelectItem>
|
|
<SelectItem value="Ginecologia">Ginecologia</SelectItem>
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
|
{filteredDoctors.map((doctor) => (
|
|
<Card
|
|
key={doctor.id}
|
|
className={selectedDoctor?.id === doctor.id ? "border-primary" : ""}
|
|
>
|
|
<CardContent className="pt-6">
|
|
<div className="flex gap-4">
|
|
{/* Adapte para seu componente de avatar */}
|
|
<div className="h-16 w-16 rounded-full bg-gray-200 flex items-center justify-center">
|
|
{doctor.name
|
|
?.split(" ")
|
|
.map((n: string) => n[0])
|
|
.join("")}
|
|
</div>
|
|
<div className="flex-1 space-y-2">
|
|
<div>
|
|
<h3>{doctor.name}</h3>
|
|
<p className="text-muted-foreground">{doctor.specialty}</p>
|
|
</div>
|
|
<div className="flex items-center gap-4 text-muted-foreground">
|
|
<div className="flex items-center gap-1">
|
|
<span>{doctor.rating || "-"}</span>
|
|
</div>
|
|
<div className="flex items-center gap-1">
|
|
<span>{doctor.location || "-"}</span>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="text-foreground">
|
|
{doctor.price || "-"}
|
|
</span>
|
|
<div className="flex gap-2">
|
|
<Button
|
|
size="sm"
|
|
variant="outline"
|
|
onClick={() => setSelectedDoctor(doctor)}
|
|
>
|
|
Ver Agenda
|
|
</Button>
|
|
<Button
|
|
size="sm"
|
|
variant={
|
|
selectedDoctor?.id === doctor.id
|
|
? "default"
|
|
: "outline"
|
|
}
|
|
onClick={() => setSelectedDoctor(doctor)}
|
|
>
|
|
{selectedDoctor?.id === doctor.id
|
|
? "Selecionado"
|
|
: "Selecionar"}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
{selectedDoctor && (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>Detalhes do Agendamento</CardTitle>
|
|
<CardDescription>
|
|
Consulta com {selectedDoctor.name} - {selectedDoctor.specialty}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-6">
|
|
<Tabs
|
|
value={appointmentType}
|
|
onValueChange={(v) =>
|
|
setAppointmentType(v as "presential" | "online")
|
|
}
|
|
>
|
|
<TabsList className="grid w-full grid-cols-2">
|
|
<TabsTrigger value="presential">Presencial</TabsTrigger>
|
|
<TabsTrigger value="online">Online</TabsTrigger>
|
|
</TabsList>
|
|
</Tabs>
|
|
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-2">
|
|
<Select
|
|
value={String(currentMonth.getMonth())}
|
|
onValueChange={handleMonthChange}
|
|
>
|
|
<SelectTrigger className="w-[130px]">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{months.map((month, index) => (
|
|
<SelectItem key={index} value={String(index)}>
|
|
{month}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
<Select
|
|
value={String(currentMonth.getFullYear())}
|
|
onValueChange={handleYearChange}
|
|
>
|
|
<SelectTrigger className="w-[90px]">
|
|
<SelectValue />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
{years.map((year) => (
|
|
<SelectItem key={year} value={String(year)}>
|
|
{year}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<Calendar
|
|
mode="single"
|
|
selected={selectedDate}
|
|
onSelect={setSelectedDate}
|
|
month={currentMonth}
|
|
onMonthChange={setCurrentMonth}
|
|
className="rounded-md border w-full"
|
|
disabled={(date) =>
|
|
date < new Date() ||
|
|
date.getDay() === 0 ||
|
|
date.getDay() === 6
|
|
}
|
|
/>
|
|
<p className="text-muted-foreground">
|
|
🔴 Finais de semana não disponíveis
|
|
</p>
|
|
</div>
|
|
<div className="space-y-4">
|
|
<div>
|
|
<div className="mb-3">
|
|
<Label>Horários Disponíveis</Label>
|
|
<p className="text-muted-foreground">
|
|
{selectedDate?.toLocaleDateString("pt-BR", {
|
|
weekday: "long",
|
|
day: "numeric",
|
|
month: "long",
|
|
year: "numeric",
|
|
})}
|
|
</p>
|
|
</div>
|
|
{/* Adapte para buscar horários reais da API se disponível */}
|
|
<div className="grid grid-cols-3 gap-2">
|
|
{["09:00", "10:00", "14:00", "15:00", "16:00"].map(
|
|
(slot) => (
|
|
<Button
|
|
key={slot}
|
|
variant={
|
|
selectedTime === slot ? "default" : "outline"
|
|
}
|
|
size="sm"
|
|
onClick={() => setSelectedTime(slot)}
|
|
>
|
|
{slot}
|
|
</Button>
|
|
)
|
|
)}
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label>Motivo da Consulta</Label>
|
|
<Textarea
|
|
placeholder="Descreva brevemente o motivo da consulta..."
|
|
value={reason}
|
|
onChange={(e) => setReason(e.target.value)}
|
|
rows={4}
|
|
/>
|
|
</div>
|
|
<div className="p-4 bg-accent rounded-lg space-y-2">
|
|
<h4>Resumo</h4>
|
|
<div className="space-y-1 text-muted-foreground">
|
|
<p>Data: {selectedDate?.toLocaleDateString("pt-BR")}</p>
|
|
<p>Horário: {selectedTime || "Não selecionado"}</p>
|
|
<p>
|
|
Tipo:{" "}
|
|
{appointmentType === "online" ? "Online" : "Presencial"}
|
|
</p>
|
|
<p>Valor: {selectedDoctor.price || "-"}</p>
|
|
</div>
|
|
</div>
|
|
<Button
|
|
className="w-full"
|
|
disabled={!selectedTime || !reason || loading}
|
|
onClick={handleBookAppointment}
|
|
>
|
|
Confirmar Agendamento
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
<Dialog open={showConfirmDialog} onOpenChange={setShowConfirmDialog}>
|
|
<DialogContent>
|
|
<DialogHeader>
|
|
<DialogTitle>Confirmar Agendamento</DialogTitle>
|
|
<DialogDescription>
|
|
Revise os detalhes da sua consulta antes de confirmar
|
|
</DialogDescription>
|
|
</DialogHeader>
|
|
<div className="space-y-4">
|
|
<div className="flex items-center gap-3">
|
|
<div className="h-12 w-12 rounded-full bg-gray-200 flex items-center justify-center">
|
|
{selectedDoctor?.name
|
|
?.split(" ")
|
|
.map((n: string) => n[0])
|
|
.join("")}
|
|
</div>
|
|
<div>
|
|
<p className="text-foreground">{selectedDoctor?.name}</p>
|
|
<p className="text-muted-foreground">
|
|
{selectedDoctor?.specialty}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2 text-muted-foreground">
|
|
<p>📅 Data: {selectedDate?.toLocaleDateString("pt-BR")}</p>
|
|
<p>⏰ Horário: {selectedTime}</p>
|
|
<p>
|
|
📍 Tipo:{" "}
|
|
{appointmentType === "online"
|
|
? "Consulta Online"
|
|
: "Consulta Presencial"}
|
|
</p>
|
|
<p>💰 Valor: {selectedDoctor?.price || "-"}</p>
|
|
<div className="mt-4">
|
|
<p className="text-foreground">Motivo:</p>
|
|
<p>{reason}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<DialogFooter>
|
|
<Button
|
|
variant="outline"
|
|
onClick={() => setShowConfirmDialog(false)}
|
|
>
|
|
Cancelar
|
|
</Button>
|
|
<Button onClick={confirmAppointment} disabled={loading}>
|
|
{loading ? "Agendando..." : "Confirmar"}
|
|
</Button>
|
|
</DialogFooter>
|
|
</DialogContent>
|
|
</Dialog>
|
|
</div>
|
|
);
|
|
}
|