From a48ba7af2b822dda1374fdb6cea6cb772b7dfb00 Mon Sep 17 00:00:00 2001 From: StsDanilo Date: Thu, 30 Oct 2025 19:11:43 -0300 Subject: [PATCH] =?UTF-8?q?corre=C3=A7=C3=A3o=20de=20erros?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/doctor/disponibilidade/page.tsx | 40 +- app/manager/usuario/novo/page.tsx | 383 +++++++----------- app/patient/appointments/page.tsx | 569 +++++++++++++------------- app/patient/schedule/page.tsx | 591 +++++++++++++--------------- components/LoginForm.tsx | 362 +++++++++-------- components/ui/toast.tsx | 147 ++----- services/api.mjs | 136 +++---- 7 files changed, 1003 insertions(+), 1225 deletions(-) diff --git a/app/doctor/disponibilidade/page.tsx b/app/doctor/disponibilidade/page.tsx index 127b93f..dc304cc 100644 --- a/app/doctor/disponibilidade/page.tsx +++ b/app/doctor/disponibilidade/page.tsx @@ -8,15 +8,45 @@ import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import DoctorLayout from "@/components/doctor-layout"; import { AvailabilityService } from "@/services/availabilityApi.mjs"; +import { usersService } from "@/services/usersApi.mjs"; import { toast } from "@/hooks/use-toast"; import { useRouter } from "next/navigation"; +interface UserPermissions { + isAdmin: boolean; + isManager: boolean; + isDoctor: boolean; + isSecretary: boolean; + isAdminOrManager: boolean; +} + +interface UserData { + user: { + id: string; + email: string; + email_confirmed_at: string | null; + created_at: string | null; + last_sign_in_at: string | null; + }; + profile: { + id: string; + full_name: string; + email: string; + phone: string; + avatar_url: string | null; + disabled: boolean; + created_at: string | null; + updated_at: string | null; + }; + roles: string[]; + permissions: UserPermissions; +} + export default function AvailabilityPage() { const [error, setError] = useState(null); const router = useRouter(); const [isLoading, setIsLoading] = useState(false); - const userInfo = JSON.parse(localStorage.getItem("user_info") || "{}"); - const doctorIdTemp = "3bb9ee4a-cfdd-4d81-b628-383907dfa225"; + const [userData, setUserData] = useState(); const [modalidadeConsulta, setModalidadeConsulta] = useState(""); useEffect(() => { @@ -24,6 +54,9 @@ export default function AvailabilityPage() { try { const response = await AvailabilityService.list(); console.log(response); + const user = await usersService.getMe(); + console.log(user); + setUserData(user); } catch (e: any) { alert(`${e?.error} ${e?.message}`); } @@ -40,8 +73,7 @@ export default function AvailabilityPage() { const formData = new FormData(form); const apiPayload = { - doctor_id: doctorIdTemp, - created_by: doctorIdTemp, + doctor_id: userData?.user.id, weekday: (formData.get("weekday") as string) || undefined, start_time: (formData.get("horarioEntrada") as string) || undefined, end_time: (formData.get("horarioSaida") as string) || undefined, diff --git a/app/manager/usuario/novo/page.tsx b/app/manager/usuario/novo/page.tsx index 4265c63..77dae32 100644 --- a/app/manager/usuario/novo/page.tsx +++ b/app/manager/usuario/novo/page.tsx @@ -6,267 +6,176 @@ import Link from "next/link"; 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 { Save, Loader2 } from "lucide-react"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Save, Loader2, Pause } from "lucide-react"; import ManagerLayout from "@/components/manager-layout"; import { usersService } from "services/usersApi.mjs"; import { login } from "services/api.mjs"; interface UserFormData { - email: string; - nomeCompleto: string; - telefone: string; - papel: string; - senha: string; - confirmarSenha: string; - cpf : string + email: string; + nomeCompleto: string; + telefone: string; + papel: string; + senha: string; + confirmarSenha: string; + cpf: string; } const defaultFormData: UserFormData = { - email: "", - nomeCompleto: "", - telefone: "", - papel: "", - senha: "", - confirmarSenha: "", - cpf : "" + email: "", + nomeCompleto: "", + telefone: "", + papel: "", + senha: "", + confirmarSenha: "", + cpf: "", }; const cleanNumber = (value: string): string => value.replace(/\D/g, ""); const formatPhone = (value: string): string => { - const cleaned = cleanNumber(value).substring(0, 11); - if (cleaned.length === 11) - return cleaned.replace(/(\d{2})(\d{5})(\d{4})/, "($1) $2-$3"); - if (cleaned.length === 10) - return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, "($1) $2-$3"); - return cleaned; + const cleaned = cleanNumber(value).substring(0, 11); + if (cleaned.length === 11) return cleaned.replace(/(\d{2})(\d{5})(\d{4})/, "($1) $2-$3"); + if (cleaned.length === 10) return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, "($1) $2-$3"); + return cleaned; }; export default function NovoUsuarioPage() { - const router = useRouter(); - const [formData, setFormData] = useState(defaultFormData); - const [isSaving, setIsSaving] = useState(false); - const [error, setError] = useState(null); + const router = useRouter(); + const [formData, setFormData] = useState(defaultFormData); + const [isSaving, setIsSaving] = useState(false); + const [error, setError] = useState(null); - const handleInputChange = (key: keyof UserFormData, value: string) => { - const updatedValue = key === "telefone" ? formatPhone(value) : value; - setFormData((prev) => ({ ...prev, [key]: updatedValue })); - }; + const handleInputChange = (key: keyof UserFormData, value: string) => { + const updatedValue = key === "telefone" ? formatPhone(value) : value; + setFormData((prev) => ({ ...prev, [key]: updatedValue })); + }; - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - setError(null); + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setError(null); - if ( - !formData.email || - !formData.nomeCompleto || - !formData.papel || - !formData.senha || - !formData.confirmarSenha - ) { - setError("Por favor, preencha todos os campos obrigatórios."); - return; - } + if (!formData.email || !formData.nomeCompleto || !formData.papel || !formData.senha || !formData.confirmarSenha) { + setError("Por favor, preencha todos os campos obrigatórios."); + return; + } - if (formData.senha !== formData.confirmarSenha) { - setError("A Senha e a Confirmação de Senha não coincidem."); - return; - } + if (formData.senha !== formData.confirmarSenha) { + setError("A Senha e a Confirmação de Senha não coincidem."); + return; + } - setIsSaving(true); + setIsSaving(true); - try { - await login(); + try { + const payload = { + full_name: formData.nomeCompleto, + email: formData.email.trim().toLowerCase(), + phone: formData.telefone || null, + role: formData.papel, + password: formData.senha, + cpf: formData.cpf, + }; - const payload = { - full_name: formData.nomeCompleto, - email: formData.email.trim().toLowerCase(), - phone: formData.telefone || null, - role: formData.papel, - password: formData.senha, - cpf : formData.cpf - }; + console.log("📤 Enviando payload:"); + console.log(payload); - console.log("📤 Enviando payload:", payload); + await usersService.create_user(payload); - await usersService.create_user(payload); + router.push("/manager/usuario"); + } catch (e: any) { + console.error("Erro ao criar usuário:", e); + setError(e?.message || "Não foi possível criar o usuário. Verifique os dados e tente novamente."); + } finally { + setIsSaving(false); + } + }; - router.push("/manager/usuario"); - } catch (e: any) { - console.error("Erro ao criar usuário:", e); - setError( - e?.message || - "Não foi possível criar o usuário. Verifique os dados e tente novamente." - ); - } finally { - setIsSaving(false); - } - }; + return ( + +
+
+
+
+

Novo Usuário

+

Preencha os dados para cadastrar um novo usuário no sistema.

+
+ + + +
- return ( - -
-
-
-
-

- Novo Usuário -

-

- Preencha os dados para cadastrar um novo usuário no sistema. -

+
+ {error && ( +
+

Erro no Cadastro:

+

{error}

+
+ )} + +
+
+ + handleInputChange("nomeCompleto", e.target.value)} placeholder="Nome e Sobrenome" required /> +
+ +
+ + handleInputChange("email", e.target.value)} placeholder="exemplo@dominio.com" required /> +
+ +
+ + +
+ +
+ + handleInputChange("senha", e.target.value)} placeholder="Mínimo 8 caracteres" minLength={8} required /> +
+ +
+ + handleInputChange("confirmarSenha", e.target.value)} placeholder="Repita a senha" required /> + {formData.senha && formData.confirmarSenha && formData.senha !== formData.confirmarSenha &&

As senhas não coincidem.

} +
+ +
+ + handleInputChange("telefone", e.target.value)} placeholder="(00) 00000-0000" maxLength={15} /> +
+
+ +
+ + handleInputChange("cpf", e.target.value)} placeholder="xxx.xxx.xxx-xx" required /> +
+ +
+ + + + +
+
+
- - - -
- -
- {error && ( -
-

Erro no Cadastro:

-

{error}

-
- )} - -
-
- - - handleInputChange("nomeCompleto", e.target.value) - } - placeholder="Nome e Sobrenome" - required - /> -
- -
- - handleInputChange("email", e.target.value)} - placeholder="exemplo@dominio.com" - required - /> -
- -
- - -
- -
- - handleInputChange("senha", e.target.value)} - placeholder="Mínimo 8 caracteres" - minLength={8} - required - /> -
- -
- - - handleInputChange("confirmarSenha", e.target.value) - } - placeholder="Repita a senha" - required - /> - {formData.senha && - formData.confirmarSenha && - formData.senha !== formData.confirmarSenha && ( -

- As senhas não coincidem. -

- )} -
- -
- - - handleInputChange("telefone", e.target.value) - } - placeholder="(00) 00000-0000" - maxLength={15} - /> -
-
- -
- - handleInputChange("cpf", e.target.value)} - placeholder="xxx.xxx.xxx-xx" - required - /> -
- - -
- - - - -
-
-
-
- - ); -} \ No newline at end of file + + ); +} diff --git a/app/patient/appointments/page.tsx b/app/patient/appointments/page.tsx index d2d02a8..78f2d8b 100644 --- a/app/patient/appointments/page.tsx +++ b/app/patient/appointments/page.tsx @@ -16,328 +16,309 @@ import { toast } from "sonner"; import { appointmentsService } from "@/services/appointmentsApi.mjs"; import { patientsService } from "@/services/patientsApi.mjs"; import { doctorsService } from "@/services/doctorsApi.mjs"; +import { usersService } from "@/services/usersApi.mjs"; const APPOINTMENTS_STORAGE_KEY = "clinic-appointments"; -// Simulação do paciente logado -const LOGGED_PATIENT_ID = "P001"; +interface UserPermissions { + isAdmin: boolean; + isManager: boolean; + isDoctor: boolean; + isSecretary: boolean; + isAdminOrManager: boolean; +} + +interface UserData { + user: { + id: string; + email: string; + email_confirmed_at: string | null; + created_at: string | null; + last_sign_in_at: string | null; + }; + profile: { + id: string; + full_name: string; + email: string; + phone: string; + avatar_url: string | null; + disabled: boolean; + created_at: string | null; + updated_at: string | null; + }; + roles: string[]; + permissions: UserPermissions; +} export default function PatientAppointments() { - const [appointments, setAppointments] = useState([]); - const [isLoading, setIsLoading] = useState(true); - const [selectedAppointment, setSelectedAppointment] = useState(null); + const [appointments, setAppointments] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [selectedAppointment, setSelectedAppointment] = useState(null); + const [userData, setUserData] = useState(); - // Modais - const [rescheduleModal, setRescheduleModal] = useState(false); - const [cancelModal, setCancelModal] = useState(false); + // Modais + const [rescheduleModal, setRescheduleModal] = useState(false); + const [cancelModal, setCancelModal] = useState(false); - // Formulário de reagendamento/cancelamento - const [rescheduleData, setRescheduleData] = useState({ date: "", time: "", reason: "" }); - const [cancelReason, setCancelReason] = useState(""); + // Formulário de reagendamento/cancelamento + const [rescheduleData, setRescheduleData] = useState({ date: "", time: "", reason: "" }); + const [cancelReason, setCancelReason] = useState(""); - 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 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 fetchData = async () => { - setIsLoading(true); - try { - const [appointmentList, patientList, doctorList] = await Promise.all([ - appointmentsService.list(), - patientsService.list(), - doctorsService.list(), - ]); + const fetchData = async () => { + setIsLoading(true); + try { + const queryParams = "order=scheduled_at.desc"; + const appointmentList = await appointmentsService.search_appointment(queryParams); + const patientList = await patientsService.list(); + const doctorList = await doctorsService.list(); - const doctorMap = new Map(doctorList.map((d: any) => [d.id, d])); - const patientMap = new Map(patientList.map((p: any) => [p.id, p])); + const user = await usersService.getMe(); + setUserData(user); - // Filtra apenas as consultas do paciente logado - const patientAppointments = appointmentList - .filter((apt: any) => apt.patient_id === LOGGED_PATIENT_ID) - .map((apt: any) => ({ - ...apt, - doctor: doctorMap.get(apt.doctor_id) || { full_name: "Médico não encontrado", specialty: "N/A" }, - patient: patientMap.get(apt.patient_id) || { full_name: "Paciente não encontrado" }, - })); + const doctorMap = new Map(doctorList.map((d: any) => [d.id, d])); + const patientMap = new Map(patientList.map((p: any) => [p.id, p])); - setAppointments(patientAppointments); - } catch (error) { - console.error("Erro ao carregar consultas:", error); - toast.error("Não foi possível carregar suas consultas."); - } finally { - setIsLoading(false); - } - }; + console.log(appointmentList); - useEffect(() => { - fetchData(); - }, []); + // Filtra apenas as consultas do paciente logado + const patientAppointments = appointmentList + .filter((apt: any) => apt.patient_id === userData?.user.id) + .map((apt: any) => ({ + ...apt, + doctor: doctorMap.get(apt.doctor_id) || { full_name: "Médico não encontrado", specialty: "N/A" }, + patient: patientMap.get(apt.patient_id) || { full_name: "Paciente não encontrado" }, + })); - const getStatusBadge = (status: string) => { - switch (status) { - case "requested": - return Solicitada; - case "confirmed": - return Confirmada; - case "checked_in": - return Check-in; - case "completed": - return Realizada; - case "cancelled": - return Cancelada; - default: - return {status}; - } - }; + setAppointments(patientAppointments); + } catch (error) { + console.error("Erro ao carregar consultas:", error); + toast.error("Não foi possível carregar suas consultas."); + } finally { + setIsLoading(false); + } + }; - const handleReschedule = (appointment: any) => { - setSelectedAppointment(appointment); - setRescheduleData({ date: "", time: "", reason: "" }); - setRescheduleModal(true); - }; + useEffect(() => { + fetchData(); + }, []); - const handleCancel = (appointment: any) => { - setSelectedAppointment(appointment); - setCancelReason(""); - setCancelModal(true); - }; + const getStatusBadge = (status: string) => { + switch (status) { + case "requested": + return Solicitada; + case "confirmed": + return Confirmada; + case "checked_in": + return Check-in; + case "completed": + return Realizada; + case "cancelled": + return Cancelada; + default: + return {status}; + } + }; - const confirmReschedule = async () => { - if (!rescheduleData.date || !rescheduleData.time) { - toast.error("Por favor, selecione uma nova data e horário."); - return; - } - try { - const newScheduledAt = new Date(`${rescheduleData.date}T${rescheduleData.time}:00Z`).toISOString(); + const handleReschedule = (appointment: any) => { + setSelectedAppointment(appointment); + setRescheduleData({ date: "", time: "", reason: "" }); + setRescheduleModal(true); + }; - await appointmentsService.update(selectedAppointment.id, { - scheduled_at: newScheduledAt, - status: "requested", - }); + const handleCancel = (appointment: any) => { + setSelectedAppointment(appointment); + setCancelReason(""); + setCancelModal(true); + }; - setAppointments((prev) => - prev.map((apt) => - apt.id === selectedAppointment.id ? { ...apt, scheduled_at: newScheduledAt, status: "requested" } : apt - ) - ); + const confirmReschedule = async () => { + if (!rescheduleData.date || !rescheduleData.time) { + toast.error("Por favor, selecione uma nova data e horário."); + return; + } + try { + const newScheduledAt = new Date(`${rescheduleData.date}T${rescheduleData.time}:00Z`).toISOString(); - setRescheduleModal(false); - toast.success("Consulta reagendada com sucesso!"); - } catch (error) { - console.error("Erro ao reagendar consulta:", error); - toast.error("Não foi possível reagendar a consulta."); - } - }; + await appointmentsService.update(selectedAppointment.id, { + scheduled_at: newScheduledAt, + status: "requested", + }); - const confirmCancel = async () => { - if (!cancelReason.trim() || cancelReason.trim().length < 10) { - toast.error("Por favor, informe um motivo de cancelamento (mínimo 10 caracteres)."); - return; - } - try { - await appointmentsService.update(selectedAppointment.id, { - status: "cancelled", - cancel_reason: cancelReason, - }); + setAppointments((prev) => prev.map((apt) => (apt.id === selectedAppointment.id ? { ...apt, scheduled_at: newScheduledAt, status: "requested" } : apt))); - setAppointments((prev) => - prev.map((apt) => - apt.id === selectedAppointment.id ? { ...apt, status: "cancelled" } : apt - ) - ); + setRescheduleModal(false); + toast.success("Consulta reagendada com sucesso!"); + } catch (error) { + console.error("Erro ao reagendar consulta:", error); + toast.error("Não foi possível reagendar a consulta."); + } + }; - setCancelModal(false); - toast.success("Consulta cancelada com sucesso!"); - } catch (error) { - console.error("Erro ao cancelar consulta:", error); - toast.error("Não foi possível cancelar a consulta."); - } - }; + const confirmCancel = async () => { + if (!cancelReason.trim() || cancelReason.trim().length < 10) { + toast.error("Por favor, informe um motivo de cancelamento (mínimo 10 caracteres)."); + return; + } + try { + await appointmentsService.update(selectedAppointment.id, { + status: "cancelled", + cancel_reason: cancelReason, + }); - return ( - -
-
-
-

