- Integrated system info fetching in `SettingsPage` for improved user insights. - Enhanced `SettingsIndexPageClient` with manual backup creation and Jira connection testing features. - Added loading states and auto-dismiss messages for user feedback during actions. - Updated UI to display system info and backup statistics dynamically.
403 lines
17 KiB
TypeScript
403 lines
17 KiB
TypeScript
'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 Link from 'next/link';
|
||
import { useState, useEffect, useTransition } from 'react';
|
||
import { backupClient } from '@/clients/backup-client';
|
||
import { jiraClient } from '@/clients/jira-client';
|
||
import { getSystemInfo } from '@/actions/system-info';
|
||
import { SystemInfo } from '@/services/system-info';
|
||
|
||
interface SettingsIndexPageClientProps {
|
||
initialPreferences: UserPreferences;
|
||
initialSystemInfo?: SystemInfo;
|
||
}
|
||
|
||
export function SettingsIndexPageClient({ initialPreferences, initialSystemInfo }: SettingsIndexPageClientProps) {
|
||
// États pour les actions
|
||
const [isBackupLoading, setIsBackupLoading] = useState(false);
|
||
const [isJiraTestLoading, setIsJiraTestLoading] = useState(false);
|
||
const [systemInfo, setSystemInfo] = useState<SystemInfo | null>(initialSystemInfo || null);
|
||
const [messages, setMessages] = useState<{
|
||
backup?: { type: 'success' | 'error', text: string };
|
||
jira?: { type: 'success' | 'error', text: string };
|
||
}>({});
|
||
|
||
// useTransition pour le server action
|
||
const [isSystemInfoLoading, startTransition] = useTransition();
|
||
|
||
// Fonction pour recharger les infos système (server action)
|
||
const loadSystemInfo = () => {
|
||
startTransition(async () => {
|
||
try {
|
||
const result = await getSystemInfo();
|
||
if (result.success && result.data) {
|
||
setSystemInfo(result.data);
|
||
} else {
|
||
console.error('Error loading system info:', result.error);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading system info:', error);
|
||
}
|
||
});
|
||
};
|
||
|
||
// Fonction pour créer une sauvegarde manuelle
|
||
const handleCreateBackup = async () => {
|
||
setIsBackupLoading(true);
|
||
try {
|
||
const backup = await backupClient.createBackup();
|
||
setMessages(prev => ({
|
||
...prev,
|
||
backup: { type: 'success', text: `Sauvegarde créée: ${backup.filename}` }
|
||
}));
|
||
|
||
// Recharger les infos système pour mettre à jour le nombre de sauvegardes
|
||
loadSystemInfo();
|
||
} catch {
|
||
setMessages(prev => ({
|
||
...prev,
|
||
backup: { type: 'error', text: 'Erreur lors de la création de la sauvegarde' }
|
||
}));
|
||
} finally {
|
||
setIsBackupLoading(false);
|
||
}
|
||
};
|
||
|
||
// Fonction pour tester la connexion Jira
|
||
const handleTestJira = async () => {
|
||
setIsJiraTestLoading(true);
|
||
try {
|
||
const status = await jiraClient.testConnection();
|
||
setMessages(prev => ({
|
||
...prev,
|
||
jira: {
|
||
type: status.connected ? 'success' : 'error',
|
||
text: status.connected ? 'Connexion Jira réussie !' : `Erreur: ${status.message || 'Connexion échouée'}`
|
||
}
|
||
}));
|
||
} catch {
|
||
setMessages(prev => ({
|
||
...prev,
|
||
jira: { type: 'error', text: 'Erreur lors du test de connexion Jira' }
|
||
}));
|
||
} finally {
|
||
setIsJiraTestLoading(false);
|
||
}
|
||
};
|
||
|
||
// Auto-dismiss des messages après 5 secondes
|
||
useEffect(() => {
|
||
Object.keys(messages).forEach(key => {
|
||
if (messages[key as keyof typeof messages]) {
|
||
const timer = setTimeout(() => {
|
||
setMessages(prev => ({ ...prev, [key]: undefined }));
|
||
}, 5000);
|
||
return () => clearTimeout(timer);
|
||
}
|
||
});
|
||
}, [messages]);
|
||
|
||
const settingsPages = [
|
||
{
|
||
href: '/settings/general',
|
||
icon: '⚙️',
|
||
title: 'Paramètres généraux',
|
||
description: 'Interface, thème, préférences d\'affichage',
|
||
status: 'Fonctionnel'
|
||
},
|
||
{
|
||
href: '/settings/integrations',
|
||
icon: '🔌',
|
||
title: 'Intégrations',
|
||
description: 'Jira, GitHub, Slack et autres services externes',
|
||
status: 'Fonctionnel'
|
||
},
|
||
{
|
||
href: '/settings/backup',
|
||
icon: '💾',
|
||
title: 'Sauvegardes',
|
||
description: 'Gestion des sauvegardes automatiques et manuelles',
|
||
status: 'Fonctionnel'
|
||
},
|
||
{
|
||
href: '/settings/advanced',
|
||
icon: '🛠️',
|
||
title: 'Paramètres avancés',
|
||
description: 'Logs, debug et maintenance système',
|
||
status: 'Fonctionnel'
|
||
}
|
||
];
|
||
|
||
return (
|
||
<UserPreferencesProvider initialPreferences={initialPreferences}>
|
||
<div className="min-h-screen bg-[var(--background)]">
|
||
<Header
|
||
title="TowerControl"
|
||
subtitle="Configuration & Paramètres"
|
||
/>
|
||
|
||
<div className="container mx-auto px-4 py-4">
|
||
<div className="max-w-4xl mx-auto">
|
||
{/* Page Header */}
|
||
<div className="mb-8">
|
||
<h1 className="text-3xl font-mono font-bold text-[var(--foreground)] mb-3">
|
||
Paramètres
|
||
</h1>
|
||
<p className="text-[var(--muted-foreground)] text-lg">
|
||
Configuration de TowerControl et de ses intégrations
|
||
</p>
|
||
</div>
|
||
|
||
{/* Quick Stats */}
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 mb-8">
|
||
<Card>
|
||
<CardContent className="p-4">
|
||
<div className="flex items-center gap-3">
|
||
<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>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardContent className="p-4">
|
||
<div className="flex items-center gap-3">
|
||
<span className="text-2xl">🔌</span>
|
||
<div>
|
||
<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é'}
|
||
</p>
|
||
{initialPreferences.jiraConfig.enabled && (
|
||
<span className="w-2 h-2 bg-green-500 rounded-full" title="Jira configuré"></span>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardContent className="p-4">
|
||
<div className="flex items-center gap-3">
|
||
<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>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardContent className="p-4">
|
||
<div className="flex items-center gap-3">
|
||
<span className="text-2xl">💾</span>
|
||
<div>
|
||
<p className="text-sm text-[var(--muted-foreground)]">Sauvegardes</p>
|
||
<p className="font-medium">
|
||
{systemInfo ? systemInfo.database.totalBackups : '...'}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
|
||
{/* Settings Sections */}
|
||
<div className="space-y-4">
|
||
<h2 className="text-xl font-semibold text-[var(--foreground)] mb-4">
|
||
Sections de configuration
|
||
</h2>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-1 gap-4">
|
||
{settingsPages.map((page) => (
|
||
<Link key={page.href} href={page.href}>
|
||
<Card className="transition-all hover:shadow-md hover:border-[var(--primary)]/30 cursor-pointer">
|
||
<CardContent className="p-6">
|
||
<div className="flex items-start justify-between">
|
||
<div className="flex items-start gap-4">
|
||
<span className="text-3xl">{page.icon}</span>
|
||
<div className="flex-1">
|
||
<h3 className="text-lg font-semibold text-[var(--foreground)] mb-1">
|
||
{page.title}
|
||
</h3>
|
||
<p className="text-[var(--muted-foreground)] mb-2">
|
||
{page.description}
|
||
</p>
|
||
<div className="flex items-center gap-2">
|
||
<span className={`px-2 py-1 rounded text-xs font-medium ${
|
||
page.status === 'Fonctionnel'
|
||
? 'bg-[var(--success)]/20 text-[var(--success)]'
|
||
: page.status === 'En développement'
|
||
? 'bg-[var(--warning)]/20 text-[var(--warning)]'
|
||
: 'bg-[var(--muted)]/20 text-[var(--muted-foreground)]'
|
||
}`}>
|
||
{page.status}
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<svg
|
||
className="w-5 h-5 text-[var(--muted-foreground)]"
|
||
fill="none"
|
||
stroke="currentColor"
|
||
viewBox="0 0 24 24"
|
||
>
|
||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
||
</svg>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</Link>
|
||
))}
|
||
</div>
|
||
</div>
|
||
|
||
{/* Quick Actions */}
|
||
<div className="mt-8">
|
||
<h2 className="text-xl font-semibold text-[var(--foreground)] mb-4">
|
||
Actions rapides
|
||
</h2>
|
||
|
||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||
<Card>
|
||
<CardContent className="p-4">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h3 className="font-medium mb-1">Sauvegarde manuelle</h3>
|
||
<p className="text-sm text-[var(--muted-foreground)]">
|
||
Créer une sauvegarde des données
|
||
</p>
|
||
{messages.backup && (
|
||
<p className={`text-xs mt-1 ${
|
||
messages.backup.type === 'success'
|
||
? 'text-green-600 dark:text-green-400'
|
||
: 'text-red-600 dark:text-red-400'
|
||
}`}>
|
||
{messages.backup.text}
|
||
</p>
|
||
)}
|
||
</div>
|
||
<button
|
||
onClick={handleCreateBackup}
|
||
disabled={isBackupLoading}
|
||
className="px-3 py-1.5 bg-[var(--primary)] text-[var(--primary-foreground)] rounded text-sm disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
{isBackupLoading ? 'En cours...' : 'Sauvegarder'}
|
||
</button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
|
||
<Card>
|
||
<CardContent className="p-4">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h3 className="font-medium mb-1">Test Jira</h3>
|
||
<p className="text-sm text-[var(--muted-foreground)]">
|
||
Tester la connexion Jira
|
||
</p>
|
||
{messages.jira && (
|
||
<p className={`text-xs mt-1 ${
|
||
messages.jira.type === 'success'
|
||
? 'text-green-600 dark:text-green-400'
|
||
: 'text-red-600 dark:text-red-400'
|
||
}`}>
|
||
{messages.jira.text}
|
||
</p>
|
||
)}
|
||
</div>
|
||
<button
|
||
onClick={handleTestJira}
|
||
disabled={!initialPreferences.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'}
|
||
</button>
|
||
</div>
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
|
||
{/* System Info */}
|
||
<Card className="mt-8">
|
||
<CardHeader>
|
||
<div className="flex items-center justify-between">
|
||
<h2 className="text-lg font-semibold">ℹ️ Informations système</h2>
|
||
<button
|
||
onClick={loadSystemInfo}
|
||
disabled={isSystemInfoLoading}
|
||
className="text-xs px-2 py-1 bg-[var(--card)] border border-[var(--border)] rounded hover:bg-[var(--card-hover)] disabled:opacity-50 disabled:cursor-not-allowed"
|
||
>
|
||
{isSystemInfoLoading ? '🔄 Chargement...' : '🔄 Actualiser'}
|
||
</button>
|
||
</div>
|
||
</CardHeader>
|
||
<CardContent>
|
||
{systemInfo ? (
|
||
<>
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 text-sm mb-4">
|
||
<div>
|
||
<p className="text-[var(--muted-foreground)]">Version</p>
|
||
<p className="font-medium">TowerControl v{systemInfo.version}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-[var(--muted-foreground)]">Dernière maj</p>
|
||
<p className="font-medium">{systemInfo.lastUpdate}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-[var(--muted-foreground)]">Environnement</p>
|
||
<p className="font-medium capitalize">{systemInfo.environment}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-[var(--muted-foreground)]">Uptime</p>
|
||
<p className="font-medium">{systemInfo.uptime}</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="border-t border-[var(--border)] pt-4">
|
||
<h3 className="text-sm font-medium mb-3 text-[var(--muted-foreground)]">Base de données</h3>
|
||
<div className="grid grid-cols-1 md:grid-cols-4 gap-4 text-sm">
|
||
<div>
|
||
<p className="text-[var(--muted-foreground)]">Tâches</p>
|
||
<p className="font-medium">{systemInfo.database.totalTasks}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-[var(--muted-foreground)]">Utilisateurs</p>
|
||
<p className="font-medium">{systemInfo.database.totalUsers}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-[var(--muted-foreground)]">Sauvegardes</p>
|
||
<p className="font-medium">{systemInfo.database.totalBackups}</p>
|
||
</div>
|
||
<div>
|
||
<p className="text-[var(--muted-foreground)]">Taille DB</p>
|
||
<p className="font-medium">{systemInfo.database.databaseSize}</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</>
|
||
) : (
|
||
<div className="text-center py-4">
|
||
<p className="text-[var(--muted-foreground)]">Chargement des informations système...</p>
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</UserPreferencesProvider>
|
||
);
|
||
}
|