import { getServerSession } from "next-auth/next"; import { authOptions } from "@/app/api/auth/options"; 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 with mission relation and sync configuration // Exclude "Privée" and "Default" calendars that are not synced (they should only exist if synced from courrier) let calendars = await prisma.calendar.findMany({ where: { userId: session?.user?.id || '', OR: [ // Keep calendars that are not "Privée" or "Default" { name: { notIn: ["Privée", "Default"] } }, // Or keep "Privée"/"Default" calendars that have active sync config { AND: [ { name: { in: ["Privée", "Default"] } }, { syncConfig: { isNot: null } } ] } ] }, include: { events: { orderBy: { start: 'asc' } }, mission: { include: { missionUsers: true } }, syncConfig: { include: { mailCredential: { select: { id: true, email: true, display_name: true, } } } } } }); // Auto-setup sync for email accounts from courrier (Infomaniak and Microsoft) // Get all Infomaniak email accounts const infomaniakAccounts = await prisma.mailCredentials.findMany({ where: { userId: session?.user?.id || '', host: { contains: 'infomaniak' }, password: { not: null } }, select: { id: true, email: true, display_name: true, password: true } }); // Get all Microsoft email accounts (OAuth) const microsoftAccounts = await prisma.mailCredentials.findMany({ where: { userId: session?.user?.id || '', host: { contains: 'outlook.office365.com' }, use_oauth: true, refresh_token: { not: null } }, select: { id: true, email: true, display_name: true, refresh_token: true, use_oauth: true } }); // For each Infomaniak account, ensure there's a synced calendar for (const account of infomaniakAccounts) { // Check if a calendar sync already exists for this account const existingSync = await prisma.calendarSync.findFirst({ where: { mailCredentialId: account.id, syncEnabled: true }, include: { calendar: true } }); if (!existingSync) { // Try to discover calendars for this account try { const { discoverInfomaniakCalendars } = await import('@/lib/services/caldav-sync'); const externalCalendars = await discoverInfomaniakCalendars( account.email, account.password! ); if (externalCalendars.length > 0) { // Use the first calendar (usually the main calendar) const mainCalendar = externalCalendars[0]; // Create a private calendar for this account const calendar = await prisma.calendar.create({ data: { name: "Privée", color: "#4F46E5", description: `Calendrier synchronisé avec ${account.display_name || account.email}`, userId: session?.user?.id || '', } }); // Create sync configuration await prisma.calendarSync.create({ data: { calendarId: calendar.id, mailCredentialId: account.id, provider: 'infomaniak', externalCalendarId: mainCalendar.id, externalCalendarUrl: mainCalendar.url, syncEnabled: true, syncFrequency: 15 } }); // Trigger initial sync try { const { syncInfomaniakCalendar } = await import('@/lib/services/caldav-sync'); const syncConfig = await prisma.calendarSync.findUnique({ where: { calendarId: calendar.id }, include: { calendar: true, mailCredential: true } }); if (syncConfig) { await syncInfomaniakCalendar(syncConfig.id, true); } } catch (syncError) { console.error('Error during initial sync:', syncError); // Don't fail if sync fails, calendar is still created } } } catch (error) { console.error(`Error auto-setting up sync for Infomaniak account ${account.email}:`, error); // Continue with other accounts even if one fails } } } // For each Microsoft account, ensure there's a synced calendar for (const account of microsoftAccounts) { // Check if a calendar sync already exists for this account const existingSync = await prisma.calendarSync.findFirst({ where: { mailCredentialId: account.id, syncEnabled: true }, include: { calendar: true } }); if (!existingSync) { // Try to discover calendars for this account try { const { discoverMicrosoftCalendars } = await import('@/lib/services/microsoft-calendar-sync'); const externalCalendars = await discoverMicrosoftCalendars( session?.user?.id || '', account.email ); if (externalCalendars.length > 0) { // Use the first calendar (usually the main calendar) const mainCalendar = externalCalendars[0]; // Create a private calendar for this account const calendar = await prisma.calendar.create({ data: { name: "Privée", color: "#0078D4", // Microsoft blue description: `Calendrier synchronisé avec ${account.display_name || account.email}`, userId: session?.user?.id || '', } }); // Create sync configuration await prisma.calendarSync.create({ data: { calendarId: calendar.id, mailCredentialId: account.id, provider: 'microsoft', externalCalendarId: mainCalendar.id, externalCalendarUrl: mainCalendar.webLink || mainCalendar.id, syncEnabled: true, syncFrequency: 15 } }); // Trigger initial sync try { const { syncMicrosoftCalendar } = await import('@/lib/services/microsoft-calendar-sync'); const syncConfig = await prisma.calendarSync.findUnique({ where: { calendarId: calendar.id }, include: { calendar: true, mailCredential: true } }); if (syncConfig) { await syncMicrosoftCalendar(syncConfig.id, true); } } catch (syncError) { console.error('Error during initial Microsoft sync:', syncError); // Don't fail if sync fails, calendar is still created } } } catch (error) { // Microsoft sync setup failed - likely because account doesn't have calendar scope yet // This is expected for accounts authenticated before calendar scope was added // User will need to re-authenticate their Microsoft account to get calendar access console.log(`Microsoft calendar sync not available for ${account.email} - account may need re-authentication with calendar permissions`); // Don't fail the page - continue with other accounts } } } // Refresh calendars after auto-setup // Exclude "Privée" and "Default" calendars that are not synced calendars = await prisma.calendar.findMany({ where: { userId: session?.user?.id || '', OR: [ // Keep calendars that are not "Privée" or "Default" { name: { notIn: ["Privée", "Default"] } }, // Or keep "Privée"/"Default" calendars that have active sync config { AND: [ { name: { in: ["Privée", "Default"] } }, { syncConfig: { isNot: null } } ] } ] }, include: { events: { orderBy: { start: 'asc' } }, mission: { include: { missionUsers: true } }, syncConfig: { include: { mailCredential: { select: { id: true, email: true, display_name: true, } } } } } }); // No default calendar creation - only synced calendars from courrier // Filter out "Privée" and "Default" calendars that don't have active sync calendars = calendars.filter(cal => { const isPrivateOrDefault = cal.name === "Privée" || cal.name === "Default"; const hasActiveSync = cal.syncConfig?.syncEnabled === true && cal.syncConfig?.mailCredential; // Exclude "Privée"/"Default" calendars that are not actively synced if (isPrivateOrDefault && !hasActiveSync) { return false; } return 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 (
); }