383 lines
11 KiB
TypeScript
383 lines
11 KiB
TypeScript
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 (
|
|
<main className="w-full h-screen bg-white">
|
|
<div className="w-full h-full px-4 pt-12 pb-4 flex">
|
|
<CalendarClient
|
|
initialCalendars={calendars}
|
|
userId={session.user.id}
|
|
userProfile={{
|
|
name: session.user.name || '',
|
|
email: session.user.email || '',
|
|
avatar: session.user.image || undefined
|
|
}}
|
|
/>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|