diff --git a/app/agenda/page.tsx b/app/agenda/page.tsx
deleted file mode 100644
index 04c2fc5b..00000000
--- a/app/agenda/page.tsx
+++ /dev/null
@@ -1,121 +0,0 @@
-import { getServerSession } from "next-auth/next";
-import { authOptions } from "@/app/api/auth/[...nextauth]/route";
-import { redirect } from "next/navigation";
-import { prisma } from "@/lib/prisma";
-import { CalendarClient } from "@/components/calendar/calendar-client";
-import { Metadata } from "next";
-import { CalendarDays, Users, Bookmark, Clock } from "lucide-react";
-import Image from "next/image";
-import { Button } from "@/components/ui/button";
-import { add } from 'date-fns';
-
-export const metadata: Metadata = {
- title: "Enkun - Calendrier | Gestion d'événements professionnelle",
- description: "Plateforme avancée pour la gestion de vos rendez-vous, réunions et événements professionnels",
- keywords: "calendrier, rendez-vous, événements, gestion du temps, enkun",
-};
-
-interface Event {
- id: string;
- title: string;
- description?: string | null;
- start: Date;
- end: Date;
- location?: string | null;
- isAllDay: boolean;
- type?: string;
- attendees?: { id: string; name: string }[];
-}
-
-interface Calendar {
- id: string;
- name: string;
- color: string;
- description?: string | null;
- events: Event[];
-}
-
-export default async function CalendarPage() {
- const session = await getServerSession(authOptions);
-
- if (!session?.user) {
- redirect("/api/auth/signin");
- }
-
- const userId = session.user.username || session.user.email || '';
-
- // Get all calendars for the user
- let calendars = await prisma.calendar.findMany({
- where: {
- userId: session?.user?.id || '',
- },
- include: {
- events: {
- orderBy: {
- start: 'asc'
- }
- }
- }
- });
-
- // If no calendars exist, create default ones
- if (calendars.length === 0) {
- const defaultCalendars = [
- {
- name: "Default",
- color: "#4F46E5",
- description: "Your default calendar"
- }
- ];
-
- calendars = await Promise.all(
- defaultCalendars.map(async (cal) => {
- return prisma.calendar.create({
- data: {
- ...cal,
- userId: session?.user?.id || '',
- },
- include: {
- events: true
- }
- });
- })
- );
- }
-
- const now = new Date();
- const nextWeek = add(now, { days: 7 });
-
- const upcomingEvents = calendars.flatMap(cal =>
- cal.events.filter(event =>
- new Date(event.start) >= now &&
- new Date(event.start) <= nextWeek
- )
- ).sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime());
-
- // Calculate statistics
- const totalEvents = calendars.flatMap(cal => cal.events).length;
-
- const totalMeetingHours = calendars
- .flatMap(cal => cal.events)
- .reduce((total, event) => {
- const start = new Date(event.start);
- const end = new Date(event.end);
- const hours = (end.getTime() - start.getTime()) / (1000 * 60 * 60);
- return total + (isNaN(hours) ? 0 : hours);
- }, 0);
-
- return (
-
-
-
- );
-}
diff --git a/app/courrier/page.tsx b/app/courrier/page.tsx
deleted file mode 100644
index 5f25efce..00000000
--- a/app/courrier/page.tsx
+++ /dev/null
@@ -1,1696 +0,0 @@
-'use client';
-
-import { useEffect, useState, useMemo, useCallback, useRef } from 'react';
-import { useRouter } from 'next/navigation';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
-import { Input } from '@/components/ui/input';
-import { Button } from '@/components/ui/button';
-import { Label } from '@/components/ui/label';
-import { Textarea } from '@/components/ui/textarea';
-import { Checkbox } from '@/components/ui/checkbox';
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from "@/components/ui/alert-dialog";
-import { Avatar, AvatarFallback } from '@/components/ui/avatar';
-import {
- MoreVertical, Settings, Plus as PlusIcon, Trash2, Edit, Mail,
- Inbox, Send, Star, Trash, Plus, ChevronLeft, ChevronRight,
- Search, ChevronDown, Folder, ChevronUp, Reply, Forward, ReplyAll,
- MoreHorizontal, FolderOpen, X, Paperclip, MessageSquare, Copy, EyeOff,
- AlertOctagon, Archive, RefreshCw
-} from 'lucide-react';
-import { ScrollArea } from '@/components/ui/scroll-area';
-import { useSession } from 'next-auth/react';
-import {
- decodeQuotedPrintable,
- decodeBase64,
- convertCharset,
- cleanHtml,
- parseEmailHeaders,
- extractBoundary,
- extractFilename,
- extractHeader
-} from '@/lib/infomaniak-mime-decoder';
-import DOMPurify from 'isomorphic-dompurify';
-import ComposeEmail from '@/components/ComposeEmail';
-
-export interface Account {
- id: number;
- name: string;
- email: string;
- color: string;
- folders?: string[];
-}
-
-export interface Email {
- id: number;
- accountId: number;
- from: string;
- fromName: string;
- to: string;
- subject: string;
- body: string;
- date: string;
- read: boolean;
- starred: boolean;
- folder: string;
- cc?: string;
- bcc?: string;
- flags?: string[];
- raw: string;
-}
-
-interface Attachment {
- name: string;
- type: string;
- content: string;
- encoding: string;
-}
-
-interface ParsedEmailContent {
- headers: string;
- body: string;
- html?: string;
- text?: string;
- attachments?: Array<{
- filename: string;
- content: string;
- contentType: string;
- }>;
-}
-
-interface ParsedEmailMetadata {
- subject: string;
- from: string;
- to: string;
- date: string;
- contentType: string;
- text: string | null;
- html: string | null;
- raw: {
- headers: string;
- body: string;
- };
-}
-
-function splitEmailHeadersAndBody(emailBody: string): { headers: string; body: string } {
- const [headers, ...bodyParts] = emailBody.split('\r\n\r\n');
- return {
- headers: headers || '',
- body: bodyParts.join('\r\n\r\n')
- };
-}
-
-function renderEmailContent(email: Email) {
- if (!email.body) {
- console.warn('No email body provided');
- return null;
- }
-
- try {
- // Split email into headers and body
- const [headersPart, ...bodyParts] = email.body.split('\r\n\r\n');
- if (!headersPart || bodyParts.length === 0) {
- throw new Error('Invalid email format: missing headers or body');
- }
-
- const body = bodyParts.join('\r\n\r\n');
-
- // Parse headers using Infomaniak MIME decoder
- const headerInfo = parseEmailHeaders(headersPart);
- const boundary = extractBoundary(headersPart);
-
- // If it's a multipart email
- if (boundary) {
- try {
- const parts = body.split(`--${boundary}`);
- let htmlContent = '';
- let textContent = '';
- let attachments: { filename: string; content: string }[] = [];
-
- for (const part of parts) {
- if (!part.trim()) continue;
-
- const [partHeaders, ...partBodyParts] = part.split('\r\n\r\n');
- if (!partHeaders || partBodyParts.length === 0) continue;
-
- const partBody = partBodyParts.join('\r\n\r\n');
- const contentType = extractHeader(partHeaders, 'Content-Type').toLowerCase();
- const encoding = extractHeader(partHeaders, 'Content-Transfer-Encoding').toLowerCase();
- const charset = extractHeader(partHeaders, 'charset') || 'utf-8';
-
- try {
- let decodedContent = '';
- if (encoding === 'base64') {
- decodedContent = decodeBase64(partBody, charset);
- } else if (encoding === 'quoted-printable') {
- decodedContent = decodeQuotedPrintable(partBody, charset);
- } else {
- decodedContent = convertCharset(partBody, charset);
- }
-
- if (contentType.includes('text/html')) {
- // For HTML content, we want to preserve the HTML structure
- // Only clean up problematic elements while keeping the formatting
- htmlContent = decodedContent
- .replace(/
-
-
-
-
-
- {/* Modal Footer */}
-
-
- {/* File Input for Attachments */}
-
-
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/components/calendar.tsx b/components/calendar.tsx
deleted file mode 100644
index 2ea62955..00000000
--- a/components/calendar.tsx
+++ /dev/null
@@ -1,172 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Button } from "@/components/ui/button";
-import { RefreshCw, Calendar as CalendarIcon } from "lucide-react";
-import { useRouter } from "next/navigation";
-
-interface Event {
- id: string;
- title: string;
- start: string;
- end: string;
- allDay: boolean;
- calendar: string;
- calendarColor: string;
-}
-
-export function Calendar() {
- const [events, setEvents] = useState([]);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const router = useRouter();
-
- const fetchEvents = async () => {
- setLoading(true);
- try {
- const response = await fetch('/api/calendars');
- if (!response.ok) {
- throw new Error('Failed to fetch events');
- }
-
- const calendarsData = await response.json();
- console.log('Calendar Widget - Fetched calendars:', calendarsData);
-
- // Get current date at the start of the day
- const now = new Date();
- now.setHours(0, 0, 0, 0);
-
- // Extract and process events from all calendars
- const allEvents = calendarsData.flatMap((calendar: any) =>
- (calendar.events || []).map((event: any) => ({
- id: event.id,
- title: event.title,
- start: event.start,
- end: event.end,
- allDay: event.isAllDay,
- calendar: calendar.name,
- calendarColor: calendar.color
- }))
- );
-
- // Filter for upcoming events
- const upcomingEvents = allEvents
- .filter((event: any) => new Date(event.start) >= now)
- .sort((a: any, b: any) => new Date(a.start).getTime() - new Date(b.start).getTime())
- .slice(0, 7);
-
- console.log('Calendar Widget - Processed events:', upcomingEvents);
- setEvents(upcomingEvents);
- setError(null);
- } catch (err) {
- console.error('Error fetching events:', err);
- setError('Failed to load events');
- } finally {
- setLoading(false);
- }
- };
-
- useEffect(() => {
- fetchEvents();
- }, []);
-
- const formatDate = (dateString: string) => {
- const date = new Date(dateString);
- return new Intl.DateTimeFormat('fr-FR', {
- day: '2-digit',
- month: 'short'
- }).format(date);
- };
-
- const formatTime = (dateString: string) => {
- const date = new Date(dateString);
- return new Intl.DateTimeFormat('fr-FR', {
- hour: '2-digit',
- minute: '2-digit',
- }).format(date);
- };
-
- return (
-
-
-
-
- Agenda
-
-
-
-
- {loading ? (
-
- ) : error ? (
- {error}
- ) : events.length === 0 ? (
- No upcoming events
- ) : (
-
- {events.map((event) => (
-
-
-
-
- {formatDate(event.start)}
-
-
- {formatTime(event.start)}
-
-
-
-
-
- {event.title}
-
- {!event.allDay && (
-
- {formatTime(event.start)} - {formatTime(event.end)}
-
- )}
-
-
- {event.calendar}
-
-
-
-
- ))}
-
- )}
-
-
- );
-}
\ No newline at end of file
diff --git a/components/calendar/calendar-client.tsx b/components/calendar/calendar-client.tsx
deleted file mode 100644
index 1533bc16..00000000
--- a/components/calendar/calendar-client.tsx
+++ /dev/null
@@ -1,1370 +0,0 @@
-"use client";
-
-import { useState, useRef, useEffect } from "react";
-import FullCalendar from "@fullcalendar/react";
-import dayGridPlugin from "@fullcalendar/daygrid";
-import timeGridPlugin from "@fullcalendar/timegrid";
-import interactionPlugin from "@fullcalendar/interaction";
-import frLocale from "@fullcalendar/core/locales/fr";
-import { Card } from "@/components/ui/card";
-import { Button } from "@/components/ui/button";
-import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
-import {
- Loader2,
- Plus,
- Calendar as CalendarIcon,
- Check,
- X,
- User,
- Clock,
- BarChart2,
- Settings,
- ChevronRight,
- ChevronLeft,
- Bell,
- Users,
- MapPin,
- Tag,
- ChevronDown,
- ChevronUp
-} from "lucide-react";
-import { Calendar, Event } from "@prisma/client";
-import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog";
-import { Input } from "@/components/ui/input";
-import { Textarea } from "@/components/ui/textarea";
-import { Label } from "@/components/ui/label";
-import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
-import { Badge } from "@/components/ui/badge";
-import { ScrollArea } from "@/components/ui/scroll-area";
-import { Separator } from "@/components/ui/separator";
-import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
-import DatePicker, { registerLocale } from "react-datepicker";
-import "react-datepicker/dist/react-datepicker.css";
-import { fr } from "date-fns/locale";
-import { Checkbox } from "@/components/ui/checkbox";
-
-// Register French locale
-registerLocale('fr', fr);
-
-// Predefined professional color palette
-const colorPalette = [
- "#4f46e5", // Indigo
- "#0891b2", // Cyan
- "#0e7490", // Teal
- "#16a34a", // Green
- "#65a30d", // Lime
- "#ca8a04", // Amber
- "#d97706", // Orange
- "#dc2626", // Red
- "#e11d48", // Rose
- "#9333ea", // Purple
- "#7c3aed", // Violet
- "#2563eb", // Blue
-];
-
-interface CalendarClientProps {
- initialCalendars: (Calendar & { events: Event[] })[];
- userId: string;
- userProfile: {
- name: string;
- email: string;
- avatar?: string;
- };
-}
-
-interface EventFormData {
- title: string;
- description: string | null;
- start: string;
- end: string;
- allDay: boolean;
- location: string | null;
- calendarId?: string;
-}
-
-interface CalendarDialogProps {
- open: boolean;
- onClose: () => void;
- onSave: (calendarData: Partial) => Promise;
- onDelete?: (calendarId: string) => Promise;
- initialData?: Partial;
-}
-
-function CalendarDialog({ open, onClose, onSave, onDelete, initialData }: CalendarDialogProps) {
- const [name, setName] = useState(initialData?.name || "");
- const [color, setColor] = useState(initialData?.color || "#4f46e5");
- const [description, setDescription] = useState(initialData?.description || "");
- const [isSubmitting, setIsSubmitting] = useState(false);
- const [customColorMode, setCustomColorMode] = useState(false);
-
- const isMainCalendar = initialData?.name === "Calendrier principal";
-
- useEffect(() => {
- if (open) {
- setName(initialData?.name || "");
- setColor(initialData?.color || "#4f46e5");
- setDescription(initialData?.description || "");
- setCustomColorMode(!colorPalette.includes(initialData?.color || "#4f46e5"));
- }
- }, [open, initialData]);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setIsSubmitting(true);
-
- try {
- await onSave({
- id: initialData?.id,
- name,
- color,
- description
- });
- resetForm();
- } catch (error) {
- console.error("Erreur lors de la création du calendrier:", error);
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const handleDelete = async () => {
- if (!initialData?.id || !onDelete || isMainCalendar) return;
-
- if (!confirm("Êtes-vous sûr de vouloir supprimer ce calendrier ? Tous les événements associés seront également supprimés.")) {
- return;
- }
-
- setIsSubmitting(true);
- try {
- await onDelete(initialData.id);
- resetForm();
- } catch (error) {
- console.error("Erreur lors de la suppression du calendrier:", error);
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const resetForm = () => {
- setName("");
- setColor("#4f46e5");
- setDescription("");
- setCustomColorMode(false);
- onClose();
- };
-
- return (
-
- );
-}
-
-function StatisticsPanel({ statistics }: {
- statistics: {
- totalEvents: number;
- upcomingEvents: number;
- completedEvents: number;
- meetingHours: number;
- };
-}) {
- return (
-
-
-
-
-
-
-
-
Total Événements
-
{statistics.totalEvents}
-
-
-
-
-
-
-
-
-
-
Heures de Réunion
-
{statistics.meetingHours}h
-
-
-
-
-
-
-
-
-
-
Prochains Événements
-
{statistics.upcomingEvents}
-
-
-
-
-
-
-
-
-
-
Événements Terminés
-
{statistics.completedEvents}
-
-
-
-
- );
-}
-
-function EventPreview({ event, calendar }: { event: Event; calendar: Calendar }) {
- const [isExpanded, setIsExpanded] = useState(false);
-
- return (
-
-
-
-
{event.title}
-
-
-
- {new Date(event.start).toLocaleDateString('fr-FR', {
- weekday: 'long',
- day: 'numeric',
- month: 'long',
- hour: '2-digit',
- minute: '2-digit'
- })}
-
-
-
-
-
-
-
-
-
- {isExpanded && (
-
- {event.description && (
-
{event.description}
- )}
-
-
- {event.location && (
-
-
- {event.location}
-
- )}
- {event.calendarId && (
-
-
- {calendar.name}
-
- )}
-
-
-
-
-
-
-
- )}
-
- );
-}
-
-export function CalendarClient({ initialCalendars, userId, userProfile }: CalendarClientProps) {
- const [calendars, setCalendars] = useState(initialCalendars.map(cal => ({
- ...cal,
- events: cal.events || []
- })));
- const [selectedCalendarId, setSelectedCalendarId] = useState(
- initialCalendars[0]?.id || ""
- );
- const [view, setView] = useState<"dayGridMonth" | "timeGridWeek" | "timeGridDay">("dayGridMonth");
- const [isEventModalOpen, setIsEventModalOpen] = useState(false);
- const [isCalendarModalOpen, setIsCalendarModalOpen] = useState(false);
- const [selectedEvent, setSelectedEvent] = useState(null);
- const [selectedCalendar, setSelectedCalendar] = useState(null);
- const [loading, setLoading] = useState(false);
- const [error, setError] = useState(null);
- const [eventForm, setEventForm] = useState({
- title: "",
- description: null,
- start: "",
- end: "",
- allDay: false,
- location: null,
- calendarId: selectedCalendarId
- });
-
- const [selectedEventPreview, setSelectedEventPreview] = useState(null);
- const [upcomingEvents, setUpcomingEvents] = useState([]);
- const [statistics, setStatistics] = useState({
- totalEvents: 0,
- upcomingEvents: 0,
- completedEvents: 0,
- meetingHours: 0
- });
-
- const [visibleCalendarIds, setVisibleCalendarIds] = useState([]);
-
- // Update useEffect to initialize visible calendars and fetch events
- useEffect(() => {
- if (calendars.length > 0) {
- setVisibleCalendarIds(calendars.map(cal => cal.id));
- updateStatistics();
- updateUpcomingEvents();
- }
- }, [calendars]);
-
- const updateStatistics = () => {
- const now = new Date();
- const stats = {
- totalEvents: 0,
- upcomingEvents: 0,
- completedEvents: 0,
- meetingHours: 0
- };
-
- calendars.forEach(cal => {
- cal.events.forEach(event => {
- stats.totalEvents++;
- const eventStart = new Date(event.start);
- const eventEnd = new Date(event.end);
-
- if (eventStart > now) {
- stats.upcomingEvents++;
- } else if (eventEnd < now) {
- stats.completedEvents++;
- }
-
- const duration = (eventEnd.getTime() - eventStart.getTime()) / (1000 * 60 * 60);
- stats.meetingHours += duration;
- });
- });
-
- setStatistics(stats);
- };
-
- const updateUpcomingEvents = () => {
- const now = new Date();
- const upcoming = calendars
- .flatMap(cal => cal.events)
- .filter(event => new Date(event.start) > now)
- .sort((a, b) => new Date(a.start).getTime() - new Date(b.start).getTime())
- .slice(0, 5);
- setUpcomingEvents(upcoming);
- };
-
- const fetchCalendars = async () => {
- try {
- setLoading(true);
- const response = await fetch("/api/calendars");
- if (!response.ok) throw new Error("Failed to fetch calendars");
- const data = await response.json();
- console.log("Raw calendars data:", data);
-
- // Process calendars and events
- const processedCalendars = data.map((cal: Calendar & { events: Event[] }) => ({
- ...cal,
- events: Array.isArray(cal.events) ? cal.events.map(event => ({
- ...event,
- start: new Date(event.start),
- end: new Date(event.end)
- })) : []
- }));
-
- console.log("Setting calendars with processed events:", processedCalendars);
- setCalendars(processedCalendars);
-
- // Update statistics and upcoming events
- updateStatistics();
- updateUpcomingEvents();
-
- // Force calendar refresh
- if (calendarRef.current) {
- const calendarApi = calendarRef.current.getApi();
- calendarApi.refetchEvents();
- }
- } catch (error) {
- console.error("Error fetching calendars:", error);
- setError(error instanceof Error ? error.message : "Failed to fetch calendars");
- } finally {
- setLoading(false);
- }
- };
-
- const calendarRef = useRef(null);
-
- const handleCalendarSelect = (calendarId: string) => {
- console.log("Calendar selected:", calendarId);
- setSelectedCalendarId(calendarId);
- setEventForm(prev => ({
- ...prev,
- calendarId: calendarId
- }));
- };
-
- const handleCalendarSave = async (calendarData: Partial) => {
- try {
- setLoading(true);
- const response = await fetch("/api/calendars", {
- method: calendarData.id ? "PUT" : "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- ...calendarData,
- userId,
- }),
- });
-
- if (!response.ok) {
- throw new Error("Failed to save calendar");
- }
-
- await fetchCalendars();
- setIsCalendarModalOpen(false);
- } catch (error) {
- console.error("Error saving calendar:", error);
- setError(error instanceof Error ? error.message : "Failed to save calendar");
- } finally {
- setLoading(false);
- }
- };
-
- const handleCalendarDelete = async (calendarId: string) => {
- try {
- setLoading(true);
- const response = await fetch(`/api/calendars/${calendarId}`, {
- method: "DELETE",
- });
-
- if (!response.ok) {
- throw new Error("Failed to delete calendar");
- }
-
- await fetchCalendars();
- setIsCalendarModalOpen(false);
-
- // If the deleted calendar was selected, select another one
- if (selectedCalendarId === calendarId) {
- const remainingCalendars = calendars.filter(cal => cal.id !== calendarId);
- setSelectedCalendarId(remainingCalendars[0]?.id || "");
- }
- } catch (error) {
- console.error("Error deleting calendar:", error);
- setError(error instanceof Error ? error.message : "Failed to delete calendar");
- } finally {
- setLoading(false);
- }
- };
-
- const handleDateSelect = (selectInfo: any) => {
- const startDate = new Date(selectInfo.start);
- const endDate = new Date(selectInfo.end);
-
- console.log("Date select handler - Current state:", {
- calendars: calendars.map(c => ({ id: c.id, name: c.name })),
- selectedCalendarId,
- availableCalendars: calendars.length
- });
-
- // If no calendar is selected, use the first available calendar
- if (!selectedCalendarId && calendars.length > 0) {
- const firstCalendar = calendars[0];
- console.log("No calendar selected, selecting first calendar:", firstCalendar);
- setSelectedCalendarId(firstCalendar.id);
-
- setEventForm({
- title: "",
- description: null,
- start: startDate.toISOString(),
- end: endDate.toISOString(),
- allDay: selectInfo.allDay,
- location: null,
- calendarId: firstCalendar.id
- });
- } else {
- setEventForm({
- title: "",
- description: null,
- start: startDate.toISOString(),
- end: endDate.toISOString(),
- allDay: selectInfo.allDay,
- location: null,
- calendarId: selectedCalendarId
- });
- }
-
- setIsEventModalOpen(true);
- };
-
- const handleEventClick = (clickInfo: any) => {
- const event = clickInfo.event;
- const startDate = new Date(event.start);
- const endDate = new Date(event.end || event.start);
-
- setSelectedEvent(event.extendedProps.originalEvent);
- setEventForm({
- title: event.title,
- description: event.extendedProps.description,
- start: startDate.toISOString().slice(0, 16),
- end: endDate.toISOString().slice(0, 16),
- allDay: event.isAllDay,
- location: event.extendedProps.location,
- calendarId: event.extendedProps.calendarId,
- });
- setIsEventModalOpen(true);
- };
-
- const handleEventSubmit = async () => {
- try {
- // Validate required fields including calendar
- if (!eventForm.title || !eventForm.start || !eventForm.end || !eventForm.calendarId) {
- console.log("Form validation failed:", {
- title: eventForm.title,
- start: eventForm.start,
- end: eventForm.end,
- calendarId: eventForm.calendarId
- });
- setError("Veuillez remplir tous les champs obligatoires et sélectionner un calendrier");
- return;
- }
-
- setLoading(true);
- const eventData = {
- ...eventForm,
- start: new Date(eventForm.start).toISOString(),
- end: new Date(eventForm.end).toISOString(),
- userId,
- ...(selectedEvent ? { id: selectedEvent.id } : {}), // Include ID for updates
- allDay: eventForm.allDay // Use allDay instead of isAllDay
- };
-
- console.log("Submitting event with data:", eventData);
-
- const response = await fetch("/api/events", {
- method: selectedEvent ? "PUT" : "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify(eventData),
- });
-
- const responseData = await response.json();
- console.log("Response from server:", responseData);
-
- if (!response.ok) {
- console.error("Error response:", responseData);
- throw new Error(responseData.error || "Failed to save event");
- }
-
- // Reset form and close modal first
- setIsEventModalOpen(false);
- setEventForm({
- title: "",
- description: null,
- start: "",
- end: "",
- allDay: false,
- location: null,
- calendarId: selectedCalendarId
- });
- setSelectedEvent(null);
- setError(null);
-
- // Update calendars state with the new event
- const updatedCalendars = calendars.map(cal => {
- if (cal.id === eventData.calendarId) {
- return {
- ...cal,
- events: [...cal.events, responseData]
- };
- }
- return cal;
- });
- setCalendars(updatedCalendars);
-
- // Fetch fresh data to ensure all calendars are up to date
- await fetchCalendars();
- } catch (error) {
- console.error("Error saving event:", error);
- setError(error instanceof Error ? error.message : "Failed to save event");
- } finally {
- setLoading(false);
- }
- };
-
- const handleEventDelete = async () => {
- if (!selectedEvent?.id) return;
-
- if (!confirm("Êtes-vous sûr de vouloir supprimer cet événement ?")) {
- return;
- }
-
- try {
- setLoading(true);
- const response = await fetch(`/api/events/${selectedEvent.id}`, {
- method: "DELETE",
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.message || "Failed to delete event");
- }
-
- // Remove the event from local state
- const updatedCalendars = calendars.map(cal => ({
- ...cal,
- events: cal.events.filter(e => e.id !== selectedEvent.id)
- }));
- setCalendars(updatedCalendars);
-
- // Close modal and reset form
- setIsEventModalOpen(false);
- setSelectedEvent(null);
- setEventForm({
- title: "",
- description: null,
- start: "",
- end: "",
- allDay: false,
- location: null,
- calendarId: selectedCalendarId
- });
-
- // Force calendar refresh
- if (calendarRef.current) {
- const calendarApi = calendarRef.current.getApi();
- calendarApi.refetchEvents();
- }
- } catch (error) {
- console.error("Error deleting event:", error);
- setError(error instanceof Error ? error.message : "Failed to delete event");
- } finally {
- setLoading(false);
- }
- };
-
- const handleViewChange = (newView: "dayGridMonth" | "timeGridWeek" | "timeGridDay") => {
- setView(newView);
- if (calendarRef.current) {
- const calendarApi = calendarRef.current.getApi();
- calendarApi.changeView(newView);
- }
- };
-
- // Update CalendarSelector to handle visibility
- const CalendarSelector = () => (
-
- {calendars.map((calendar) => (
-
-
- {calendar.name !== "Calendrier principal" && (
-
- )}
-
- ))}
-
- );
-
- // Add these helper functions for date handling
- const getDateFromString = (dateString: string) => {
- return dateString ? new Date(dateString) : new Date();
- };
-
- const handleStartDateChange = (date: Date | null) => {
- if (!date) return;
-
- const endDate = getDateFromString(eventForm.end);
- if (date > endDate) {
- // If start date is after end date, set end date to start date + 1 hour
- const newEndDate = new Date(date);
- newEndDate.setHours(date.getHours() + 1);
- setEventForm({
- ...eventForm,
- start: date.toISOString(),
- end: newEndDate.toISOString(),
- });
- } else {
- setEventForm({
- ...eventForm,
- start: date.toISOString(),
- });
- }
- };
-
- const handleEndDateChange = (date: Date | null) => {
- if (!date) return;
-
- const startDate = getDateFromString(eventForm.start);
- if (date < startDate) {
- // If end date is before start date, set start date to end date - 1 hour
- const newStartDate = new Date(date);
- newStartDate.setHours(date.getHours() - 1);
- setEventForm({
- ...eventForm,
- start: newStartDate.toISOString(),
- end: date.toISOString(),
- });
- } else {
- setEventForm({
- ...eventForm,
- end: date.toISOString(),
- });
- }
- };
-
- // Update the date handlers to maintain consistent time format
- const formatTimeForInput = (date: Date) => {
- return date.toLocaleTimeString('fr-FR', {
- hour: '2-digit',
- minute: '2-digit',
- hour12: false
- });
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
- handleViewChange("dayGridMonth")}
- >
- Mois
-
- handleViewChange("timeGridWeek")}
- >
- Semaine
-
- handleViewChange("timeGridDay")}
- >
- Jour
-
-
-
-
-
-
-
-
-
- {loading ? (
-
-
- Chargement des événements...
-
- ) : (
- visibleCalendarIds.includes(cal.id))
- .flatMap(cal =>
- (cal.events || []).map(event => ({
- id: event.id,
- title: event.title,
- start: new Date(event.start),
- end: new Date(event.end),
- allDay: event.isAllDay,
- description: event.description,
- location: event.location,
- calendarId: event.calendarId,
- backgroundColor: `${cal.color}dd`,
- borderColor: cal.color,
- textColor: '#ffffff',
- extendedProps: {
- calendarName: cal.name,
- location: event.location,
- description: event.description,
- calendarId: event.calendarId,
- originalEvent: event,
- color: cal.color
- }
- }))
- )}
- eventContent={(arg) => {
- return (
-
-
-
-
-
- {!arg.event.allDay && (
-
- {new Date(arg.event.start).toLocaleTimeString('fr-FR', {
- hour: '2-digit',
- minute: '2-digit'
- })}
-
- )}
-
- {arg.event.title}
-
-
-
-
-
-
-
{arg.event.title}
- {!arg.event.allDay && (
-
- {new Date(arg.event.start).toLocaleTimeString('fr-FR', {
- hour: '2-digit',
- minute: '2-digit'
- })}
-
- )}
-
-
-
-
- );
- }}
- eventClassNames="rounded-md overflow-hidden"
- dayCellContent={(arg) => {
- return (
-
- {arg.dayNumberText}
-
- );
- }}
- locale={frLocale}
- selectable={true}
- selectMirror={true}
- dayMaxEventRows={false}
- dayMaxEvents={false}
- weekends={true}
- select={handleDateSelect}
- eventClick={handleEventClick}
- height="auto"
- aspectRatio={1.8}
- slotMinTime="06:00:00"
- slotMaxTime="22:00:00"
- allDaySlot={true}
- allDayText=""
- views={{
- timeGridWeek: {
- allDayText: "",
- dayHeaderFormat: { weekday: 'long', day: 'numeric', month: 'numeric' }
- },
- timeGridDay: {
- allDayText: "",
- dayHeaderFormat: { weekday: 'long', day: 'numeric', month: 'numeric' }
- }
- }}
- slotLabelFormat={{
- hour: '2-digit',
- minute: '2-digit',
- hour12: false
- }}
- />
- )}
-
-
- {/* Calendar dialog */}
-
setIsCalendarModalOpen(false)}
- onSave={handleCalendarSave}
- onDelete={handleCalendarDelete}
- initialData={selectedCalendar || undefined}
- />
-
- {/* Event dialog */}
-
-
- );
-}
diff --git a/components/calendar/calendar-dialog.tsx b/components/calendar/calendar-dialog.tsx
deleted file mode 100644
index 02208432..00000000
--- a/components/calendar/calendar-dialog.tsx
+++ /dev/null
@@ -1,114 +0,0 @@
-"use client";
-
-import { useState } from "react";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogFooter,
-} from "@/components/ui/dialog";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Textarea } from "@/components/ui/textarea";
-import { Label } from "@/components/ui/label";
-import { Calendar } from "@prisma/client";
-
-interface CalendarDialogProps {
- open: boolean;
- onClose: () => void;
- onSave: (calendarData: Partial) => Promise;
-}
-
-export function CalendarDialog({ open, onClose, onSave }: CalendarDialogProps) {
- const [name, setName] = useState("");
- const [color, setColor] = useState("#0082c9");
- const [description, setDescription] = useState("");
- const [isSubmitting, setIsSubmitting] = useState(false);
-
- const handleSubmit = async (e: React.FormEvent) => {
- e.preventDefault();
- setIsSubmitting(true);
-
- try {
- await onSave({ name, color, description });
- resetForm();
- } catch (error) {
- console.error("Erreur lors de la création du calendrier:", error);
- } finally {
- setIsSubmitting(false);
- }
- };
-
- const resetForm = () => {
- setName("");
- setColor("#0082c9");
- setDescription("");
- onClose();
- };
-
- return (
-
- );
-}
diff --git a/components/calendar/event-dialog.tsx b/components/calendar/event-dialog.tsx
deleted file mode 100644
index b0bb292c..00000000
--- a/components/calendar/event-dialog.tsx
+++ /dev/null
@@ -1,261 +0,0 @@
-import { useState, useEffect } from "react";
-import {
- Dialog,
- DialogContent,
- DialogHeader,
- DialogTitle,
- DialogFooter,
-} from "@/components/ui/dialog";
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { Textarea } from "@/components/ui/textarea";
-import { Label } from "@/components/ui/label";
-import { Checkbox } from "@/components/ui/checkbox";
-import {
- AlertDialog,
- AlertDialogAction,
- AlertDialogCancel,
- AlertDialogContent,
- AlertDialogDescription,
- AlertDialogFooter,
- AlertDialogHeader,
- AlertDialogTitle,
-} from "@/components/ui/alert-dialog";
-import { format, parseISO } from "date-fns";
-import { fr } from "date-fns/locale";
-import {
- Select,
- SelectContent,
- SelectItem,
- SelectTrigger,
- SelectValue,
-} from "@/components/ui/select";
-import { Calendar as CalendarType } from "@prisma/client";
-
-interface EventDialogProps {
- open: boolean;
- event?: any;
- onClose: () => void;
- onSave: (eventData: any) => Promise;
- onDelete?: (eventId: string) => Promise;
- calendars: CalendarType[];
-}
-
-export function EventDialog({
- open,
- event,
- onClose,
- onSave,
- onDelete,
- calendars,
-}: EventDialogProps) {
- const [title, setTitle] = useState(event?.title || "");
- const [description, setDescription] = useState(event?.description || "");
- const [location, setLocation] = useState(event?.location || "");
- const [start, setStart] = useState(event?.start || "");
- const [end, setEnd] = useState(event?.end || "");
- const [allDay, setAllDay] = useState(event?.allDay || false);
- const [calendarId, setCalendarId] = useState(event?.calendarId || "");
- const [confirmDelete, setConfirmDelete] = useState(false);
-
- // Formater les dates pour l'affichage
- const formatDate = (dateStr: string) => {
- if (!dateStr) return "";
- try {
- // @ts-ignore
- const date = parseISO(dateStr);
- // @ts-ignore
- return format(date, allDay ? "yyyy-MM-dd" : "yyyy-MM-dd'T'HH:mm", {
- // @ts-ignore
- locale: fr,
- });
- } catch (e) {
- return dateStr;
- }
- };
-
- // Gérer le changement de l'option "Toute la journée"
- const handleAllDayChange = (checked: boolean) => {
- setAllDay(checked);
-
- // Ajuster les dates si nécessaire
- if (checked && start) {
- // @ts-ignore
- const startDate = parseISO(start);
- // @ts-ignore
- setStart(format(startDate, "yyyy-MM-dd"));
-
- if (end) {
- // @ts-ignore
- const endDate = parseISO(end);
- // @ts-ignore
- setEnd(format(endDate, "yyyy-MM-dd"));
- }
- }
- };
-
- // Enregistrer l'événement
- const handleSave = () => {
- onSave({
- id: event?.id,
- title,
- description,
- location,
- start,
- end,
- calendarId,
- isAllDay: allDay,
- });
- };
-
- // Supprimer l'événement
- const handleDelete = () => {
- if (onDelete && event?.id) {
- onDelete(event.id);
- }
- };
-
- return (
- <>
-
-
-
-
-
- Supprimer l'événement
-
- Êtes-vous sûr de vouloir supprimer cet événement ? Cette action
- est irréversible.
-
-
-
- Annuler
-
- Supprimer
-
-
-
-
- >
- );
-}
diff --git a/components/email.tsx b/components/email.tsx
deleted file mode 100644
index d12a9493..00000000
--- a/components/email.tsx
+++ /dev/null
@@ -1,192 +0,0 @@
-"use client";
-
-import { useEffect, useState } from "react";
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Button } from "@/components/ui/button";
-import { RefreshCw, Mail } from "lucide-react";
-import { useSession } from "next-auth/react";
-import { formatDistance } from 'date-fns/formatDistance';
-import { fr } from 'date-fns/locale/fr';
-import { useRouter } from "next/navigation";
-
-interface Email {
- id: string;
- subject: string;
- from: string;
- fromName?: string;
- date: string;
- read: boolean;
- starred: boolean;
- folder: string;
-}
-
-interface EmailResponse {
- emails: Email[];
- mailUrl: string;
- error?: string;
-}
-
-export function Email() {
- const [emails, setEmails] = useState([]);
- const [mailUrl, setMailUrl] = useState(null);
- const [loading, setLoading] = useState(true);
- const [error, setError] = useState(null);
- const [refreshing, setRefreshing] = useState(false);
- const { data: session, status } = useSession();
- const router = useRouter();
-
- const fetchEmails = async (isRefresh = false) => {
- if (status !== 'authenticated') {
- setError('Please sign in to view emails');
- setLoading(false);
- setRefreshing(false);
- return;
- }
-
- if (isRefresh) setRefreshing(true);
- if (!isRefresh) setLoading(true);
-
- try {
- const response = await fetch('/api/mail');
-
- if (!response.ok) {
- const errorData = await response.json();
- throw new Error(errorData.error || 'Failed to fetch emails');
- }
-
- const data = await response.json();
-
- if (data.error) {
- throw new Error(data.error);
- }
-
- const validatedEmails = data.emails.map((email: any) => ({
- id: email.id || Date.now().toString(),
- subject: email.subject || '(No subject)',
- from: email.from || '',
- fromName: email.fromName || email.from?.split('@')[0] || 'Unknown',
- date: email.date || new Date().toISOString(),
- read: !!email.read,
- starred: !!email.starred,
- folder: email.folder || 'INBOX'
- }));
-
- setEmails(validatedEmails);
- setMailUrl(data.mailUrl || 'https://espace.slm-lab.net/apps/courrier/');
- setError(null);
- } catch (err) {
- setError(err instanceof Error ? err.message : 'Error fetching emails');
- setEmails([]);
- } finally {
- setLoading(false);
- setRefreshing(false);
- }
- };
-
- // Initial fetch
- useEffect(() => {
- if (status === 'authenticated') {
- fetchEmails();
- } else if (status === 'unauthenticated') {
- setError('Please sign in to view emails');
- setLoading(false);
- }
- }, [status]);
-
- // Auto-refresh every 5 minutes
- useEffect(() => {
- if (status !== 'authenticated') return;
-
- const interval = setInterval(() => {
- fetchEmails(true);
- }, 5 * 60 * 1000);
-
- return () => clearInterval(interval);
- }, [status]);
-
- const formatDate = (dateString: string) => {
- try {
- const date = new Date(dateString);
- return formatDistance(date, new Date(), {
- addSuffix: true,
- locale: fr
- });
- } catch (err) {
- return dateString;
- }
- };
-
- if (status === 'loading' || loading) {
- return (
-
-
-
-
-
- Courrier
-
-
-
-
-
-
-
-
-
- );
- }
-
- return (
-
-
-
-
-
- Courrier
-
-
-
-
-
- {error ? (
- {error}
- ) : (
-
- {emails.length === 0 ? (
-
- {loading ? 'Loading emails...' : 'No unread emails'}
-
- ) : (
- emails.map((email) => (
-
router.push('/mail')}
- >
-
-
- {email.fromName || email.from}
-
-
- {!email.read && }
- {formatDate(email.date)}
-
-
-
{email.subject}
-
- ))
- )}
-
- )}
-
-
- );
-}
\ No newline at end of file
diff --git a/components/emails.tsx b/components/emails.tsx
deleted file mode 100644
index e4286ffd..00000000
--- a/components/emails.tsx
+++ /dev/null
@@ -1,10 +0,0 @@
-import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
-import { Button } from "@/components/ui/button";
-import { RefreshCw, MessageSquare } from "lucide-react";
-
-
-
-
- Emails non lus
-
-
\ No newline at end of file
diff --git a/components/mail/mail-list.tsx b/components/mail/mail-list.tsx
deleted file mode 100644
index 7b63a854..00000000
--- a/components/mail/mail-list.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { Mail } from "@/types/mail";
-import { Star, StarOff, Paperclip } from "lucide-react";
-import { format } from "date-fns";
-
-interface MailListProps {
- mails: Mail[];
- onMailClick: (mail: Mail) => void;
-}
-
-export function MailList({ mails, onMailClick }: MailListProps) {
- if (!mails || mails.length === 0) {
- return (
-
- );
- }
-
- return (
-
- {mails.map((mail) => (
-
onMailClick(mail)}
- >
-
-
- {mail.starred ? (
-
- ) : (
-
- )}
- {mail.from}
-
-
- {format(new Date(mail.date), "MMM d, yyyy")}
-
-
-
-
{mail.subject}
-
- {mail.body}
-
-
- {mail.attachments && mail.attachments.length > 0 && (
-
-
- {mail.attachments.length} attachment
- {mail.attachments.length > 1 ? "s" : ""}
-
- )}
-
- ))}
-
- );
-}
\ No newline at end of file
diff --git a/components/mail/mail-toolbar.tsx b/components/mail/mail-toolbar.tsx
deleted file mode 100644
index 423fb00a..00000000
--- a/components/mail/mail-toolbar.tsx
+++ /dev/null
@@ -1,35 +0,0 @@
-import { Button } from "@/components/ui/button";
-import { Input } from "@/components/ui/input";
-import { RefreshCw, Plus, Search } from "lucide-react";
-
-interface MailToolbarProps {
- onRefresh: () => void;
- onCompose: () => void;
- onSearch: (query: string) => void;
-}
-
-export function MailToolbar({ onRefresh, onCompose, onSearch }: MailToolbarProps) {
- return (
-
-
-
-
-
- onSearch(e.target.value)}
- />
-
-
-
- );
-}
\ No newline at end of file
diff --git a/lib/compose-mime-decoder.ts b/lib/compose-mime-decoder.ts
deleted file mode 100644
index 96b4c140..00000000
--- a/lib/compose-mime-decoder.ts
+++ /dev/null
@@ -1,75 +0,0 @@
-/**
- * Simple MIME decoder for compose message box
- * Handles basic email content without creating nested structures
- */
-
-export function decodeComposeContent(content: string): string {
- if (!content) return '';
-
- // Basic HTML cleaning without creating nested structures
- let cleaned = content
- // Remove script and style tags
- .replace(/