|
@@ -13,11 +13,12 @@ import {
|
|
|
DialogHeader,
|
|
DialogHeader,
|
|
|
DialogTitle,
|
|
DialogTitle,
|
|
|
} from "@/components/ui/dialog";
|
|
} from "@/components/ui/dialog";
|
|
|
-import { Loader2, Video, AlertTriangle, FileText } from "lucide-react";
|
|
|
|
|
|
|
+import { Loader2, Video, AlertTriangle, FileText, Clock, XCircle } from "lucide-react";
|
|
|
import { ConsultationNotes } from "@/components/appointments/ConsultationNotes";
|
|
import { ConsultationNotes } from "@/components/appointments/ConsultationNotes";
|
|
|
import RecordsModal from "@/components/records/RecordsModal";
|
|
import RecordsModal from "@/components/records/RecordsModal";
|
|
|
import type { Record as MedicalRecord } from "@/components/records/types";
|
|
import type { Record as MedicalRecord } from "@/components/records/types";
|
|
|
import type { Appointment } from "@/types/appointments";
|
|
import type { Appointment } from "@/types/appointments";
|
|
|
|
|
+import { canJoinMeeting, getAppointmentTimeStatus } from "@/utils/appointments";
|
|
|
|
|
|
|
|
interface JitsiMeetExternalAPI {
|
|
interface JitsiMeetExternalAPI {
|
|
|
dispose: () => void;
|
|
dispose: () => void;
|
|
@@ -41,6 +42,9 @@ export default function MeetPage() {
|
|
|
const [showExitDialog, setShowExitDialog] = useState(false);
|
|
const [showExitDialog, setShowExitDialog] = useState(false);
|
|
|
const [showRecordsModal, setShowRecordsModal] = useState(false);
|
|
const [showRecordsModal, setShowRecordsModal] = useState(false);
|
|
|
const [appointment, setAppointment] = useState<Appointment | null>(null);
|
|
const [appointment, setAppointment] = useState<Appointment | null>(null);
|
|
|
|
|
+ const [loading, setLoading] = useState(true);
|
|
|
|
|
+ const [accessDenied, setAccessDenied] = useState(false);
|
|
|
|
|
+ const [denialReason, setDenialReason] = useState("");
|
|
|
|
|
|
|
|
// Cargar información del appointment
|
|
// Cargar información del appointment
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
@@ -50,9 +54,29 @@ export default function MeetPage() {
|
|
|
if (response.ok) {
|
|
if (response.ok) {
|
|
|
const data = await response.json();
|
|
const data = await response.json();
|
|
|
setAppointment(data);
|
|
setAppointment(data);
|
|
|
|
|
+
|
|
|
|
|
+ // Validar acceso por tiempo
|
|
|
|
|
+ const timeCheck = canJoinMeeting(data.fechaSolicitada);
|
|
|
|
|
+ if (!timeCheck.canJoin) {
|
|
|
|
|
+ setAccessDenied(true);
|
|
|
|
|
+ setDenialReason(timeCheck.reason || "No puedes acceder a esta videollamada");
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // Validar que la cita esté aprobada
|
|
|
|
|
+ if (data.estado !== "APROBADA" && data.estado !== "COMPLETADA") {
|
|
|
|
|
+ setAccessDenied(true);
|
|
|
|
|
+ setDenialReason("Esta cita no está aprobada");
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ setAccessDenied(true);
|
|
|
|
|
+ setDenialReason("No se pudo cargar la información de la cita");
|
|
|
}
|
|
}
|
|
|
} catch (error) {
|
|
} catch (error) {
|
|
|
console.error("Error loading appointment:", error);
|
|
console.error("Error loading appointment:", error);
|
|
|
|
|
+ setAccessDenied(true);
|
|
|
|
|
+ setDenialReason("Error al cargar la cita");
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setLoading(false);
|
|
|
}
|
|
}
|
|
|
};
|
|
};
|
|
|
|
|
|
|
@@ -211,7 +235,7 @@ export default function MeetPage() {
|
|
|
};
|
|
};
|
|
|
}, [status, session, initJitsi]);
|
|
}, [status, session, initJitsi]);
|
|
|
|
|
|
|
|
- if (status === "loading") {
|
|
|
|
|
|
|
+ if (status === "loading" || loading) {
|
|
|
return (
|
|
return (
|
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
<div className="flex items-center justify-center min-h-screen">
|
|
|
<Loader2 className="h-8 w-8 animate-spin" />
|
|
<Loader2 className="h-8 w-8 animate-spin" />
|
|
@@ -223,6 +247,46 @@ export default function MeetPage() {
|
|
|
redirect("/auth/login");
|
|
redirect("/auth/login");
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // Mostrar pantalla de acceso denegado si no cumple las condiciones
|
|
|
|
|
+ if (accessDenied) {
|
|
|
|
|
+ return (
|
|
|
|
|
+ <div className="container mx-auto px-4 py-8 max-w-2xl">
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <div className="flex items-center gap-2 text-destructive">
|
|
|
|
|
+ <XCircle className="h-6 w-6" />
|
|
|
|
|
+ <CardTitle>Acceso no permitido</CardTitle>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-4">
|
|
|
|
|
+ <p className="text-muted-foreground">{denialReason}</p>
|
|
|
|
|
+
|
|
|
|
|
+ {appointment?.fechaSolicitada && (
|
|
|
|
|
+ <div className="bg-muted p-4 rounded-lg">
|
|
|
|
|
+ <div className="flex items-center gap-2 mb-2">
|
|
|
|
|
+ <Clock className="h-5 w-5" />
|
|
|
|
|
+ <p className="font-medium">Estado de la cita</p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <p className="text-sm text-muted-foreground">
|
|
|
|
|
+ {getAppointmentTimeStatus(appointment.fechaSolicitada)}
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ <div className="flex gap-2">
|
|
|
|
|
+ <Button onClick={() => router.push(`/appointments/${params.id}`)}>
|
|
|
|
|
+ Ver detalles de la cita
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button variant="outline" onClick={() => router.push("/appointments")}>
|
|
|
|
|
+ Volver a mis citas
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ );
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
const isDoctor = session.user.role === "DOCTOR";
|
|
const isDoctor = session.user.role === "DOCTOR";
|
|
|
const appointmentId = params.id as string;
|
|
const appointmentId = params.id as string;
|
|
|
|
|
|