vision refactor

This commit is contained in:
alma 2026-01-15 22:03:25 +01:00
parent 0a912b3ac9
commit 0b54ec93ee

View File

@ -1,6 +1,7 @@
"use client";
import { useState, useRef, useEffect } from "react";
import { useSession } from "next-auth/react";
import FullCalendar from "@fullcalendar/react";
import dayGridPlugin from "@fullcalendar/daygrid";
import timeGridPlugin from "@fullcalendar/timegrid";
@ -116,6 +117,7 @@ interface EventFormData {
allDay: boolean;
location: string | null;
calendarId?: string;
isVideoConference?: boolean;
}
interface CalendarDialogProps {
@ -726,6 +728,10 @@ function EventPreview({ event, calendar }: { event: Event; calendar: Calendar })
}
export function CalendarClient({ initialCalendars, userId, userProfile }: CalendarClientProps) {
const { data: session } = useSession();
const [groups, setGroups] = useState<Array<{ id: string; name: string }>>([]);
const [missions, setMissions] = useState<Array<{ id: string; name: string }>>([]);
// Filter out "Privée" and "Default" calendars that are not synced
const filterCalendars = (cals: typeof initialCalendars) => {
return cals.filter(cal => {
@ -1068,7 +1074,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
end: endDate.toISOString(),
allDay: selectInfo.allDay || false,
location: null,
calendarId: calendarToUse
calendarId: calendarToUse,
isVideoConference: false
});
setIsEventModalOpen(true);
@ -1088,6 +1095,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
// If not available, use the first available calendar instead
const calendarIdToUse = canEditEventCalendar ? eventCalendarId : (availableCalendars[0]?.id || eventCalendarId);
const location = event.extendedProps.location || null;
const isVideoConference = !!location?.includes('vision.slm-lab.net') || !!location?.includes('jitsi');
setSelectedEvent(event.extendedProps.originalEvent);
setEventForm({
title: event.title,
@ -1095,6 +1104,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
start: startDate.toISOString().slice(0, 16),
end: endDate.toISOString().slice(0, 16),
allDay: event.isAllDay,
isVideoConference: isVideoConference,
location: event.extendedProps.location,
calendarId: calendarIdToUse,
});
@ -1418,6 +1428,110 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
});
};
// Fetch groups and missions when event modal opens
useEffect(() => {
if (isEventModalOpen && session?.user?.id) {
const fetchData = async () => {
try {
// Fetch groups
const groupsRes = await fetch(`/api/users/${session.user.id}/groups`);
if (groupsRes.ok) {
const groupsData = await groupsRes.json();
setGroups(Array.isArray(groupsData) ? groupsData : []);
}
// Fetch missions
const missionsRes = await fetch("/api/missions?limit=1000");
if (missionsRes.ok) {
const missionsData = await missionsRes.json();
const allMissions = missionsData.missions || [];
// Filter missions where user is creator or member
const userMissions = allMissions.filter((mission: any) => {
const isCreator = mission.creator?.id === session.user.id;
const isMember = mission.missionUsers?.some(
(mu: any) => mu.user?.id === session.user.id
);
return isCreator || isMember;
});
setMissions(userMissions);
}
} catch (error) {
console.error("Error fetching groups/missions:", error);
}
};
fetchData();
}
}, [isEventModalOpen, session]);
// Generate Jitsi URL based on calendar type
const generateJitsiUrl = (): string => {
if (!eventForm.calendarId) return "";
const selectedCalendar = calendars.find(cal => cal.id === eventForm.calendarId);
if (!selectedCalendar) return "";
const baseUrl = process.env.NEXT_PUBLIC_IFRAME_CONFERENCE_URL || 'https://vision.slm-lab.net';
const calendarName = selectedCalendar.name || "";
// Check if it's a Group calendar
if (calendarName.startsWith("Groupe:")) {
const groupName = calendarName.replace("Groupe: ", "");
// Find matching group by name to get the real ID
const matchingGroup = groups.find((g: { id: string; name: string }) => g.name === groupName);
if (matchingGroup) {
return `${baseUrl}/Groupe-${matchingGroup.id}`;
}
// Fallback: use group name as ID
const groupId = groupName.toLowerCase().replace(/\s+/g, '-');
return `${baseUrl}/Groupe-${groupId}`;
}
// Check if it's a Mission calendar
if (calendarName.startsWith("Mission:")) {
// Use missionId from calendar if available (most reliable)
const missionId = (selectedCalendar as CalendarWithMission).missionId;
if (missionId) {
return `${baseUrl}/${missionId}`;
}
// Fallback: find matching mission by name
const missionName = calendarName.replace("Mission: ", "");
const matchingMission = missions.find((m: { id: string; name: string }) => m.name === missionName);
if (matchingMission) {
return `${baseUrl}/${matchingMission.id}`;
}
// Last fallback: use mission name as ID
const missionIdFromName = missionName.toLowerCase().replace(/\s+/g, '-');
return `${baseUrl}/${missionIdFromName}`;
}
// For "Mon Calendrier" or Microsoft calendars, generate a generic Jitsi room
const userId = session?.user?.id || 'user';
const timestamp = Date.now();
const randomId = Math.random().toString(36).substring(2, 9);
return `${baseUrl}/${randomId}-${timestamp}`;
};
// Handle video conference checkbox change
const handleVideoConferenceChange = (checked: boolean) => {
setEventForm(prev => ({
...prev,
isVideoConference: checked,
location: checked ? generateJitsiUrl() : null
}));
};
// Update location when calendar changes and video conference is checked
useEffect(() => {
if (eventForm.isVideoConference && eventForm.calendarId) {
const jitsiUrl = generateJitsiUrl();
setEventForm(prev => ({
...prev,
location: jitsiUrl
}));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [eventForm.calendarId, eventForm.isVideoConference, groups.length, missions.length]);
// Get events for the selected date
const getDayEvents = () => {
if (!selectedDate) return [];
@ -1907,15 +2021,16 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
<Dialog open={isEventModalOpen} onOpenChange={(open) => {
if (!open) {
setIsEventModalOpen(false);
setEventForm({
title: "",
description: null,
start: "",
end: "",
allDay: false,
location: null,
calendarId: selectedCalendarId || calendars[0]?.id
});
setEventForm({
title: "",
description: null,
start: "",
end: "",
allDay: false,
location: null,
calendarId: selectedCalendarId || calendars[0]?.id,
isVideoConference: false
});
setSelectedEvent(null);
setError(null);
}
@ -1989,7 +2104,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
<div className="space-y-2">
<Label className="text-gray-800">Début</Label>
<div className="flex gap-2">
<div className="flex-1">
<div className="flex-[2] min-w-0">
<DatePicker
selected={getDateFromString(eventForm.start)}
onChange={handleStartDateChange}
@ -1997,7 +2112,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
locale="fr"
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary bg-white text-gray-900"
placeholderText="Date"
customInput={<Input className="bg-white text-gray-900" />}
customInput={<Input className="bg-white text-gray-900 w-full" />}
/>
</div>
<DatePicker
@ -2008,8 +2123,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
timeIntervals={15}
timeCaption="Heure"
dateFormat="HH:mm"
className="w-32 bg-white text-gray-900"
customInput={<Input className="bg-white text-gray-900" />}
className="w-20 bg-white text-gray-900"
customInput={<Input className="bg-white text-gray-900 w-20" />}
/>
</div>
</div>
@ -2017,7 +2132,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
<div className="space-y-2">
<Label className="text-gray-800">Fin</Label>
<div className="flex gap-2">
<div className="flex-1">
<div className="flex-[2] min-w-0">
<DatePicker
selected={getDateFromString(eventForm.end)}
onChange={handleEndDateChange}
@ -2025,7 +2140,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
locale="fr"
className="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary bg-white text-gray-900"
placeholderText="Date"
customInput={<Input className="bg-white text-gray-900" />}
customInput={<Input className="bg-white text-gray-900 w-full" />}
minDate={getDateFromString(eventForm.start)}
/>
</div>
@ -2037,8 +2152,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
timeIntervals={15}
timeCaption="Heure"
dateFormat="HH:mm"
className="w-32 bg-white text-gray-900"
customInput={<Input className="bg-white text-gray-900" />}
className="w-20 bg-white text-gray-900"
customInput={<Input className="bg-white text-gray-900 w-20" />}
/>
</div>
</div>
@ -2056,6 +2171,16 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
<Label htmlFor="allDay" className="text-gray-800">Toute la journée</Label>
</div>
<div className="flex items-center space-x-2">
<Checkbox
id="video-conference"
checked={eventForm.isVideoConference || false}
onCheckedChange={handleVideoConferenceChange}
disabled={!eventForm.calendarId}
/>
<Label htmlFor="video-conference" className="text-gray-800">Visioconférence</Label>
</div>
<div className="space-y-2">
<Label className="text-gray-800">Lieu</Label>
<Input
@ -2063,7 +2188,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
onChange={(e) =>
setEventForm({ ...eventForm, location: e.target.value })
}
placeholder="Ajouter un lieu"
placeholder={eventForm.isVideoConference ? 'Lien Jitsi généré automatiquement' : 'Ajouter un lieu'}
disabled={eventForm.isVideoConference}
className="bg-white text-gray-900"
/>
</div>