forked from RiseUP/riseup-squad21
correção de erros
This commit is contained in:
parent
2a015a7f63
commit
a48ba7af2b
@ -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,
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@ -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>
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -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 };
|
||||
|
||||
@ -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 }),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user