correção de erros #30

Merged
LiraS2 merged 1 commits from StsDanilo/riseup-squad21:main into main 2025-10-30 22:13:57 +00:00
7 changed files with 1003 additions and 1225 deletions
Showing only changes of commit a48ba7af2b - Show all commits

View File

@ -8,15 +8,45 @@ import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import DoctorLayout from "@/components/doctor-layout"; import DoctorLayout from "@/components/doctor-layout";
import { AvailabilityService } from "@/services/availabilityApi.mjs"; import { AvailabilityService } from "@/services/availabilityApi.mjs";
import { usersService } from "@/services/usersApi.mjs";
import { toast } from "@/hooks/use-toast"; import { toast } from "@/hooks/use-toast";
import { useRouter } from "next/navigation"; 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() { export default function AvailabilityPage() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const router = useRouter(); const router = useRouter();
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
const userInfo = JSON.parse(localStorage.getItem("user_info") || "{}"); const [userData, setUserData] = useState<UserData>();
const doctorIdTemp = "3bb9ee4a-cfdd-4d81-b628-383907dfa225";
const [modalidadeConsulta, setModalidadeConsulta] = useState<string>(""); const [modalidadeConsulta, setModalidadeConsulta] = useState<string>("");
useEffect(() => { useEffect(() => {
@ -24,6 +54,9 @@ export default function AvailabilityPage() {
try { try {
const response = await AvailabilityService.list(); const response = await AvailabilityService.list();
console.log(response); console.log(response);
const user = await usersService.getMe();
console.log(user);
setUserData(user);
} catch (e: any) { } catch (e: any) {
alert(`${e?.error} ${e?.message}`); alert(`${e?.error} ${e?.message}`);
} }
@ -40,8 +73,7 @@ export default function AvailabilityPage() {
const formData = new FormData(form); const formData = new FormData(form);
const apiPayload = { const apiPayload = {
doctor_id: doctorIdTemp, doctor_id: userData?.user.id,
created_by: doctorIdTemp,
weekday: (formData.get("weekday") as string) || undefined, weekday: (formData.get("weekday") as string) || undefined,
start_time: (formData.get("horarioEntrada") as string) || undefined, start_time: (formData.get("horarioEntrada") as string) || undefined,
end_time: (formData.get("horarioSaida") 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 { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
Select, import { Save, Loader2, Pause } from "lucide-react";
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Save, Loader2 } from "lucide-react";
import ManagerLayout from "@/components/manager-layout"; import ManagerLayout from "@/components/manager-layout";
import { usersService } from "services/usersApi.mjs"; import { usersService } from "services/usersApi.mjs";
import { login } from "services/api.mjs"; import { login } from "services/api.mjs";
@ -25,7 +19,7 @@ interface UserFormData {
papel: string; papel: string;
senha: string; senha: string;
confirmarSenha: string; confirmarSenha: string;
cpf : string cpf: string;
} }
const defaultFormData: UserFormData = { const defaultFormData: UserFormData = {
@ -35,16 +29,14 @@ const defaultFormData: UserFormData = {
papel: "", papel: "",
senha: "", senha: "",
confirmarSenha: "", confirmarSenha: "",
cpf : "" cpf: "",
}; };
const cleanNumber = (value: string): string => value.replace(/\D/g, ""); const cleanNumber = (value: string): string => value.replace(/\D/g, "");
const formatPhone = (value: string): string => { const formatPhone = (value: string): string => {
const cleaned = cleanNumber(value).substring(0, 11); const cleaned = cleanNumber(value).substring(0, 11);
if (cleaned.length === 11) if (cleaned.length === 11) return cleaned.replace(/(\d{2})(\d{5})(\d{4})/, "($1) $2-$3");
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 === 10)
return cleaned.replace(/(\d{2})(\d{4})(\d{4})/, "($1) $2-$3");
return cleaned; return cleaned;
}; };
@ -63,13 +55,7 @@ export default function NovoUsuarioPage() {
e.preventDefault(); e.preventDefault();
setError(null); setError(null);
if ( if (!formData.email || !formData.nomeCompleto || !formData.papel || !formData.senha || !formData.confirmarSenha) {
!formData.email ||
!formData.nomeCompleto ||
!formData.papel ||
!formData.senha ||
!formData.confirmarSenha
) {
setError("Por favor, preencha todos os campos obrigatórios."); setError("Por favor, preencha todos os campos obrigatórios.");
return; return;
} }
@ -82,28 +68,24 @@ export default function NovoUsuarioPage() {
setIsSaving(true); setIsSaving(true);
try { try {
await login();
const payload = { const payload = {
full_name: formData.nomeCompleto, full_name: formData.nomeCompleto,
email: formData.email.trim().toLowerCase(), email: formData.email.trim().toLowerCase(),
phone: formData.telefone || null, phone: formData.telefone || null,
role: formData.papel, role: formData.papel,
password: formData.senha, 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); await usersService.create_user(payload);
router.push("/manager/usuario"); router.push("/manager/usuario");
} catch (e: any) { } catch (e: any) {
console.error("Erro ao criar usuário:", e); console.error("Erro ao criar usuário:", e);
setError( setError(e?.message || "Não foi possível criar o usuário. Verifique os dados e tente novamente.");
e?.message ||
"Não foi possível criar o usuário. Verifique os dados e tente novamente."
);
} finally { } finally {
setIsSaving(false); setIsSaving(false);
} }
@ -115,22 +97,15 @@ export default function NovoUsuarioPage() {
<div className="w-full max-w-screen-lg space-y-8"> <div className="w-full max-w-screen-lg space-y-8">
<div className="flex items-center justify-between border-b pb-4"> <div className="flex items-center justify-between border-b pb-4">
<div> <div>
<h1 className="text-3xl font-extrabold text-gray-900"> <h1 className="text-3xl font-extrabold text-gray-900">Novo Usuário</h1>
Novo Usuário <p className="text-md text-gray-500">Preencha os dados para cadastrar um novo usuário no sistema.</p>
</h1>
<p className="text-md text-gray-500">
Preencha os dados para cadastrar um novo usuário no sistema.
</p>
</div> </div>
<Link href="/manager/usuario"> <Link href="/manager/usuario">
<Button variant="outline">Cancelar</Button> <Button variant="outline">Cancelar</Button>
</Link> </Link>
</div> </div>
<form <form onSubmit={handleSubmit} className="space-y-6 bg-white p-6 md:p-10 border rounded-xl shadow-lg">
onSubmit={handleSubmit}
className="space-y-6 bg-white p-6 md:p-10 border rounded-xl shadow-lg"
>
{error && ( {error && (
<div className="p-4 bg-red-50 text-red-700 rounded-lg border border-red-300"> <div className="p-4 bg-red-50 text-red-700 rounded-lg border border-red-300">
<p className="font-semibold">Erro no Cadastro:</p> <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="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2 md:col-span-2"> <div className="space-y-2 md:col-span-2">
<Label htmlFor="nomeCompleto">Nome Completo *</Label> <Label htmlFor="nomeCompleto">Nome Completo *</Label>
<Input <Input id="nomeCompleto" value={formData.nomeCompleto} onChange={(e) => handleInputChange("nomeCompleto", e.target.value)} placeholder="Nome e Sobrenome" required />
id="nomeCompleto"
value={formData.nomeCompleto}
onChange={(e) =>
handleInputChange("nomeCompleto", e.target.value)
}
placeholder="Nome e Sobrenome"
required
/>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="email">E-mail *</Label> <Label htmlFor="email">E-mail *</Label>
<Input <Input id="email" type="email" value={formData.email} onChange={(e) => handleInputChange("email", e.target.value)} placeholder="exemplo@dominio.com" required />
id="email"
type="email"
value={formData.email}
onChange={(e) => handleInputChange("email", e.target.value)}
placeholder="exemplo@dominio.com"
required
/>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="papel">Papel (Função) *</Label> <Label htmlFor="papel">Papel (Função) *</Label>
<Select <Select value={formData.papel} onValueChange={(v) => handleInputChange("papel", v)} required>
value={formData.papel}
onValueChange={(v) => handleInputChange("papel", v)}
required
>
<SelectTrigger id="papel"> <SelectTrigger id="papel">
<SelectValue placeholder="Selecione uma função" /> <SelectValue placeholder="Selecione uma função" />
</SelectTrigger> </SelectTrigger>
@ -179,88 +135,41 @@ export default function NovoUsuarioPage() {
<SelectItem value="gestor">Gestor</SelectItem> <SelectItem value="gestor">Gestor</SelectItem>
<SelectItem value="medico">Médico</SelectItem> <SelectItem value="medico">Médico</SelectItem>
<SelectItem value="secretaria">Secretária</SelectItem> <SelectItem value="secretaria">Secretária</SelectItem>
<SelectItem value="user">Usuário</SelectItem> <SelectItem value="paciente">Usuário</SelectItem>
</SelectContent> </SelectContent>
</Select> </Select>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="senha">Senha *</Label> <Label htmlFor="senha">Senha *</Label>
<Input <Input id="senha" type="password" value={formData.senha} onChange={(e) => handleInputChange("senha", e.target.value)} placeholder="Mínimo 8 caracteres" minLength={8} required />
id="senha"
type="password"
value={formData.senha}
onChange={(e) => handleInputChange("senha", e.target.value)}
placeholder="Mínimo 8 caracteres"
minLength={8}
required
/>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="confirmarSenha">Confirmar Senha *</Label> <Label htmlFor="confirmarSenha">Confirmar Senha *</Label>
<Input <Input id="confirmarSenha" type="password" value={formData.confirmarSenha} onChange={(e) => handleInputChange("confirmarSenha", e.target.value)} placeholder="Repita a senha" required />
id="confirmarSenha" {formData.senha && formData.confirmarSenha && formData.senha !== formData.confirmarSenha && <p className="text-xs text-red-500">As senhas não coincidem.</p>}
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>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="telefone">Telefone</Label> <Label htmlFor="telefone">Telefone</Label>
<Input <Input id="telefone" value={formData.telefone} onChange={(e) => handleInputChange("telefone", e.target.value)} placeholder="(00) 00000-0000" maxLength={15} />
id="telefone"
value={formData.telefone}
onChange={(e) =>
handleInputChange("telefone", e.target.value)
}
placeholder="(00) 00000-0000"
maxLength={15}
/>
</div> </div>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="cpf">Cpf *</Label> <Label htmlFor="cpf">Cpf *</Label>
<Input <Input id="cpf" type="cpf" value={formData.cpf} onChange={(e) => handleInputChange("cpf", e.target.value)} placeholder="xxx.xxx.xxx-xx" required />
id="cpf"
type="cpf"
value={formData.cpf}
onChange={(e) => handleInputChange("cpf", e.target.value)}
placeholder="xxx.xxx.xxx-xx"
required
/>
</div> </div>
<div className="flex justify-end gap-4 pt-6 border-t mt-6"> <div className="flex justify-end gap-4 pt-6 border-t mt-6">
<Link href="/manager/usuario"> <Link href="/manager/usuario">
<Button type="button" variant="outline" disabled={isSaving}> <Button type="button" variant="outline" disabled={isSaving}>
Cancelar Cancelar
</Button> </Button>
</Link> </Link>
<Button <Button type="submit" className="bg-green-600 hover:bg-green-700" disabled={isSaving}>
type="submit" {isSaving ? <Loader2 className="w-4 h-4 mr-2 animate-spin" /> : <Save className="w-4 h-4 mr-2" />}
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"} {isSaving ? "Salvando..." : "Salvar Usuário"}
</Button> </Button>
</div> </div>

View File

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

View File

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

View File

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

View File

@ -1,129 +1,52 @@
'use client' "use client";
import * as React from 'react' import * as React from "react";
import * as ToastPrimitives from '@radix-ui/react-toast' import * as ToastPrimitives from "@radix-ui/react-toast";
import { cva, type VariantProps } from 'class-variance-authority' import { cva, type VariantProps } from "class-variance-authority";
import { X } from 'lucide-react' 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< 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} />);
React.ElementRef<typeof ToastPrimitives.Viewport>, ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
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( 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", {
'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: { variants: {
variant: { variant: {
default: 'border bg-background text-foreground', default: "border bg-background text-foreground",
destructive: destructive: "destructive group border-destructive bg-destructive text-foreground",
'destructive group border-destructive bg-destructive text-destructive-foreground',
}, },
}, },
defaultVariants: { defaultVariants: {
variant: 'default', variant: "default",
}, },
}, });
)
const Toast = React.forwardRef< const Toast = React.forwardRef<React.ElementRef<typeof ToastPrimitives.Root>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>>(({ className, variant, ...props }, ref) => {
React.ElementRef<typeof ToastPrimitives.Root>, return <ToastPrimitives.Root ref={ref} className={cn(toastVariants({ variant }), className)} {...props} />;
React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & });
VariantProps<typeof toastVariants> Toast.displayName = ToastPrimitives.Root.displayName;
>(({ className, variant, ...props }, ref) => {
return (
<ToastPrimitives.Root
ref={ref}
className={cn(toastVariants({ variant }), className)}
{...props}
/>
)
})
Toast.displayName = ToastPrimitives.Root.displayName
const ToastAction = React.forwardRef< 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} />);
React.ElementRef<typeof ToastPrimitives.Action>, ToastAction.displayName = ToastPrimitives.Action.displayName;
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< const ToastClose = React.forwardRef<React.ElementRef<typeof ToastPrimitives.Close>, React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>>(({ className, ...props }, ref) => (
React.ElementRef<typeof ToastPrimitives.Close>, <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}>
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" /> <X className="h-4 w-4" />
</ToastPrimitives.Close> </ToastPrimitives.Close>
)) ));
ToastClose.displayName = ToastPrimitives.Close.displayName ToastClose.displayName = ToastPrimitives.Close.displayName;
const ToastTitle = React.forwardRef< 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} />);
React.ElementRef<typeof ToastPrimitives.Title>, ToastTitle.displayName = ToastPrimitives.Title.displayName;
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< 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} />);
React.ElementRef<typeof ToastPrimitives.Description>, ToastDescription.displayName = ToastPrimitives.Description.displayName;
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 { export { type ToastProps, type ToastActionElement, ToastProvider, ToastViewport, Toast, ToastTitle, ToastDescription, ToastClose, ToastAction };
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. * Função de login que o seu formulário usa.
* Ela continua exatamente como era. * Ela continua exatamente como era.
*/ */
export async function login() { export async function login(email, senha) {
console.log("🔐 Iniciando login..."); console.log("🔐 Iniciando login...");
const res = await fetch(`${BASE_URL}/auth/v1/token?grant_type=password`, { const res = await fetch(`${BASE_URL}/auth/v1/token?grant_type=password`, {
method: "POST", method: "POST",
@ -19,8 +19,8 @@ export async function login() {
Prefer: "return=representation", Prefer: "return=representation",
}, },
body: JSON.stringify({ body: JSON.stringify({
email: "riseup@popcode.com.br", email: email,
password: "riseup", password: senha,
}), }),
}); });
@ -51,8 +51,8 @@ async function logout() {
await fetch(`${BASE_URL}/auth/v1/logout`, { await fetch(`${BASE_URL}/auth/v1/logout`, {
method: "POST", method: "POST",
headers: { headers: {
"apikey": API_KEY, apikey: API_KEY,
"Authorization": `Bearer ${token}`, Authorization: `Bearer ${token}`,
}, },
}); });
} catch (error) { } catch (error) {
@ -68,12 +68,12 @@ async function logout() {
* Agora com a correção para respostas vazias. * Agora com a correção para respostas vazias.
*/ */
async function request(endpoint, options = {}) { 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 = { const headers = {
"Content-Type": "application/json", "Content-Type": "application/json",
"apikey": API_KEY, apikey: API_KEY,
...(token && { "Authorization": `Bearer ${token}` }), ...(token && { Authorization: `Bearer ${token}` }),
...options.headers, ...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. // Exportamos o objeto 'api' com os métodos que os componentes vão usar.
export const api = { export const api = {
// --- CORREÇÃO 2: PARA CARREGAR O ID DO USUÁRIO --- // --- 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 }), get: (endpoint, options) => request(endpoint, { method: "GET", ...options }),
post: (endpoint, data, options) => request(endpoint, { method: "POST", body: JSON.stringify(data), ...options }), post: (endpoint, data, options) => request(endpoint, { method: "POST", body: JSON.stringify(data), ...options }),