Merge branch 'Stage' of https://github.com/m1guelmcf/MedConnect into ajustes-visuais-paginas
This commit is contained in:
commit
00e8b4310e
@ -31,7 +31,7 @@ interface EnrichedAppointment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function DoctorAppointmentsPage() {
|
export default function DoctorAppointmentsPage() {
|
||||||
const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: 'medico' });
|
const { user, isLoading: isAuthLoading } = useAuthLayout({ requiredRole: "medico" });
|
||||||
|
|
||||||
const [allAppointments, setAllAppointments] = useState<EnrichedAppointment[]>([]);
|
const [allAppointments, setAllAppointments] = useState<EnrichedAppointment[]>([]);
|
||||||
const [isLoading, setIsLoading] = useState(true);
|
const [isLoading, setIsLoading] = useState(true);
|
||||||
@ -111,13 +111,22 @@ export default function DoctorAppointmentsPage() {
|
|||||||
return format(date, "EEEE, dd 'de' MMMM", { locale: ptBR });
|
return format(date, "EEEE, dd 'de' MMMM", { locale: ptBR });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const statusPT: Record<string, string> = {
|
||||||
|
confirmed: "Confirmada",
|
||||||
|
completed: "Concluída",
|
||||||
|
cancelled: "Cancelada",
|
||||||
|
requested: "Solicitada",
|
||||||
|
no_show: "oculta",
|
||||||
|
checked_in: "Aguardando",
|
||||||
|
};
|
||||||
|
|
||||||
const getStatusVariant = (status: EnrichedAppointment['status']) => {
|
const getStatusVariant = (status: EnrichedAppointment['status']) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "confirmed": case "checked_in": return "default";
|
case "confirmed": case "checked_in": return "text-foreground bg-blue-100 hover:bg-blue-150";
|
||||||
case "completed": return "secondary";
|
case "completed": return "text-foreground bg-green-100 hover:bg-green-150";
|
||||||
case "cancelled": case "no_show": return "destructive";
|
case "cancelled": case "no_show": return "text-foreground bg-red-200 hover:bg-red-250";
|
||||||
case "requested": return "outline";
|
case "requested": return "text-foreground bg-yellow-100 hover:bg-yellow-150";
|
||||||
default: return "outline";
|
default: return "border-gray bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -191,7 +200,7 @@ export default function DoctorAppointmentsPage() {
|
|||||||
|
|
||||||
{/* Coluna 2: Status e Telefone */}
|
{/* Coluna 2: Status e Telefone */}
|
||||||
<div className="col-span-1 flex flex-col items-center gap-2">
|
<div className="col-span-1 flex flex-col items-center gap-2">
|
||||||
<Badge variant={getStatusVariant(appointment.status)} className="capitalize text-xs">{appointment.status.replace('_', ' ')}</Badge>
|
<Badge variant="outline" className={getStatusVariant(appointment.status)}>{statusPT[appointment.status].replace('_', ' ')}</Badge>
|
||||||
<div className="flex items-center text-sm text-muted-foreground">
|
<div className="flex items-center text-sm text-muted-foreground">
|
||||||
<Phone className="mr-2 h-4 w-4" />
|
<Phone className="mr-2 h-4 w-4" />
|
||||||
{appointment.patientPhone}
|
{appointment.patientPhone}
|
||||||
|
|||||||
@ -29,6 +29,7 @@ import { exceptionsService } from "@/services/exceptionApi.mjs";
|
|||||||
import { doctorsService } from "@/services/doctorsApi.mjs";
|
import { doctorsService } from "@/services/doctorsApi.mjs";
|
||||||
import { usersService } from "@/services/usersApi.mjs";
|
import { usersService } from "@/services/usersApi.mjs";
|
||||||
import Sidebar from "@/components/Sidebar";
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
import WeeklyScheduleCard from "@/components/ui/WeeklyScheduleCard";
|
||||||
|
|
||||||
type Availability = {
|
type Availability = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -165,20 +166,17 @@ export default function PatientDashboard() {
|
|||||||
);
|
);
|
||||||
setAvailability(filteredAvail);
|
setAvailability(filteredAvail);
|
||||||
|
|
||||||
// Busca exceções
|
// Busca exceções
|
||||||
const exceptionsList = await exceptionsService.list();
|
const exceptionsList = await exceptionsService.list();
|
||||||
const filteredExc = exceptionsList.filter(
|
const filteredExc = exceptionsList.filter((exc: { doctor_id: string }) => exc.doctor_id === doctor?.id);
|
||||||
(exc: { doctor_id: string }) => exc.doctor_id === doctor?.id
|
setExceptions(filteredExc);
|
||||||
);
|
} catch (e: any) {
|
||||||
console.log(exceptionsList);
|
alert(`${e?.error} ${e?.message}`);
|
||||||
setExceptions(filteredExc);
|
}
|
||||||
} catch (e: any) {
|
};
|
||||||
alert(`${e?.error} ${e?.message}`);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchData();
|
fetchData();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
// Função auxiliar para filtrar o id do doctor correspondente ao user logado
|
// Função auxiliar para filtrar o id do doctor correspondente ao user logado
|
||||||
function findDoctorById(id: string, doctors: Doctor[]) {
|
function findDoctorById(id: string, doctors: Doctor[]) {
|
||||||
@ -320,82 +318,42 @@ export default function PatientDashboard() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle>Próximas Consultas</CardTitle>
|
<CardTitle>Próximas Consultas</CardTitle>
|
||||||
<CardDescription>Suas consultas agendadas</CardDescription>
|
<CardDescription>Suas consultas agendadas</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg">
|
<div className="flex items-center justify-between p-3 bg-blue-50 rounded-lg">
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">Dr. João Santos</p>
|
<p className="font-medium">Dr. João Santos</p>
|
||||||
<p className="text-sm text-gray-600">Cardiologia</p>
|
<p className="text-sm text-gray-600">Cardiologia</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-right">
|
<div className="text-right">
|
||||||
<p className="font-medium">02 out</p>
|
<p className="font-medium">02 out</p>
|
||||||
<p className="text-sm text-gray-600">14:30</p>
|
<p className="text-sm text-gray-600">14:30</p>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="grid md:grid-cols-1 gap-6">
|
||||||
</CardContent>
|
<Card>
|
||||||
</Card>
|
<CardHeader>
|
||||||
</div>
|
<CardTitle>Horário Semanal</CardTitle>
|
||||||
<div className="grid md:grid-cols-1 gap-6">
|
<CardDescription>Confira rapidamente a sua disponibilidade da semana</CardDescription>
|
||||||
<Card>
|
</CardHeader>
|
||||||
<CardHeader>
|
<CardContent>{loggedDoctor && <WeeklyScheduleCard doctorId={loggedDoctor.id} />}</CardContent>
|
||||||
<CardTitle>Horário Semanal</CardTitle>
|
</Card>
|
||||||
<CardDescription>
|
</div>
|
||||||
Confira rapidamente a sua disponibilidade da semana
|
<div className="grid md:grid-cols-1 gap-6">
|
||||||
</CardDescription>
|
<Card>
|
||||||
</CardHeader>
|
<CardHeader>
|
||||||
<CardContent className="space-y-4 grid md:grid-cols-7 gap-2">
|
<CardTitle>Exceções</CardTitle>
|
||||||
{[
|
<CardDescription>Bloqueios e liberações eventuais de agenda</CardDescription>
|
||||||
"sunday",
|
</CardHeader>
|
||||||
"monday",
|
|
||||||
"tuesday",
|
|
||||||
"wednesday",
|
|
||||||
"thursday",
|
|
||||||
"friday",
|
|
||||||
"saturday",
|
|
||||||
].map((day) => {
|
|
||||||
const times = schedule[day] || [];
|
|
||||||
return (
|
|
||||||
<div key={day} className="space-y-4">
|
|
||||||
<div className="flex flex-col items-center justify-between p-3 bg-blue-50 rounded-lg">
|
|
||||||
<div>
|
|
||||||
<p className="font-medium capitalize">
|
|
||||||
{weekdaysPT[day]}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div className="text-center">
|
|
||||||
{times.length > 0 ? (
|
|
||||||
times.map((t, i) => (
|
|
||||||
<p key={i} className="text-sm text-gray-600">
|
|
||||||
{formatTime(t.start)} <br /> {formatTime(t.end)}
|
|
||||||
</p>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<p className="text-sm text-gray-400 italic">
|
|
||||||
Sem horário
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
<div className="grid md:grid-cols-1 gap-6">
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Exceções</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Bloqueios e liberações eventuais de agenda
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
|
|
||||||
<CardContent className="space-y-4 grid md:grid-cols-7 gap-2">
|
<CardContent className="space-y-4 grid md:grid-cols-7 gap-2">
|
||||||
{exceptions && exceptions.length > 0 ? (
|
{exceptions && exceptions.length > 0 ? (
|
||||||
@ -411,75 +369,47 @@ export default function PatientDashboard() {
|
|||||||
const startTime = formatTime(ex.start_time);
|
const startTime = formatTime(ex.start_time);
|
||||||
const endTime = formatTime(ex.end_time);
|
const endTime = formatTime(ex.end_time);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={ex.id} className="space-y-4">
|
<div key={ex.id} className="space-y-4">
|
||||||
<div className="flex flex-col items-center justify-between p-3 bg-blue-50 rounded-lg shadow-sm">
|
<div className="flex flex-col items-center justify-between p-3 bg-blue-50 rounded-lg shadow-sm">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<p className="font-semibold capitalize">{date}</p>
|
<p className="font-semibold capitalize">{date}</p>
|
||||||
<p className="text-sm text-gray-600">
|
<p className="text-sm text-gray-600">{startTime && endTime ? `${startTime} - ${endTime}` : "Dia todo"}</p>
|
||||||
{startTime && endTime
|
</div>
|
||||||
? `${startTime} - ${endTime}`
|
<div className="text-center mt-2">
|
||||||
: "Dia todo"}
|
<p className={`text-sm font-medium ${ex.kind === "bloqueio" ? "text-red-600" : "text-green-600"}`}>{ex.kind === "bloqueio" ? "Bloqueio" : "Liberação"}</p>
|
||||||
</p>
|
<p className="text-xs text-gray-500 italic">{ex.reason || "Sem motivo especificado"}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-center mt-2">
|
<div>
|
||||||
<p
|
<Button className="text-red-600" variant="outline" onClick={() => openDeleteDialog(String(ex.id))}>
|
||||||
className={`text-sm font-medium ${
|
<Trash2></Trash2>
|
||||||
ex.kind === "bloqueio"
|
</Button>
|
||||||
? "text-red-600"
|
</div>
|
||||||
: "text-green-600"
|
</div>
|
||||||
}`}
|
</div>
|
||||||
>
|
);
|
||||||
{ex.kind === "bloqueio" ? "Bloqueio" : "Liberação"}
|
})
|
||||||
</p>
|
) : (
|
||||||
<p className="text-xs text-gray-500 italic">
|
<p className="text-sm text-gray-400 italic col-span-7 text-center">Nenhuma exceção registrada.</p>
|
||||||
{ex.reason || "Sem motivo especificado"}
|
)}
|
||||||
</p>
|
</CardContent>
|
||||||
</div>
|
</Card>
|
||||||
<div>
|
</div>
|
||||||
<Button
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
className="text-red-600"
|
<AlertDialogContent>
|
||||||
variant="outline"
|
<AlertDialogHeader>
|
||||||
onClick={() => openDeleteDialog(String(ex.id))}
|
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
|
||||||
>
|
<AlertDialogDescription>Tem certeza que deseja excluir esta exceção? Esta ação não pode ser desfeita.</AlertDialogDescription>
|
||||||
<Trash2></Trash2>
|
</AlertDialogHeader>
|
||||||
</Button>
|
<AlertDialogFooter>
|
||||||
</div>
|
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
||||||
</div>
|
<AlertDialogAction onClick={() => exceptionToDelete && handleDeleteException(exceptionToDelete)} className="bg-red-600 hover:bg-red-700">
|
||||||
</div>
|
Excluir
|
||||||
);
|
</AlertDialogAction>
|
||||||
})
|
</AlertDialogFooter>
|
||||||
) : (
|
</AlertDialogContent>
|
||||||
<p className="text-sm text-gray-400 italic col-span-7 text-center">
|
</AlertDialog>
|
||||||
Nenhuma exceção registrada.
|
</div>
|
||||||
</p>
|
</Sidebar>
|
||||||
)}
|
);
|
||||||
</CardContent>
|
|
||||||
</Card>
|
|
||||||
</div>
|
|
||||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
|
||||||
<AlertDialogContent>
|
|
||||||
<AlertDialogHeader>
|
|
||||||
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
|
|
||||||
<AlertDialogDescription>
|
|
||||||
Tem certeza que deseja excluir esta exceção? Esta ação não pode
|
|
||||||
ser desfeita.
|
|
||||||
</AlertDialogDescription>
|
|
||||||
</AlertDialogHeader>
|
|
||||||
<AlertDialogFooter>
|
|
||||||
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
|
||||||
<AlertDialogAction
|
|
||||||
onClick={() =>
|
|
||||||
exceptionToDelete && handleDeleteException(exceptionToDelete)
|
|
||||||
}
|
|
||||||
className="bg-red-600 hover:bg-red-700"
|
|
||||||
>
|
|
||||||
Excluir
|
|
||||||
</AlertDialogAction>
|
|
||||||
</AlertDialogFooter>
|
|
||||||
</AlertDialogContent>
|
|
||||||
</AlertDialog>
|
|
||||||
</div>
|
|
||||||
</Sidebar>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -218,36 +218,36 @@ export default function AvailabilityPage() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mapa de tradução
|
// Mapa de tradução
|
||||||
const weekdaysPT: Record<string, string> = {
|
const weekdaysPT: Record<string, string> = {
|
||||||
sunday: "Domingo",
|
sunday: "Domingo",
|
||||||
monday: "Segunda",
|
monday: "Segunda",
|
||||||
tuesday: "Terça",
|
tuesday: "Terça",
|
||||||
wednesday: "Quarta",
|
wednesday: "Quarta",
|
||||||
thursday: "Quinta",
|
thursday: "Quinta",
|
||||||
friday: "Sexta",
|
friday: "Sexta",
|
||||||
saturday: "Sábado",
|
saturday: "Sábado",
|
||||||
};
|
};
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
try {
|
try {
|
||||||
const loggedUser = await usersService.getMe();
|
const loggedUser = await usersService.getMe();
|
||||||
const doctorList = await doctorsService.list();
|
const doctorList = await doctorsService.list();
|
||||||
setUserData(loggedUser);
|
setUserData(loggedUser);
|
||||||
const doctor = findDoctorById(loggedUser.user.id, doctorList);
|
const doctor = findDoctorById(loggedUser.user.id, doctorList);
|
||||||
setDoctorId(doctor?.id);
|
setDoctorId(doctor?.id);
|
||||||
console.log(doctor);
|
console.log(doctor);
|
||||||
// Busca disponibilidade
|
// Busca disponibilidade
|
||||||
const availabilityList = await AvailabilityService.list();
|
const availabilityList = await AvailabilityService.list();
|
||||||
|
|
||||||
// Filtra já com a variável local
|
// Filtra já com a variável local
|
||||||
const filteredAvail = availabilityList.filter(
|
const filteredAvail = availabilityList.filter(
|
||||||
(disp: { doctor_id: string }) => disp.doctor_id === doctor?.id
|
(disp: { doctor_id: string }) => disp.doctor_id === doctor?.id
|
||||||
);
|
);
|
||||||
setAvailability(filteredAvail);
|
setAvailability(filteredAvail);
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
alert(`${e?.error} ${e?.message}`);
|
alert(`${e?.error} ${e?.message}`);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchData();
|
fetchData();
|
||||||
@ -320,20 +320,21 @@ export default function AvailabilityPage() {
|
|||||||
}
|
}
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "Sucesso",
|
title: "Sucesso",
|
||||||
description: message,
|
description: message,
|
||||||
});
|
});
|
||||||
router.push("#"); // adicionar página para listar a disponibilidade
|
router.push("#"); // adicionar página para listar a disponibilidade
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
toast({
|
toast({
|
||||||
title: "Erro",
|
title: "Erro",
|
||||||
description: err?.message || "Não foi possível criar a disponibilidade",
|
description: err?.message || "Não foi possível criar a disponibilidade",
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false);
|
fetchData()
|
||||||
}
|
setIsLoading(false);
|
||||||
};
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const openDeleteDialog = (
|
const openDeleteDialog = (
|
||||||
schedule: { start: string; end: string },
|
schedule: { start: string; end: string },
|
||||||
@ -343,38 +344,35 @@ export default function AvailabilityPage() {
|
|||||||
setDeleteDialogOpen(true);
|
setDeleteDialogOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDeleteAvailability = async (AvailabilityId: string) => {
|
const handleDeleteAvailability = async (AvailabilityId: string) => {
|
||||||
try {
|
try {
|
||||||
const res = await AvailabilityService.delete(AvailabilityId);
|
const res = await AvailabilityService.delete(AvailabilityId);
|
||||||
|
|
||||||
let message = "Disponibilidade deletada com sucesso";
|
let message = "Disponibilidade deletada com sucesso";
|
||||||
try {
|
try {
|
||||||
if (res) {
|
if (res) {
|
||||||
throw new Error(
|
throw new Error(`${res.error} ${res.message}` || "A API retornou erro");
|
||||||
`${res.error} ${res.message}` || "A API retornou erro"
|
} else {
|
||||||
);
|
console.log(message);
|
||||||
} else {
|
}
|
||||||
console.log(message);
|
} catch {}
|
||||||
}
|
|
||||||
} catch {}
|
toast({
|
||||||
|
title: "Sucesso",
|
||||||
toast({
|
description: message,
|
||||||
title: "Sucesso",
|
});
|
||||||
description: message,
|
|
||||||
});
|
setAvailability((prev: Availability[]) => prev.filter((p) => String(p.id) !== String(AvailabilityId)));
|
||||||
|
} catch (e: any) {
|
||||||
setAvailability((prev: Availability[]) =>
|
toast({
|
||||||
prev.filter((p) => String(p.id) !== String(AvailabilityId))
|
title: "Erro",
|
||||||
);
|
description: e?.message || "Não foi possível deletar a disponibilidade",
|
||||||
} catch (e: any) {
|
});
|
||||||
toast({
|
}
|
||||||
title: "Erro",
|
fetchData()
|
||||||
description: e?.message || "Não foi possível deletar a disponibilidade",
|
setDeleteDialogOpen(false);
|
||||||
});
|
setSelectedAvailability(null);
|
||||||
}
|
};
|
||||||
setDeleteDialogOpen(false);
|
|
||||||
setSelectedAvailability(null);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Sidebar>
|
<Sidebar>
|
||||||
@ -542,142 +540,99 @@ export default function AvailabilityPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* **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 */}
|
{/* 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
|
<Link href="/doctor/disponibilidade/excecoes" className="w-full sm:w-auto">
|
||||||
href="/doctor/disponibilidade/excecoes"
|
<Button variant="default" className="w-full sm:w-auto">Adicionar Exceção</Button>
|
||||||
className="w-full sm:w-auto"
|
</Link>
|
||||||
>
|
<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 */}
|
||||||
<Button
|
<Link href="/doctor/dashboard" className="w-full sm:w-auto">
|
||||||
variant="default"
|
<Button variant="outline" className="w-full sm:w-auto">Cancelar</Button>
|
||||||
className="w-full sm:w-auto bg-blue-600 hover:bg-blue-700 text-white cursor-pointer"
|
</Link>
|
||||||
>
|
<Button type="submit" className="bg-green-600 hover:bg-green-700 w-full sm:w-auto">
|
||||||
Adicionar Exceção
|
Salvar Disponibilidade
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</div>
|
||||||
<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">
|
|
||||||
<Button
|
|
||||||
variant="outline"
|
|
||||||
className="w-full sm:w-auto cursor-pointer"
|
|
||||||
>
|
|
||||||
Cancelar
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
className="bg-blue-600 hover:bg-blue-700 w-full sm:w-auto cursor-pointer"
|
|
||||||
>
|
|
||||||
Salvar Disponibilidade
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
{/* **AJUSTE DE RESPONSIVIDADE: CARD DE HORÁRIO SEMANAL** */}
|
|
||||||
<div>
|
|
||||||
<Card>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle>Horário Semanal</CardTitle>
|
|
||||||
<CardDescription>
|
|
||||||
Confira ou altere a sua disponibilidade da semana
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
{/* Define um grid responsivo para os dias da semana (1 coluna em móvel, 2 em pequeno, 3 em médio e 7 em telas grandes) */}
|
|
||||||
<CardContent className="space-y-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-7 gap-2">
|
|
||||||
{[
|
|
||||||
"sunday",
|
|
||||||
"monday",
|
|
||||||
"tuesday",
|
|
||||||
"wednesday",
|
|
||||||
"thursday",
|
|
||||||
"friday",
|
|
||||||
"saturday",
|
|
||||||
].map((day) => {
|
|
||||||
const times = schedule[day] || [];
|
|
||||||
return (
|
|
||||||
<div key={day} className="space-y-4">
|
|
||||||
<div className="flex flex-col items-center justify-between p-3 bg-blue-50 rounded-lg h-full">
|
|
||||||
<p className="font-medium capitalize text-center mb-2">
|
|
||||||
{weekdaysPT[day]}
|
|
||||||
</p>
|
|
||||||
<div className="text-center w-full">
|
|
||||||
{times.length > 0 ? (
|
|
||||||
times.map((t, i) => (
|
|
||||||
<div key={i}>
|
|
||||||
<DropdownMenu>
|
|
||||||
<DropdownMenuTrigger asChild>
|
|
||||||
<p className="text-sm text-gray-600 cursor-pointer p-1 rounded hover:text-accent-foreground hover:bg-gray-200 transition-colors duration-150">
|
|
||||||
{formatTime(t.start)} - {formatTime(t.end)}
|
|
||||||
</p>
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
<DropdownMenuContent align="end">
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => handleOpenModal(t, day)}
|
|
||||||
>
|
|
||||||
<Edit className="w-4 h-4 mr-2" />
|
|
||||||
Editar
|
|
||||||
</DropdownMenuItem>
|
|
||||||
<DropdownMenuItem
|
|
||||||
onClick={() => openDeleteDialog(t, day)}
|
|
||||||
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>
|
|
||||||
))
|
|
||||||
) : (
|
|
||||||
<p className="text-sm text-gray-400 italic">
|
|
||||||
Sem horário
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</form>
|
||||||
);
|
|
||||||
})}
|
{/* **AJUSTE DE RESPONSIVIDADE: CARD DE HORÁRIO SEMANAL** */}
|
||||||
</CardContent>
|
<div>
|
||||||
</Card>
|
<Card>
|
||||||
</div>
|
<CardHeader>
|
||||||
|
<CardTitle>Horário Semanal</CardTitle>
|
||||||
{/* AlertDialog e Modal de Edição (não precisam de grandes ajustes de layout, apenas garantindo que os componentes sejam responsivos internamente) */}
|
<CardDescription>Confira ou altere a sua disponibilidade da semana</CardDescription>
|
||||||
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
</CardHeader>
|
||||||
<AlertDialogContent>
|
{/* Define um grid responsivo para os dias da semana (1 coluna em móvel, 2 em pequeno, 3 em médio e 7 em telas grandes) */}
|
||||||
<AlertDialogHeader>
|
<CardContent className="space-y-4 grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-7 gap-2">
|
||||||
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
|
{["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "saturday"].map((day) => {
|
||||||
<AlertDialogDescription>
|
const times = schedule[day] || [];
|
||||||
Tem certeza que deseja excluir esta disponibilidade? Esta ação
|
return (
|
||||||
não pode ser desfeita.
|
<div key={day} className="space-y-4">
|
||||||
</AlertDialogDescription>
|
<div className="flex flex-col items-center justify-between p-3 bg-blue-50 rounded-lg h-full">
|
||||||
</AlertDialogHeader>
|
<p className="font-medium capitalize text-center mb-2">{weekdaysPT[day]}</p>
|
||||||
<AlertDialogFooter>
|
<div className="text-center w-full">
|
||||||
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
{times.length > 0 ? (
|
||||||
<AlertDialogAction
|
times.map((t, i) => (
|
||||||
onClick={() =>
|
<div key={i}>
|
||||||
selectedAvailability &&
|
<DropdownMenu>
|
||||||
handleDeleteAvailability(selectedAvailability.id)
|
<DropdownMenuTrigger asChild>
|
||||||
}
|
<p className="text-sm text-gray-600 cursor-pointer rounded hover:text-accent-foreground hover:bg-gray-200 transition-colors duration-150">
|
||||||
className="bg-red-600 hover:bg-red-700"
|
{formatTime(t.start)} - {formatTime(t.end)}
|
||||||
>
|
</p>
|
||||||
Excluir
|
</DropdownMenuTrigger>
|
||||||
</AlertDialogAction>
|
<DropdownMenuContent align="end">
|
||||||
</AlertDialogFooter>
|
<DropdownMenuItem onClick={() => handleOpenModal(t, day)}>
|
||||||
</AlertDialogContent>
|
<Edit className="w-4 h-4 mr-2" />
|
||||||
</AlertDialog>
|
Editar
|
||||||
</div>
|
</DropdownMenuItem>
|
||||||
<AvailabilityEditModal
|
<DropdownMenuItem
|
||||||
availability={selectedAvailability}
|
onClick={() => openDeleteDialog(t, day)}
|
||||||
isOpen={isModalOpen}
|
className="text-red-600 focus:bg-red-50 focus:text-red-600">
|
||||||
onClose={handleCloseModal}
|
<Trash2 className="w-4 h-4 mr-2" />
|
||||||
onSubmit={handleEdit}
|
Excluir
|
||||||
/>
|
</DropdownMenuItem>
|
||||||
</Sidebar>
|
</DropdownMenuContent>
|
||||||
);
|
</DropdownMenu>
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-400 italic">Sem horário</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* AlertDialog e Modal de Edição (não precisam de grandes ajustes de layout, apenas garantindo que os componentes sejam responsivos internamente) */}
|
||||||
|
<AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
|
<AlertDialogContent>
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>Confirmar exclusão</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>Tem certeza que deseja excluir esta disponibilidade? Esta ação não pode ser desfeita.</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel>Cancelar</AlertDialogCancel>
|
||||||
|
<AlertDialogAction onClick={() => selectedAvailability && handleDeleteAvailability(selectedAvailability.id)} className="bg-red-600 hover:bg-red-700">
|
||||||
|
Excluir
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
|
</div>
|
||||||
|
<AvailabilityEditModal
|
||||||
|
availability={selectedAvailability}
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={handleCloseModal}
|
||||||
|
onSubmit={handleEdit}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</Sidebar>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
185
app/manager/disponibilidade/page.tsx
Normal file
185
app/manager/disponibilidade/page.tsx
Normal file
@ -0,0 +1,185 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
import WeeklyScheduleCard from "@/components/ui/WeeklyScheduleCard";
|
||||||
|
|
||||||
|
import { useEffect, useState, useMemo } from "react";
|
||||||
|
|
||||||
|
import { AvailabilityService } from "@/services/availabilityApi.mjs";
|
||||||
|
import { doctorsService } from "@/services/doctorsApi.mjs";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Filter } from "lucide-react";
|
||||||
|
|
||||||
|
type Doctor = {
|
||||||
|
id: string;
|
||||||
|
full_name: string;
|
||||||
|
specialty: string;
|
||||||
|
active: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Availability = {
|
||||||
|
id: string;
|
||||||
|
doctor_id: string;
|
||||||
|
weekday: string;
|
||||||
|
start_time: string;
|
||||||
|
end_time: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function AllAvailabilities() {
|
||||||
|
const [availabilities, setAvailabilities] = useState<Availability[] | null>(null);
|
||||||
|
const [doctors, setDoctors] = useState<Doctor[] | null>(null);
|
||||||
|
|
||||||
|
// 🔎 Filtros
|
||||||
|
const [search, setSearch] = useState("");
|
||||||
|
const [specialty, setSpecialty] = useState("all");
|
||||||
|
|
||||||
|
// 🔄 Paginação
|
||||||
|
const ITEMS_PER_PAGE = 6;
|
||||||
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
try {
|
||||||
|
const doctorsList = await doctorsService.list();
|
||||||
|
setDoctors(doctorsList);
|
||||||
|
|
||||||
|
const availabilityList = await AvailabilityService.list();
|
||||||
|
setAvailabilities(availabilityList);
|
||||||
|
} catch (e: any) {
|
||||||
|
alert(`${e?.error} ${e?.message}`);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchData();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// 🎯 Obter todas as especialidades existentes
|
||||||
|
const specialties = useMemo(() => {
|
||||||
|
if (!doctors) return [];
|
||||||
|
const unique = Array.from(new Set(doctors.map((d) => d.specialty)));
|
||||||
|
return unique;
|
||||||
|
}, [doctors]);
|
||||||
|
|
||||||
|
// 🔍 Filtrar médicos por especialidade + nome
|
||||||
|
const filteredDoctors = useMemo(() => {
|
||||||
|
if (!doctors) return [];
|
||||||
|
|
||||||
|
return doctors.filter((doctor) => (specialty === "all" ? true : doctor.specialty === specialty)).filter((doctor) => doctor.full_name.toLowerCase().includes(search.toLowerCase()));
|
||||||
|
}, [doctors, search, specialty]);
|
||||||
|
|
||||||
|
// 📄 Paginação (após filtros!)
|
||||||
|
const totalPages = Math.ceil(filteredDoctors.length / ITEMS_PER_PAGE);
|
||||||
|
const paginatedDoctors = filteredDoctors.slice((page - 1) * ITEMS_PER_PAGE, page * ITEMS_PER_PAGE);
|
||||||
|
|
||||||
|
const goNext = () => setPage((p) => Math.min(p + 1, totalPages));
|
||||||
|
const goPrev = () => setPage((p) => Math.max(p - 1, 1));
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Sidebar>
|
||||||
|
<div className="p-6 text-gray-500">Carregando dados...</div>
|
||||||
|
</Sidebar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doctors || !availabilities) {
|
||||||
|
return (
|
||||||
|
<Sidebar>
|
||||||
|
<div className="p-6 text-red-600 font-medium">Não foi possível carregar médicos ou disponibilidades.</div>
|
||||||
|
</Sidebar>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Sidebar>
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-3xl font-bold text-gray-900">Disponibilidade dos Médicos</h1>
|
||||||
|
<p className="text-gray-600">Visualize a agenda semanal individual de cada médico.</p>
|
||||||
|
</div>
|
||||||
|
<Card>
|
||||||
|
<CardContent>
|
||||||
|
{/* 🔎 Filtros */}
|
||||||
|
<div className="flex flex-col md:flex-row gap-4 items-center">
|
||||||
|
{/* Filtro por nome */}
|
||||||
|
<Filter className="w-4 h-4 mr-2" />
|
||||||
|
<Input
|
||||||
|
placeholder="Buscar por nome do médico..."
|
||||||
|
value={search}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSearch(e.target.value);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
className="w-full md:w-1/3"
|
||||||
|
/>
|
||||||
|
|
||||||
|
{/* Filtro por especialidade */}
|
||||||
|
<Select
|
||||||
|
onValueChange={(value) => {
|
||||||
|
setSpecialty(value);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
defaultValue="all"
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-full md:w-64">
|
||||||
|
<SelectValue placeholder="Especialidade" />
|
||||||
|
</SelectTrigger>
|
||||||
|
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="all">Todas as especialidades</SelectItem>
|
||||||
|
{specialties.map((sp) => (
|
||||||
|
<SelectItem key={sp} value={sp}>
|
||||||
|
{sp}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
{/* GRID de cards */}
|
||||||
|
<div className="grid md:grid-cols-1 lg:grid-cols-1 gap-6">
|
||||||
|
{paginatedDoctors.map((doctor) => {
|
||||||
|
const doctorAvailabilities = availabilities.filter((a) => a.doctor_id === doctor.id);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card key={doctor.id}>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="text-xl font-semibold">{doctor.full_name}</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
|
||||||
|
<CardContent>
|
||||||
|
<WeeklyScheduleCard doctorId={doctor.id} />
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 📄 Paginação */}
|
||||||
|
{totalPages > 1 && (
|
||||||
|
<div className="flex justify-center items-center gap-4 pt-4">
|
||||||
|
<Button variant="outline" onClick={goPrev} disabled={page === 1}>
|
||||||
|
Anterior
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<span className="text-gray-700 font-medium">
|
||||||
|
Página {page} de {totalPages}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<Button variant="outline" onClick={goNext} disabled={page === totalPages}>
|
||||||
|
Próxima
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Sidebar>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -188,18 +188,14 @@ export default function Sidebar({ children }: SidebarProps) {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const managerItems: MenuItem[] = [
|
const managerItems: MenuItem[] = [
|
||||||
{ href: "/manager/dashboard", icon: Home, label: "Dashboard" },
|
{ href: "/manager/dashboard", icon: Home, label: "Dashboard" },
|
||||||
{ href: "#", icon: ClipboardMinus, label: "Relatórios gerenciais" },
|
{ href: "/manager/usuario", icon: Users, label: "Gestão de Usuários" },
|
||||||
{ href: "/manager/usuario", icon: Users, label: "Gestão de Usuários" },
|
{ href: "/manager/home", icon: Stethoscope, label: "Gestão de Médicos" },
|
||||||
{ href: "/manager/home", icon: Stethoscope, label: "Gestão de Médicos" },
|
{ href: "/manager/pacientes", icon: Users, label: "Gestão de Pacientes" },
|
||||||
{ href: "/manager/pacientes", icon: Users, label: "Gestão de Pacientes" },
|
{ href: "/secretary/appointments", icon: CalendarCheck2, label: "Consultas" },
|
||||||
{
|
{ href: "/manager/disponibilidade", icon: ClipboardList, label: "Disponibilidade" },
|
||||||
href: "/secretary/appointments",
|
];
|
||||||
icon: CalendarCheck2,
|
|
||||||
label: "Consultas",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case "gestor":
|
case "gestor":
|
||||||
|
|||||||
105
components/ui/WeeklyScheduleCard.tsx
Normal file
105
components/ui/WeeklyScheduleCard.tsx
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
|
||||||
|
import { AvailabilityService } from "@/services/availabilityApi.mjs";
|
||||||
|
import { doctorsService } from "@/services/doctorsApi.mjs";
|
||||||
|
|
||||||
|
type Availability = {
|
||||||
|
id: string;
|
||||||
|
doctor_id: string;
|
||||||
|
weekday: string;
|
||||||
|
start_time: string;
|
||||||
|
end_time: string;
|
||||||
|
slot_minutes: number;
|
||||||
|
appointment_type: string;
|
||||||
|
active: boolean;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
|
created_by: string;
|
||||||
|
updated_by: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface WeeklyScheduleProps {
|
||||||
|
doctorId?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WeeklyScheduleCard({ doctorId }: WeeklyScheduleProps) {
|
||||||
|
const [schedule, setSchedule] = useState<Record<string, { start: string; end: string }[]>>({});
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
const weekdaysPT: Record<string, string> = {
|
||||||
|
sunday: "Domingo",
|
||||||
|
monday: "Segunda",
|
||||||
|
tuesday: "Terça",
|
||||||
|
wednesday: "Quarta",
|
||||||
|
thursday: "Quinta",
|
||||||
|
friday: "Sexta",
|
||||||
|
saturday: "Sábado",
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatTime = (time?: string | null) => time?.split(":")?.slice(0, 2).join(":") ?? "";
|
||||||
|
|
||||||
|
function formatAvailability(data: Availability[]) {
|
||||||
|
const grouped = data.reduce((acc: any, item) => {
|
||||||
|
const { weekday, start_time, end_time } = item;
|
||||||
|
|
||||||
|
if (!acc[weekday]) acc[weekday] = [];
|
||||||
|
|
||||||
|
acc[weekday].push({ start: start_time, end: end_time });
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return grouped;
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchSchedule = async () => {
|
||||||
|
try {
|
||||||
|
const availabilityList = await AvailabilityService.list();
|
||||||
|
|
||||||
|
const filtered = availabilityList.filter((a: Availability) => a.doctor_id == doctorId);
|
||||||
|
|
||||||
|
const formatted = formatAvailability(filtered);
|
||||||
|
setSchedule(formatted);
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Erro ao carregar horários:", err);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchSchedule();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4 grid md:grid-cols-7 gap-2">
|
||||||
|
{loading ? (
|
||||||
|
<p className="text-sm text-gray-500 col-span-7 text-center">Carregando...</p>
|
||||||
|
) : (
|
||||||
|
["sunday", "monday", "tuesday", "wednesday", "thursday", "friday", "Saturday"].map((day) => {
|
||||||
|
const times = schedule[day] || [];
|
||||||
|
return (
|
||||||
|
<div key={day} className="space-y-4">
|
||||||
|
<div className="flex flex-col items-center justify-between p-3 bg-blue-50 rounded-lg">
|
||||||
|
<p className="font-medium capitalize">{weekdaysPT[day]}</p>
|
||||||
|
<div className="text-center">
|
||||||
|
{times.length > 0 ? (
|
||||||
|
times.map((t, i) => (
|
||||||
|
<p key={i} className="text-sm text-gray-600">
|
||||||
|
{formatTime(t.start)} <br /> {formatTime(t.end)}
|
||||||
|
</p>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-gray-400 italic">Sem horário</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user