feat: jira and synchro
This commit is contained in:
181
components/jira/JiraSync.tsx
Normal file
181
components/jira/JiraSync.tsx
Normal file
@@ -0,0 +1,181 @@
|
||||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { Button } from '@/components/ui/Button';
|
||||
import { Card, CardHeader, CardContent } from '@/components/ui/Card';
|
||||
import { Badge } from '@/components/ui/Badge';
|
||||
import { jiraClient } from '@/clients/jira-client';
|
||||
import { JiraSyncResult } from '@/services/jira';
|
||||
|
||||
interface JiraSyncProps {
|
||||
onSyncComplete?: () => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function JiraSync({ onSyncComplete, className = "" }: JiraSyncProps) {
|
||||
const [isConnected, setIsConnected] = useState<boolean | null>(null);
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [isSyncing, setIsSyncing] = useState(false);
|
||||
const [lastSyncResult, setLastSyncResult] = useState<JiraSyncResult | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
const testConnection = async () => {
|
||||
setIsLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const status = await jiraClient.testConnection();
|
||||
setIsConnected(status.connected);
|
||||
if (!status.connected) {
|
||||
setError(status.message);
|
||||
}
|
||||
} catch (err) {
|
||||
setIsConnected(false);
|
||||
setError(err instanceof Error ? err.message : 'Erreur de connexion');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const startSync = async () => {
|
||||
setIsSyncing(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const result = await jiraClient.syncTasks();
|
||||
setLastSyncResult(result);
|
||||
|
||||
if (result.success) {
|
||||
onSyncComplete?.();
|
||||
}
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : 'Erreur de synchronisation');
|
||||
} finally {
|
||||
setIsSyncing(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getConnectionStatus = () => {
|
||||
if (isConnected === null) return null;
|
||||
return isConnected ? (
|
||||
<Badge variant="success" size="sm">✓ Connecté</Badge>
|
||||
) : (
|
||||
<Badge variant="danger" size="sm">✗ Déconnecté</Badge>
|
||||
);
|
||||
};
|
||||
|
||||
const getSyncStatus = () => {
|
||||
if (!lastSyncResult) return null;
|
||||
|
||||
const { success, tasksCreated, tasksUpdated, tasksSkipped, errors } = lastSyncResult;
|
||||
|
||||
return (
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex items-center gap-2">
|
||||
<Badge variant={success ? "success" : "danger"} size="sm">
|
||||
{success ? "✓ Succès" : "⚠ Erreurs"}
|
||||
</Badge>
|
||||
<span className="text-[var(--muted-foreground)]">
|
||||
{new Date().toLocaleTimeString()}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-3 gap-2 text-xs">
|
||||
<div className="text-center p-2 bg-[var(--card)] rounded">
|
||||
<div className="font-mono font-bold text-emerald-400">{tasksCreated}</div>
|
||||
<div className="text-[var(--muted-foreground)]">Créées</div>
|
||||
</div>
|
||||
<div className="text-center p-2 bg-[var(--card)] rounded">
|
||||
<div className="font-mono font-bold text-blue-400">{tasksUpdated}</div>
|
||||
<div className="text-[var(--muted-foreground)]">Mises à jour</div>
|
||||
</div>
|
||||
<div className="text-center p-2 bg-[var(--card)] rounded">
|
||||
<div className="font-mono font-bold text-orange-400">{tasksSkipped}</div>
|
||||
<div className="text-[var(--muted-foreground)]">Ignorées</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{errors.length > 0 && (
|
||||
<div className="p-2 bg-[var(--destructive)]/10 border border-[var(--destructive)]/20 rounded text-xs">
|
||||
<div className="font-semibold text-[var(--destructive)] mb-1">Erreurs:</div>
|
||||
{errors.map((err, i) => (
|
||||
<div key={i} className="text-[var(--destructive)] font-mono">{err}</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<Card className={`${className}`}>
|
||||
<CardHeader className="pb-3">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-2 h-2 rounded-full bg-blue-400 animate-pulse"></div>
|
||||
<h3 className="font-mono text-sm font-bold text-blue-400 uppercase tracking-wider">
|
||||
JIRA SYNC
|
||||
</h3>
|
||||
</div>
|
||||
{getConnectionStatus()}
|
||||
</div>
|
||||
</CardHeader>
|
||||
|
||||
<CardContent className="space-y-4">
|
||||
{/* Test de connexion */}
|
||||
<div className="flex gap-2">
|
||||
<Button
|
||||
onClick={testConnection}
|
||||
disabled={isLoading}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
>
|
||||
{isLoading ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 border border-[var(--muted-foreground)] border-t-transparent rounded-full animate-spin"></div>
|
||||
Test...
|
||||
</div>
|
||||
) : (
|
||||
'Tester connexion'
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
onClick={startSync}
|
||||
disabled={isSyncing || isConnected === false}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
className="flex-1"
|
||||
>
|
||||
{isSyncing ? (
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-3 h-3 border border-white border-t-transparent rounded-full animate-spin"></div>
|
||||
Sync...
|
||||
</div>
|
||||
) : (
|
||||
'🔄 Synchroniser'
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{/* Messages d'erreur */}
|
||||
{error && (
|
||||
<div className="p-3 bg-[var(--destructive)]/10 border border-[var(--destructive)]/20 rounded text-sm text-[var(--destructive)]">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Résultats de sync */}
|
||||
{getSyncStatus()}
|
||||
|
||||
{/* Info */}
|
||||
<div className="text-xs text-[var(--muted-foreground)] space-y-1">
|
||||
<div>• Synchronisation unidirectionnelle (Jira → TowerControl)</div>
|
||||
<div>• Les modifications locales sont préservées</div>
|
||||
<div>• Seuls les tickets assignés sont synchronisés</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user