feat: integrate UserPreferencesContext for improved preference management

- Added `UserPreferencesProvider` to `RootLayout` for centralized user preferences handling.
- Updated components to remove direct user preferences fetching, relying on context instead.
- Enhanced SSR data fetching by consolidating user preferences retrieval into a single service call.
- Cleaned up unused props in various components to streamline the codebase.
This commit is contained in:
Julien Froidefond
2025-09-21 15:03:19 +02:00
parent 4ba6ba2c0b
commit c650c67627
17 changed files with 86 additions and 138 deletions

View File

@@ -4,8 +4,8 @@ import { useState } from 'react';
import { KanbanBoardContainer } from '@/components/kanban/BoardContainer';
import { Header } from '@/components/ui/Header';
import { TasksProvider, useTasksContext } from '@/contexts/TasksContext';
import { UserPreferencesProvider, useUserPreferences } from '@/contexts/UserPreferencesContext';
import { Task, Tag, UserPreferences } from '@/lib/types';
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
import { Task, Tag } from '@/lib/types';
import { CreateTaskData } from '@/clients/tasks-client';
import { CreateTaskForm } from '@/components/forms/CreateTaskForm';
import { Button } from '@/components/ui/Button';
@@ -15,7 +15,6 @@ import { FontSizeToggle } from '@/components/ui/FontSizeToggle';
interface KanbanPageClientProps {
initialTasks: Task[];
initialTags: (Tag & { usage: number })[];
initialPreferences: UserPreferences;
}
function KanbanPageContent() {
@@ -179,15 +178,13 @@ function KanbanPageContent() {
);
}
export function KanbanPageClient({ initialTasks, initialTags, initialPreferences }: KanbanPageClientProps) {
export function KanbanPageClient({ initialTasks, initialTags }: KanbanPageClientProps) {
return (
<UserPreferencesProvider initialPreferences={initialPreferences}>
<TasksProvider
initialTasks={initialTasks}
initialTags={initialTags}
>
<KanbanPageContent />
</TasksProvider>
</UserPreferencesProvider>
<TasksProvider
initialTasks={initialTasks}
initialTags={initialTags}
>
<KanbanPageContent />
</TasksProvider>
);
}

View File

@@ -1,6 +1,5 @@
import { tasksService } from '@/services/tasks';
import { tagsService } from '@/services/tags';
import { userPreferencesService } from '@/services/user-preferences';
import { KanbanPageClient } from './KanbanPageClient';
// Force dynamic rendering (no static generation)
@@ -8,17 +7,15 @@ export const dynamic = 'force-dynamic';
export default async function KanbanPage() {
// SSR - Récupération des données côté serveur
const [initialTasks, initialTags, initialPreferences] = await Promise.all([
const [initialTasks, initialTags] = await Promise.all([
tasksService.getTasks(),
tagsService.getTags(),
userPreferencesService.getAllPreferences()
tagsService.getTags()
]);
return (
<KanbanPageClient
initialTasks={initialTasks}
initialTags={initialTags}
initialPreferences={initialPreferences}
/>
);
}

View File

@@ -3,6 +3,7 @@ import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/contexts/ThemeContext";
import { JiraConfigProvider } from "@/contexts/JiraConfigContext";
import { UserPreferencesProvider } from "@/contexts/UserPreferencesContext";
import { userPreferencesService } from "@/services/user-preferences";
const geistSans = Geist({
@@ -25,20 +26,19 @@ export default async function RootLayout({
}: Readonly<{
children: React.ReactNode;
}>) {
// Récupérer les données côté serveur pour le SSR
const [initialTheme, jiraConfig] = await Promise.all([
userPreferencesService.getTheme(),
userPreferencesService.getJiraConfig()
]);
// Récupérer toutes les préférences côté serveur pour le SSR
const initialPreferences = await userPreferencesService.getAllPreferences();
return (
<html lang="en" className={initialTheme}>
<html lang="en" className={initialPreferences.viewPreferences.theme}>
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
>
<ThemeProvider initialTheme={initialTheme}>
<JiraConfigProvider config={jiraConfig}>
{children}
<ThemeProvider initialTheme={initialPreferences.viewPreferences.theme}>
<JiraConfigProvider config={initialPreferences.jiraConfig}>
<UserPreferencesProvider initialPreferences={initialPreferences}>
{children}
</UserPreferencesProvider>
</JiraConfigProvider>
</ThemeProvider>
</body>

View File

@@ -1,6 +1,5 @@
import { tasksService } from '@/services/tasks';
import { tagsService } from '@/services/tags';
import { userPreferencesService } from '@/services/user-preferences';
import { HomePageClient } from '@/components/HomePageClient';
// Force dynamic rendering (no static generation)
@@ -8,10 +7,9 @@ export const dynamic = 'force-dynamic';
export default async function HomePage() {
// SSR - Récupération des données côté serveur
const [initialTasks, initialTags, initialPreferences, initialStats] = await Promise.all([
const [initialTasks, initialTags, initialStats] = await Promise.all([
tasksService.getTasks(),
tagsService.getTags(),
userPreferencesService.getAllPreferences(),
tasksService.getTaskStats()
]);
@@ -19,7 +17,6 @@ export default async function HomePage() {
<HomePageClient
initialTasks={initialTasks}
initialTags={initialTags}
initialPreferences={initialPreferences}
initialStats={initialStats}
/>
);

View File

@@ -1,4 +1,3 @@
import { userPreferencesService } from '@/services/user-preferences';
import { tasksService } from '@/services/tasks';
import { tagsService } from '@/services/tags';
import { backupService } from '@/services/backup';
@@ -10,8 +9,7 @@ export const dynamic = 'force-dynamic';
export default async function AdvancedSettingsPage() {
// Fetch all data server-side
const [preferences, taskStats, tags] = await Promise.all([
userPreferencesService.getAllPreferences(),
const [taskStats, tags] = await Promise.all([
tasksService.getTaskStats(),
tagsService.getTags()
]);
@@ -38,7 +36,6 @@ export default async function AdvancedSettingsPage() {
return (
<AdvancedSettingsPageClient
initialPreferences={preferences}
initialDbStats={dbStats}
initialBackupData={backupData}
/>

View File

@@ -1,4 +1,3 @@
import { userPreferencesService } from '@/services/user-preferences';
import { tagsService } from '@/services/tags';
import { GeneralSettingsPageClient } from '@/components/settings/GeneralSettingsPageClient';
@@ -7,10 +6,7 @@ export const dynamic = 'force-dynamic';
export default async function GeneralSettingsPage() {
// Fetch data server-side
const [preferences, tags] = await Promise.all([
userPreferencesService.getAllPreferences(),
tagsService.getTags()
]);
const tags = await tagsService.getTags();
return <GeneralSettingsPageClient initialPreferences={preferences} initialTags={tags} />;
return <GeneralSettingsPageClient initialTags={tags} />;
}

View File

@@ -6,12 +6,11 @@ export const dynamic = 'force-dynamic';
export default async function IntegrationsSettingsPage() {
// Fetch data server-side
const preferences = await userPreferencesService.getAllPreferences();
// Preferences are now available via context
const jiraConfig = await userPreferencesService.getJiraConfig();
return (
<IntegrationsSettingsPageClient
initialPreferences={preferences}
initialJiraConfig={jiraConfig}
/>
);

View File

@@ -1,4 +1,3 @@
import { userPreferencesService } from '@/services/user-preferences';
import { SystemInfoService } from '@/services/system-info';
import { SettingsIndexPageClient } from '@/components/settings/SettingsIndexPageClient';
@@ -7,14 +6,10 @@ export const dynamic = 'force-dynamic';
export default async function SettingsPage() {
// Fetch data in parallel for better performance
const [preferences, systemInfo] = await Promise.all([
userPreferencesService.getAllPreferences(),
SystemInfoService.getSystemInfo()
]);
const systemInfo = await SystemInfoService.getSystemInfo();
return (
<SettingsIndexPageClient
initialPreferences={preferences}
initialSystemInfo={systemInfo}
/>
);

View File

@@ -1,32 +1,27 @@
'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';
import { Task, Tag } from '@/lib/types';
interface WeeklyManagerPageClientProps {
initialSummary: ManagerSummary;
initialTasks: Task[];
initialTags: (Tag & { usage: number })[];
initialPreferences: UserPreferences;
}
export function WeeklyManagerPageClient({
initialSummary,
initialTasks,
initialTags,
initialPreferences
initialTags
}: WeeklyManagerPageClientProps) {
return (
<UserPreferencesProvider initialPreferences={initialPreferences}>
<TasksProvider
initialTasks={initialTasks}
initialTags={initialTags}
>
<ManagerWeeklySummary initialSummary={initialSummary} />
</TasksProvider>
</UserPreferencesProvider>
<TasksProvider
initialTasks={initialTasks}
initialTags={initialTags}
>
<ManagerWeeklySummary initialSummary={initialSummary} />
</TasksProvider>
);
}

View File

@@ -2,7 +2,6 @@ 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)
@@ -10,11 +9,10 @@ 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([
const [summary, initialTasks, initialTags] = await Promise.all([
ManagerSummaryService.getManagerSummary(),
tasksService.getTasks(),
tagsService.getTags(),
userPreferencesService.getAllPreferences()
tagsService.getTags()
]);
return (
@@ -27,7 +25,6 @@ export default async function WeeklyManagerPage() {
initialSummary={summary}
initialTasks={initialTasks}
initialTags={initialTags}
initialPreferences={initialPreferences}
/>
</div>
</div>

View File

@@ -2,8 +2,7 @@
import { Header } from '@/components/ui/Header';
import { TasksProvider, useTasksContext } from '@/contexts/TasksContext';
import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext';
import { Task, Tag, UserPreferences, TaskStats } from '@/lib/types';
import { Task, Tag, TaskStats } from '@/lib/types';
import { CreateTaskData } from '@/clients/tasks-client';
import { DashboardStats } from '@/components/dashboard/DashboardStats';
import { QuickActions } from '@/components/dashboard/QuickActions';
@@ -13,7 +12,6 @@ import { ProductivityAnalytics } from '@/components/dashboard/ProductivityAnalyt
interface HomePageClientProps {
initialTasks: Task[];
initialTags: (Tag & { usage: number })[];
initialPreferences: UserPreferences;
initialStats: TaskStats;
}
@@ -51,16 +49,14 @@ function HomePageContent() {
);
}
export function HomePageClient({ initialTasks, initialTags, initialPreferences, initialStats }: HomePageClientProps) {
export function HomePageClient({ initialTasks, initialTags, initialStats }: HomePageClientProps) {
return (
<UserPreferencesProvider initialPreferences={initialPreferences}>
<TasksProvider
initialTasks={initialTasks}
initialTags={initialTags}
initialStats={initialStats}
>
<HomePageContent />
</TasksProvider>
</UserPreferencesProvider>
<TasksProvider
initialTasks={initialTasks}
initialTags={initialTags}
initialStats={initialStats}
>
<HomePageContent />
</TasksProvider>
);
}

View File

@@ -1,11 +1,9 @@
'use client';
import { useState } from 'react';
import { UserPreferences } from '@/lib/types';
import { Header } from '@/components/ui/Header';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext';
import { backupClient, BackupListResponse } from '@/clients/backup-client';
import Link from 'next/link';
import { parseDate, getToday, formatDateForDisplay } from '@/lib/date-utils';
@@ -17,13 +15,11 @@ interface DatabaseStats {
}
interface AdvancedSettingsPageClientProps {
initialPreferences: UserPreferences;
initialDbStats: DatabaseStats;
initialBackupData: BackupListResponse;
}
export function AdvancedSettingsPageClient({
initialPreferences,
initialDbStats,
initialBackupData
}: AdvancedSettingsPageClientProps) {
@@ -107,8 +103,7 @@ export function AdvancedSettingsPageClient({
};
return (
<UserPreferencesProvider initialPreferences={initialPreferences}>
<div className="min-h-screen bg-[var(--background)]">
<div className="min-h-screen bg-[var(--background)]">
<Header
title="TowerControl"
subtitle="Paramètres avancés"
@@ -251,6 +246,5 @@ export function AdvancedSettingsPageClient({
</div>
</div>
</div>
</UserPreferencesProvider>
);
}

View File

@@ -1,23 +1,21 @@
'use client';
import { useState, useMemo } from 'react';
import { UserPreferences, Tag } from '@/lib/types';
import { Tag } from '@/lib/types';
import { useTags } from '@/hooks/useTags';
import { Header } from '@/components/ui/Header';
import { Card, CardContent, CardHeader } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { TagForm } from '@/components/forms/TagForm';
import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext';
import Link from 'next/link';
import { formatDateForDisplay } from '@/lib/date-utils';
interface GeneralSettingsPageClientProps {
initialPreferences: UserPreferences;
initialTags: Tag[];
}
export function GeneralSettingsPageClient({ initialPreferences, initialTags }: GeneralSettingsPageClientProps) {
export function GeneralSettingsPageClient({ initialTags }: GeneralSettingsPageClientProps) {
const {
tags,
refreshTags,
@@ -82,8 +80,7 @@ export function GeneralSettingsPageClient({ initialPreferences, initialTags }: G
}
};
return (
<UserPreferencesProvider initialPreferences={initialPreferences}>
<div className="min-h-screen bg-[var(--background)]">
<div className="min-h-screen bg-[var(--background)]">
<Header
title="TowerControl"
subtitle="Paramètres généraux"
@@ -332,31 +329,30 @@ export function GeneralSettingsPageClient({ initialPreferences, initialTags }: G
</div>
</div>
</div>
</div>
{/* Modals pour les tags */}
{isCreateModalOpen && (
<TagForm
isOpen={isCreateModalOpen}
onClose={() => setIsCreateModalOpen(false)}
onSuccess={async () => {
setIsCreateModalOpen(false);
await refreshTags();
}}
/>
)}
{editingTag && (
<TagForm
isOpen={!!editingTag}
tag={editingTag}
onClose={() => setEditingTag(null)}
onSuccess={async () => {
setEditingTag(null);
await refreshTags();
}}
/>
)}
</UserPreferencesProvider>
{/* Modals pour les tags */}
{isCreateModalOpen && (
<TagForm
isOpen={isCreateModalOpen}
onClose={() => setIsCreateModalOpen(false)}
onSuccess={async () => {
setIsCreateModalOpen(false);
await refreshTags();
}}
/>
)}
{editingTag && (
<TagForm
isOpen={!!editingTag}
tag={editingTag}
onClose={() => setEditingTag(null)}
onSuccess={async () => {
setEditingTag(null);
await refreshTags();
}}
/>
)}
</div>
);
}

View File

@@ -1,27 +1,23 @@
'use client';
import { UserPreferences, JiraConfig } from '@/lib/types';
import { JiraConfig } from '@/lib/types';
import { Header } from '@/components/ui/Header';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { JiraConfigForm } from '@/components/settings/JiraConfigForm';
import { JiraSync } from '@/components/jira/JiraSync';
import { JiraLogs } from '@/components/jira/JiraLogs';
import { JiraSchedulerConfig } from '@/components/jira/JiraSchedulerConfig';
import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext';
import Link from 'next/link';
interface IntegrationsSettingsPageClientProps {
initialPreferences: UserPreferences;
initialJiraConfig: JiraConfig;
}
export function IntegrationsSettingsPageClient({
initialPreferences,
initialJiraConfig
}: IntegrationsSettingsPageClientProps) {
return (
<UserPreferencesProvider initialPreferences={initialPreferences}>
<div className="min-h-screen bg-[var(--background)]">
<div className="min-h-screen bg-[var(--background)]">
<Header
title="TowerControl"
subtitle="Intégrations externes"
@@ -169,6 +165,5 @@ export function IntegrationsSettingsPageClient({
</div>
</div>
</div>
</UserPreferencesProvider>
);
}

View File

@@ -1,9 +1,8 @@
'use client';
import { UserPreferences } from '@/lib/types';
import { Header } from '@/components/ui/Header';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { UserPreferencesProvider } from '@/contexts/UserPreferencesContext';
import { useUserPreferences } from '@/contexts/UserPreferencesContext';
import Link from 'next/link';
import { useState, useEffect, useTransition } from 'react';
import { backupClient } from '@/clients/backup-client';
@@ -12,11 +11,11 @@ import { getSystemInfo } from '@/actions/system-info';
import { SystemInfo } from '@/services/system-info';
interface SettingsIndexPageClientProps {
initialPreferences: UserPreferences;
initialSystemInfo?: SystemInfo;
}
export function SettingsIndexPageClient({ initialPreferences, initialSystemInfo }: SettingsIndexPageClientProps) {
export function SettingsIndexPageClient({ initialSystemInfo }: SettingsIndexPageClientProps) {
const { preferences } = useUserPreferences();
// États pour les actions
const [isBackupLoading, setIsBackupLoading] = useState(false);
const [isJiraTestLoading, setIsJiraTestLoading] = useState(false);
@@ -140,8 +139,7 @@ export function SettingsIndexPageClient({ initialPreferences, initialSystemInfo
];
return (
<UserPreferencesProvider initialPreferences={initialPreferences}>
<div className="min-h-screen bg-[var(--background)]">
<div className="min-h-screen bg-[var(--background)]">
<Header
title="TowerControl"
subtitle="Configuration & Paramètres"
@@ -167,7 +165,7 @@ export function SettingsIndexPageClient({ initialPreferences, initialSystemInfo
<span className="text-2xl">🎨</span>
<div>
<p className="text-sm text-[var(--muted-foreground)]">Thème actuel</p>
<p className="font-medium capitalize">{initialPreferences.viewPreferences.theme}</p>
<p className="font-medium capitalize">{preferences.viewPreferences.theme}</p>
</div>
</div>
</CardContent>
@@ -181,9 +179,9 @@ export function SettingsIndexPageClient({ initialPreferences, initialSystemInfo
<p className="text-sm text-[var(--muted-foreground)]">Jira</p>
<div className="flex items-center gap-2">
<p className="font-medium">
{initialPreferences.jiraConfig.enabled ? 'Configuré' : 'Non configuré'}
{preferences.jiraConfig.enabled ? 'Configuré' : 'Non configuré'}
</p>
{initialPreferences.jiraConfig.enabled && (
{preferences.jiraConfig.enabled && (
<span className="w-2 h-2 bg-green-500 rounded-full" title="Jira configuré"></span>
)}
</div>
@@ -198,7 +196,7 @@ export function SettingsIndexPageClient({ initialPreferences, initialSystemInfo
<span className="text-2xl">📏</span>
<div>
<p className="text-sm text-[var(--muted-foreground)]">Taille police</p>
<p className="font-medium capitalize">{initialPreferences.viewPreferences.fontSize}</p>
<p className="font-medium capitalize">{preferences.viewPreferences.fontSize}</p>
</div>
</div>
</CardContent>
@@ -325,7 +323,7 @@ export function SettingsIndexPageClient({ initialPreferences, initialSystemInfo
</div>
<button
onClick={handleTestJira}
disabled={!initialPreferences.jiraConfig.enabled || isJiraTestLoading}
disabled={!preferences.jiraConfig.enabled || isJiraTestLoading}
className="px-3 py-1.5 bg-[var(--card)] text-[var(--foreground)] border border-[var(--border)] rounded text-sm disabled:opacity-50 disabled:cursor-not-allowed"
>
{isJiraTestLoading ? 'Test...' : 'Tester'}
@@ -404,6 +402,5 @@ export function SettingsIndexPageClient({ initialPreferences, initialSystemInfo
</div>
</div>
</div>
</UserPreferencesProvider>
);
}

View File

@@ -1,6 +1,6 @@
import { Task, TaskStatus, TaskPriority, TaskSource } from '@/lib/types';
import { prisma } from './database';
import { getToday, parseDate, subtractDays, addDays } from '@/lib/date-utils';
import { getToday, parseDate, subtractDays } from '@/lib/date-utils';
export interface ProductivityMetrics {
completionTrend: Array<{

View File

@@ -5,7 +5,7 @@
import { JiraService } from './jira';
import { jiraAnalyticsCache } from './jira-analytics-cache';
import { getToday, parseDate, addDays, subtractDays } from '@/lib/date-utils';
import { getToday, parseDate, subtractDays } from '@/lib/date-utils';
import {
JiraAnalytics,
JiraTask,