feat: TFS Sync
This commit is contained in:
100
src/components/tfs/TfsSync.tsx
Normal file
100
src/components/tfs/TfsSync.tsx
Normal file
@@ -0,0 +1,100 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useTransition } from 'react';
|
||||
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
||||
import { syncTfsPullRequests } from '@/actions/tfs';
|
||||
|
||||
export function TfsSync() {
|
||||
const [isPending, startTransition] = useTransition();
|
||||
const [lastSync, setLastSync] = useState<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
stats?: {
|
||||
created: number;
|
||||
updated: number;
|
||||
skipped: number;
|
||||
deleted: number;
|
||||
}
|
||||
} | null>(null);
|
||||
|
||||
const handleSync = () => {
|
||||
startTransition(async () => {
|
||||
setLastSync(null);
|
||||
|
||||
const result = await syncTfsPullRequests();
|
||||
|
||||
if (result.success) {
|
||||
setLastSync({
|
||||
success: true,
|
||||
message: result.message || 'Synchronisation réussie',
|
||||
stats: result.data ? {
|
||||
created: result.data.pullRequestsCreated,
|
||||
updated: result.data.pullRequestsUpdated,
|
||||
skipped: result.data.pullRequestsSkipped,
|
||||
deleted: result.data.pullRequestsDeleted
|
||||
} : undefined
|
||||
});
|
||||
} else {
|
||||
setLastSync({
|
||||
success: false,
|
||||
message: result.error || 'Erreur lors de la synchronisation'
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<h3 className="text-lg font-semibold flex items-center gap-2">
|
||||
<span className="text-blue-600">🔄</span>
|
||||
Synchronisation TFS
|
||||
</h3>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<p className="text-sm text-[var(--muted-foreground)]">
|
||||
Synchronise manuellement les Pull Requests depuis Azure DevOps
|
||||
</p>
|
||||
|
||||
{/* Résultat de la dernière synchronisation */}
|
||||
{lastSync && (
|
||||
<div className={`p-3 rounded-lg text-sm ${
|
||||
lastSync.success
|
||||
? 'bg-green-50 text-green-800 border border-green-200'
|
||||
: 'bg-red-50 text-red-800 border border-red-200'
|
||||
}`}>
|
||||
<div className="font-medium mb-1">
|
||||
{lastSync.success ? '✅' : '❌'} {lastSync.message}
|
||||
</div>
|
||||
{lastSync.stats && (
|
||||
<div className="text-xs opacity-80">
|
||||
Créées: {lastSync.stats.created} |
|
||||
Mises à jour: {lastSync.stats.updated} |
|
||||
Ignorées: {lastSync.stats.skipped} |
|
||||
Supprimées: {lastSync.stats.deleted}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<button
|
||||
onClick={handleSync}
|
||||
disabled={isPending}
|
||||
className="w-full px-4 py-2 bg-[var(--primary)] text-[var(--primary-foreground)] rounded-lg hover:bg-[var(--primary)]/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2"
|
||||
>
|
||||
{isPending && (
|
||||
<svg className="animate-spin h-4 w-4" viewBox="0 0 24 24">
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" fill="none" />
|
||||
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 0 1 8-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 0 1 4 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" />
|
||||
</svg>
|
||||
)}
|
||||
{isPending ? 'Synchronisation en cours...' : 'Synchroniser maintenant'}
|
||||
</button>
|
||||
|
||||
<div className="text-xs text-[var(--muted-foreground)] text-center">
|
||||
Les Pull Requests seront importées comme tâches dans le tableau Kanban
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user