Files
towercontrol/src/components/settings/tfs/TfsSchedulerConfig.tsx
Julien Froidefond a1f82a4c9b feat: refactor TFS integration structure and add scheduler functionality
- Updated TFS service imports to a new directory structure for better organization.
- Introduced new API routes for TFS scheduler configuration and status retrieval.
- Implemented TFS scheduler logic to manage automatic synchronization based on user preferences.
- Added components for TFS configuration and scheduler management, enhancing user interaction with TFS settings.
- Removed deprecated TfsSync component, consolidating functionality into the new structure.
2025-10-03 08:15:12 +02:00

247 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
'use client';
import { useState, useEffect } from 'react';
import { Button } from '@/components/ui/Button';
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
import { Badge } from '@/components/ui/Badge';
import { parseDate, getToday } from '@/lib/date-utils';
interface TfsSchedulerStatus {
isRunning: boolean;
isEnabled: boolean;
interval: 'hourly' | 'daily' | 'weekly';
nextSync: Date | null;
tfsConfigured: boolean;
}
interface TfsSchedulerConfigProps {
className?: string;
}
export function TfsSchedulerConfig({ className = "" }: TfsSchedulerConfigProps) {
const [schedulerStatus, setSchedulerStatus] = useState<TfsSchedulerStatus | null>(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// Charger le statut initial
useEffect(() => {
loadSchedulerStatus();
}, []);
const loadSchedulerStatus = async () => {
try {
const response = await fetch('/api/tfs/scheduler-status');
if (response.ok) {
const status = await response.json();
setSchedulerStatus(status.data);
}
} catch (err) {
console.error('Erreur lors du chargement du statut scheduler:', err);
}
};
const toggleScheduler = async () => {
if (!schedulerStatus) return;
setIsLoading(true);
setError(null);
try {
const response = await fetch('/api/tfs/scheduler-config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tfsAutoSync: !schedulerStatus.isRunning,
tfsSyncInterval: schedulerStatus.interval
})
});
if (response.ok) {
const result = await response.json();
setSchedulerStatus(result.data);
} else {
const error = await response.json();
setError(error.error || 'Erreur lors du toggle scheduler');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Erreur lors du toggle scheduler');
} finally {
setIsLoading(false);
}
};
const updateInterval = async (interval: 'hourly' | 'daily' | 'weekly') => {
if (!schedulerStatus) return;
setIsLoading(true);
setError(null);
try {
const response = await fetch('/api/tfs/scheduler-config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
tfsAutoSync: true,
tfsSyncInterval: interval
})
});
if (response.ok) {
const result = await response.json();
setSchedulerStatus(result.data);
} else {
const error = await response.json();
setError(error.error || 'Erreur lors de la mise à jour');
}
} catch (err) {
setError(err instanceof Error ? err.message : 'Erreur lors de la mise à jour');
} finally {
setIsLoading(false);
}
};
const getStatusBadge = () => {
if (!schedulerStatus) return null;
if (!schedulerStatus.tfsConfigured) {
return <Badge variant="warning" size="sm"> TFS non configuré</Badge>;
}
if (!schedulerStatus.isEnabled) {
return <Badge variant="default" size="sm"> Désactivé</Badge>;
}
return schedulerStatus.isRunning ? (
<Badge variant="success" size="sm"> Actif</Badge>
) : (
<Badge variant="danger" size="sm"> Arrêté</Badge>
);
};
const getNextSyncText = () => {
if (!schedulerStatus?.nextSync) return 'Aucune synchronisation planifiée';
const nextSync = typeof schedulerStatus.nextSync === 'string'
? parseDate(schedulerStatus.nextSync)
: schedulerStatus.nextSync;
const now = getToday();
const diffMs = nextSync.getTime() - now.getTime();
if (diffMs <= 0) return 'Synchronisation en cours...';
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffMinutes = Math.floor((diffMs % (1000 * 60 * 60)) / (1000 * 60));
if (diffHours > 0) {
return `Dans ${diffHours}h ${diffMinutes}min`;
} else {
return `Dans ${diffMinutes}min`;
}
};
const getIntervalText = (interval: string) => {
switch (interval) {
case 'hourly': return 'Horaire';
case 'daily': return 'Quotidien';
case 'weekly': return 'Hebdomadaire';
default: return interval;
}
};
if (!schedulerStatus) {
return (
<Card className={className}>
<CardHeader>
<h3 className="text-lg font-semibold"> Synchronisation automatique</h3>
</CardHeader>
<CardContent>
<p className="text-gray-500">Chargement...</p>
</CardContent>
</Card>
);
}
return (
<Card className={className}>
<CardHeader>
<div className="flex items-center justify-between gap-3">
<h3 className="text-base sm:text-lg font-semibold flex-1 min-w-0 truncate"> Synchronisation automatique</h3>
<div className="flex-shrink-0">
{getStatusBadge()}
</div>
</div>
</CardHeader>
<CardContent className="space-y-4">
{error && (
<div className="p-3 bg-red-50 border border-red-200 rounded-md">
<p className="text-red-700 text-sm">{error}</p>
</div>
)}
{/* Statut actuel */}
<div className="grid grid-cols-2 gap-4 text-sm">
<div>
<span className="font-medium text-gray-600">Statut:</span>
<p className="mt-1">
{schedulerStatus.isEnabled && schedulerStatus.isRunning ? '🟢 Actif' : '🔴 Arrêté'}
</p>
</div>
<div>
<span className="font-medium text-gray-600">Fréquence:</span>
<p className="mt-1">{getIntervalText(schedulerStatus.interval)}</p>
</div>
<div className="col-span-2">
<span className="font-medium text-gray-600">Prochaine synchronisation:</span>
<p className="mt-1">{getNextSyncText()}</p>
</div>
</div>
{/* Contrôles */}
<div className="flex flex-col gap-3">
{/* Toggle scheduler */}
<div className="flex items-center justify-between">
<span className="text-sm font-medium">Synchronisation automatique</span>
<Button
variant={schedulerStatus.isRunning ? "danger" : "primary"}
size="sm"
onClick={toggleScheduler}
disabled={isLoading || !schedulerStatus.tfsConfigured}
>
{schedulerStatus.isRunning ? 'Désactiver' : 'Activer'}
</Button>
</div>
{/* Sélecteur d'intervalle */}
{schedulerStatus.isEnabled && (
<div>
<span className="text-sm font-medium text-gray-600 block mb-2">Fréquence de synchronisation</span>
<div className="flex gap-2">
{(['hourly', 'daily', 'weekly'] as const).map((interval) => (
<Button
key={interval}
variant={schedulerStatus.interval === interval ? "primary" : "secondary"}
size="sm"
onClick={() => updateInterval(interval)}
disabled={isLoading}
>
{getIntervalText(interval)}
</Button>
))}
</div>
</div>
)}
</div>
{/* Avertissement si TFS non configuré */}
{!schedulerStatus.tfsConfigured && (
<div className="p-3 bg-yellow-50 border border-yellow-200 rounded-md">
<p className="text-yellow-700 text-sm">
Configurez d&apos;abord votre connexion TFS pour activer la synchronisation automatique.
</p>
</div>
)}
</CardContent>
</Card>
);
}