diff --git a/components/dashboard/ManagerWeeklySummary.tsx b/components/dashboard/ManagerWeeklySummary.tsx new file mode 100644 index 0000000..93dd36e --- /dev/null +++ b/components/dashboard/ManagerWeeklySummary.tsx @@ -0,0 +1,481 @@ +'use client'; + +import { useState } from 'react'; +import { ManagerSummary } from '@/services/manager-summary'; +import { Card, CardHeader, CardContent } from '@/components/ui/Card'; +import { Button } from '@/components/ui/Button'; +import { TagDisplay } from '@/components/ui/TagDisplay'; +import { getPriorityConfig } from '@/lib/status-config'; +import { useTasksContext } from '@/contexts/TasksContext'; +import { format } from 'date-fns'; +import { fr } from 'date-fns/locale'; + +interface ManagerWeeklySummaryProps { + initialSummary: ManagerSummary; +} + +export default function ManagerWeeklySummary({ initialSummary }: ManagerWeeklySummaryProps) { + const [summary] = useState(initialSummary); + const [activeView, setActiveView] = useState<'narrative' | 'accomplishments' | 'challenges'>('narrative'); + const { tags: availableTags } = useTasksContext(); + + const handleRefresh = () => { + // SSR - refresh via page reload + window.location.reload(); + }; + + + const formatPeriod = () => { + return `Semaine du ${format(summary.period.start, 'dd MMM', { locale: fr })} au ${format(summary.period.end, 'dd MMM yyyy', { locale: fr })}`; + }; + + const getPriorityBadgeStyle = (priority: 'low' | 'medium' | 'high') => { + const config = getPriorityConfig(priority); + const baseClasses = 'text-xs px-2 py-0.5 rounded font-medium'; + + switch (config.color) { + case 'blue': + return `${baseClasses} bg-blue-100 dark:bg-blue-900/20 text-blue-600 dark:text-blue-400`; + case 'yellow': + return `${baseClasses} bg-yellow-100 dark:bg-yellow-900/20 text-yellow-600 dark:text-yellow-400`; + case 'purple': + return `${baseClasses} bg-purple-100 dark:bg-purple-900/20 text-purple-600 dark:text-purple-400`; + case 'red': + return `${baseClasses} bg-red-100 dark:bg-red-900/20 text-red-600 dark:text-red-400`; + default: + return `${baseClasses} bg-gray-100 dark:bg-gray-900/20 text-gray-600 dark:text-gray-400`; + } + }; + + + return ( +
+ {/* Header avec navigation */} +
+
+

👔 Résumé Manager

+

{formatPeriod()}

+
+ +
+ + {/* Navigation des vues */} +
+ +
+ + {/* Vue Executive / Narrative */} + {activeView === 'narrative' && ( +
+ {/* Résumé narratif */} + + +

+ 📊 Résumé de la semaine +

+
+ +
+

🎯 Points clés accomplis

+

{summary.narrative.weekHighlight}

+
+ +
+

⚡ Défis traités

+

{summary.narrative.mainChallenges}

+
+ +
+

🔮 Focus semaine prochaine

+

{summary.narrative.nextWeekFocus}

+
+
+
+ + {/* Métriques rapides */} + + +

📈 Métriques en bref

+
+ +
+
+
+ {summary.metrics.totalTasksCompleted} +
+
Tâches complétées
+
+ dont {summary.metrics.highPriorityTasksCompleted} priorité haute +
+
+ +
+
+ {summary.metrics.totalCheckboxesCompleted} +
+
Todos complétés
+
+ dont {summary.metrics.meetingCheckboxesCompleted} meetings +
+
+ +
+
+ {summary.keyAccomplishments.filter(a => a.impact === 'high').length} +
+
Items à fort impact
+
+ / {summary.keyAccomplishments.length} accomplissements +
+
+ +
+
+ {summary.upcomingChallenges.filter(c => c.priority === 'high').length} +
+
Priorités critiques
+
+ / {summary.upcomingChallenges.length} enjeux +
+
+
+
+
+ + {/* Top accomplissements */} + + +

🏆 Top accomplissements

+
+ +
+ {summary.keyAccomplishments.length === 0 ? ( +
+

Aucun accomplissement significatif trouvé cette semaine.

+

Ajoutez des tâches avec priorité haute/medium ou des meetings.

+
+ ) : ( + summary.keyAccomplishments.slice(0, 6).map((accomplishment, index) => ( +
+ {/* Barre colorée gauche */} +
+ + {/* Header compact */} +
+
+ + #{index + 1} + + + {getPriorityConfig(accomplishment.impact).label} + +
+ + {format(accomplishment.completedAt, 'dd/MM', { locale: fr })} + +
+ + {/* Titre */} +

+ {accomplishment.title} +

+ + {/* Tags */} + {accomplishment.tags && accomplishment.tags.length > 0 && ( +
+ +
+ )} + + {/* Description si disponible */} + {accomplishment.description && ( +

+ {accomplishment.description} +

+ )} + + {/* Count de todos */} + {accomplishment.todosCount > 0 && ( +
+ 📋 + {accomplishment.todosCount} todo{accomplishment.todosCount > 1 ? 's' : ''} +
+ )} +
+ )) + )} +
+
+
+ + {/* Top challenges */} + + +

🎯 Top enjeux à venir

+
+ +
+ {summary.upcomingChallenges.length === 0 ? ( +
+

Aucun enjeu prioritaire trouvé.

+

Ajoutez des tâches non complétées avec priorité haute/medium.

+
+ ) : ( + summary.upcomingChallenges.slice(0, 6).map((challenge, index) => ( +
+ {/* Barre colorée gauche */} +
+ + {/* Header compact */} +
+
+ + #{index + 1} + + + {getPriorityConfig(challenge.priority).label} + +
+ {challenge.deadline && ( + + {format(challenge.deadline, 'dd/MM', { locale: fr })} + + )} +
+ + {/* Titre */} +

+ {challenge.title} +

+ + {/* Tags */} + {challenge.tags && challenge.tags.length > 0 && ( +
+ +
+ )} + + {/* Description si disponible */} + {challenge.description && ( +

+ {challenge.description} +

+ )} + + {/* Count de todos */} + {challenge.todosCount > 0 && ( +
+ 📋 + {challenge.todosCount} todo{challenge.todosCount > 1 ? 's' : ''} +
+ )} +
+ )) + )} +
+
+
+
+ )} + + {/* Vue détaillée des accomplissements */} + {activeView === 'accomplishments' && ( + + +

✅ Accomplissements de la semaine

+

+ {summary.keyAccomplishments.length} accomplissements significatifs • {summary.metrics.totalTasksCompleted} tâches • {summary.metrics.totalCheckboxesCompleted} todos complétés +

+
+ +
+ {summary.keyAccomplishments.map((accomplishment, index) => ( +
+ {/* Barre colorée gauche */} +
+ + {/* Header compact */} +
+
+ + #{index + 1} + + + {getPriorityConfig(accomplishment.impact).label} + +
+ + {format(accomplishment.completedAt, 'dd/MM', { locale: fr })} + +
+ + {/* Titre */} +

+ {accomplishment.title} +

+ + {/* Tags */} + {accomplishment.tags && accomplishment.tags.length > 0 && ( +
+ +
+ )} + + {/* Description si disponible */} + {accomplishment.description && ( +

+ {accomplishment.description} +

+ )} + + {/* Count de todos */} + {accomplishment.todosCount > 0 && ( +
+ 📋 + {accomplishment.todosCount} todo{accomplishment.todosCount > 1 ? 's' : ''} +
+ )} +
+ ))} +
+
+
+ )} + + {/* Vue détaillée des challenges */} + {activeView === 'challenges' && ( + + +

🎯 Enjeux et défis à venir

+

+ {summary.upcomingChallenges.length} défis identifiés • {summary.upcomingChallenges.filter(c => c.priority === 'high').length} priorité haute • {summary.upcomingChallenges.filter(c => c.blockers.length > 0).length} avec blockers +

+
+ +
+ {summary.upcomingChallenges.map((challenge, index) => ( +
+ {/* Barre colorée gauche */} +
+ + {/* Header compact */} +
+
+ + #{index + 1} + + + {getPriorityConfig(challenge.priority).label} + +
+ {challenge.deadline && ( + + {format(challenge.deadline, 'dd/MM', { locale: fr })} + + )} +
+ + {/* Titre */} +

+ {challenge.title} +

+ + {/* Tags */} + {challenge.tags && challenge.tags.length > 0 && ( +
+ +
+ )} + + {/* Description si disponible */} + {challenge.description && ( +

+ {challenge.description} +

+ )} + + {/* Count de todos */} + {challenge.todosCount > 0 && ( +
+ 📋 + {challenge.todosCount} todo{challenge.todosCount > 1 ? 's' : ''} +
+ )} +
+ ))} +
+
+
+ )} +
+ ); +} diff --git a/components/ui/Header.tsx b/components/ui/Header.tsx index 6a5af58..bcce456 100644 --- a/components/ui/Header.tsx +++ b/components/ui/Header.tsx @@ -54,6 +54,7 @@ export function Header({ title = "TowerControl", subtitle = "Task Management", s { href: '/kanban', label: 'Kanban' }, { href: '/daily', label: 'Daily' }, { href: '/weekly-summary', label: 'Hebdo' }, + { href: '/weekly-manager', label: 'Manager' }, ...(isJiraConfigured ? [{ href: '/jira-dashboard', label: `Jira${jiraConfig?.projectKey ? ` (${jiraConfig.projectKey})` : ''}` }] : []), { href: '/settings', label: 'Settings' } ]; diff --git a/services/manager-summary.ts b/services/manager-summary.ts new file mode 100644 index 0000000..90408e5 --- /dev/null +++ b/services/manager-summary.ts @@ -0,0 +1,563 @@ +import { prisma } from './database'; +import { startOfWeek, endOfWeek } from 'date-fns'; + +type TaskType = { + id: string; + title: string; + description?: string | null; + priority: string; // high, medium, low + completedAt?: Date | null; + createdAt: Date; + taskTags?: { + tag: { + name: string; + } + }[]; +}; + +type CheckboxType = { + id: string; + text: string; + isChecked: boolean; + type: string; // task, meeting + date: Date; + createdAt: Date; + task?: { + id: string; + title: string; + priority: string; + taskTags?: { + tag: { + name: string; + } + }[]; + } | null; +}; + +export interface KeyAccomplishment { + id: string; + title: string; + description?: string; + tags: string[]; + impact: 'high' | 'medium' | 'low'; + completedAt: Date; + relatedItems: string[]; // IDs des tâches/checkboxes liées + todosCount: number; // Nombre de todos associés +} + +export interface UpcomingChallenge { + id: string; + title: string; + description?: string; + tags: string[]; + priority: 'high' | 'medium' | 'low'; + estimatedEffort: 'days' | 'weeks' | 'hours'; + blockers: string[]; + deadline?: Date; + relatedItems: string[]; // IDs des tâches/checkboxes liées + todosCount: number; // Nombre de todos associés +} + +export interface ManagerSummary { + period: { + start: Date; + end: Date; + }; + keyAccomplishments: KeyAccomplishment[]; + upcomingChallenges: UpcomingChallenge[]; + metrics: { + totalTasksCompleted: number; + totalCheckboxesCompleted: number; + highPriorityTasksCompleted: number; + meetingCheckboxesCompleted: number; + completionRate: number; + focusAreas: { [category: string]: number }; + }; + narrative: { + weekHighlight: string; + mainChallenges: string; + nextWeekFocus: string; + }; +} + +export class ManagerSummaryService { + /** + * Génère un résumé orienté manager pour la semaine + */ + static async getManagerSummary(date: Date = new Date()): Promise { + const weekStart = startOfWeek(date, { weekStartsOn: 1 }); // Lundi + const weekEnd = endOfWeek(date, { weekStartsOn: 1 }); // Dimanche + + // Récupérer les données de base + const [tasks, checkboxes] = await Promise.all([ + this.getCompletedTasks(weekStart, weekEnd), + this.getCompletedCheckboxes(weekStart, weekEnd) + ]); + + // Analyser et extraire les accomplissements clés + const keyAccomplishments = this.extractKeyAccomplishments(tasks, checkboxes); + + // Identifier les défis à venir + const upcomingChallenges = await this.identifyUpcomingChallenges(); + + // Calculer les métriques + const metrics = this.calculateMetrics(tasks, checkboxes); + + // Générer le narratif + const narrative = this.generateNarrative(keyAccomplishments, upcomingChallenges); + + return { + period: { start: weekStart, end: weekEnd }, + keyAccomplishments, + upcomingChallenges, + metrics, + narrative + }; + } + + /** + * Récupère les tâches complétées de la semaine + */ + private static async getCompletedTasks(startDate: Date, endDate: Date) { + const tasks = await prisma.task.findMany({ + where: { + OR: [ + // Tâches avec completedAt dans la période (priorité) + { + completedAt: { + gte: startDate, + lte: endDate + } + }, + // Tâches avec status 'done' et updatedAt dans la période + { + status: 'done', + updatedAt: { + gte: startDate, + lte: endDate + } + }, + // Tâches avec status 'archived' récemment (aussi des accomplissements) + { + status: 'archived', + updatedAt: { + gte: startDate, + lte: endDate + } + } + ] + }, + orderBy: { + completedAt: 'desc' + }, + select: { + id: true, + title: true, + description: true, + priority: true, + completedAt: true, + createdAt: true, + taskTags: { + select: { + tag: { + select: { + name: true + } + } + } + } + } + }); + + return tasks; + } + + /** + * Récupère les checkboxes complétées de la semaine + */ + private static async getCompletedCheckboxes(startDate: Date, endDate: Date) { + const checkboxes = await prisma.dailyCheckbox.findMany({ + where: { + isChecked: true, + date: { + gte: startDate, + lte: endDate + } + }, + select: { + id: true, + text: true, + isChecked: true, + type: true, + date: true, + createdAt: true, + task: { + select: { + id: true, + title: true, + priority: true, + taskTags: { + select: { + tag: { + select: { + name: true + } + } + } + } + } + } + }, + orderBy: { + date: 'desc' + } + }); + + return checkboxes; + } + + /** + * Extrait les accomplissements clés basés sur la priorité + */ + private static extractKeyAccomplishments(tasks: TaskType[], checkboxes: CheckboxType[]): KeyAccomplishment[] { + const accomplishments: KeyAccomplishment[] = []; + + // Tâches: prendre toutes les high/medium priority, et quelques low si significatives + tasks.forEach(task => { + const priority = task.priority.toLowerCase(); + + // Convertir priorité task en impact accomplissement + let impact: 'high' | 'medium' | 'low'; + if (priority === 'high') { + impact = 'high'; + } else if (priority === 'medium') { + impact = 'medium'; + } else { + // Pour les low priority, ne garder que si c'est vraiment significatif + if (!this.isSignificantTask(task.title)) { + return; + } + impact = 'low'; + } + + // Compter les todos (checkboxes) associés à cette tâche + const relatedTodos = checkboxes.filter(cb => cb.task?.id === task.id); + + accomplishments.push({ + id: `task-${task.id}`, + title: task.title, + description: task.description || undefined, + tags: task.taskTags?.map(tt => tt.tag.name) || [], + impact, + completedAt: task.completedAt || new Date(), + relatedItems: [task.id, ...relatedTodos.map(t => t.id)], + todosCount: relatedTodos.length // Nombre réel de todos associés + }); + }); + + // AJOUTER SEULEMENT les meetings importants standalone (non liés à une tâche) + const standaloneMeetings = checkboxes.filter(checkbox => + checkbox.type === 'meeting' && !checkbox.task // Meetings non liés à une tâche + ); + + standaloneMeetings.forEach(meeting => { + accomplishments.push({ + id: `meeting-${meeting.id}`, + title: `📅 ${meeting.text}`, + tags: [], // Meetings n'ont pas de tags par défaut + impact: 'medium', // Meetings sont importants + completedAt: meeting.date, + relatedItems: [meeting.id], + todosCount: 1 // Un meeting = 1 todo + }); + }); + + // Trier par impact puis par date + return accomplishments + .sort((a, b) => { + const impactOrder = { high: 3, medium: 2, low: 1 }; + if (impactOrder[a.impact] !== impactOrder[b.impact]) { + return impactOrder[b.impact] - impactOrder[a.impact]; + } + return b.completedAt.getTime() - a.completedAt.getTime(); + }) + .slice(0, 12); // Plus d'items maintenant qu'on filtre mieux + } + + /** + * Identifie les défis et enjeux à venir + */ + private static async identifyUpcomingChallenges(): Promise { + + // Récupérer les tâches à venir (priorité high/medium en premier) + const upcomingTasks = await prisma.task.findMany({ + where: { + completedAt: null + }, + orderBy: [ + { priority: 'asc' }, // high < medium < low + { createdAt: 'desc' } + ], + select: { + id: true, + title: true, + description: true, + priority: true, + createdAt: true, + taskTags: { + select: { + tag: { + select: { + name: true + } + } + } + } + }, + take: 30 + }); + + // Récupérer les checkboxes récurrentes non complétées (meetings + tâches prioritaires) + const upcomingCheckboxes = await prisma.dailyCheckbox.findMany({ + where: { + isChecked: false, + date: { + gte: new Date() + }, + OR: [ + { type: 'meeting' }, + { + task: { + priority: { + in: ['high', 'medium'] + } + } + } + ] + }, + select: { + id: true, + text: true, + isChecked: true, + type: true, + date: true, + createdAt: true, + task: { + select: { + id: true, + title: true, + priority: true, + taskTags: { + select: { + tag: { + select: { + name: true + } + } + } + } + } + } + }, + orderBy: [ + { date: 'asc' }, + { createdAt: 'asc' } + ], + take: 20 + }); + + const challenges: UpcomingChallenge[] = []; + + // Analyser les tâches - se baser sur la priorité réelle + upcomingTasks.forEach((task) => { + const taskPriority = task.priority.toLowerCase(); + + // Convertir priorité task en priorité challenge + let priority: 'high' | 'medium' | 'low'; + if (taskPriority === 'high') { + priority = 'high'; + } else if (taskPriority === 'medium') { + priority = 'medium'; + } else { + // Pour les low priority, ne garder que si c'est vraiment challengeant + if (!this.isChallengingTask(task.title)) { + return; + } + priority = 'low'; + } + + const estimatedEffort = this.estimateEffort(task.title, task.description || undefined); + + challenges.push({ + id: `task-${task.id}`, + title: task.title, + description: task.description || undefined, + tags: task.taskTags?.map(tt => tt.tag.name) || [], + priority, + estimatedEffort, + blockers: this.identifyBlockers(task.title, task.description || undefined), + relatedItems: [task.id], + todosCount: 0 // TODO: compter les todos associés à cette tâche + }); + }); + + // Ajouter les meetings importants comme challenges + upcomingCheckboxes.forEach(checkbox => { + if (checkbox.type === 'meeting') { + challenges.push({ + id: `checkbox-${checkbox.id}`, + title: checkbox.text, + tags: checkbox.task?.taskTags?.map(tt => tt.tag.name) || [], + priority: 'medium', // Meetings sont medium par défaut + estimatedEffort: 'hours', + blockers: [], + relatedItems: [checkbox.id], + todosCount: 1 // Une checkbox = 1 todo + }); + } + }); + + return challenges + .sort((a, b) => { + const priorityOrder = { high: 3, medium: 2, low: 1 }; + return priorityOrder[b.priority] - priorityOrder[a.priority]; + }) + .slice(0, 10); // Plus d'items maintenant qu'on filtre mieux + } + + /** + * Estime l'effort requis + */ + private static estimateEffort(title: string, description?: string): 'days' | 'weeks' | 'hours' { + const content = `${title} ${description || ''}`.toLowerCase(); + + if (content.includes('architecture') || content.includes('migration') || content.includes('refactor')) { + return 'weeks'; + } + if (content.includes('feature') || content.includes('implement') || content.includes('integration')) { + return 'days'; + } + return 'hours'; + } + + /** + * Identifie les blockers potentiels + */ + private static identifyBlockers(title: string, description?: string): string[] { + const content = `${title} ${description || ''}`.toLowerCase(); + const blockers: string[] = []; + + if (content.includes('depends') || content.includes('waiting')) { + blockers.push('Dépendances externes'); + } + if (content.includes('approval') || content.includes('review')) { + blockers.push('Validation requise'); + } + if (content.includes('design') && !content.includes('implement')) { + blockers.push('Spécifications incomplètes'); + } + + return blockers; + } + + /** + * Détermine si une tâche est significative + */ + private static isSignificantTask(title: string): boolean { + const significantKeywords = [ + 'release', 'deploy', 'launch', 'milestone', + 'architecture', 'design', 'strategy', + 'integration', 'migration', 'optimization' + ]; + return significantKeywords.some(keyword => title.toLowerCase().includes(keyword)); + } + + /** + * Détermine si une checkbox est significative + */ + private static isSignificantCheckbox(text: string): boolean { + const content = text.toLowerCase(); + return content.length > 30 || // Checkboxes détaillées + content.includes('meeting') || + content.includes('review') || + content.includes('call') || + content.includes('presentation'); + } + + /** + * Détermine si une tâche représente un défi + */ + private static isChallengingTask(title: string): boolean { + const challengingKeywords = [ + 'complex', 'difficult', 'challenge', + 'architecture', 'performance', 'security', + 'integration', 'migration', 'optimization' + ]; + return challengingKeywords.some(keyword => title.toLowerCase().includes(keyword)); + } + + /** + * Analyse les patterns dans les checkboxes pour identifier des enjeux + */ + private static analyzeCheckboxPatterns(): UpcomingChallenge[] { + // Pour l'instant, retourner un array vide + // À implémenter selon les besoins spécifiques + return []; + } + + /** + * Calcule les métriques résumées + */ + private static calculateMetrics(tasks: TaskType[], checkboxes: CheckboxType[]) { + const totalTasksCompleted = tasks.length; + const totalCheckboxesCompleted = checkboxes.length; + + // Calculer les métriques détaillées + const highPriorityTasksCompleted = tasks.filter(t => t.priority.toLowerCase() === 'high').length; + const meetingCheckboxesCompleted = checkboxes.filter(c => c.type === 'meeting').length; + + // Analyser la répartition par catégorie + const focusAreas: { [category: string]: number } = {}; + + return { + totalTasksCompleted, + totalCheckboxesCompleted, + highPriorityTasksCompleted, + meetingCheckboxesCompleted, + completionRate: 0, // À calculer par rapport aux objectifs + focusAreas + }; + } + + /** + * Génère le narratif pour le manager + */ + private static generateNarrative( + accomplishments: KeyAccomplishment[], + challenges: UpcomingChallenge[] + ) { + // Points forts de la semaine + const topAccomplishments = accomplishments.slice(0, 3); + const weekHighlight = topAccomplishments.length > 0 + ? `Cette semaine, j'ai principalement progressé sur ${topAccomplishments.map(a => a.title).join(', ')}.` + : 'Semaine focalisée sur l\'exécution des tâches quotidiennes.'; + + // Défis rencontrés + const highImpactItems = accomplishments.filter(a => a.impact === 'high'); + const mainChallenges = highImpactItems.length > 0 + ? `Les principaux enjeux traités ont été liés aux ${[...new Set(highImpactItems.flatMap(a => a.tags))].join(', ')}.` + : 'Pas de blockers majeurs rencontrés cette semaine.'; + + // Focus semaine prochaine + const topChallenges = challenges.slice(0, 3); + const nextWeekFocus = topChallenges.length > 0 + ? `La semaine prochaine sera concentrée sur ${topChallenges.map(c => c.title).join(', ')}.` + : 'Continuation du travail en cours selon les priorités établies.'; + + return { + weekHighlight, + mainChallenges, + nextWeekFocus + }; + } +} diff --git a/src/app/weekly-manager/WeeklyManagerPageClient.tsx b/src/app/weekly-manager/WeeklyManagerPageClient.tsx new file mode 100644 index 0000000..da3000f --- /dev/null +++ b/src/app/weekly-manager/WeeklyManagerPageClient.tsx @@ -0,0 +1,32 @@ +'use client'; + +import { TasksProvider } from '@/contexts/TasksContext'; +import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext'; +import ManagerWeeklySummary from '@/components/dashboard/ManagerWeeklySummary'; +import { ManagerSummary } from '@/services/manager-summary'; +import { Task, Tag, UserPreferences } from '@/lib/types'; + +interface WeeklyManagerPageClientProps { + initialSummary: ManagerSummary; + initialTasks: Task[]; + initialTags: (Tag & { usage: number })[]; + initialPreferences: UserPreferences; +} + +export function WeeklyManagerPageClient({ + initialSummary, + initialTasks, + initialTags, + initialPreferences +}: WeeklyManagerPageClientProps) { + return ( + + + + + + ); +} diff --git a/src/app/weekly-manager/page.tsx b/src/app/weekly-manager/page.tsx new file mode 100644 index 0000000..ef40891 --- /dev/null +++ b/src/app/weekly-manager/page.tsx @@ -0,0 +1,36 @@ +import { Header } from '@/components/ui/Header'; +import { ManagerSummaryService } from '@/services/manager-summary'; +import { tasksService } from '@/services/tasks'; +import { tagsService } from '@/services/tags'; +import { userPreferencesService } from '@/services/user-preferences'; +import { WeeklyManagerPageClient } from './WeeklyManagerPageClient'; + +// Force dynamic rendering (no static generation) +export const dynamic = 'force-dynamic'; + +export default async function WeeklyManagerPage() { + // SSR - Récupération des données côté serveur + const [summary, initialTasks, initialTags, initialPreferences] = await Promise.all([ + ManagerSummaryService.getManagerSummary(), + tasksService.getTasks(), + tagsService.getTags(), + userPreferencesService.getAllPreferences() + ]); + + return ( +
+
+ +
+
+ +
+
+
+ ); +}