Vision Refactor
This commit is contained in:
parent
1257f8cff3
commit
8c51fae1a7
@ -4,9 +4,14 @@ import { useState, useEffect } from "react";
|
||||
import { useSession } from "next-auth/react";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ResponsiveIframe } from "@/app/components/responsive-iframe";
|
||||
import { Users, FolderKanban, Video, ArrowLeft, Loader2 } from "lucide-react";
|
||||
import { Users, FolderKanban, Video, ArrowLeft, Loader2, Calendar, Clock, Plus, X } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { useToast } from "@/components/ui/use-toast";
|
||||
import { Calendar as CalendarComponent } from "@/components/ui/calendar";
|
||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Badge } from "@/components/ui/badge";
|
||||
|
||||
interface Group {
|
||||
id: string;
|
||||
@ -20,6 +25,16 @@ interface Mission {
|
||||
logoUrl?: string | null;
|
||||
}
|
||||
|
||||
interface ScheduledMeeting {
|
||||
id: string;
|
||||
type: "group" | "mission";
|
||||
entityId: string;
|
||||
entityName: string;
|
||||
date: string; // ISO date string
|
||||
time: string; // HH:mm format
|
||||
title?: string;
|
||||
}
|
||||
|
||||
type ConferenceType = "group" | "mission" | null;
|
||||
|
||||
export default function VisionPage() {
|
||||
@ -34,6 +49,25 @@ export default function VisionPage() {
|
||||
name: string;
|
||||
} | null>(null);
|
||||
const [jitsiUrl, setJitsiUrl] = useState<string | null>(null);
|
||||
const [scheduledMeetings, setScheduledMeetings] = useState<ScheduledMeeting[]>([]);
|
||||
const [showCalendar, setShowCalendar] = useState(false);
|
||||
const [showMeetingDialog, setShowMeetingDialog] = useState(false);
|
||||
const [selectedDate, setSelectedDate] = useState<Date | undefined>(new Date());
|
||||
const [meetingForm, setMeetingForm] = useState<{
|
||||
type: "group" | "mission" | "";
|
||||
entityId: string;
|
||||
entityName: string;
|
||||
date: string;
|
||||
time: string;
|
||||
title: string;
|
||||
}>({
|
||||
type: "",
|
||||
entityId: "",
|
||||
entityName: "",
|
||||
date: "",
|
||||
time: "",
|
||||
title: "",
|
||||
});
|
||||
|
||||
// Redirect if not authenticated
|
||||
useEffect(() => {
|
||||
@ -42,6 +76,27 @@ export default function VisionPage() {
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
// Load scheduled meetings from localStorage
|
||||
useEffect(() => {
|
||||
if (status === "authenticated" && session?.user?.id) {
|
||||
const stored = localStorage.getItem(`meetings_${session.user.id}`);
|
||||
if (stored) {
|
||||
try {
|
||||
setScheduledMeetings(JSON.parse(stored));
|
||||
} catch (e) {
|
||||
console.error("Error loading meetings:", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [session, status]);
|
||||
|
||||
// Save scheduled meetings to localStorage
|
||||
useEffect(() => {
|
||||
if (status === "authenticated" && session?.user?.id && scheduledMeetings.length >= 0) {
|
||||
localStorage.setItem(`meetings_${session.user.id}`, JSON.stringify(scheduledMeetings));
|
||||
}
|
||||
}, [scheduledMeetings, session, status]);
|
||||
|
||||
// Fetch user groups and missions
|
||||
useEffect(() => {
|
||||
const fetchData = async () => {
|
||||
@ -119,6 +174,84 @@ export default function VisionPage() {
|
||||
setJitsiUrl(null);
|
||||
};
|
||||
|
||||
// Get today's meetings
|
||||
const getTodayMeetings = (): ScheduledMeeting[] => {
|
||||
const today = new Date();
|
||||
const todayStr = today.toISOString().split('T')[0];
|
||||
return scheduledMeetings.filter(meeting => meeting.date === todayStr);
|
||||
};
|
||||
|
||||
// Get meetings for a specific date
|
||||
const getMeetingsForDate = (date: Date): ScheduledMeeting[] => {
|
||||
const dateStr = date.toISOString().split('T')[0];
|
||||
return scheduledMeetings.filter(meeting => meeting.date === dateStr);
|
||||
};
|
||||
|
||||
// Handle open meeting dialog
|
||||
const handleOpenMeetingDialog = (type: "group" | "mission", id: string, name: string) => {
|
||||
setMeetingForm({
|
||||
type,
|
||||
entityId: id,
|
||||
entityName: name,
|
||||
date: selectedDate ? selectedDate.toISOString().split('T')[0] : new Date().toISOString().split('T')[0],
|
||||
time: "",
|
||||
title: "",
|
||||
});
|
||||
setShowMeetingDialog(true);
|
||||
};
|
||||
|
||||
// Handle save meeting
|
||||
const handleSaveMeeting = () => {
|
||||
if (!meetingForm.type || !meetingForm.entityId || !meetingForm.date || !meetingForm.time) {
|
||||
toast({
|
||||
title: "Erreur",
|
||||
description: "Veuillez remplir tous les champs requis",
|
||||
variant: "destructive",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const newMeeting: ScheduledMeeting = {
|
||||
id: `${Date.now()}-${Math.random()}`,
|
||||
type: meetingForm.type,
|
||||
entityId: meetingForm.entityId,
|
||||
entityName: meetingForm.entityName,
|
||||
date: meetingForm.date,
|
||||
time: meetingForm.time,
|
||||
title: meetingForm.title || `${meetingForm.type === "group" ? "Groupe" : "Mission"}: ${meetingForm.entityName}`,
|
||||
};
|
||||
|
||||
setScheduledMeetings([...scheduledMeetings, newMeeting]);
|
||||
setShowMeetingDialog(false);
|
||||
setMeetingForm({
|
||||
type: "",
|
||||
entityId: "",
|
||||
entityName: "",
|
||||
date: "",
|
||||
time: "",
|
||||
title: "",
|
||||
});
|
||||
toast({
|
||||
title: "Succès",
|
||||
description: "Réunion planifiée avec succès",
|
||||
});
|
||||
};
|
||||
|
||||
// Handle delete meeting
|
||||
const handleDeleteMeeting = (meetingId: string) => {
|
||||
setScheduledMeetings(scheduledMeetings.filter(m => m.id !== meetingId));
|
||||
toast({
|
||||
title: "Succès",
|
||||
description: "Réunion supprimée",
|
||||
});
|
||||
};
|
||||
|
||||
// Format date for display
|
||||
const formatDate = (dateStr: string): string => {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' });
|
||||
};
|
||||
|
||||
// Show loading state
|
||||
if (status === "loading" || loading) {
|
||||
return (
|
||||
@ -165,12 +298,14 @@ export default function VisionPage() {
|
||||
}
|
||||
|
||||
// Show list of groups and missions
|
||||
const todayMeetings = getTodayMeetings();
|
||||
|
||||
return (
|
||||
<main className="w-full h-screen bg-gray-50">
|
||||
<div className="w-full h-full px-4 pt-12 pb-4">
|
||||
<main className="w-full h-screen bg-gray-50 overflow-auto">
|
||||
<div className="w-full px-4 pt-20 pb-4">
|
||||
<div className="max-w-6xl mx-auto">
|
||||
{/* Header */}
|
||||
<div className="mb-6">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-2xl font-bold text-gray-900 mb-2">
|
||||
Espaces de réunion
|
||||
</h1>
|
||||
@ -179,6 +314,150 @@ export default function VisionPage() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Today's Meetings Section */}
|
||||
{todayMeetings.length > 0 && (
|
||||
<div className="mb-8 bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||
<div className="flex items-center justify-between mb-3">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-5 w-5 text-blue-600" />
|
||||
<h2 className="text-lg font-semibold text-blue-900">
|
||||
Réunions du jour ({todayMeetings.length})
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{todayMeetings.map((meeting) => (
|
||||
<div
|
||||
key={meeting.id}
|
||||
className="bg-white rounded-lg border border-blue-200 p-3 flex items-center justify-between"
|
||||
>
|
||||
<div className="flex items-center gap-3 flex-1">
|
||||
<div className="h-10 w-10 rounded-full bg-blue-100 flex items-center justify-center">
|
||||
<Clock className="h-5 w-5 text-blue-600" />
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className="font-medium text-gray-900">
|
||||
{meeting.title || `${meeting.type === "group" ? "Groupe" : "Mission"}: ${meeting.entityName}`}
|
||||
</h3>
|
||||
<p className="text-sm text-gray-500">
|
||||
{meeting.time} - {formatDate(meeting.date)}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white"
|
||||
onClick={() => handleConferenceClick(meeting.type, meeting.entityId, meeting.entityName)}
|
||||
>
|
||||
<Video className="h-4 w-4 mr-1" />
|
||||
Rejoindre
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteMeeting(meeting.id)}
|
||||
className="text-red-600 hover:text-red-700 hover:bg-red-50"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Calendar Section */}
|
||||
<div className="mb-8">
|
||||
<div className="bg-white rounded-lg border border-gray-200 p-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<Calendar className="h-5 w-5 text-purple-600" />
|
||||
<h2 className="text-xl font-semibold text-gray-900">
|
||||
Calendrier des réunions
|
||||
</h2>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setShowCalendar(!showCalendar)}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
{showCalendar ? "Masquer" : "Afficher"} le calendrier
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{showCalendar && (
|
||||
<div className="flex flex-col lg:flex-row gap-6">
|
||||
<div className="flex-1">
|
||||
<CalendarComponent
|
||||
mode="single"
|
||||
selected={selectedDate}
|
||||
onSelect={setSelectedDate}
|
||||
className="rounded-md border"
|
||||
modifiers={{
|
||||
hasMeeting: (date) => {
|
||||
if (!date) return false;
|
||||
return getMeetingsForDate(date).length > 0;
|
||||
},
|
||||
}}
|
||||
modifiersClassNames={{
|
||||
hasMeeting: "bg-blue-100 text-blue-900 font-semibold",
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
{selectedDate && (
|
||||
<div>
|
||||
<h3 className="font-semibold text-gray-900 mb-3">
|
||||
Réunions du {selectedDate.toLocaleDateString('fr-FR', { weekday: 'long', day: 'numeric', month: 'long' })}
|
||||
</h3>
|
||||
{getMeetingsForDate(selectedDate).length === 0 ? (
|
||||
<p className="text-gray-500 text-sm">Aucune réunion planifiée</p>
|
||||
) : (
|
||||
<div className="space-y-2">
|
||||
{getMeetingsForDate(selectedDate).map((meeting) => (
|
||||
<div
|
||||
key={meeting.id}
|
||||
className="bg-gray-50 rounded-lg border border-gray-200 p-3 flex items-center justify-between"
|
||||
>
|
||||
<div>
|
||||
<h4 className="font-medium text-gray-900">
|
||||
{meeting.title || `${meeting.type === "group" ? "Groupe" : "Mission"}: ${meeting.entityName}`}
|
||||
</h4>
|
||||
<p className="text-sm text-gray-500">{meeting.time}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => handleConferenceClick(meeting.type, meeting.entityId, meeting.entityName)}
|
||||
>
|
||||
<Video className="h-4 w-4 mr-1" />
|
||||
Rejoindre
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={() => handleDeleteMeeting(meeting.id)}
|
||||
className="text-red-600 hover:text-red-700"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Groups Section */}
|
||||
<div className="mb-8">
|
||||
<div className="flex items-center gap-2 mb-4">
|
||||
@ -215,18 +494,32 @@ export default function VisionPage() {
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-2 flex items-center gap-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleConferenceClick("group", group.id, group.name);
|
||||
}}
|
||||
>
|
||||
<Video className="h-4 w-4" />
|
||||
Réunion
|
||||
</Button>
|
||||
<div className="flex items-center gap-2 ml-2">
|
||||
<Button
|
||||
size="sm"
|
||||
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleConferenceClick("group", group.id, group.name);
|
||||
}}
|
||||
>
|
||||
<Video className="h-4 w-4" />
|
||||
Réunion
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center gap-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedDate(new Date());
|
||||
handleOpenMeetingDialog("group", group.id, group.name);
|
||||
}}
|
||||
title="Planifier une réunion"
|
||||
>
|
||||
<Calendar className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@ -280,18 +573,32 @@ export default function VisionPage() {
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="ml-2 flex items-center gap-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleConferenceClick("mission", mission.id, mission.name);
|
||||
}}
|
||||
>
|
||||
<Video className="h-4 w-4" />
|
||||
Réunion
|
||||
</Button>
|
||||
<div className="flex items-center gap-2 ml-2">
|
||||
<Button
|
||||
size="sm"
|
||||
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-700 text-white"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleConferenceClick("mission", mission.id, mission.name);
|
||||
}}
|
||||
>
|
||||
<Video className="h-4 w-4" />
|
||||
Réunion
|
||||
</Button>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
className="flex items-center gap-2"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
setSelectedDate(new Date());
|
||||
handleOpenMeetingDialog("mission", mission.id, mission.name);
|
||||
}}
|
||||
title="Planifier une réunion"
|
||||
>
|
||||
<Calendar className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@ -300,6 +607,78 @@ export default function VisionPage() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Meeting Dialog */}
|
||||
<Dialog open={showMeetingDialog} onOpenChange={setShowMeetingDialog}>
|
||||
<DialogContent className="sm:max-w-[500px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Planifier une réunion</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div className="space-y-4 py-4">
|
||||
<div>
|
||||
<Label htmlFor="meeting-title">Titre (optionnel)</Label>
|
||||
<Input
|
||||
id="meeting-title"
|
||||
value={meetingForm.title}
|
||||
onChange={(e) => setMeetingForm({ ...meetingForm, title: e.target.value })}
|
||||
placeholder={`Réunion ${meetingForm.type === "group" ? "du groupe" : "de la mission"}`}
|
||||
className="mt-1"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="meeting-date">Date *</Label>
|
||||
<Input
|
||||
id="meeting-date"
|
||||
type="date"
|
||||
value={meetingForm.date}
|
||||
onChange={(e) => setMeetingForm({ ...meetingForm, date: e.target.value })}
|
||||
className="mt-1"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Label htmlFor="meeting-time">Heure *</Label>
|
||||
<Input
|
||||
id="meeting-time"
|
||||
type="time"
|
||||
value={meetingForm.time}
|
||||
onChange={(e) => setMeetingForm({ ...meetingForm, time: e.target.value })}
|
||||
className="mt-1"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
<div className="bg-gray-50 p-3 rounded-lg">
|
||||
<p className="text-sm text-gray-600">
|
||||
<strong>{meetingForm.type === "group" ? "Groupe" : "Mission"}:</strong> {meetingForm.entityName}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={() => {
|
||||
setShowMeetingDialog(false);
|
||||
setMeetingForm({
|
||||
type: "",
|
||||
entityId: "",
|
||||
entityName: "",
|
||||
date: "",
|
||||
time: "",
|
||||
title: "",
|
||||
});
|
||||
}}
|
||||
>
|
||||
Annuler
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSaveMeeting}
|
||||
className="bg-blue-600 hover:bg-blue-700 text-white"
|
||||
>
|
||||
Planifier
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user