Minhas Consultas

-

Veja, reagende ou cancele suas consultas

-
-
+ setAppointments((prev) => prev.map((apt) => (apt.id === selectedAppointment.id ? { ...apt, status: "cancelled" } : apt))); -
- {isLoading ? ( -

Carregando suas consultas...

- ) : appointments.length > 0 ? ( - appointments.map((appointment) => ( - - -
+ setCancelModal(false); + toast.success("Consulta cancelada com sucesso!"); + } catch (error) { + console.error("Erro ao cancelar consulta:", error); + toast.error("Não foi possível cancelar a consulta."); + } + }; + + return ( + +
+
- {appointment.doctor.full_name} - {appointment.doctor.specialty} +

Minhas Consultas

+

Veja, reagende ou cancele suas consultas

- {getStatusBadge(appointment.status)} -
- - -
-
-
- - {new Date(appointment.scheduled_at).toLocaleDateString("pt-BR", { timeZone: "UTC" })} -
-
- - {new Date(appointment.scheduled_at).toLocaleTimeString("pt-BR", { - hour: "2-digit", - minute: "2-digit", - timeZone: "UTC", - })} -
-
- - {appointment.doctor.location || "Local a definir"} -
-
- - {appointment.doctor.phone || "N/A"} -
+
+ +
+ {isLoading ? ( +

Carregando suas consultas...

+ ) : appointments.length > 0 ? ( + appointments.map((appointment) => ( + + +
+
+ {appointment.doctor.full_name} + {appointment.doctor.specialty} +
+ {getStatusBadge(appointment.status)} +
+
+ +
+
+
+ + {new Date(appointment.scheduled_at).toLocaleDateString("pt-BR", { timeZone: "UTC" })} +
+
+ + {new Date(appointment.scheduled_at).toLocaleTimeString("pt-BR", { + hour: "2-digit", + minute: "2-digit", + timeZone: "UTC", + })} +
+
+ + {appointment.doctor.location || "Local a definir"} +
+
+ + {appointment.doctor.phone || "N/A"} +
+
+
+ + {appointment.status !== "cancelled" && ( +
+ + +
+ )} +
+
+ )) + ) : ( +

Você ainda não possui consultas agendadas.

+ )} +
+
+ + {/* MODAL DE REAGENDAMENTO */} + + + + Reagendar Consulta + + Escolha uma nova data e horário para sua consulta com {selectedAppointment?.doctor?.full_name}. + + +
+
+ + setRescheduleData((prev) => ({ ...prev, date: e.target.value }))} min={new Date().toISOString().split("T")[0]} /> +
+
+ + +
+
+ +