correção de erros

This commit is contained in:
StsDanilo 2025-10-30 19:11:43 -03:00
parent 2a015a7f63
commit a48ba7af2b
7 changed files with 1003 additions and 1225 deletions

View File

@ -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<string | null>(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<UserData>();
const [modalidadeConsulta, setModalidadeConsulta] = useState<string>("");
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,

View File

@ -6,14 +6,8 @@ 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";
@ -25,7 +19,7 @@ interface UserFormData {
papel: string;
senha: string;
confirmarSenha: string;
cpf : string
cpf: string;
}
const defaultFormData: UserFormData = {
@ -35,16 +29,14 @@ const defaultFormData: UserFormData = {
papel: "",
senha: "",
confirmarSenha: "",
cpf : ""
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");
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;
};
@ -63,13 +55,7 @@ export default function NovoUsuarioPage() {
e.preventDefault();
setError(null);
if (
!formData.email ||
!formData.nomeCompleto ||
!formData.papel ||
!formData.senha ||
!formData.confirmarSenha
) {
if (!formData.email || !formData.nomeCompleto || !formData.papel || !formData.senha || !formData.confirmarSenha) {
setError("Por favor, preencha todos os campos obrigatórios.");
return;
}
@ -82,28 +68,24 @@ export default function NovoUsuarioPage() {
setIsSaving(true);
try {
await login();
const payload = {
full_name: formData.nomeCompleto,
email: formData.email.trim().toLowerCase(),
phone: formData.telefone || null,
role: formData.papel,
password: formData.senha,
cpf : formData.cpf
cpf: formData.cpf,
};
console.log("📤 Enviando payload:", payload);
console.log("📤 Enviando payload:");
console.log(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."
);
setError(e?.message || "Não foi possível criar o usuário. Verifique os dados e tente novamente.");
} finally {
setIsSaving(false);
}
@ -115,22 +97,15 @@ export default function NovoUsuarioPage() {
<div className="w-full max-w-screen-lg space-y-8">
<div className="flex items-center justify-between border-b pb-4">
<div>
<h1 className="text-3xl font-extrabold text-gray-900">
Novo Usuário
</h1>
<p className="text-md text-gray-500">
Preencha os dados para cadastrar um novo usuário no sistema.
</p>
<h1 className="text-3xl font-extrabold text-gray-900">Novo Usuário</h1>
<p className="text-md text-gray-500">Preencha os dados para cadastrar um novo usuário no sistema.</p>
</div>
<Link href="/manager/usuario">
<Button variant="outline">Cancelar</Button>
</Link>
</div>
<form
onSubmit={handleSubmit}
className="space-y-6 bg-white p-6 md:p-10 border rounded-xl shadow-lg"
>
<form onSubmit={handleSubmit} className="space-y-6 bg-white p-6 md:p-10 border rounded-xl shadow-lg">
{error && (
<div className="p-4 bg-red-50 text-red-700 rounded-lg border border-red-300">
<p className="font-semibold">Erro no Cadastro:</p>
@ -141,36 +116,17 @@ export default function NovoUsuarioPage() {
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2 md:col-span-2">
<Label htmlFor="nomeCompleto">Nome Completo *</Label>
<Input
id="nomeCompleto"
value={formData.nomeCompleto}
onChange={(e) =>
handleInputChange("nomeCompleto", e.target.value)
}
placeholder="Nome e Sobrenome"
required
/>
<Input id="nomeCompleto" value={formData.nomeCompleto} onChange={(e) => handleInputChange("nomeCompleto", e.target.value)} placeholder="Nome e Sobrenome" required />
</div>
<div className="space-y-2">
<Label htmlFor="email">E-mail *</Label>
<Input
id="email"
type="email"
value={formData.email}
onChange={(e) => handleInputChange("email", e.target.value)}
placeholder="exemplo@dominio.com"
required
/>
<Input id="email" type="email" value={formData.email} onChange={(e) => handleInputChange("email", e.target.value)} placeholder="exemplo@dominio.com" required />
</div>
<div className="space-y-2">
<Label htmlFor="papel">Papel (Função) *</Label>
<Select
value={formData.papel}
onValueChange={(v) => handleInputChange("papel", v)}
required
>
<Select value={formData.papel} onValueChange={(v) => handleInputChange("papel", v)} required>
<SelectTrigger id="papel">
<SelectValue placeholder="Selecione uma função" />
</SelectTrigger>
@ -179,88 +135,41 @@ export default function NovoUsuarioPage() {
<SelectItem value="gestor">Gestor</SelectItem>
<SelectItem value="medico">Médico</SelectItem>
<SelectItem value="secretaria">Secretária</SelectItem>
<SelectItem value="user">Usuário</SelectItem>
<SelectItem value="paciente">Usuário</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label htmlFor="senha">Senha *</Label>
<Input
id="senha"
type="password"
value={formData.senha}
onChange={(e) => handleInputChange("senha", e.target.value)}
placeholder="Mínimo 8 caracteres"
minLength={8}
required
/>
<Input id="senha" type="password" value={formData.senha} onChange={(e) => handleInputChange("senha", e.target.value)} placeholder="Mínimo 8 caracteres" minLength={8} required />
</div>
<div className="space-y-2">
<Label htmlFor="confirmarSenha">Confirmar Senha *</Label>
<Input
id="confirmarSenha"
type="password"
value={formData.confirmarSenha}
onChange={(e) =>
handleInputChange("confirmarSenha", e.target.value)
}
placeholder="Repita a senha"
required
/>
{formData.senha &&
formData.confirmarSenha &&
formData.senha !== formData.confirmarSenha && (
<p className="text-xs text-red-500">
As senhas não coincidem.
</p>
)}
<Input id="confirmarSenha" type="password" value={formData.confirmarSenha} onChange={(e) => handleInputChange("confirmarSenha", e.target.value)} placeholder="Repita a senha" required />
{formData.senha && formData.confirmarSenha && formData.senha !== formData.confirmarSenha && <p className="text-xs text-red-500">As senhas não coincidem.</p>}
</div>
<div className="space-y-2">
<Label htmlFor="telefone">Telefone</Label>
<Input
id="telefone"
value={formData.telefone}
onChange={(e) =>
handleInputChange("telefone", e.target.value)
}
placeholder="(00) 00000-0000"
maxLength={15}
/>
<Input id="telefone" value={formData.telefone} onChange={(e) => handleInputChange("telefone", e.target.value)} placeholder="(00) 00000-0000" maxLength={15} />
</div>
</div>
<div className="space-y-2">
<Label htmlFor="cpf">Cpf *</Label>
<Input
id="cpf"
type="cpf"
value={formData.cpf}
onChange={(e) => handleInputChange("cpf", e.target.value)}
placeholder="xxx.xxx.xxx-xx"
required
/>
<Input id="cpf" type="cpf" value={formData.cpf} onChange={(e) => handleInputChange("cpf", e.target.value)} placeholder="xxx.xxx.xxx-xx" required />
</div>
<div className="flex justify-end gap-4 pt-6 border-t mt-6">
<Link href="/manager/usuario">
<Button type="button" variant="outline" disabled={isSaving}>
Cancelar
</Button>
</Link>
<Button
type="submit"
className="bg-green-600 hover:bg-green-700"
disabled={isSaving}
>
{isSaving ? (
<Loader2 className="w-4 h-4 mr-2 animate-spin" />
) : (
<Save className="w-4 h-4 mr-2" />
)}
<Button type="submit" className="bg-green-600 hover:bg-green-700" disabled={isSaving}>
{isSaving ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <Save className="w-4 h-4 mr-2" />}
{isSaving ? "Salvando..." : "Salvar Usuário"}
</Button>
</div>

View File

@ -16,16 +16,45 @@ 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<any[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [selectedAppointment, setSelectedAppointment] = useState<any>(null);
const [userData, setUserData] = useState<UserData>();
// Modais
const [rescheduleModal, setRescheduleModal] = useState(false);
@ -35,40 +64,27 @@ export default function PatientAppointments() {
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 queryParams = "order=scheduled_at.desc";
const appointmentList = await appointmentsService.search_appointment(queryParams);
const patientList = await patientsService.list();
const doctorList = await doctorsService.list();
const user = await usersService.getMe();
setUserData(user);
const doctorMap = new Map(doctorList.map((d: any) => [d.id, d]));
const patientMap = new Map(patientList.map((p: any) => [p.id, p]));
console.log(appointmentList);
// Filtra apenas as consultas do paciente logado
const patientAppointments = appointmentList
.filter((apt: any) => apt.patient_id === LOGGED_PATIENT_ID)
.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" },
@ -130,11 +146,7 @@ export default function PatientAppointments() {
status: "requested",
});
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, scheduled_at: newScheduledAt, status: "requested" } : apt)));
setRescheduleModal(false);
toast.success("Consulta reagendada com sucesso!");
@ -155,11 +167,7 @@ export default function PatientAppointments() {
cancel_reason: cancelReason,
});
setAppointments((prev) =>
prev.map((apt) =>
apt.id === selectedAppointment.id ? { ...apt, status: "cancelled" } : apt
)
);
setAppointments((prev) => prev.map((apt) => (apt.id === selectedAppointment.id ? { ...apt, status: "cancelled" } : apt)));
setCancelModal(false);
toast.success("Consulta cancelada com sucesso!");
@ -226,12 +234,7 @@ export default function PatientAppointments() {
<CalendarDays className="mr-2 h-4 w-4" />
Reagendar
</Button>
<Button
variant="outline"
size="sm"
className="text-red-600 hover:text-red-700 hover:bg-red-50"
onClick={() => handleCancel(appointment)}
>
<Button variant="outline" size="sm" className="text-red-600 hover:text-red-700 hover:bg-red-50" onClick={() => handleCancel(appointment)}>
<X className="mr-2 h-4 w-4" />
Cancelar
</Button>
@ -252,27 +255,17 @@ export default function PatientAppointments() {
<DialogHeader>
<DialogTitle>Reagendar Consulta</DialogTitle>
<DialogDescription>
Escolha uma nova data e horário para sua consulta com{" "}
<strong>{selectedAppointment?.doctor?.full_name}</strong>.
Escolha uma nova data e horário para sua consulta com <strong>{selectedAppointment?.doctor?.full_name}</strong>.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="date">Nova Data</Label>
<Input
id="date"
type="date"
value={rescheduleData.date}
onChange={(e) => setRescheduleData((prev) => ({ ...prev, date: e.target.value }))}
min={new Date().toISOString().split("T")[0]}
/>
<Input id="date" type="date" value={rescheduleData.date} onChange={(e) => setRescheduleData((prev) => ({ ...prev, date: e.target.value }))} min={new Date().toISOString().split("T")[0]} />
</div>
<div className="grid gap-2">
<Label htmlFor="time">Novo Horário</Label>
<Select
value={rescheduleData.time}
onValueChange={(value) => setRescheduleData((prev) => ({ ...prev, time: value }))}
>
<Select value={rescheduleData.time} onValueChange={(value) => setRescheduleData((prev) => ({ ...prev, time: value }))}>
<SelectTrigger>
<SelectValue placeholder="Selecione um horário" />
</SelectTrigger>
@ -287,12 +280,7 @@ export default function PatientAppointments() {
</div>
<div className="grid gap-2">
<Label htmlFor="reason">Motivo (opcional)</Label>
<Textarea
id="reason"
placeholder="Explique brevemente o motivo do reagendamento..."
value={rescheduleData.reason}
onChange={(e) => setRescheduleData((prev) => ({ ...prev, reason: e.target.value }))}
/>
<Textarea id="reason" placeholder="Explique brevemente o motivo do reagendamento..." value={rescheduleData.reason} onChange={(e) => setRescheduleData((prev) => ({ ...prev, reason: e.target.value }))} />
</div>
</div>
<DialogFooter>
@ -310,8 +298,7 @@ export default function PatientAppointments() {
<DialogHeader>
<DialogTitle>Cancelar Consulta</DialogTitle>
<DialogDescription>
Deseja realmente cancelar sua consulta com{" "}
<strong>{selectedAppointment?.doctor?.full_name}</strong>?
Deseja realmente cancelar sua consulta com <strong>{selectedAppointment?.doctor?.full_name}</strong>?
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
@ -319,13 +306,7 @@ export default function PatientAppointments() {
<Label htmlFor="cancel-reason" className="text-sm font-medium">
Motivo do Cancelamento <span className="text-red-500">*</span>
</Label>
<Textarea
id="cancel-reason"
placeholder="Informe o motivo do cancelamento (mínimo 10 caracteres)"
value={cancelReason}
onChange={(e) => setCancelReason(e.target.value)}
className="min-h-[100px]"
/>
<Textarea id="cancel-reason" placeholder="Informe o motivo do cancelamento (mínimo 10 caracteres)" value={cancelReason} onChange={(e) => setCancelReason(e.target.value)} className="min-h-[100px]" />
</div>
</div>
<DialogFooter>

View File

@ -1,91 +1,79 @@
"use client"
"use client";
import { useState, useEffect, useCallback } from "react"
import { Calendar, Clock, User } from "lucide-react"
import PatientLayout from "@/components/patient-layout"
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
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 { Textarea } from "@/components/ui/textarea"
import { doctorsService } from "services/doctorsApi.mjs"
import { useState, useEffect, useCallback } from "react";
import { Calendar, Clock, User } from "lucide-react";
import PatientLayout from "@/components/patient-layout";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
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 { Textarea } from "@/components/ui/textarea";
import { doctorsService } from "services/doctorsApi.mjs";
interface Doctor {
id: string
full_name: string
specialty: string
phone_mobile: string
id: string;
full_name: string;
specialty: string;
phone_mobile: string;
}
const APPOINTMENTS_STORAGE_KEY = "clinic-appointments"
const APPOINTMENTS_STORAGE_KEY = "clinic-appointments";
export default function ScheduleAppointment() {
const [selectedDoctor, setSelectedDoctor] = useState("")
const [selectedDate, setSelectedDate] = useState("")
const [selectedTime, setSelectedTime] = useState("")
const [notes, setNotes] = useState("")
const [selectedDoctor, setSelectedDoctor] = useState("");
const [selectedDate, setSelectedDate] = useState("");
const [selectedTime, setSelectedTime] = useState("");
const [notes, setNotes] = useState("");
// novos campos
const [tipoConsulta, setTipoConsulta] = useState("presencial")
const [duracao, setDuracao] = useState("30")
const [convenio, setConvenio] = useState("")
const [queixa, setQueixa] = useState("")
const [obsPaciente, setObsPaciente] = useState("")
const [obsInternas, setObsInternas] = useState("")
const [tipoConsulta, setTipoConsulta] = useState("presencial");
const [duracao, setDuracao] = useState("30");
const [convenio, setConvenio] = useState("");
const [queixa, setQueixa] = useState("");
const [obsPaciente, setObsPaciente] = useState("");
const [obsInternas, setObsInternas] = useState("");
const [doctors, setDoctors] = useState<Doctor[]>([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState<string | null>(null)
const [doctors, setDoctors] = useState<Doctor[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const fetchDoctors = useCallback(async () => {
setLoading(true)
setError(null)
setLoading(true);
setError(null);
try {
const data: Doctor[] = await doctorsService.list()
setDoctors(data || [])
const data: Doctor[] = await doctorsService.list();
console.log(data);
setDoctors(data || []);
} catch (e: any) {
console.error("Erro ao carregar lista de médicos:", e)
setError("Não foi possível carregar a lista de médicos. Verifique a conexão com a API.")
setDoctors([])
console.error("Erro ao carregar lista de médicos:", e);
setError("Não foi possível carregar a lista de médicos. Verifique a conexão com a API.");
setDoctors([]);
} finally {
setLoading(false)
setLoading(false);
}
}, [])
}, []);
useEffect(() => {
fetchDoctors()
}, [fetchDoctors])
fetchDoctors();
}, [fetchDoctors]);
const availableTimes = [
"08:00",
"08:30",
"09:00",
"09:30",
"10:00",
"10:30",
"14:00",
"14:30",
"15:00",
"15:30",
"16:00",
"16:30",
]
const availableTimes = ["08:00", "08:30", "09:00", "09:30", "10:00", "10:30", "14:00", "14:30", "15:00", "15:30", "16:00", "16:30"];
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
e.preventDefault();
const doctorDetails = doctors.find((d) => d.id === selectedDoctor)
const doctorDetails = doctors.find((d) => d.id === selectedDoctor);
const patientDetails = {
id: "P001",
full_name: "Paciente Exemplo Único",
location: "Clínica Geral",
phone: "(11) 98765-4321",
}
};
if (!patientDetails || !doctorDetails) {
alert("Erro: Selecione o médico ou dados do paciente indisponíveis.")
return
alert("Erro: Selecione o médico ou dados do paciente indisponíveis.");
return;
}
const newAppointment = {
@ -104,27 +92,27 @@ export default function ScheduleAppointment() {
notes,
status: "agendada",
phone: patientDetails.phone,
}
};
const storedAppointmentsRaw = localStorage.getItem(APPOINTMENTS_STORAGE_KEY)
const currentAppointments = storedAppointmentsRaw ? JSON.parse(storedAppointmentsRaw) : []
const updatedAppointments = [...currentAppointments, newAppointment]
localStorage.setItem(APPOINTMENTS_STORAGE_KEY, JSON.stringify(updatedAppointments))
const storedAppointmentsRaw = localStorage.getItem(APPOINTMENTS_STORAGE_KEY);
const currentAppointments = storedAppointmentsRaw ? JSON.parse(storedAppointmentsRaw) : [];
const updatedAppointments = [...currentAppointments, newAppointment];
localStorage.setItem(APPOINTMENTS_STORAGE_KEY, JSON.stringify(updatedAppointments));
alert(`Consulta com ${doctorDetails.full_name} agendada com sucesso!`)
alert(`Consulta com ${doctorDetails.full_name} agendada com sucesso!`);
// resetar campos
setSelectedDoctor("")
setSelectedDate("")
setSelectedTime("")
setNotes("")
setTipoConsulta("presencial")
setDuracao("30")
setConvenio("")
setQueixa("")
setObsPaciente("")
setObsInternas("")
}
setSelectedDoctor("");
setSelectedDate("");
setSelectedTime("");
setNotes("");
setTipoConsulta("presencial");
setDuracao("30");
setConvenio("");
setQueixa("");
setObsPaciente("");
setObsInternas("");
};
return (
<PatientLayout>
@ -143,8 +131,6 @@ export default function ScheduleAppointment() {
</CardHeader>
<CardContent>
<form onSubmit={handleSubmit} className="space-y-6">
{/* Médico */}
<div className="space-y-2">
<Label htmlFor="doctor">Médico</Label>
@ -176,13 +162,7 @@ export default function ScheduleAppointment() {
<div className="grid md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="date">Data</Label>
<Input
id="date"
type="date"
value={selectedDate}
onChange={(e) => setSelectedDate(e.target.value)}
min={new Date().toISOString().split("T")[0]}
/>
<Input id="date" type="date" value={selectedDate} onChange={(e) => setSelectedDate(e.target.value)} min={new Date().toISOString().split("T")[0]} />
</div>
<div className="space-y-2">
@ -218,79 +198,42 @@ export default function ScheduleAppointment() {
<div className="space-y-2">
<Label htmlFor="duracao">Duração (minutos)</Label>
<Input
id="duracao"
type="number"
min={10}
max={120}
value={duracao}
onChange={(e) => setDuracao(e.target.value)}
/>
<Input id="duracao" type="number" min={10} max={120} value={duracao} onChange={(e) => setDuracao(e.target.value)} />
</div>
</div>
{/* Convênio */}
<div className="space-y-2">
<Label htmlFor="convenio">Convênio (opcional)</Label>
<Input
id="convenio"
placeholder="Nome do convênio do paciente"
value={convenio}
onChange={(e) => setConvenio(e.target.value)}
/>
<Input id="convenio" placeholder="Nome do convênio do paciente" value={convenio} onChange={(e) => setConvenio(e.target.value)} />
</div>
{/* Queixa Principal */}
<div className="space-y-2">
<Label htmlFor="queixa">Queixa Principal (opcional)</Label>
<Textarea
id="queixa"
placeholder="Descreva brevemente o motivo da consulta..."
value={queixa}
onChange={(e) => setQueixa(e.target.value)}
/>
<Textarea id="queixa" placeholder="Descreva brevemente o motivo da consulta..." value={queixa} onChange={(e) => setQueixa(e.target.value)} />
</div>
{/* Observações do Paciente */}
<div className="space-y-2">
<Label htmlFor="obsPaciente">Observações do Paciente (opcional)</Label>
<Textarea
id="obsPaciente"
placeholder="Anotações relevantes informadas pelo paciente..."
value={obsPaciente}
onChange={(e) => setObsPaciente(e.target.value)}
/>
<Textarea id="obsPaciente" placeholder="Anotações relevantes informadas pelo paciente..." value={obsPaciente} onChange={(e) => setObsPaciente(e.target.value)} />
</div>
{/* Observações Internas */}
<div className="space-y-2">
<Label htmlFor="obsInternas">Observações Internas (opcional)</Label>
<Textarea
id="obsInternas"
placeholder="Anotações para a equipe da clínica..."
value={obsInternas}
onChange={(e) => setObsInternas(e.target.value)}
/>
<Textarea id="obsInternas" placeholder="Anotações para a equipe da clínica..." value={obsInternas} onChange={(e) => setObsInternas(e.target.value)} />
</div>
{/* Observações gerais */}
<div className="space-y-2">
<Label htmlFor="notes">Observações gerais (opcional)</Label>
<Textarea
id="notes"
placeholder="Descreva brevemente o motivo da consulta ou observações importantes"
value={notes}
onChange={(e) => setNotes(e.target.value)}
rows={3}
/>
<Textarea id="notes" placeholder="Descreva brevemente o motivo da consulta ou observações importantes" value={notes} onChange={(e) => setNotes(e.target.value)} rows={3} />
</div>
{/* Botão */}
<Button
type="submit"
className="w-full"
disabled={!selectedDoctor || !selectedDate || !selectedTime}
>
<Button type="submit" className="w-full" disabled={!selectedDoctor || !selectedDate || !selectedTime}>
Agendar Consulta
</Button>
</form>
@ -311,18 +254,14 @@ export default function ScheduleAppointment() {
{selectedDoctor && (
<div className="flex items-center space-x-2">
<User className="h-4 w-4 text-gray-500" />
<span className="text-sm">
{doctors.find((d) => d.id === selectedDoctor)?.full_name}
</span>
<span className="text-sm">{doctors.find((d) => d.id === selectedDoctor)?.full_name}</span>
</div>
)}
{selectedDate && (
<div className="flex items-center space-x-2">
<Calendar className="h-4 w-4 text-gray-500" />
<span className="text-sm">
{new Date(selectedDate).toLocaleDateString("pt-BR")}
</span>
<span className="text-sm">{new Date(selectedDate).toLocaleDateString("pt-BR")}</span>
</div>
)}
@ -350,5 +289,5 @@ export default function ScheduleAppointment() {
</div>
</div>
</PatientLayout>
)
);
}

View File

@ -1,9 +1,9 @@
// Caminho: components/LoginForm.tsx
"use client"
"use client";
import type React from "react"
import { useState } from "react"
import { useRouter } from "next/navigation"
import type React from "react";
import { useState } from "react";
import { useRouter } from "next/navigation";
// Nossos serviços de API centralizados e limpos
import { login, api } from "@/services/api.mjs";
@ -16,20 +16,20 @@ import { useToast } from "@/hooks/use-toast";
import { Eye, EyeOff, Mail, Lock, Loader2 } from "lucide-react";
interface LoginFormProps {
children?: React.ReactNode
children?: React.ReactNode;
}
interface FormState {
email: string
password: string
email: string;
password: string;
}
export function LoginForm({ children }: LoginFormProps) {
const [form, setForm] = useState<FormState>({ email: "", password: "" })
const [showPassword, setShowPassword] = useState(false)
const [isLoading, setIsLoading] = useState(false)
const router = useRouter()
const { toast } = useToast()
const [form, setForm] = useState<FormState>({ email: "", password: "" });
const [showPassword, setShowPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const router = useRouter();
const { toast } = useToast();
// --- NOVOS ESTADOS PARA CONTROLE DE MÚLTIPLOS PERFIS ---
const [userRoles, setUserRoles] = useState<string[]>([]);
@ -48,15 +48,25 @@ export function LoginForm({ children }: LoginFormProps) {
}
const completeUserInfo = { ...user, user_metadata: { ...user.user_metadata, role: selectedDashboardRole } };
localStorage.setItem('user_info', JSON.stringify(completeUserInfo));
localStorage.setItem("user_info", JSON.stringify(completeUserInfo));
let redirectPath = "";
switch (selectedDashboardRole) {
case "manager": redirectPath = "/manager/home"; break;
case "doctor": redirectPath = "/doctor/medicos"; break;
case "secretary": redirectPath = "/secretary/pacientes"; break;
case "patient": redirectPath = "/patient/dashboard"; break;
case "finance": redirectPath = "/finance/home"; break;
case "manager":
redirectPath = "/manager/home";
break;
case "doctor":
redirectPath = "/doctor/medicos";
break;
case "secretary":
redirectPath = "/secretary/pacientes";
break;
case "patient":
redirectPath = "/patient/dashboard";
break;
case "finance":
redirectPath = "/finance/home";
break;
}
if (redirectPath) {
@ -79,7 +89,7 @@ export function LoginForm({ children }: LoginFormProps) {
try {
// A chamada de login continua a mesma
const authData = await login();
const authData = await login(form.email, form.password);
const user = authData.user;
if (!user || !user.id) {
throw new Error("Resposta de autenticação inválida.");
@ -100,28 +110,28 @@ export function LoginForm({ children }: LoginFormProps) {
// --- AQUI COMEÇA A NOVA LÓGICA DE DECISÃO ---
// Caso 1: Usuário é ADMIN, mostra todos os dashboards possíveis.
if (rolesFromApi.includes('admin')) {
setUserRoles(["manager", "doctor", "secretary", "patient", "finance"]);
if (rolesFromApi.includes("admin")) {
setUserRoles(["manager", "doctor", "secretary", "paciente", "finance"]);
setIsLoading(false); // Para o loading para mostrar a tela de seleção
return;
}
// Mapeia os roles da API para os perfis de dashboard que o usuário pode acessar
const displayRoles = new Set<string>();
rolesFromApi.forEach(role => {
rolesFromApi.forEach((role) => {
switch (role) {
case 'gestor':
displayRoles.add('manager');
displayRoles.add('finance');
case "gestor":
displayRoles.add("manager");
displayRoles.add("finance");
break;
case 'medico':
displayRoles.add('doctor');
case "medico":
displayRoles.add("doctor");
break;
case 'secretaria':
displayRoles.add('secretary');
case "secretaria":
displayRoles.add("secretary");
break;
case 'patient': // Mapeamento de 'patient' (ou outro nome que você use para paciente)
displayRoles.add('patient');
case "paciente": // Mapeamento de 'patient' (ou outro nome que você use para paciente)
displayRoles.add("patient");
break;
}
});
@ -135,8 +145,8 @@ export function LoginForm({ children }: LoginFormProps) {
// Caso 3: Se tem múltiplos perfis (ex: 'gestor'), mostra a tela de seleção.
else {
setUserRoles(finalRoles);
setIsLoading(false);
}
} catch (error) {
localStorage.removeItem("token");
localStorage.removeItem("user_info");
@ -146,12 +156,9 @@ export function LoginForm({ children }: LoginFormProps) {
description: error instanceof Error ? error.message : "Ocorreu um erro inesperado.",
variant: "destructive",
});
} finally {
// Apenas para o loading se não houver redirecionamento ou seleção de perfil
if (userRoles.length === 0) {
}
setIsLoading(false);
}
}
};
// --- JSX ATUALIZADO COM RENDERIZAÇÃO CONDICIONAL ---
@ -165,22 +172,14 @@ export function LoginForm({ children }: LoginFormProps) {
<Label htmlFor="email">E-mail</Label>
<div className="relative">
<Mail className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-5 h-5" />
<Input
id="email" type="email" placeholder="seu.email@exemplo.com"
value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })}
className="pl-10 h-11" required disabled={isLoading} autoComplete="username"
/>
<Input id="email" type="email" placeholder="seu.email@exemplo.com" value={form.email} onChange={(e) => setForm({ ...form, email: e.target.value })} className="pl-10 h-11" required disabled={isLoading} autoComplete="username" />
</div>
</div>
<div className="space-y-2">
<Label htmlFor="password">Senha</Label>
<div className="relative">
<Lock className="absolute left-3 top-1/2 -translate-y-1/2 text-muted-foreground w-5 h-5" />
<Input
id="password" type={showPassword ? "text" : "password"} placeholder="Digite sua senha"
value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })}
className="pl-10 pr-12 h-11" required disabled={isLoading} autoComplete="current-password"
/>
<Input id="password" type={showPassword ? "text" : "password"} placeholder="Digite sua senha" value={form.password} onChange={(e) => setForm({ ...form, password: e.target.value })} className="pl-10 pr-12 h-11" required disabled={isLoading} autoComplete="current-password" />
<button type="button" onClick={() => setShowPassword(!showPassword)} className="absolute right-2 top-1/2 -translate-y-1/2 h-8 w-8 p-0 text-muted-foreground hover:text-foreground" disabled={isLoading}>
{showPassword ? <EyeOff className="w-5 h-5" /> : <Eye className="w-5 h-5" />}
</button>
@ -197,12 +196,7 @@ export function LoginForm({ children }: LoginFormProps) {
<p className="text-sm text-muted-foreground text-center">Selecione com qual perfil deseja entrar:</p>
<div className="flex flex-col space-y-3 pt-2">
{userRoles.map((role) => (
<Button
key={role}
variant="outline"
className="h-11 text-base"
onClick={() => handleRoleSelection(role)}
>
<Button key={role} variant="outline" className="h-11 text-base" onClick={() => handleRoleSelection(role)}>
Entrar como: {role.charAt(0).toUpperCase() + role.slice(1)}
</Button>
))}
@ -213,5 +207,5 @@ export function LoginForm({ children }: LoginFormProps) {
{children}
</CardContent>
</Card>
)
);
}

View File

@ -1,129 +1,52 @@
'use client'
"use client";
import * as React from 'react'
import * as ToastPrimitives from '@radix-ui/react-toast'
import { cva, type VariantProps } from 'class-variance-authority'
import { X } from 'lucide-react'
import * as React from "react";
import * as ToastPrimitives from "@radix-ui/react-toast";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";
import { cn } from '@/lib/utils'
import { cn } from "@/lib/utils";
const ToastProvider = ToastPrimitives.Provider
const ToastProvider = ToastPrimitives.Provider;
const ToastViewport = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Viewport>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Viewport
ref={ref}
className={cn(
'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
className,
)}
{...props}
/>
))
ToastViewport.displayName = ToastPrimitives.Viewport.displayName
const ToastViewport = React.forwardRef<React.ElementRef<typeof ToastPrimitives.Viewport>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>>(({ className, ...props }, ref) => <ToastPrimitives.Viewport ref={ref} className={cn("fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]", className)} {...props} />);
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
const toastVariants = cva(
'group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
{
const toastVariants = cva("group pointer-events-auto relative flex w-full items-center justify-between space-x-4 overflow-hidden rounded-md border p-6 pr-8 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full", {
variants: {
variant: {
default: 'border bg-background text-foreground',
destructive:
'destructive group border-destructive bg-destructive text-destructive-foreground',
default: "border bg-background text-foreground",
destructive: "destructive group border-destructive bg-destructive text-foreground",
},
},
defaultVariants: {
variant: 'default',
variant: "default",
},
},
)
});
const Toast = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Root>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> &
VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const Toast = React.forwardRef<React.ElementRef<typeof ToastPrimitives.Root>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>>(({ className, variant, ...props }, ref) => {
return <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} />;
});
Toast.displayName = ToastPrimitives.Root.displayName;
const ToastAction = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Action>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Action
ref={ref}
className={cn(
'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
className,
)}
{...props}
/>
))
ToastAction.displayName = ToastPrimitives.Action.displayName
const ToastAction = React.forwardRef<React.ElementRef<typeof ToastPrimitives.Action>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>>(({ className, ...props }, ref) => <ToastPrimitives.Action ref={ref} className={cn("inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium ring-offset-background transition-colors hover:bg-secondary focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive", className)} {...props} />);
ToastAction.displayName = ToastPrimitives.Action.displayName;
const ToastClose = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Close>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Close
ref={ref}
className={cn(
'absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
className,
)}
toast-close=""
{...props}
>
const ToastClose = React.forwardRef<React.ElementRef<typeof ToastPrimitives.Close>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>>(({ className, ...props }, ref) => (
<ToastPrimitives.Close ref={ref} className={cn("absolute right-2 top-2 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-2 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600", className)} toast-close="" {...props}>
<X className="h-4 w-4" />
</ToastPrimitives.Close>
))
ToastClose.displayName = ToastPrimitives.Close.displayName
));
ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Title>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Title
ref={ref}
className={cn('text-sm font-semibold', className)}
{...props}
/>
))
ToastTitle.displayName = ToastPrimitives.Title.displayName
const ToastTitle = React.forwardRef<React.ElementRef<typeof ToastPrimitives.Title>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>>(({ className, ...props }, ref) => <ToastPrimitives.Title ref={ref} className={cn("text-sm font-semibold", className)} {...props} />);
ToastTitle.displayName = ToastPrimitives.Title.displayName;
const ToastDescription = React.forwardRef<
React.ElementRef<typeof ToastPrimitives.Description>,
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
<ToastPrimitives.Description
ref={ref}
className={cn('text-sm opacity-90', className)}
{...props}
/>
))
ToastDescription.displayName = ToastPrimitives.Description.displayName
const ToastDescription = React.forwardRef<React.ElementRef<typeof ToastPrimitives.Description>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>>(({ className, ...props }, ref) => <ToastPrimitives.Description ref={ref} className={cn("text-sm opacity-90", className)} {...props} />);
ToastDescription.displayName = ToastPrimitives.Description.displayName;
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
type ToastActionElement = React.ReactElement<typeof ToastAction>
type ToastActionElement = React.ReactElement<typeof ToastAction>;
export {
type ToastProps,
type ToastActionElement,
ToastProvider,
ToastViewport,
Toast,
ToastTitle,
ToastDescription,
ToastClose,
ToastAction,
}
export { type ToastProps, type ToastActionElement, ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction };

View File

@ -9,7 +9,7 @@ const API_KEY = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
* Função de login que o seu formulário usa.
* Ela continua exatamente como era.
*/
export async function login() {
export async function login(email, senha) {
console.log("🔐 Iniciando login...");
const res = await fetch(`${BASE_URL}/auth/v1/token?grant_type=password`, {
method: "POST",
@ -19,8 +19,8 @@ export async function login() {
Prefer: "return=representation",
},
body: JSON.stringify({
email: "riseup@popcode.com.br",
password: "riseup",
email: email,
password: senha,
}),
});
@ -51,8 +51,8 @@ async function logout() {
await fetch(`${BASE_URL}/auth/v1/logout`, {
method: "POST",
headers: {
"apikey": API_KEY,
"Authorization": `Bearer ${token}`,
apikey: API_KEY,
Authorization: `Bearer ${token}`,
},
});
} catch (error) {
@ -68,12 +68,12 @@ async function logout() {
* Agora com a correção para respostas vazias.
*/
async function request(endpoint, options = {}) {
const token = typeof window !== 'undefined' ? localStorage.getItem("token") : null;
const token = typeof window !== "undefined" ? localStorage.getItem("token") : null;
const headers = {
"Content-Type": "application/json",
"apikey": API_KEY,
...(token && { "Authorization": `Bearer ${token}` }),
apikey: API_KEY,
...(token && { Authorization: `Bearer ${token}` }),
...options.headers,
};
@ -97,7 +97,7 @@ async function request(endpoint, options = {}) {
// Exportamos o objeto 'api' com os métodos que os componentes vão usar.
export const api = {
// --- CORREÇÃO 2: PARA CARREGAR O ID DO USUÁRIO ---
getSession: () => request('/auth/v1/user'),
getSession: () => request("/auth/v1/user"),
get: (endpoint, options) => request(endpoint, { method: "GET", ...options }),
post: (endpoint, data, options) => request(endpoint, { method: "POST", body: JSON.stringify(data), ...options }),