commit
48b0c409ea
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
import type React from "react";
|
import type React from "react";
|
||||||
import { useState, useEffect, useMemo } from "react";
|
import { useState, useEffect, useMemo } from "react";
|
||||||
import DoctorLayout from "@/components/doctor-layout";
|
|
||||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
||||||
import { appointmentsService } from "@/services/appointmentsApi.mjs";
|
import { appointmentsService } from "@/services/appointmentsApi.mjs";
|
||||||
import { patientsService } from "@/services/patientsApi.mjs";
|
import { patientsService } from "@/services/patientsApi.mjs";
|
||||||
@ -19,6 +18,7 @@ import { Clock, Calendar as CalendarIcon, User, X, RefreshCw, Loader2, MapPin, P
|
|||||||
import { format, isFuture, parseISO, isValid, isToday, isTomorrow } from "date-fns";
|
import { format, isFuture, parseISO, isValid, isToday, isTomorrow } from "date-fns";
|
||||||
import { ptBR } from "date-fns/locale";
|
import { ptBR } from "date-fns/locale";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
// Interfaces (sem alteração)
|
// Interfaces (sem alteração)
|
||||||
interface EnrichedAppointment {
|
interface EnrichedAppointment {
|
||||||
@ -129,11 +129,11 @@ export default function DoctorAppointmentsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isAuthLoading) {
|
if (isAuthLoading) {
|
||||||
return <DoctorLayout><div>Carregando...</div></DoctorLayout>;
|
return <Sidebar><div>Carregando...</div></Sidebar>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-foreground">Agenda Médica</h1>
|
<h1 className="text-3xl font-bold text-foreground">Agenda Médica</h1>
|
||||||
@ -224,6 +224,6 @@ export default function DoctorAppointmentsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import DoctorLayout from "@/components/doctor-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 { Calendar, Clock, User, Trash2 } from "lucide-react";
|
import { Calendar, Clock, User, Trash2 } from "lucide-react";
|
||||||
@ -14,6 +13,7 @@ import { AvailabilityService } from "@/services/availabilityApi.mjs";
|
|||||||
import { exceptionsService } from "@/services/exceptionApi.mjs";
|
import { exceptionsService } from "@/services/exceptionApi.mjs";
|
||||||
import { doctorsService } from "@/services/doctorsApi.mjs";
|
import { doctorsService } from "@/services/doctorsApi.mjs";
|
||||||
import { usersService } from "@/services/usersApi.mjs";
|
import { usersService } from "@/services/usersApi.mjs";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
type Availability = {
|
type Availability = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -231,7 +231,7 @@ export default function PatientDashboard() {
|
|||||||
}, [availability]);
|
}, [availability]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Dashboard</h1>
|
<h1 className="text-3xl font-bold text-gray-900">Dashboard</h1>
|
||||||
@ -409,6 +409,6 @@ export default function PatientDashboard() {
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,14 +3,12 @@
|
|||||||
import type React from "react";
|
import type React from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import DoctorLayout from "@/components/doctor-layout";
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, 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 { Clock, Calendar as CalendarIcon, MapPin, Phone, User, X, RefreshCw } from "lucide-react";
|
import { Calendar as CalendarIcon, RefreshCw } from "lucide-react";
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { toast } from "@/hooks/use-toast";
|
import { toast } from "@/hooks/use-toast";
|
||||||
import { exceptionsService } from "@/services/exceptionApi.mjs";
|
import { exceptionsService } from "@/services/exceptionApi.mjs";
|
||||||
@ -19,6 +17,7 @@ import { exceptionsService } from "@/services/exceptionApi.mjs";
|
|||||||
import { Calendar } from "@/components/ui/calendar";
|
import { Calendar } from "@/components/ui/calendar";
|
||||||
import { format } from "date-fns"; // Usaremos o date-fns para formatação e comparação de datas
|
import { format } from "date-fns"; // Usaremos o date-fns para formatação e comparação de datas
|
||||||
import { doctorsService } from "@/services/doctorsApi.mjs";
|
import { doctorsService } from "@/services/doctorsApi.mjs";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
type Doctor = {
|
type Doctor = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -147,7 +146,7 @@ export default function ExceptionPage() {
|
|||||||
const displayDate = selectedCalendarDate ? new Date(selectedCalendarDate).toLocaleDateString("pt-BR", { weekday: "long", day: "2-digit", month: "long" }) : "Selecione uma data";
|
const displayDate = selectedCalendarDate ? new Date(selectedCalendarDate).toLocaleDateString("pt-BR", { weekday: "long", day: "2-digit", month: "long" }) : "Selecione uma data";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Adicione exceções</h1>
|
<h1 className="text-3xl font-bold text-gray-900">Adicione exceções</h1>
|
||||||
@ -254,6 +253,6 @@ export default function ExceptionPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,7 +7,6 @@ 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 DoctorLayout from "@/components/doctor-layout";
|
|
||||||
|
|
||||||
import { AvailabilityService } from "@/services/availabilityApi.mjs";
|
import { AvailabilityService } from "@/services/availabilityApi.mjs";
|
||||||
import { usersService } from "@/services/usersApi.mjs";
|
import { usersService } from "@/services/usersApi.mjs";
|
||||||
@ -17,9 +16,10 @@ import { toast } from "@/hooks/use-toast";
|
|||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
|
import { Card, CardHeader, CardTitle, CardDescription, CardContent } from "@/components/ui/card";
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||||
import { Eye, Edit, Calendar, Trash2 } from "lucide-react";
|
import { Edit, Trash2 } from "lucide-react";
|
||||||
import { AvailabilityEditModal } from "@/components/ui/availability-edit-modal";
|
import { AvailabilityEditModal } from "@/components/ui/availability-edit-modal";
|
||||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
// ... (Interfaces de tipo omitidas para brevidade, pois não foram alteradas)
|
// ... (Interfaces de tipo omitidas para brevidade, pois não foram alteradas)
|
||||||
|
|
||||||
@ -323,7 +323,7 @@ export default function AvailabilityPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@ -506,6 +506,6 @@ export default function AvailabilityPage() {
|
|||||||
onSubmit={handleEdit}
|
onSubmit={handleEdit}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -12,7 +12,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||||||
import { Checkbox } from "@/components/ui/checkbox";
|
import { Checkbox } from "@/components/ui/checkbox";
|
||||||
import { ArrowLeft, Save } from "lucide-react";
|
import { ArrowLeft, Save } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import DoctorLayout from "@/components/doctor-layout";
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
// Mock data - in a real app, this would come from an API
|
// Mock data - in a real app, this would come from an API
|
||||||
const mockDoctors = [
|
const mockDoctors = [
|
||||||
@ -124,7 +124,7 @@ export default function EditarMedicoPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Link href="/medicos">
|
<Link href="/medicos">
|
||||||
@ -512,6 +512,6 @@ export default function EditarMedicoPage() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import { useParams, useRouter } from "next/navigation";
|
import { useParams, useRouter } from "next/navigation";
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import DoctorLayout from "@/components/doctor-layout";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
@ -17,6 +16,7 @@ import { format } from "date-fns";
|
|||||||
import TiptapEditor from "@/components/ui/tiptap-editor";
|
import TiptapEditor from "@/components/ui/tiptap-editor";
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
import { Skeleton } from "@/components/ui/skeleton";
|
||||||
import { reportsApi } from "@/services/reportsApi.mjs";
|
import { reportsApi } from "@/services/reportsApi.mjs";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
export default function EditarLaudoPage() {
|
export default function EditarLaudoPage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -108,7 +108,7 @@ export default function EditarLaudoPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="container mx-auto p-4">
|
<div className="container mx-auto p-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@ -130,12 +130,12 @@ export default function EditarLaudoPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="container mx-auto p-4">
|
<div className="container mx-auto p-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@ -232,6 +232,6 @@ export default function EditarLaudoPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -17,7 +17,7 @@ import { format } from "date-fns";
|
|||||||
import TiptapEditor from "@/components/ui/tiptap-editor";
|
import TiptapEditor from "@/components/ui/tiptap-editor";
|
||||||
|
|
||||||
import { reportsApi } from "@/services/reportsApi.mjs";
|
import { reportsApi } from "@/services/reportsApi.mjs";
|
||||||
import DoctorLayout from "@/components/doctor-layout";
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -97,7 +97,7 @@ export default function NovoLaudoPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="container mx-auto p-4">
|
<div className="container mx-auto p-4">
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
@ -189,6 +189,6 @@ export default function NovoLaudoPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -8,7 +8,7 @@ import Link from 'next/link';
|
|||||||
import { useParams } from 'next/navigation';
|
import { useParams } from 'next/navigation';
|
||||||
import { api } from '@/services/api.mjs';
|
import { api } from '@/services/api.mjs';
|
||||||
import { reportsApi } from '@/services/reportsApi.mjs';
|
import { reportsApi } from '@/services/reportsApi.mjs';
|
||||||
import DoctorLayout from '@/components/doctor-layout';
|
import Sidebar from '@/components/Sidebar';
|
||||||
|
|
||||||
export default function LaudosPage() {
|
export default function LaudosPage() {
|
||||||
const [patient, setPatient] = useState(null);
|
const [patient, setPatient] = useState(null);
|
||||||
@ -49,7 +49,7 @@ export default function LaudosPage() {
|
|||||||
const paginate = (pageNumber) => setCurrentPage(pageNumber);
|
const paginate = (pageNumber) => setCurrentPage(pageNumber);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="container mx-auto p-4">
|
<div className="container mx-auto p-4">
|
||||||
{loading ? (
|
{loading ? (
|
||||||
<p>Carregando...</p>
|
<p>Carregando...</p>
|
||||||
@ -123,6 +123,6 @@ export default function LaudosPage() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -9,7 +9,7 @@ import { Textarea } from "@/components/ui/textarea";
|
|||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Upload, Plus, X, ChevronDown } from "lucide-react";
|
import { Upload, Plus, X, ChevronDown } from "lucide-react";
|
||||||
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
|
||||||
import DoctorLayout from "@/components/doctor-layout";
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
export default function NovoMedicoPage() {
|
export default function NovoMedicoPage() {
|
||||||
const [anexosOpen, setAnexosOpen] = useState(false);
|
const [anexosOpen, setAnexosOpen] = useState(false);
|
||||||
@ -24,7 +24,7 @@ export default function NovoMedicoPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@ -466,6 +466,6 @@ export default function NovoMedicoPage() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,25 +2,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState, useCallback } from "react";
|
import { useEffect, useState, useCallback } from "react";
|
||||||
import DoctorLayout from "@/components/doctor-layout";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||||
DropdownMenu,
|
|
||||||
DropdownMenuContent,
|
|
||||||
DropdownMenuItem,
|
|
||||||
DropdownMenuTrigger,
|
|
||||||
} from "@/components/ui/dropdown-menu";
|
|
||||||
import { Eye, Edit, Calendar, Trash2, Loader2 } from "lucide-react";
|
import { Eye, Edit, Calendar, Trash2, Loader2 } from "lucide-react";
|
||||||
import { api } from "@/services/api.mjs";
|
import { api } from "@/services/api.mjs";
|
||||||
import { PatientDetailsModal } from "@/components/ui/patient-details-modal";
|
import { PatientDetailsModal } from "@/components/ui/patient-details-modal";
|
||||||
import {
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
interface Paciente {
|
interface Paciente {
|
||||||
id: string;
|
id: string;
|
||||||
@ -171,7 +160,7 @@ export default function PacientesPage() {
|
|||||||
}, [fetchPacientes]);
|
}, [fetchPacientes]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DoctorLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
||||||
{/* Cabeçalho */}
|
{/* Cabeçalho */}
|
||||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||||
@ -363,6 +352,6 @@ export default function PacientesPage() {
|
|||||||
isOpen={isModalOpen}
|
isOpen={isModalOpen}
|
||||||
onClose={handleCloseModal}
|
onClose={handleCloseModal}
|
||||||
/>
|
/>
|
||||||
</DoctorLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import FinancierLayout from "@/components/finance-layout";
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
interface Paciente {
|
interface Paciente {
|
||||||
id: string;
|
id: string;
|
||||||
@ -14,43 +14,10 @@ interface Paciente {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default function PacientesPage() {
|
export default function PacientesPage() {
|
||||||
const [pacientes, setPacientes] = useState<Paciente[]>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [error, setError] = useState<string | null>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
async function fetchPacientes() {
|
|
||||||
try {
|
|
||||||
setLoading(true);
|
|
||||||
setError(null);
|
|
||||||
const res = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes");
|
|
||||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
||||||
const json = await res.json();
|
|
||||||
const items = Array.isArray(json?.data) ? json.data : [];
|
|
||||||
|
|
||||||
const mapped = items.map((p: any) => ({
|
|
||||||
id: String(p.id ?? ""),
|
|
||||||
nome: p.nome ?? "",
|
|
||||||
telefone: p?.contato?.celular ?? p?.contato?.telefone1 ?? p?.telefone ?? "",
|
|
||||||
cidade: p?.endereco?.cidade ?? p?.cidade ?? "",
|
|
||||||
estado: p?.endereco?.estado ?? p?.estado ?? "",
|
|
||||||
ultimoAtendimento: p.ultimo_atendimento ?? p.ultimoAtendimento ?? "",
|
|
||||||
proximoAtendimento: p.proximo_atendimento ?? p.proximoAtendimento ?? "",
|
|
||||||
}));
|
|
||||||
|
|
||||||
setPacientes(mapped);
|
|
||||||
} catch (e: any) {
|
|
||||||
setError(e?.message || "Erro ao carregar pacientes");
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchPacientes();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<FinancierLayout>
|
<Sidebar>
|
||||||
<div></div>
|
<div></div>
|
||||||
</FinancierLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,13 +1,93 @@
|
|||||||
// Caminho: app/login/page.tsx
|
// Caminho: app/login/page.tsx
|
||||||
|
|
||||||
|
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
|
||||||
|
import {usersService} from "@/services/usersApi.mjs";
|
||||||
import { LoginForm } from "@/components/LoginForm";
|
import { LoginForm } from "@/components/LoginForm";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ArrowLeft } from "lucide-react"; // Importa o ícone de seta
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { ArrowLeft, X } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import RenderFromTemplateContext from "next/dist/client/components/render-from-template-context";
|
||||||
|
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [email, setEmail] = useState("");
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [message, setMessage] = useState<{ type: "success" | "error"; text: string } | null>(null);
|
||||||
|
|
||||||
|
|
||||||
|
const handleOpenModal = () => {
|
||||||
|
// Tenta pegar o email do input do formulário de login
|
||||||
|
const emailInput = document.querySelector('input[type="email"]') as HTMLInputElement;
|
||||||
|
if (emailInput?.value) {
|
||||||
|
setEmail(emailInput.value);
|
||||||
|
}
|
||||||
|
setIsModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const handleResetPassword = async () => {
|
||||||
|
if (!email.trim()) {
|
||||||
|
setMessage({ type: "error", text: "Por favor, insira um e-mail válido." });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
setIsLoading(true);
|
||||||
|
setMessage(null);
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Chama o método que já faz o fetch corretamente
|
||||||
|
const data = await usersService.resetPassword(email);
|
||||||
|
|
||||||
|
|
||||||
|
console.log("Resposta resetPassword:", data);
|
||||||
|
|
||||||
|
|
||||||
|
setMessage({
|
||||||
|
type: "success",
|
||||||
|
text: "E-mail de recuperação enviado! Verifique sua caixa de entrada.",
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setMessage(null);
|
||||||
|
setEmail("");
|
||||||
|
}, 2000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erro no reset de senha:", error);
|
||||||
|
setMessage({
|
||||||
|
type: "error",
|
||||||
|
text:
|
||||||
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "Erro ao enviar e-mail. Tente novamente.",
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
setMessage(null);
|
||||||
|
setEmail("");
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-2">
|
<div className="min-h-screen grid grid-cols-1 lg:grid-cols-2">
|
||||||
|
|
||||||
{/* PAINEL ESQUERDO: O Formulário */}
|
{/* PAINEL ESQUERDO: O Formulário */}
|
||||||
@ -21,6 +101,7 @@ export default function LoginPage() {
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* O contêiner principal que agora terá a sombra e o estilo de card */}
|
{/* O contêiner principal que agora terá a sombra e o estilo de card */}
|
||||||
<div className="w-full max-w-md bg-card p-10 rounded-2xl shadow-xl">
|
<div className="w-full max-w-md bg-card p-10 rounded-2xl shadow-xl">
|
||||||
<div className="text-center mb-8">
|
<div className="text-center mb-8">
|
||||||
@ -28,17 +109,20 @@ export default function LoginPage() {
|
|||||||
<p className="text-muted-foreground mt-2">Bem-vindo(a) de volta ao MedConnect!</p>
|
<p className="text-muted-foreground mt-2">Bem-vindo(a) de volta ao MedConnect!</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<LoginForm>
|
<LoginForm>
|
||||||
{/* Children para o LoginForm */}
|
{/* Children para o LoginForm */}
|
||||||
<div className="mt-4 text-center text-sm">
|
<div className="mt-4 text-center text-sm">
|
||||||
<Link href="/esqueci-minha-senha">
|
<button
|
||||||
<span className="text-muted-foreground hover:text-primary cursor-pointer underline">
|
onClick={handleOpenModal}
|
||||||
|
className="text-muted-foreground hover:text-primary cursor-pointer underline bg-transparent border-none"
|
||||||
|
>
|
||||||
Esqueceu sua senha?
|
Esqueceu sua senha?
|
||||||
</span>
|
</button>
|
||||||
</Link>
|
|
||||||
</div>
|
</div>
|
||||||
</LoginForm>
|
</LoginForm>
|
||||||
|
|
||||||
|
|
||||||
<div className="mt-6 text-center text-sm">
|
<div className="mt-6 text-center text-sm">
|
||||||
<span className="text-muted-foreground">Não tem uma conta de paciente? </span>
|
<span className="text-muted-foreground">Não tem uma conta de paciente? </span>
|
||||||
<Link href="/patient/register">
|
<Link href="/patient/register">
|
||||||
@ -50,15 +134,16 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
{/* PAINEL DIREITO: A Imagem e Branding */}
|
{/* PAINEL DIREITO: A Imagem e Branding */}
|
||||||
<div className="hidden lg:block relative">
|
<div className="hidden lg:block relative">
|
||||||
{/* Usamos o componente <Image> para otimização e performance */}
|
{/* Usamos o componente <Image> para otimização e performance */}
|
||||||
<Image
|
<Image
|
||||||
src="https://images.unsplash.com/photo-1576091160550-2173dba999ef?q=80&w=2070" // Uma imagem profissional de alta qualidade
|
src="https://images.unsplash.com/photo-1576091160550-2173dba999ef?q=80&w=2070"
|
||||||
alt="Médica utilizando um tablet na clínica MedConnect"
|
alt="Médica utilizando um tablet na clínica MedConnect"
|
||||||
fill
|
fill
|
||||||
style={{ objectFit: 'cover' }}
|
style={{ objectFit: 'cover' }}
|
||||||
priority // Ajuda a carregar a imagem mais rápido
|
priority
|
||||||
/>
|
/>
|
||||||
{/* Camada de sobreposição para escurecer a imagem e destacar o texto */}
|
{/* Camada de sobreposição para escurecer a imagem e destacar o texto */}
|
||||||
<div className="absolute inset-0 bg-primary/80 flex flex-col items-start justify-end p-12 text-left">
|
<div className="absolute inset-0 bg-primary/80 flex flex-col items-start justify-end p-12 text-left">
|
||||||
@ -77,6 +162,86 @@ export default function LoginPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Modal de Recuperação de Senha */}
|
||||||
|
{isModalOpen && (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
||||||
|
<div className="relative w-full max-w-md bg-card p-8 rounded-2xl shadow-2xl mx-4">
|
||||||
|
{/* Botão de fechar */}
|
||||||
|
<button
|
||||||
|
onClick={closeModal}
|
||||||
|
className="absolute top-4 right-4 text-muted-foreground hover:text-foreground transition-colors"
|
||||||
|
>
|
||||||
|
<X className="w-5 h-5" />
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Cabeçalho */}
|
||||||
|
<div className="mb-6">
|
||||||
|
<h2 className="text-2xl font-bold text-foreground">Recuperar Senha</h2>
|
||||||
|
<p className="text-muted-foreground mt-2">
|
||||||
|
Insira seu e-mail e enviaremos um link para redefinir sua senha.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Input de e-mail */}
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<label htmlFor="email" className="block text-sm font-medium text-foreground mb-2">
|
||||||
|
E-mail
|
||||||
|
</label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
placeholder="seu@email.com"
|
||||||
|
disabled={isLoading}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{/* Mensagem de feedback */}
|
||||||
|
{message && (
|
||||||
|
<div
|
||||||
|
className={`p-3 rounded-lg text-sm ${
|
||||||
|
message.type === "success"
|
||||||
|
? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300"
|
||||||
|
: "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{message.text}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
|
||||||
|
{/* Botões */}
|
||||||
|
<div className="flex gap-3 pt-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
onClick={closeModal}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={handleResetPassword}
|
||||||
|
disabled={isLoading}
|
||||||
|
className="flex-1"
|
||||||
|
>
|
||||||
|
{isLoading ? "Enviando..." : "Resetar Senha"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,6 +1,5 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import ManagerLayout from "@/components/manager-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 { Calendar, Clock, Plus, User } from "lucide-react";
|
import { Calendar, Clock, Plus, User } from "lucide-react";
|
||||||
@ -8,6 +7,7 @@ import Link from "next/link";
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { usersService } from "services/usersApi.mjs";
|
import { usersService } from "services/usersApi.mjs";
|
||||||
import { doctorsService } from "services/doctorsApi.mjs";
|
import { doctorsService } from "services/doctorsApi.mjs";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
export default function ManagerDashboard() {
|
export default function ManagerDashboard() {
|
||||||
// 🔹 Estados para usuários
|
// 🔹 Estados para usuários
|
||||||
@ -55,7 +55,7 @@ export default function ManagerDashboard() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManagerLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Cabeçalho */}
|
{/* Cabeçalho */}
|
||||||
<div>
|
<div>
|
||||||
@ -185,6 +185,6 @@ export default function ManagerDashboard() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,7 +10,7 @@ import { Textarea } from "@/components/ui/textarea"
|
|||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||||
import { Checkbox } from "@/components/ui/checkbox"
|
import { Checkbox } from "@/components/ui/checkbox"
|
||||||
import { Save, Loader2, ArrowLeft } from "lucide-react"
|
import { Save, Loader2, ArrowLeft } from "lucide-react"
|
||||||
import ManagerLayout from "@/components/manager-layout"
|
import Sidebar from "@/components/Sidebar"
|
||||||
import { doctorsService } from "services/doctorsApi.mjs";
|
import { doctorsService } from "services/doctorsApi.mjs";
|
||||||
|
|
||||||
const UF_LIST = ["AC", "AL", "AP", "AM", "BA", "CE", "DF", "ES", "GO", "MA", "MT", "MS", "MG", "PA", "PB", "PR", "PE", "PI", "RJ", "RN", "RS", "RO", "RR", "SC", "SP", "SE", "TO"];
|
const UF_LIST = ["AC", "AL", "AP", "AM", "BA", "CE", "DF", "ES", "GO", "MA", "MT", "MS", "MG", "PA", "PB", "PR", "PE", "PI", "RJ", "RN", "RS", "RO", "RR", "SC", "SP", "SE", "TO"];
|
||||||
@ -207,17 +207,17 @@ export default function EditarMedicoPage() {
|
|||||||
};
|
};
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ManagerLayout>
|
<Sidebar>
|
||||||
<div className="flex justify-center items-center h-full w-full py-16">
|
<div className="flex justify-center items-center h-full w-full py-16">
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-green-600" />
|
<Loader2 className="w-8 h-8 animate-spin text-green-600" />
|
||||||
<p className="ml-2 text-gray-600">Carregando dados do médico...</p>
|
<p className="ml-2 text-gray-600">Carregando dados do médico...</p>
|
||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManagerLayout>
|
<Sidebar>
|
||||||
<div className="w-full space-y-6 p-4 md:p-8">
|
<div className="w-full space-y-6 p-4 md:p-8">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@ -487,6 +487,6 @@ export default function EditarMedicoPage() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,25 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState, useCallback, useMemo } from "react"
|
import React, { useEffect, useState, useCallback, useMemo } from "react"
|
||||||
import ManagerLayout from "@/components/manager-layout";
|
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
|
||||||
import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react"
|
import { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react"
|
||||||
import {
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"
|
||||||
AlertDialog,
|
|
||||||
AlertDialogAction,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
} from "@/components/ui/alert-dialog"
|
|
||||||
|
|
||||||
import { doctorsService } from "services/doctorsApi.mjs";
|
import { doctorsService } from "services/doctorsApi.mjs";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
|
|
||||||
interface Doctor {
|
interface Doctor {
|
||||||
@ -193,7 +184,7 @@ export default function DoctorsPage() {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManagerLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
<div className="space-y-6 px-2 sm:px-4 md:px-6">
|
||||||
|
|
||||||
{/* Cabeçalho */}
|
{/* Cabeçalho */}
|
||||||
@ -430,6 +421,6 @@ export default function DoctorsPage() {
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -13,9 +13,8 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||||||
import { ArrowLeft, Save, Trash2, Paperclip, Upload } from "lucide-react";
|
import { ArrowLeft, Save, Trash2, Paperclip, Upload } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import SecretaryLayout from "@/components/secretary-layout";
|
|
||||||
import { patientsService } from "@/services/patientsApi.mjs";
|
import { patientsService } from "@/services/patientsApi.mjs";
|
||||||
import { json } from "stream/consumers";
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
export default function EditarPacientePage() {
|
export default function EditarPacientePage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -247,7 +246,7 @@ export default function EditarPacientePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecretaryLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Link href="/manager/pacientes">
|
<Link href="/manager/pacientes">
|
||||||
@ -677,6 +676,6 @@ export default function EditarPacientePage() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</SecretaryLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,10 +6,10 @@ import Link from "next/link";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react";
|
import { Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react";
|
||||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||||
import { patientsService } from "@/services/patientsApi.mjs";
|
import { patientsService } from "@/services/patientsApi.mjs";
|
||||||
import ManagerLayout from "@/components/manager-layout";
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
// Defina o tamanho da página.
|
// Defina o tamanho da página.
|
||||||
const PAGE_SIZE = 5;
|
const PAGE_SIZE = 5;
|
||||||
@ -145,7 +145,7 @@ export default function PacientesPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManagerLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6 px-2 sm:px-4 md:px-8">
|
<div className="space-y-6 px-2 sm:px-4 md:px-8">
|
||||||
{/* Header (Responsividade OK) */}
|
{/* Header (Responsividade OK) */}
|
||||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||||
@ -449,6 +449,6 @@ export default function PacientesPage() {
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -8,7 +8,7 @@ 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 { Save, Loader2, ArrowLeft } from "lucide-react"
|
import { Save, Loader2, ArrowLeft } from "lucide-react"
|
||||||
import ManagerLayout from "@/components/manager-layout"
|
import Sidebar from "@/components/Sidebar"
|
||||||
|
|
||||||
// Mock user service for demonstration. Replace with your actual API service.
|
// Mock user service for demonstration. Replace with your actual API service.
|
||||||
const usersService = {
|
const usersService = {
|
||||||
@ -155,17 +155,17 @@ export default function EditarUsuarioPage() {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<ManagerLayout>
|
<Sidebar>
|
||||||
<div className="flex justify-center items-center h-full w-full py-16">
|
<div className="flex justify-center items-center h-full w-full py-16">
|
||||||
<Loader2 className="w-8 h-8 animate-spin text-green-600" />
|
<Loader2 className="w-8 h-8 animate-spin text-green-600" />
|
||||||
<p className="ml-2 text-gray-600">Carregando dados do usuário...</p>
|
<p className="ml-2 text-gray-600">Carregando dados do usuário...</p>
|
||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManagerLayout>
|
<Sidebar>
|
||||||
<div className="w-full max-w-2xl mx-auto space-y-6 p-4 md:p-8">
|
<div className="w-full max-w-2xl mx-auto space-y-6 p-4 md:p-8">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
@ -274,6 +274,6 @@ export default function EditarUsuarioPage() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -10,11 +10,11 @@ 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 { Save, Loader2 } from "lucide-react";
|
import { Save, Loader2 } from "lucide-react";
|
||||||
import ManagerLayout from "@/components/manager-layout";
|
|
||||||
import { usersService } from "@/services/usersApi.mjs";
|
import { usersService } from "@/services/usersApi.mjs";
|
||||||
import { doctorsService } from "@/services/doctorsApi.mjs";
|
import { doctorsService } from "@/services/doctorsApi.mjs";
|
||||||
import { login } from "services/api.mjs";
|
import { login } from "services/api.mjs";
|
||||||
import { isValidCPF } from "@/lib/utils"; // 1. IMPORTAÇÃO DA FUNÇÃO DE VALIDAÇÃO
|
import { isValidCPF } from "@/lib/utils"; // 1. IMPORTAÇÃO DA FUNÇÃO DE VALIDAÇÃO
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
interface UserFormData {
|
interface UserFormData {
|
||||||
email: string;
|
email: string;
|
||||||
@ -135,7 +135,7 @@ export default function NovoUsuarioPage() {
|
|||||||
const isMedico = formData.papel === "medico";
|
const isMedico = formData.papel === "medico";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManagerLayout>
|
<Sidebar>
|
||||||
<div className="w-full h-full p-4 md:p-8 flex justify-center items-start">
|
<div className="w-full h-full p-4 md:p-8 flex justify-center items-start">
|
||||||
<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">
|
||||||
@ -236,6 +236,6 @@ export default function NovoUsuarioPage() {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -2,28 +2,14 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState, useCallback } from "react";
|
import React, { useEffect, useState, useCallback } from "react";
|
||||||
import ManagerLayout from "@/components/manager-layout";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import {
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
Select,
|
|
||||||
SelectContent,
|
|
||||||
SelectItem,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValue,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import { Plus, Eye, Filter, Loader2 } from "lucide-react";
|
import { Plus, Eye, Filter, Loader2 } from "lucide-react";
|
||||||
import {
|
import { AlertDialog, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||||
AlertDialog,
|
|
||||||
AlertDialogCancel,
|
|
||||||
AlertDialogContent,
|
|
||||||
AlertDialogDescription,
|
|
||||||
AlertDialogFooter,
|
|
||||||
AlertDialogHeader,
|
|
||||||
AlertDialogTitle,
|
|
||||||
} from "@/components/ui/alert-dialog";
|
|
||||||
import { api, login } from "services/api.mjs";
|
import { api, login } from "services/api.mjs";
|
||||||
import { usersService } from "services/usersApi.mjs";
|
import { usersService } from "services/usersApi.mjs";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
interface FlatUser {
|
interface FlatUser {
|
||||||
id: string;
|
id: string;
|
||||||
@ -192,7 +178,7 @@ export default function UsersPage() {
|
|||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManagerLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6 px-2 sm:px-4 md:px-8">
|
<div className="space-y-6 px-2 sm:px-4 md:px-8">
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
@ -424,6 +410,6 @@ export default function UsersPage() {
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import PatientLayout from "@/components/patient-layout";
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
@ -10,6 +9,7 @@ import { toast } from "sonner";
|
|||||||
|
|
||||||
import { appointmentsService } from "@/services/appointmentsApi.mjs";
|
import { appointmentsService } from "@/services/appointmentsApi.mjs";
|
||||||
import { usersService } from "@/services/usersApi.mjs";
|
import { usersService } from "@/services/usersApi.mjs";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
// Tipagem correta para o usuário
|
// Tipagem correta para o usuário
|
||||||
interface UserProfile {
|
interface UserProfile {
|
||||||
@ -129,7 +129,7 @@ export default function PatientAppointmentsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PatientLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
@ -185,6 +185,6 @@ export default function PatientAppointmentsPage() {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PatientLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,12 +1,12 @@
|
|||||||
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 { Calendar, Clock, User, Plus } from "lucide-react"
|
import { Calendar, Clock, User, Plus } from "lucide-react"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
import Sidebar from "@/components/Sidebar"
|
||||||
|
|
||||||
export default function PatientDashboard() {
|
export default function PatientDashboard() {
|
||||||
return (
|
return (
|
||||||
<PatientLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Dashboard</h1>
|
<h1 className="text-3xl font-bold text-gray-900">Dashboard</h1>
|
||||||
@ -108,6 +108,6 @@ export default function PatientDashboard() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PatientLayout>
|
</Sidebar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import PatientLayout from "@/components/patient-layout";
|
import Sidebar from "@/components/Sidebar"
|
||||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
||||||
import { patientsService } from "@/services/patientsApi.mjs";
|
import { patientsService } from "@/services/patientsApi.mjs";
|
||||||
import { api } from "@/services/api.mjs";
|
import { api } from "@/services/api.mjs";
|
||||||
@ -121,11 +121,11 @@ export default function PatientProfile() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isAuthLoading || !patientData) {
|
if (isAuthLoading || !patientData) {
|
||||||
return <PatientLayout><div>Carregando seus dados...</div></PatientLayout>;
|
return <Sidebar><div>Carregando seus dados...</div></Sidebar>;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PatientLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
@ -198,6 +198,6 @@ export default function PatientProfile() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</PatientLayout>
|
</Sidebar>
|
||||||
);
|
)
|
||||||
}
|
}
|
||||||
@ -1,13 +1,13 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useState, useEffect } from "react"
|
import { useState, useEffect } from "react"
|
||||||
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 { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
||||||
import { toast } from "@/hooks/use-toast"
|
import { toast } from "@/hooks/use-toast"
|
||||||
import { FileText, Download, Eye, Calendar, User, X } from "lucide-react"
|
import { FileText, Download, Eye, Calendar, User, X } from "lucide-react"
|
||||||
|
import Sidebar from "@/components/Sidebar"
|
||||||
|
|
||||||
interface Report {
|
interface Report {
|
||||||
id: string
|
id: string
|
||||||
@ -287,7 +287,7 @@ export default function ReportsPage() {
|
|||||||
const pendingReports = reports.filter((report) => report.status === "pendente")
|
const pendingReports = reports.filter((report) => report.status === "pendente")
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PatientLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-gray-900">Meus Laudos</h1>
|
<h1 className="text-3xl font-bold text-gray-900">Meus Laudos</h1>
|
||||||
@ -536,6 +536,6 @@ export default function ReportsPage() {
|
|||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
</PatientLayout>
|
</Sidebar>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
// app/patient/appointments/page.tsx
|
// app/patient/appointments/page.tsx
|
||||||
import PatientLayout from "@/components/patient-layout";
|
import Sidebar from "@/components/Sidebar";
|
||||||
import ScheduleForm from "@/components/schedule/schedule-form";
|
import ScheduleForm from "@/components/schedule/schedule-form";
|
||||||
|
|
||||||
|
|
||||||
export default function PatientAppointments() {
|
export default function PatientAppointments() {
|
||||||
return (
|
return (
|
||||||
<PatientLayout>
|
<Sidebar>
|
||||||
<ScheduleForm />
|
<ScheduleForm />
|
||||||
</PatientLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,20 +1,17 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import SecretaryLayout from "@/components/secretary-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 { Badge } from "@/components/ui/badge";
|
import { Badge } from "@/components/ui/badge";
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
import { Dialog } from "@/components/ui/dialog";
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
|
||||||
import { Calendar, Clock, MapPin, Phone, User, Trash2, Pencil } from "lucide-react";
|
import { Calendar, Clock, MapPin, Phone, User, Trash2, Pencil } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
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 Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
export default function SecretaryAppointments() {
|
export default function SecretaryAppointments() {
|
||||||
const [appointments, setAppointments] = useState<any[]>([]);
|
const [appointments, setAppointments] = useState<any[]>([]);
|
||||||
@ -144,7 +141,7 @@ export default function SecretaryAppointments() {
|
|||||||
const appointmentStatuses = ["requested", "confirmed", "checked_in", "completed", "cancelled", "no_show"];
|
const appointmentStatuses = ["requested", "confirmed", "checked_in", "completed", "cancelled", "no_show"];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecretaryLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div>
|
<div>
|
||||||
@ -225,6 +222,6 @@ export default function SecretaryAppointments() {
|
|||||||
<Dialog open={deleteModal} onOpenChange={setDeleteModal}>
|
<Dialog open={deleteModal} onOpenChange={setDeleteModal}>
|
||||||
{/* ... (código do modal de deleção) ... */}
|
{/* ... (código do modal de deleção) ... */}
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</SecretaryLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,10 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import SecretaryLayout from "@/components/secretary-layout";
|
import { Card, CardContent, CardDescription,
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardContent,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
CardHeader,
|
||||||
CardTitle,
|
CardTitle,
|
||||||
} from "@/components/ui/card";
|
} from "@/components/ui/card";
|
||||||
@ -14,6 +10,7 @@ import Link from "next/link";
|
|||||||
import React, { useState, useEffect } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
import { patientsService } from "@/services/patientsApi.mjs";
|
import { patientsService } from "@/services/patientsApi.mjs";
|
||||||
import { appointmentsService } from "@/services/appointmentsApi.mjs";
|
import { appointmentsService } from "@/services/appointmentsApi.mjs";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
export default function SecretaryDashboard() {
|
export default function SecretaryDashboard() {
|
||||||
// Estados
|
// Estados
|
||||||
@ -100,7 +97,7 @@ export default function SecretaryDashboard() {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecretaryLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Cabeçalho */}
|
{/* Cabeçalho */}
|
||||||
<div>
|
<div>
|
||||||
@ -299,6 +296,6 @@ export default function SecretaryDashboard() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</SecretaryLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,9 +13,8 @@ import { Checkbox } from "@/components/ui/checkbox";
|
|||||||
import { ArrowLeft, Save, Trash2, Paperclip, Upload } from "lucide-react";
|
import { ArrowLeft, Save, Trash2, Paperclip, Upload } from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import SecretaryLayout from "@/components/secretary-layout";
|
|
||||||
import { patientsService } from "@/services/patientsApi.mjs";
|
import { patientsService } from "@/services/patientsApi.mjs";
|
||||||
import { json } from "stream/consumers";
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
export default function EditarPacientePage() {
|
export default function EditarPacientePage() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -247,7 +246,7 @@ export default function EditarPacientePage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecretaryLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Link href="/secretary/pacientes">
|
<Link href="/secretary/pacientes">
|
||||||
@ -677,6 +676,6 @@ export default function EditarPacientePage() {
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</SecretaryLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
// Caminho: app/(manager)/usuario/novo/page.tsx
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
@ -7,13 +6,9 @@ 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";
|
||||||
// O Select foi removido pois não é mais necessário
|
|
||||||
import { Save, Loader2 } from "lucide-react";
|
import { Save, Loader2 } from "lucide-react";
|
||||||
import ManagerLayout from "@/components/manager-layout";
|
|
||||||
// Os imports originais foram mantidos, como solicitado
|
|
||||||
import { usersService } from "services/usersApi.mjs";
|
import { usersService } from "services/usersApi.mjs";
|
||||||
import { doctorsService } from "services/doctorsApi.mjs";
|
import Sidebar from "@/components/Sidebar";
|
||||||
import { login } from "services/api.mjs";
|
|
||||||
|
|
||||||
// Interface simplificada para refletir apenas os campos necessários
|
// Interface simplificada para refletir apenas os campos necessários
|
||||||
interface UserFormData {
|
interface UserFormData {
|
||||||
@ -97,7 +92,7 @@ export default function NovoUsuarioPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ManagerLayout>
|
<Sidebar>
|
||||||
<div className="w-full h-full p-4 md:p-8 flex justify-center items-start">
|
<div className="w-full h-full p-4 md:p-8 flex justify-center items-start">
|
||||||
<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">
|
||||||
@ -167,6 +162,6 @@ export default function NovoUsuarioPage() {
|
|||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ManagerLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -8,8 +8,8 @@ import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigge
|
|||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
|
||||||
import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react";
|
import { Plus, Edit, Trash2, Eye, Calendar, Filter, Loader2 } from "lucide-react";
|
||||||
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog";
|
||||||
import SecretaryLayout from "@/components/secretary-layout";
|
|
||||||
import { patientsService } from "@/services/patientsApi.mjs";
|
import { patientsService } from "@/services/patientsApi.mjs";
|
||||||
|
import Sidebar from "@/components/Sidebar";
|
||||||
|
|
||||||
// Defina o tamanho da página.
|
// Defina o tamanho da página.
|
||||||
const PAGE_SIZE = 5;
|
const PAGE_SIZE = 5;
|
||||||
@ -145,7 +145,7 @@ export default function PacientesPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SecretaryLayout>
|
<Sidebar>
|
||||||
<div className="space-y-6 px-2 sm:px-4 md:px-8">
|
<div className="space-y-6 px-2 sm:px-4 md:px-8">
|
||||||
{/* Header (Responsividade OK) */}
|
{/* Header (Responsividade OK) */}
|
||||||
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
<div className="flex flex-col md:flex-row md:items-center md:justify-between gap-4">
|
||||||
@ -457,6 +457,6 @@ export default function PacientesPage() {
|
|||||||
</AlertDialogContent>
|
</AlertDialogContent>
|
||||||
</AlertDialog>
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
</SecretaryLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1,11 +1,11 @@
|
|||||||
import SecretaryLayout from "@/components/secretary-layout";
|
import Sidebar from "@/components/Sidebar";
|
||||||
import ScheduleForm from "@/components/schedule/schedule-form";
|
import ScheduleForm from "@/components/schedule/schedule-form";
|
||||||
|
|
||||||
export default function SecretaryAppointments() {
|
export default function SecretaryAppointments() {
|
||||||
return (
|
return (
|
||||||
<SecretaryLayout>
|
<Sidebar>
|
||||||
<ScheduleForm />
|
<ScheduleForm />
|
||||||
</SecretaryLayout>
|
</Sidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,6 +13,7 @@ import { Label } from "@/components/ui/label";
|
|||||||
import { Card, CardContent } from "@/components/ui/card";
|
import { Card, CardContent } from "@/components/ui/card";
|
||||||
import { useToast } from "@/hooks/use-toast";
|
import { useToast } from "@/hooks/use-toast";
|
||||||
import { Eye, EyeOff, Mail, Lock, Loader2 } from "lucide-react";
|
import { Eye, EyeOff, Mail, Lock, Loader2 } from "lucide-react";
|
||||||
|
import { usersService } from "@/services/usersApi.mjs";
|
||||||
|
|
||||||
interface LoginFormProps {
|
interface LoginFormProps {
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
@ -31,8 +32,12 @@ export function LoginForm({ children }: LoginFormProps) {
|
|||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
const [userRoles, setUserRoles] = useState<string[]>([]);
|
const [userRoles, setUserRoles] = useState<string[]>([]);
|
||||||
|
const [authenticatedUser, setAuthenticatedUser] = useState<any>(null);
|
||||||
|
|
||||||
// *** MUDANÇA 1: A função agora recebe o objeto 'user' como parâmetro ***
|
/**
|
||||||
|
* --- NOVA FUNÇÃO ---
|
||||||
|
* Finaliza o login com o perfil de dashboard escolhido e redireciona.
|
||||||
|
*/
|
||||||
const handleRoleSelection = (selectedDashboardRole: string, user: any) => {
|
const handleRoleSelection = (selectedDashboardRole: string, user: any) => {
|
||||||
if (!user) {
|
if (!user) {
|
||||||
toast({ title: "Erro de Sessão", description: "Não foi possível encontrar os dados do usuário. Tente novamente.", variant: "destructive" });
|
toast({ title: "Erro de Sessão", description: "Não foi possível encontrar os dados do usuário. Tente novamente.", variant: "destructive" });
|
||||||
@ -47,12 +52,12 @@ export function LoginForm({ children }: LoginFormProps) {
|
|||||||
localStorage.setItem("user_info", JSON.stringify(completeUserInfo));
|
localStorage.setItem("user_info", JSON.stringify(completeUserInfo));
|
||||||
|
|
||||||
let redirectPath = "";
|
let redirectPath = "";
|
||||||
switch (roleInLowerCase) {
|
switch (selectedDashboardRole) {
|
||||||
case "manager": redirectPath = "/manager/home"; break;
|
case "gestor": redirectPath = "/manager/dashboard"; break;
|
||||||
case "doctor": redirectPath = "/doctor/medicos"; break;
|
case "admin": redirectPath = "/manager/dashboard"; break;
|
||||||
case "secretary": redirectPath = "/secretary/pacientes"; break;
|
case "medico": redirectPath = "/doctor/dashboard"; break;
|
||||||
case "patient": redirectPath = "/patient/dashboard"; break;
|
case "secretaria": redirectPath = "/secretary/dashboard"; break;
|
||||||
case "finance": redirectPath = "/finance/home"; break;
|
case "paciente": redirectPath = "/patient/dashboard"; break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (redirectPath) {
|
if (redirectPath) {
|
||||||
@ -77,55 +82,16 @@ export function LoginForm({ children }: LoginFormProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const rolesData = await api.get(`/rest/v1/user_roles?user_id=eq.${user.id}&select=role`);
|
const rolesData = await api.get(`/rest/v1/user_roles?user_id=eq.${user.id}&select=role`);
|
||||||
if (!rolesData || rolesData.length === 0) {
|
|
||||||
|
const me = await usersService.getMeSimple()
|
||||||
|
console.log(me.roles)
|
||||||
|
|
||||||
|
if (!me.roles || me.roles.length === 0) {
|
||||||
throw new Error("Nenhum perfil de acesso foi encontrado para este usuário.");
|
throw new Error("Nenhum perfil de acesso foi encontrado para este usuário.");
|
||||||
}
|
}
|
||||||
|
|
||||||
const rolesFromApi: string[] = rolesData.map((r: any) => r.role);
|
handleRoleSelection(me.roles[0], user);
|
||||||
|
|
||||||
// *** MUDANÇA 2: Passamos o objeto 'user' diretamente para a função de seleção ***
|
|
||||||
const handleSelectionWithUser = (role: string) => handleRoleSelection(role, user);
|
|
||||||
|
|
||||||
if (rolesFromApi.includes("admin")) {
|
|
||||||
const allRoles = ["manager", "doctor", "secretary", "patient", "finance"];
|
|
||||||
setUserRoles(allRoles);
|
|
||||||
// Atualizamos o onClick para usar a nova função que já tem o 'user'
|
|
||||||
const roleButtons = allRoles.map((role) => (
|
|
||||||
<Button key={role} variant="outline" className="h-11 text-base" onClick={() => handleSelectionWithUser(role)}>
|
|
||||||
Entrar como: {role.charAt(0).toUpperCase() + role.slice(1)}
|
|
||||||
</Button>
|
|
||||||
));
|
|
||||||
// Precisamos de um estado para renderizar os botões
|
|
||||||
setRoleSelectionUI(roleButtons);
|
|
||||||
setIsLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const displayRoles = new Set<string>();
|
|
||||||
rolesFromApi.forEach((role) => {
|
|
||||||
switch (role) {
|
|
||||||
case "gestor": displayRoles.add("manager"); displayRoles.add("finance"); break;
|
|
||||||
case "medico": displayRoles.add("doctor"); break;
|
|
||||||
case "secretaria": displayRoles.add("secretary"); break;
|
|
||||||
case "paciente": displayRoles.add("patient"); break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const finalRoles = Array.from(displayRoles);
|
|
||||||
|
|
||||||
if (finalRoles.length === 1) {
|
|
||||||
handleSelectionWithUser(finalRoles[0]);
|
|
||||||
} else {
|
|
||||||
setUserRoles(finalRoles);
|
|
||||||
// Atualizamos o onClick aqui também
|
|
||||||
const roleButtons = finalRoles.map((role) => (
|
|
||||||
<Button key={role} variant="outline" className="h-11 text-base" onClick={() => handleSelectionWithUser(role)}>
|
|
||||||
Entrar como: {role.charAt(0).toUpperCase() + role.slice(1)}
|
|
||||||
</Button>
|
|
||||||
));
|
|
||||||
setRoleSelectionUI(roleButtons);
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
localStorage.removeItem("token");
|
localStorage.removeItem("token");
|
||||||
localStorage.removeItem("user_info");
|
localStorage.removeItem("user_info");
|
||||||
@ -172,7 +138,11 @@ export function LoginForm({ children }: LoginFormProps) {
|
|||||||
<h3 className="text-lg font-medium text-center text-foreground">Você tem múltiplos perfis</h3>
|
<h3 className="text-lg font-medium text-center text-foreground">Você tem múltiplos perfis</h3>
|
||||||
<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">
|
||||||
{roleSelectionUI}
|
{userRoles.map((role) => (
|
||||||
|
<Button key={role} variant="outline" className="h-11 text-base" onClick={() => handleRoleSelection(role, authenticatedUser)}>
|
||||||
|
Entrar como: {role.charAt(0).toUpperCase() + role.slice(1)}
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
291
components/Sidebar.tsx
Normal file
291
components/Sidebar.tsx
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
// Caminho: [seu-caminho]/ManagerLayout.tsx
|
||||||
|
"use client";
|
||||||
|
|
||||||
|
import type React from "react";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { useRouter, usePathname } from "next/navigation";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Cookies from "js-cookie"; // Mantido apenas para a limpeza de segurança no logout
|
||||||
|
import { api } from "@/services/api.mjs";
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Badge } from "@/components/ui/badge";
|
||||||
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
||||||
|
import { Search, Bell, Calendar, User, LogOut, ChevronLeft, ChevronRight, Home, CalendarCheck2, ClipboardPlus, SquareUserRound, CalendarClock, Users, SquareUser, ClipboardList, Stethoscope, ClipboardMinus } from "lucide-react";
|
||||||
|
import SidebarUserSection from "@/components/ui/userToolTip";
|
||||||
|
|
||||||
|
interface UserData {
|
||||||
|
id: string;
|
||||||
|
email: string;
|
||||||
|
app_metadata: {
|
||||||
|
user_role: string;
|
||||||
|
};
|
||||||
|
user_metadata: {
|
||||||
|
cpf: string;
|
||||||
|
email_verified: boolean;
|
||||||
|
full_name: string;
|
||||||
|
phone_mobile: string;
|
||||||
|
role: string;
|
||||||
|
};
|
||||||
|
identities: {
|
||||||
|
identity_id: string;
|
||||||
|
id: string;
|
||||||
|
user_id: string;
|
||||||
|
provider: string;
|
||||||
|
}[];
|
||||||
|
is_anonymous: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
interface MenuItem {
|
||||||
|
href: string;
|
||||||
|
icon: React.ElementType;
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface SidebarProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Sidebar({ children }: SidebarProps) {
|
||||||
|
const [userData, setUserData] = useState<UserData>();
|
||||||
|
const [role, setRole] = useState<string>();
|
||||||
|
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
||||||
|
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
||||||
|
const router = useRouter();
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const userInfoString = localStorage.getItem("user_info");
|
||||||
|
// --- ALTERAÇÃO 1: Buscando o token no localStorage ---
|
||||||
|
const token = localStorage.getItem("token");
|
||||||
|
|
||||||
|
if (userInfoString && token) {
|
||||||
|
const userInfo = JSON.parse(userInfoString);
|
||||||
|
|
||||||
|
setUserData({
|
||||||
|
id: userInfo.id ?? "",
|
||||||
|
email: userInfo.email ?? "",
|
||||||
|
app_metadata: {
|
||||||
|
user_role: userInfo.app_metadata?.user_role ?? "patient",
|
||||||
|
},
|
||||||
|
user_metadata: {
|
||||||
|
cpf: userInfo.user_metadata?.cpf ?? "",
|
||||||
|
email_verified: userInfo.user_metadata?.email_verified ?? false,
|
||||||
|
full_name: userInfo.user_metadata?.full_name ?? "",
|
||||||
|
phone_mobile: userInfo.user_metadata?.phone_mobile ?? "",
|
||||||
|
role: userInfo.user_metadata?.role ?? "",
|
||||||
|
},
|
||||||
|
identities:
|
||||||
|
userInfo.identities?.map((identity: any) => ({
|
||||||
|
identity_id: identity.identity_id ?? "",
|
||||||
|
id: identity.id ?? "",
|
||||||
|
user_id: identity.user_id ?? "",
|
||||||
|
provider: identity.provider ?? "",
|
||||||
|
})) ?? [],
|
||||||
|
is_anonymous: userInfo.is_anonymous ?? false,
|
||||||
|
});
|
||||||
|
setRole(userInfo.user_metadata?.role)
|
||||||
|
} else {
|
||||||
|
// O redirecionamento para /login já estava correto. Ótimo!
|
||||||
|
router.push("/login");
|
||||||
|
}
|
||||||
|
}, [router]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
|
||||||
|
|
||||||
|
const handleResize = () => {
|
||||||
|
if (window.innerWidth < 1024) {
|
||||||
|
setSidebarCollapsed(true);
|
||||||
|
} else {
|
||||||
|
setSidebarCollapsed(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
handleResize();
|
||||||
|
window.addEventListener("resize", handleResize);
|
||||||
|
return () => window.removeEventListener("resize", handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleLogout = () => setShowLogoutDialog(true);
|
||||||
|
|
||||||
|
// --- ALTERAÇÃO 2: A função de logout agora é MUITO mais simples ---
|
||||||
|
const confirmLogout = async () => {
|
||||||
|
try {
|
||||||
|
// Chama a função centralizada para fazer o logout no servidor
|
||||||
|
await api.logout();
|
||||||
|
} catch (error) {
|
||||||
|
// O erro já é logado dentro da função api.logout, não precisamos fazer nada aqui
|
||||||
|
} finally {
|
||||||
|
// A responsabilidade do componente é apenas limpar o estado local e redirecionar
|
||||||
|
localStorage.removeItem("user_info");
|
||||||
|
localStorage.removeItem("token");
|
||||||
|
Cookies.remove("access_token"); // Limpeza de segurança
|
||||||
|
|
||||||
|
setShowLogoutDialog(false);
|
||||||
|
router.push("/"); // Redireciona para a home
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const cancelLogout = () => setShowLogoutDialog(false);
|
||||||
|
|
||||||
|
const SetMenuItems = (role: any) => {
|
||||||
|
const patientItems: MenuItem[] = [
|
||||||
|
{ href: "/patient/dashboard", icon: Home, label: "Dashboard" },
|
||||||
|
{ href: "/patient/schedule", icon: CalendarClock, label: "Agendar Consulta" },
|
||||||
|
{ href: "/patient/appointments", icon: CalendarCheck2, label: "Minhas Consultas" },
|
||||||
|
{ href: "/patient/reports", icon: ClipboardPlus, label: "Meus Laudos" },
|
||||||
|
{ href: "/patient/profile", icon: SquareUser, label: "Meus Dados" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const doctorItems: MenuItem[] = [
|
||||||
|
{ href: "/doctor/dashboard", icon: Home, label: "Dashboard" },
|
||||||
|
{ href: "/doctor/medicos", icon: Users, label: "Gestão de Pacientes" },
|
||||||
|
{ href: "/doctor/consultas", icon: CalendarCheck2, label: "Consultas" },
|
||||||
|
{ href: "/doctor/disponibilidade", icon: ClipboardList, label: "Disponibilidade" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const secretaryItems: MenuItem[] = [
|
||||||
|
{ href: "/secretary/dashboard", icon: Home, label: "Dashboard" },
|
||||||
|
{ href: "/secretary/appointments", icon: CalendarCheck2, label: "Consultas" },
|
||||||
|
{ href: "/secretary/schedule", icon: CalendarClock, label: "Agendar Consulta" },
|
||||||
|
{ href: "/secretary/pacientes", icon: Users, label: "Gestão de Pacientes" },
|
||||||
|
]
|
||||||
|
|
||||||
|
const managerItems: MenuItem[] = [
|
||||||
|
{ href: "/manager/dashboard", icon: Home, label: "Dashboard" },
|
||||||
|
{ href: "#", icon: ClipboardMinus, label: "Relatórios gerenciais" },
|
||||||
|
{ href: "/manager/usuario", icon: Users, label: "Gestão de Usuários" },
|
||||||
|
{ href: "/manager/home", icon: Stethoscope, label: "Gestão de Médicos" },
|
||||||
|
{ href: "/manager/pacientes", icon: Users, label: "Gestão de Pacientes" },
|
||||||
|
{ href: "/doctor/consultas", icon: CalendarCheck2, label: "Consultas" }, //adicionar botão de voltar pra pagina anterior
|
||||||
|
]
|
||||||
|
|
||||||
|
let menuItems: MenuItem[];
|
||||||
|
switch (role) {
|
||||||
|
case "gestor":
|
||||||
|
menuItems = managerItems;
|
||||||
|
break;
|
||||||
|
case "admin":
|
||||||
|
menuItems = managerItems;
|
||||||
|
break;
|
||||||
|
case "medico":
|
||||||
|
menuItems = doctorItems;
|
||||||
|
break;
|
||||||
|
case "secretaria":
|
||||||
|
menuItems = secretaryItems;
|
||||||
|
break;
|
||||||
|
case "paciente":
|
||||||
|
menuItems = patientItems;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
menuItems = patientItems;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return menuItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
const menuItems = SetMenuItems(role)
|
||||||
|
|
||||||
|
if (!userData) {
|
||||||
|
return (
|
||||||
|
<div className="flex h-screen w-full items-center justify-center">
|
||||||
|
Carregando...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen bg-gray-50 flex">
|
||||||
|
<div
|
||||||
|
className={`bg-white border-r border-gray-200 transition-all duration-300 fixed top-0 h-screen flex flex-col z-30 ${
|
||||||
|
sidebarCollapsed ? "w-16" : "w-64"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
||||||
|
{!sidebarCollapsed && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
|
||||||
|
<div className="w-4 h-4 bg-white rounded-sm"></div>
|
||||||
|
</div>
|
||||||
|
<span className="font-semibold text-gray-900">MedConnect</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setSidebarCollapsed(!sidebarCollapsed)}
|
||||||
|
className="p-1"
|
||||||
|
>
|
||||||
|
{sidebarCollapsed ? (
|
||||||
|
<ChevronRight className="w-4 h-4" />
|
||||||
|
) : (
|
||||||
|
<ChevronLeft className="w-4 h-4" />
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<nav className="flex-1 p-2 overflow-y-auto">
|
||||||
|
{menuItems.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
const isActive = pathname === item.href;
|
||||||
|
return (
|
||||||
|
<Link key={item.label} href={item.href}>
|
||||||
|
<div
|
||||||
|
className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${
|
||||||
|
isActive
|
||||||
|
? "bg-blue-50 text-blue-600 border-r-2 border-blue-600"
|
||||||
|
: "text-gray-600 hover:bg-gray-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||||
|
{!sidebarCollapsed && (
|
||||||
|
<span className="font-medium">{item.label}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
<SidebarUserSection
|
||||||
|
userData={userData}
|
||||||
|
sidebarCollapsed={false}
|
||||||
|
handleLogout={handleLogout}
|
||||||
|
isActive={role === "paciente"? false: true}>
|
||||||
|
</SidebarUserSection>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
className={`flex-1 flex flex-col transition-all duration-300 w-full ${
|
||||||
|
sidebarCollapsed ? "ml-16" : "ml-64"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<header className="bg-gray-50 px-4 md:px-6 py-4 flex items-center justify-between"></header>
|
||||||
|
<main className="flex-1 p-4 md:p-6">{children}</main>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
||||||
|
<DialogContent className="sm:max-w-md">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Confirmar Saída</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Deseja realmente sair do sistema? Você precisará fazer login
|
||||||
|
novamente para acessar sua conta.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter className="flex gap-2">
|
||||||
|
<Button variant="outline" onClick={cancelLogout}>
|
||||||
|
Cancelar
|
||||||
|
</Button>
|
||||||
|
<Button variant="destructive" onClick={confirmLogout}>
|
||||||
|
<LogOut className="mr-2 h-4 w-4" />
|
||||||
|
Sair
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,128 +0,0 @@
|
|||||||
// CÓDIGO REATORADO PARA: components/doctor-layout.tsx
|
|
||||||
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import type React from "react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useRouter, usePathname } from "next/navigation";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useAuthLayout } from "@/hooks/useAuthLayout"; // 1. Importamos nosso novo hook
|
|
||||||
import { api } from "@/services/api.mjs";
|
|
||||||
|
|
||||||
// Componentes da UI
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
||||||
import { Home, Calendar, Clock, User, LogOut, ChevronLeft, ChevronRight, Bell, FileText } from "lucide-react";
|
|
||||||
import { Badge } from "./ui/badge";
|
|
||||||
|
|
||||||
export default function DoctorLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
// 2. Usamos o hook para buscar o usuário e controlar o acesso para 'medico'
|
|
||||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'medico' });
|
|
||||||
|
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
||||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const confirmLogout = async () => {
|
|
||||||
await api.logout();
|
|
||||||
setShowLogoutDialog(false);
|
|
||||||
router.push("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
// ESTA PARTE É ÚNICA DE CADA LAYOUT E DEVE SER MANTIDA
|
|
||||||
const menuItems = [
|
|
||||||
{ href: "/doctor/dashboard", icon: Home, label: "Dashboard" },
|
|
||||||
{ href: "/doctor/consultas", icon: Calendar, label: "Consultas" },
|
|
||||||
{ href: "/doctor/medicos/editorlaudo", icon: Clock, label: "Editor de Laudo" },
|
|
||||||
{ href: "/doctor/medicos", icon: User, label: "patientes" },
|
|
||||||
{ href: "/doctor/disponibilidade", icon: Calendar, label: "Disponibilidade" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 3. Adicionamos o estado de carregamento
|
|
||||||
if (isLoading || !user) {
|
|
||||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gray-50 flex">
|
|
||||||
<div className={`bg-white border-r border-gray-200 transition-all duration-300 fixed top-0 h-screen flex flex-col z-30 ${sidebarCollapsed ? "w-16" : "w-64"}`}>
|
|
||||||
{/* Header da Sidebar */}
|
|
||||||
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center"><div className="w-4 h-4 bg-white rounded-sm"></div></div>
|
|
||||||
<span className="font-semibold text-gray-900">MediConnect</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
|
||||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Menu (específico deste layout) */}
|
|
||||||
<nav className="flex-1 p-2 overflow-y-auto">
|
|
||||||
{menuItems.map((item) => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
|
|
||||||
return (
|
|
||||||
<Link key={item.href} href={item.href}>
|
|
||||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-blue-50 text-blue-600 border-r-2 border-blue-600" : "text-gray-600 hover:bg-gray-50"}`}>
|
|
||||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
|
||||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{/* Rodapé com Avatar e Logout */}
|
|
||||||
<div className="border-t p-4 mt-auto">
|
|
||||||
<div className="flex items-center space-x-3 mb-4">
|
|
||||||
{/* 4. A LÓGICA DO AVATAR AGORA É APLICADA AQUI */}
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src={user.avatarFullUrl} />
|
|
||||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium text-gray-900 truncate">{user.name}</p>
|
|
||||||
<p className="text-xs text-gray-500 truncate">{user.roles.join(', ')}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="sm" className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} onClick={() => setShowLogoutDialog(true)}>
|
|
||||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
|
||||||
{!sidebarCollapsed && "Sair"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
|
||||||
<div className={`flex-1 flex flex-col transition-all duration-300 w-full ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
|
||||||
<header className="bg-white border-b border-gray-200 px-4 md:px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4 flex-1 max-w-md"></div>
|
|
||||||
<div className="flex items-center gap-4 ml-auto">
|
|
||||||
<Button variant="ghost" size="sm" className="relative">
|
|
||||||
<Bell className="w-5 h-5" />
|
|
||||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-red-500 text-white text-xs">1</Badge>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main className="flex-1 p-4 md:p-6">{children}</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Dialog de Logout */}
|
|
||||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader><DialogTitle>Confirmar Saída</DialogTitle><DialogDescription>Deseja realmente sair do sistema?</DialogDescription></DialogHeader>
|
|
||||||
<DialogFooter className="flex gap-2">
|
|
||||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
|
||||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
// CÓDIGO COMPLETO PARA: components/finance-layout.tsx
|
|
||||||
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import type React from "react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useRouter, usePathname } from "next/navigation";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
|
||||||
import { api } from "@/services/api.mjs";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
||||||
import { Home, Calendar, User, LogOut, ChevronLeft, ChevronRight, Bell } from "lucide-react";
|
|
||||||
import { Badge } from "./ui/badge";
|
|
||||||
|
|
||||||
export default function FinancierLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'finance' });
|
|
||||||
|
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
||||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const confirmLogout = async () => {
|
|
||||||
await api.logout();
|
|
||||||
setShowLogoutDialog(false);
|
|
||||||
router.push("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
const menuItems = [
|
|
||||||
{ href: "#", icon: Home, label: "Dashboard" },
|
|
||||||
{ href: "#", icon: Calendar, label: "Relatórios financeiros" },
|
|
||||||
{ href: "#", icon: User, label: "Finanças Gerais" },
|
|
||||||
{ href: "#", icon: Calendar, label: "Configurações" },
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isLoading || !user) {
|
|
||||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-background flex">
|
|
||||||
<div className={`bg-card border-r border-border transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} fixed left-0 top-0 h-screen flex flex-col z-10`}>
|
|
||||||
<div className="p-4 border-b border-border">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"><div className="w-4 h-4 bg-primary-foreground rounded-sm"></div></div>
|
|
||||||
<span className="font-semibold text-foreground">MediConnect</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
|
||||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nav className="flex-1 p-2 overflow-y-auto">
|
|
||||||
{menuItems.map((item) => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
|
|
||||||
return (
|
|
||||||
<Link key={item.href} href={item.href}>
|
|
||||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"}`}>
|
|
||||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
|
||||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
<div className="border-t p-4 mt-auto">
|
|
||||||
<div className="flex items-center space-x-3 mb-4">
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src={user.avatarFullUrl} />
|
|
||||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium text-foreground truncate">{user.name}</p>
|
|
||||||
<p className="text-xs text-muted-foreground truncate">{user.roles.join(', ')}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="sm" className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} onClick={() => setShowLogoutDialog(true)}>
|
|
||||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
|
||||||
{!sidebarCollapsed && "Sair"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
|
||||||
<header className="bg-card border-b border-border px-6 py-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4 flex-1 max-w-md"></div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button variant="ghost" size="sm" className="relative">
|
|
||||||
<Bell className="w-5 h-5" />
|
|
||||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-destructive text-destructive-foreground text-xs">1</Badge>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main className="flex-1 p-6">{children}</main>
|
|
||||||
</div>
|
|
||||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader><DialogTitle>Confirmar Saída</DialogTitle><DialogDescription>Deseja realmente sair do sistema?</DialogDescription></DialogHeader>
|
|
||||||
<DialogFooter className="flex gap-2">
|
|
||||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
|
||||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,121 +0,0 @@
|
|||||||
// CÓDIGO COMPLETO PARA: components/hospital-layout.tsx
|
|
||||||
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import type React from "react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter, usePathname } from "next/navigation";
|
|
||||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
|
||||||
import { api } from "@/services/api.mjs";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
||||||
import { Home, Calendar, Clock, FileText, User, LogOut, ChevronLeft, ChevronRight, Bell, Search } from "lucide-react";
|
|
||||||
import { Badge } from "./ui/badge";
|
|
||||||
import { Input } from "./ui/input";
|
|
||||||
|
|
||||||
export default function HospitalLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'patiente' });
|
|
||||||
|
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
||||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const confirmLogout = async () => {
|
|
||||||
await api.logout();
|
|
||||||
setShowLogoutDialog(false);
|
|
||||||
router.push("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
const menuItems = [
|
|
||||||
{ href: "/patient/dashboard", icon: Home, label: "Dashboard" },
|
|
||||||
{ href: "/patient/appointments", icon: Calendar, label: "Minhas Consultas" },
|
|
||||||
{ href: "/patient/schedule", icon: Clock, label: "Agendar Consulta" },
|
|
||||||
{ href: "/patient/reports", icon: FileText, label: "Meus Laudos" },
|
|
||||||
{ href: "/patient/profile", icon: User, label: "Meus Dados" },
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isLoading || !user) {
|
|
||||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-background flex">
|
|
||||||
<div className={`bg-card border-r border-border transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} h-screen flex flex-col`}>
|
|
||||||
<div className="p-4 border-b border-border">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"><div className="w-4 h-4 bg-primary-foreground rounded-sm"></div></div>
|
|
||||||
<span className="font-semibold text-foreground">MediConnect</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
|
||||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nav className="flex-1 p-2">
|
|
||||||
{menuItems.map((item) => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
|
|
||||||
return (
|
|
||||||
<Link key={item.href} href={item.href}>
|
|
||||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"}`}>
|
|
||||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
|
||||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
<div className="border-t p-4">
|
|
||||||
<div className="flex items-center space-x-3 mb-4">
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src={user.avatarFullUrl} />
|
|
||||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium text-foreground truncate">{user.name}</p>
|
|
||||||
<p className="text-xs text-muted-foreground truncate">{user.email}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="sm" className="w-full bg-transparent" onClick={() => setShowLogoutDialog(true)}>
|
|
||||||
<LogOut className="mr-2 h-4 w-4" /> Sair
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex-1 flex flex-col">
|
|
||||||
<header className="bg-card border-b border-border px-6 py-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4 flex-1 max-w-md">
|
|
||||||
<div className="relative flex-1">
|
|
||||||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
|
||||||
<Input placeholder="Buscar patiente" className="pl-10 bg-background border-border" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button variant="ghost" size="sm" className="relative">
|
|
||||||
<Bell className="w-5 h-5" />
|
|
||||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-destructive text-destructive-foreground text-xs">1</Badge>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main className="flex-1 p-6">{children}</main>
|
|
||||||
</div>
|
|
||||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader><DialogTitle>Confirmar Saída</DialogTitle><DialogDescription>Deseja realmente sair do sistema?</DialogDescription></DialogHeader>
|
|
||||||
<DialogFooter className="flex gap-2">
|
|
||||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
|
||||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,134 +0,0 @@
|
|||||||
// CÓDIGO REATORADO PARA: components/manager-layout.tsx
|
|
||||||
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import type React from "react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useRouter, usePathname } from "next/navigation";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useAuthLayout } from "@/hooks/useAuthLayout"; // 1. Importamos nosso novo hook
|
|
||||||
import { api } from "@/services/api.mjs";
|
|
||||||
|
|
||||||
// Componentes da UI (Button, Avatar, etc.)
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
||||||
import { Home, Calendar, User, LogOut, ChevronLeft, ChevronRight, Bell } from "lucide-react";
|
|
||||||
import { Badge } from "./ui/badge";
|
|
||||||
|
|
||||||
export default function ManagerLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
// 2. Usamos o hook para buscar o usuário e controlar o acesso
|
|
||||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'gestor' });
|
|
||||||
|
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
||||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const confirmLogout = async () => {
|
|
||||||
await api.logout();
|
|
||||||
setShowLogoutDialog(false);
|
|
||||||
router.push("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
const menuItems = [
|
|
||||||
{ href: "/manager/dashboard", icon: Home, label: "Dashboard" },
|
|
||||||
{ href: "#", icon: Calendar, label: "Relatórios gerenciais" },
|
|
||||||
{ href: "/manager/usuario", icon: User, label: "Gestão de Usuários" },
|
|
||||||
{ href: "/manager/home", icon: User, label: "Gestão de Médicos" },
|
|
||||||
{ href: "/manager/patientes", icon: User, label: "Gestão de patientes" },
|
|
||||||
{ href: "#", icon: Calendar, label: "Configurações" },
|
|
||||||
];
|
|
||||||
|
|
||||||
// 3. Enquanto o hook está carregando, mostramos uma tela de loading
|
|
||||||
if (isLoading || !user) {
|
|
||||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
// O resto do seu JSX continua igual, mas agora usando a variável 'user' do hook
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-gray-50 flex">
|
|
||||||
<div className={`bg-white border-r border-gray-200 transition-all duration-300 fixed top-0 h-screen flex flex-col z-30 ${sidebarCollapsed ? "w-16" : "w-64"}`}>
|
|
||||||
{/* Header da Sidebar */}
|
|
||||||
<div className="p-4 border-b border-gray-200 flex items-center justify-between">
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center">
|
|
||||||
<div className="w-4 h-4 bg-white rounded-sm"></div>
|
|
||||||
</div>
|
|
||||||
<span className="font-semibold text-gray-900">MediConnect</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
|
||||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Menu */}
|
|
||||||
<nav className="flex-1 p-2 overflow-y-auto">
|
|
||||||
{menuItems.map((item) => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
const isActive = pathname === item.href;
|
|
||||||
return (
|
|
||||||
<Link key={item.label} href={item.href}>
|
|
||||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-blue-50 text-blue-600 border-r-2 border-blue-600" : "text-gray-600 hover:bg-gray-50"}`}>
|
|
||||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
|
||||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
|
|
||||||
{/* Rodapé com Avatar e Logout */}
|
|
||||||
<div className="border-t p-4 mt-auto">
|
|
||||||
<div className="flex items-center space-x-3 mb-4">
|
|
||||||
{/* 4. A LÓGICA DO AVATAR AGORA É APLICADA AQUI */}
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src={user.avatarFullUrl} />
|
|
||||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium text-gray-900 truncate">{user.name}</p>
|
|
||||||
<p className="text-xs text-gray-500 truncate">{user.roles.join(', ')}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="sm" className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} onClick={() => setShowLogoutDialog(true)}>
|
|
||||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
|
||||||
{!sidebarCollapsed && "Sair"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Main Content */}
|
|
||||||
<div className={`flex-1 flex flex-col transition-all duration-300 w-full ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
|
||||||
<header className="bg-white border-b border-gray-200 px-4 md:px-6 py-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4 flex-1 max-w-md"></div>
|
|
||||||
<div className="flex items-center gap-4 ml-auto">
|
|
||||||
<Button variant="ghost" size="sm" className="relative">
|
|
||||||
<Bell className="w-5 h-5" />
|
|
||||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-red-500 text-white text-xs">1</Badge>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main className="flex-1 p-4 md:p-6">{children}</main>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Dialog de Logout */}
|
|
||||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader>
|
|
||||||
<DialogTitle>Confirmar Saída</DialogTitle>
|
|
||||||
<DialogDescription>Deseja realmente sair do sistema?</DialogDescription>
|
|
||||||
</DialogHeader>
|
|
||||||
<DialogFooter className="flex gap-2">
|
|
||||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
|
||||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,118 +0,0 @@
|
|||||||
// CÓDIGO COMPLETO PARA: components/patient-layout.tsx
|
|
||||||
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import type React from "react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useRouter, usePathname } from "next/navigation";
|
|
||||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
|
||||||
import { api } from "@/services/api.mjs";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
||||||
import { Home, Calendar, Clock, FileText, User, LogOut, ChevronLeft, ChevronRight, Bell } from "lucide-react";
|
|
||||||
import { Badge } from "./ui/badge";
|
|
||||||
|
|
||||||
export default function patientLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'patiente' });
|
|
||||||
|
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
||||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const confirmLogout = async () => {
|
|
||||||
await api.logout();
|
|
||||||
setShowLogoutDialog(false);
|
|
||||||
router.push("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
const menuItems = [
|
|
||||||
{ href: "/patient/dashboard", icon: Home, label: "Dashboard" },
|
|
||||||
{ href: "/patient/appointments", icon: Calendar, label: "Minhas Consultas" },
|
|
||||||
{ href: "/patient/schedule", icon: Clock, label: "Agendar Consulta" },
|
|
||||||
{ href: "/patient/reports", icon: FileText, label: "Meus Laudos" },
|
|
||||||
{ href: "/patient/profile", icon: User, label: "Meus Dados" },
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isLoading || !user) {
|
|
||||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-background flex">
|
|
||||||
<div className={`bg-card border-r border-border transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} fixed left-0 top-0 h-screen flex flex-col z-10`}>
|
|
||||||
<div className="p-4 border-b border-border">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"><div className="w-4 h-4 bg-primary-foreground rounded-sm"></div></div>
|
|
||||||
<span className="font-semibold text-foreground">MediConnect</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
|
||||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nav className="flex-1 p-2 overflow-y-auto">
|
|
||||||
{menuItems.map((item) => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
|
|
||||||
return (
|
|
||||||
<Link key={item.href} href={item.href}>
|
|
||||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"}`}>
|
|
||||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
|
||||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
<div className="border-t p-4 mt-auto">
|
|
||||||
<div className="flex items-center space-x-3 mb-4">
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src={user.avatarFullUrl} />
|
|
||||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium text-foreground truncate">{user.name}</p>
|
|
||||||
<p className="text-xs text-muted-foreground truncate">{user.email}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="sm" className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} onClick={() => setShowLogoutDialog(true)}>
|
|
||||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
|
||||||
{!sidebarCollapsed && "Sair"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
|
||||||
<header className="bg-card border-b border-border px-6 py-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4 flex-1 max-w-md"></div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button variant="ghost" size="sm" className="relative">
|
|
||||||
<Bell className="w-5 h-5" />
|
|
||||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-destructive text-destructive-foreground text-xs">1</Badge>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main className="flex-1 p-6">{children}</main>
|
|
||||||
</div>
|
|
||||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader><DialogTitle>Confirmar Saída</DialogTitle><DialogDescription>Deseja realmente sair do sistema?</DialogDescription></DialogHeader>
|
|
||||||
<DialogFooter className="flex gap-2">
|
|
||||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
|
||||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@ -1,117 +0,0 @@
|
|||||||
// CÓDIGO COMPLETO PARA: components/secretary-layout.tsx
|
|
||||||
|
|
||||||
"use client";
|
|
||||||
|
|
||||||
import type React from "react";
|
|
||||||
import { useState } from "react";
|
|
||||||
import { useRouter, usePathname } from "next/navigation";
|
|
||||||
import Link from "next/link";
|
|
||||||
import { useAuthLayout } from "@/hooks/useAuthLayout";
|
|
||||||
import { api } from "@/services/api.mjs";
|
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
||||||
import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog";
|
|
||||||
import { Home, Calendar, Clock, User, LogOut, ChevronLeft, ChevronRight, Bell } from "lucide-react";
|
|
||||||
import { Badge } from "./ui/badge";
|
|
||||||
|
|
||||||
export default function SecretaryLayout({ children }: { children: React.ReactNode }) {
|
|
||||||
const { user, isLoading } = useAuthLayout({ requiredRole: 'secretaria' });
|
|
||||||
|
|
||||||
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
||||||
const [showLogoutDialog, setShowLogoutDialog] = useState(false);
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
const confirmLogout = async () => {
|
|
||||||
await api.logout();
|
|
||||||
setShowLogoutDialog(false);
|
|
||||||
router.push("/");
|
|
||||||
};
|
|
||||||
|
|
||||||
const menuItems = [
|
|
||||||
{ href: "/secretary/dashboard", icon: Home, label: "Dashboard" },
|
|
||||||
{ href: "/secretary/appointments", icon: Calendar, label: "Consultas" },
|
|
||||||
{ href: "/secretary/schedule", icon: Clock, label: "Agendar Consulta" },
|
|
||||||
{ href: "/secretary/pacientes", icon: User, label: "pacientes" },
|
|
||||||
];
|
|
||||||
|
|
||||||
if (isLoading || !user) {
|
|
||||||
return <div className="flex h-screen w-full items-center justify-center">Carregando...</div>;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="min-h-screen bg-background flex">
|
|
||||||
<div className={`bg-card border-r border-border transition-all duration-300 ${sidebarCollapsed ? "w-16" : "w-64"} fixed left-0 top-0 h-screen flex flex-col z-10`}>
|
|
||||||
<div className="p-4 border-b border-border">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="w-8 h-8 bg-primary rounded-lg flex items-center justify-center"><div className="w-4 h-4 bg-primary-foreground rounded-sm"></div></div>
|
|
||||||
<span className="font-semibold text-foreground">MediConnect</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<Button variant="ghost" size="sm" onClick={() => setSidebarCollapsed(!sidebarCollapsed)} className="p-1">
|
|
||||||
{sidebarCollapsed ? <ChevronRight className="w-4 h-4" /> : <ChevronLeft className="w-4 h-4" />}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<nav className="flex-1 p-2 overflow-y-auto">
|
|
||||||
{menuItems.map((item) => {
|
|
||||||
const Icon = item.icon;
|
|
||||||
const isActive = pathname === item.href || (item.href !== "/" && pathname.startsWith(item.href));
|
|
||||||
return (
|
|
||||||
<Link key={item.href} href={item.href}>
|
|
||||||
<div className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${isActive ? "bg-accent text-accent-foreground" : "text-muted-foreground hover:bg-accent hover:text-accent-foreground"}`}>
|
|
||||||
<Icon className="w-5 h-5 flex-shrink-0" />
|
|
||||||
{!sidebarCollapsed && <span className="font-medium">{item.label}</span>}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
<div className="border-t p-4 mt-auto">
|
|
||||||
<div className="flex items-center space-x-3 mb-4">
|
|
||||||
<Avatar>
|
|
||||||
<AvatarImage src={user.avatarFullUrl} />
|
|
||||||
<AvatarFallback>{user.name.split(" ").map((n) => n[0]).join("")}</AvatarFallback>
|
|
||||||
</Avatar>
|
|
||||||
{!sidebarCollapsed && (
|
|
||||||
<div className="flex-1 min-w-0">
|
|
||||||
<p className="text-sm font-medium text-foreground truncate">{user.name}</p>
|
|
||||||
<p className="text-xs text-muted-foreground truncate">{user.email}</p>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<Button variant="outline" size="sm" className={sidebarCollapsed ? "w-full bg-transparent flex justify-center items-center p-2" : "w-full bg-transparent"} onClick={() => setShowLogoutDialog(true)}>
|
|
||||||
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
|
||||||
{!sidebarCollapsed && "Sair"}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div className={`flex-1 flex flex-col transition-all duration-300 ${sidebarCollapsed ? "ml-16" : "ml-64"}`}>
|
|
||||||
<header className="bg-card border-b border-border px-6 py-4">
|
|
||||||
<div className="flex items-center justify-between">
|
|
||||||
<div className="flex items-center gap-4 flex-1 max-w-md"></div>
|
|
||||||
<div className="flex items-center gap-4">
|
|
||||||
<Button variant="ghost" size="sm" className="relative">
|
|
||||||
<Bell className="w-5 h-5" />
|
|
||||||
<Badge className="absolute -top-1 -right-1 w-5 h-5 p-0 flex items-center justify-center bg-destructive text-destructive-foreground text-xs">1</Badge>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
<main className="flex-1 p-6">{children}</main>
|
|
||||||
</div>
|
|
||||||
<Dialog open={showLogoutDialog} onOpenChange={setShowLogoutDialog}>
|
|
||||||
<DialogContent className="sm:max-w-md">
|
|
||||||
<DialogHeader><DialogTitle>Confirmar Saída</DialogTitle><DialogDescription>Deseja realmente sair do sistema?</DialogDescription></DialogHeader>
|
|
||||||
<DialogFooter className="flex gap-2">
|
|
||||||
<Button variant="outline" onClick={() => setShowLogoutDialog(false)}>Cancelar</Button>
|
|
||||||
<Button variant="destructive" onClick={confirmLogout}><LogOut className="mr-2 h-4 w-4" />Sair</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
125
components/ui/userToolTip.tsx
Normal file
125
components/ui/userToolTip.tsx
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { CalendarCheck2, CalendarClock, ClipboardPlus, Home, LogOut, SquareUser } from "lucide-react";
|
||||||
|
import {
|
||||||
|
Popover,
|
||||||
|
PopoverTrigger,
|
||||||
|
PopoverContent,
|
||||||
|
} from "@/components/ui/popover";
|
||||||
|
import { usePathname } from "next/navigation";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
|
interface UserData {
|
||||||
|
user_metadata: {
|
||||||
|
full_name: string;
|
||||||
|
};
|
||||||
|
app_metadata: {
|
||||||
|
user_role: string;
|
||||||
|
};
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
userData: UserData;
|
||||||
|
sidebarCollapsed: boolean;
|
||||||
|
handleLogout: () => void;
|
||||||
|
isActive: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function SidebarUserSection({
|
||||||
|
userData,
|
||||||
|
sidebarCollapsed,
|
||||||
|
handleLogout,
|
||||||
|
isActive,
|
||||||
|
}: Props) {
|
||||||
|
const pathname = usePathname();
|
||||||
|
const menuItems: any[] = [
|
||||||
|
{ href: "/patient/schedule", icon: CalendarClock, label: "Agendar Consulta" },
|
||||||
|
{ href: "/patient/appointments", icon: CalendarCheck2, label: "Minhas Consultas" },
|
||||||
|
{ href: "/patient/reports", icon: ClipboardPlus, label: "Meus Laudos" },
|
||||||
|
{ href: "/patient/profile", icon: SquareUser, label: "Meus Dados" },
|
||||||
|
]
|
||||||
|
return (
|
||||||
|
<div className="border-t p-4 mt-auto">
|
||||||
|
{/* POPUP DE INFORMAÇÕES DO USUÁRIO */}
|
||||||
|
<Popover>
|
||||||
|
<PopoverTrigger asChild>
|
||||||
|
<div
|
||||||
|
className={`flex items-center space-x-3 mb-4 p-2 rounded-md transition-colors ${
|
||||||
|
isActive
|
||||||
|
? "cursor-pointer hover:bg-gray-100"
|
||||||
|
: "cursor-default pointer-events-none"
|
||||||
|
}`}>
|
||||||
|
<Avatar>
|
||||||
|
<AvatarImage src="/placeholder.svg?height=40&width=40" />
|
||||||
|
<AvatarFallback>
|
||||||
|
{userData.user_metadata.full_name
|
||||||
|
.split(" ")
|
||||||
|
.map((n) => n[0])
|
||||||
|
.join("")}
|
||||||
|
</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
|
||||||
|
{!sidebarCollapsed && (
|
||||||
|
<div className="flex-1 min-w-0">
|
||||||
|
<p className="text-sm font-medium text-gray-900 truncate">
|
||||||
|
{userData.user_metadata.full_name}
|
||||||
|
</p>
|
||||||
|
<p className="text-xs text-gray-500 truncate">
|
||||||
|
{userData.app_metadata.user_role}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</PopoverTrigger>
|
||||||
|
|
||||||
|
{/* Card flutuante */}
|
||||||
|
<PopoverContent
|
||||||
|
align="center"
|
||||||
|
side="top"
|
||||||
|
className="w-64 p-4 shadow-lg border bg-white"
|
||||||
|
>
|
||||||
|
<nav>
|
||||||
|
{menuItems.map((item) => {
|
||||||
|
const Icon = item.icon;
|
||||||
|
const isActive = pathname === item.href;
|
||||||
|
return (
|
||||||
|
<Link key={item.label} href={item.href}>
|
||||||
|
<div
|
||||||
|
className={`flex items-center gap-3 px-3 py-2 rounded-lg mb-1 transition-colors ${
|
||||||
|
isActive
|
||||||
|
? "bg-blue-50 text-blue-600 border-r-2 border-blue-600"
|
||||||
|
: "text-gray-600 hover:bg-gray-50"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<Icon className="w-5 h-5 flex-shrink-0" />
|
||||||
|
{!sidebarCollapsed && (
|
||||||
|
<span className="font-medium">{item.label}</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Link>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</nav>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
|
||||||
|
{/* Botão de sair */}
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className={
|
||||||
|
sidebarCollapsed
|
||||||
|
? "w-full bg-transparent flex justify-center items-center p-2"
|
||||||
|
: "w-full bg-transparent"
|
||||||
|
}
|
||||||
|
onClick={handleLogout}
|
||||||
|
>
|
||||||
|
<LogOut className={sidebarCollapsed ? "h-5 w-5" : "mr-2 h-4 w-4"} />
|
||||||
|
{sidebarCollapsed && "Sair"}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -21,6 +21,10 @@ export const usersService = {
|
|||||||
return await api.post(`/functions/v1/create-user-with-password`, data);
|
return await api.post(`/functions/v1/create-user-with-password`, data);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async getMeSimple() {
|
||||||
|
return await api.post(`/functions/v1/user-info`);
|
||||||
|
},
|
||||||
|
|
||||||
async full_data(user_id) {
|
async full_data(user_id) {
|
||||||
if (!user_id) throw new Error("user_id é obrigatório");
|
if (!user_id) throw new Error("user_id é obrigatório");
|
||||||
|
|
||||||
@ -57,4 +61,40 @@ export const usersService = {
|
|||||||
permissions,
|
permissions,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
async resetPassword(email) {
|
||||||
|
if (!email) throw new Error("Email é obrigatório para resetar a senha.");
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(
|
||||||
|
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/auth/v1/recover`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
apikey: process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ email }),
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const data = await res.json().catch(() => ({}));
|
||||||
|
|
||||||
|
|
||||||
|
if (!res.ok) {
|
||||||
|
console.error("Erro no resetPassword:", res.status, data);
|
||||||
|
throw new Error(`Erro ${res.status}: ${data.message || "Falha ao resetar senha."}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
console.log("✅ Reset de senha:", data);
|
||||||
|
return data;
|
||||||
|
} catch (err) {
|
||||||
|
console.error("❌ Erro na chamada resetPassword:", err);
|
||||||
|
throw new Error(err.message || "Erro inesperado na recuperação de senha.");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
};
|
};
|
||||||
Loading…
x
Reference in New Issue
Block a user