vision refactor
This commit is contained in:
parent
0a912b3ac9
commit
0b54ec93ee
@ -1,6 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState, useRef, useEffect } from "react";
|
import { useState, useRef, useEffect } from "react";
|
||||||
|
import { useSession } from "next-auth/react";
|
||||||
import FullCalendar from "@fullcalendar/react";
|
import FullCalendar from "@fullcalendar/react";
|
||||||
import dayGridPlugin from "@fullcalendar/daygrid";
|
import dayGridPlugin from "@fullcalendar/daygrid";
|
||||||
import timeGridPlugin from "@fullcalendar/timegrid";
|
import timeGridPlugin from "@fullcalendar/timegrid";
|
||||||
@ -116,6 +117,7 @@ interface EventFormData {
|
|||||||
allDay: boolean;
|
allDay: boolean;
|
||||||
location: string | null;
|
location: string | null;
|
||||||
calendarId?: string;
|
calendarId?: string;
|
||||||
|
isVideoConference?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CalendarDialogProps {
|
interface CalendarDialogProps {
|
||||||
@ -726,6 +728,10 @@ function EventPreview({ event, calendar }: { event: Event; calendar: Calendar })
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function CalendarClient({ initialCalendars, userId, userProfile }: CalendarClientProps) {
|
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
|
// Filter out "Privée" and "Default" calendars that are not synced
|
||||||
const filterCalendars = (cals: typeof initialCalendars) => {
|
const filterCalendars = (cals: typeof initialCalendars) => {
|
||||||
return cals.filter(cal => {
|
return cals.filter(cal => {
|
||||||
@ -1068,7 +1074,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
end: endDate.toISOString(),
|
end: endDate.toISOString(),
|
||||||
allDay: selectInfo.allDay || false,
|
allDay: selectInfo.allDay || false,
|
||||||
location: null,
|
location: null,
|
||||||
calendarId: calendarToUse
|
calendarId: calendarToUse,
|
||||||
|
isVideoConference: false
|
||||||
});
|
});
|
||||||
|
|
||||||
setIsEventModalOpen(true);
|
setIsEventModalOpen(true);
|
||||||
@ -1088,6 +1095,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
// If not available, use the first available calendar instead
|
// If not available, use the first available calendar instead
|
||||||
const calendarIdToUse = canEditEventCalendar ? eventCalendarId : (availableCalendars[0]?.id || eventCalendarId);
|
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);
|
setSelectedEvent(event.extendedProps.originalEvent);
|
||||||
setEventForm({
|
setEventForm({
|
||||||
title: event.title,
|
title: event.title,
|
||||||
@ -1095,6 +1104,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
start: startDate.toISOString().slice(0, 16),
|
start: startDate.toISOString().slice(0, 16),
|
||||||
end: endDate.toISOString().slice(0, 16),
|
end: endDate.toISOString().slice(0, 16),
|
||||||
allDay: event.isAllDay,
|
allDay: event.isAllDay,
|
||||||
|
isVideoConference: isVideoConference,
|
||||||
location: event.extendedProps.location,
|
location: event.extendedProps.location,
|
||||||
calendarId: calendarIdToUse,
|
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
|
// Get events for the selected date
|
||||||
const getDayEvents = () => {
|
const getDayEvents = () => {
|
||||||
if (!selectedDate) return [];
|
if (!selectedDate) return [];
|
||||||
@ -1907,15 +2021,16 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<Dialog open={isEventModalOpen} onOpenChange={(open) => {
|
<Dialog open={isEventModalOpen} onOpenChange={(open) => {
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setIsEventModalOpen(false);
|
setIsEventModalOpen(false);
|
||||||
setEventForm({
|
setEventForm({
|
||||||
title: "",
|
title: "",
|
||||||
description: null,
|
description: null,
|
||||||
start: "",
|
start: "",
|
||||||
end: "",
|
end: "",
|
||||||
allDay: false,
|
allDay: false,
|
||||||
location: null,
|
location: null,
|
||||||
calendarId: selectedCalendarId || calendars[0]?.id
|
calendarId: selectedCalendarId || calendars[0]?.id,
|
||||||
});
|
isVideoConference: false
|
||||||
|
});
|
||||||
setSelectedEvent(null);
|
setSelectedEvent(null);
|
||||||
setError(null);
|
setError(null);
|
||||||
}
|
}
|
||||||
@ -1989,7 +2104,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-gray-800">Début</Label>
|
<Label className="text-gray-800">Début</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-[2] min-w-0">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={getDateFromString(eventForm.start)}
|
selected={getDateFromString(eventForm.start)}
|
||||||
onChange={handleStartDateChange}
|
onChange={handleStartDateChange}
|
||||||
@ -1997,7 +2112,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
locale="fr"
|
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"
|
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"
|
placeholderText="Date"
|
||||||
customInput={<Input className="bg-white text-gray-900" />}
|
customInput={<Input className="bg-white text-gray-900 w-full" />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<DatePicker
|
<DatePicker
|
||||||
@ -2008,8 +2123,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
timeIntervals={15}
|
timeIntervals={15}
|
||||||
timeCaption="Heure"
|
timeCaption="Heure"
|
||||||
dateFormat="HH:mm"
|
dateFormat="HH:mm"
|
||||||
className="w-32 bg-white text-gray-900"
|
className="w-20 bg-white text-gray-900"
|
||||||
customInput={<Input className="bg-white text-gray-900" />}
|
customInput={<Input className="bg-white text-gray-900 w-20" />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -2017,7 +2132,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label className="text-gray-800">Fin</Label>
|
<Label className="text-gray-800">Fin</Label>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<div className="flex-1">
|
<div className="flex-[2] min-w-0">
|
||||||
<DatePicker
|
<DatePicker
|
||||||
selected={getDateFromString(eventForm.end)}
|
selected={getDateFromString(eventForm.end)}
|
||||||
onChange={handleEndDateChange}
|
onChange={handleEndDateChange}
|
||||||
@ -2025,7 +2140,7 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
locale="fr"
|
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"
|
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"
|
placeholderText="Date"
|
||||||
customInput={<Input className="bg-white text-gray-900" />}
|
customInput={<Input className="bg-white text-gray-900 w-full" />}
|
||||||
minDate={getDateFromString(eventForm.start)}
|
minDate={getDateFromString(eventForm.start)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -2037,8 +2152,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
timeIntervals={15}
|
timeIntervals={15}
|
||||||
timeCaption="Heure"
|
timeCaption="Heure"
|
||||||
dateFormat="HH:mm"
|
dateFormat="HH:mm"
|
||||||
className="w-32 bg-white text-gray-900"
|
className="w-20 bg-white text-gray-900"
|
||||||
customInput={<Input className="bg-white text-gray-900" />}
|
customInput={<Input className="bg-white text-gray-900 w-20" />}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
<Label htmlFor="allDay" className="text-gray-800">Toute la journée</Label>
|
||||||
</div>
|
</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">
|
<div className="space-y-2">
|
||||||
<Label className="text-gray-800">Lieu</Label>
|
<Label className="text-gray-800">Lieu</Label>
|
||||||
<Input
|
<Input
|
||||||
@ -2063,7 +2188,8 @@ export function CalendarClient({ initialCalendars, userId, userProfile }: Calend
|
|||||||
onChange={(e) =>
|
onChange={(e) =>
|
||||||
setEventForm({ ...eventForm, location: e.target.value })
|
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"
|
className="bg-white text-gray-900"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user