'use client'; import { useState, useEffect, useTransition } from 'react'; import { TfsConfig } from '@/services/tfs'; import { getTfsConfig, saveTfsConfig, deleteAllTfsTasks } from '@/actions/tfs'; import { Button } from '@/components/ui/Button'; import { Badge } from '@/components/ui/Badge'; export function TfsConfigForm() { const [config, setConfig] = useState({ enabled: false, organizationUrl: '', projectName: '', personalAccessToken: '', repositories: [], ignoredRepositories: [], }); const [isPending, startTransition] = useTransition(); const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string; } | null>(null); const [testingConnection, setTestingConnection] = useState(false); const [showForm, setShowForm] = useState(false); const [isLoading, setIsLoading] = useState(true); const [deletingTasks, setDeletingTasks] = useState(false); // Charger la configuration existante useEffect(() => { loadConfig(); }, []); const loadConfig = async () => { try { setIsLoading(true); const result = await getTfsConfig(); if (result.success) { setConfig(result.data); // Afficher le formulaire par défaut si TFS n'est pas configuré const isConfigured = result.data?.enabled && result.data?.organizationUrl && result.data?.personalAccessToken; if (!isConfigured) { setShowForm(true); } } else { setMessage({ type: 'error', text: result.error || 'Erreur lors du chargement de la configuration', }); setShowForm(true); // Afficher le formulaire en cas d'erreur } } catch (error) { console.error('Erreur chargement config TFS:', error); setMessage({ type: 'error', text: 'Erreur lors du chargement de la configuration', }); setShowForm(true); } finally { setIsLoading(false); } }; const handleSaveConfig = () => { startTransition(async () => { setMessage(null); const result = await saveTfsConfig(config); if (result.success) { setMessage({ type: 'success', text: result.message || 'Configuration sauvegardée', }); // Masquer le formulaire après une sauvegarde réussie setShowForm(false); } else { setMessage({ type: 'error', text: result.error || 'Erreur lors de la sauvegarde', }); } }); }; const handleDelete = async () => { if (!confirm('Êtes-vous sûr de vouloir supprimer la configuration TFS ?')) { return; } startTransition(async () => { setMessage(null); // Réinitialiser la config const resetConfig = { enabled: false, organizationUrl: '', projectName: '', personalAccessToken: '', repositories: [], ignoredRepositories: [], }; const result = await saveTfsConfig(resetConfig); if (result.success) { setConfig(resetConfig); setMessage({ type: 'success', text: 'Configuration TFS supprimée' }); setShowForm(true); // Afficher le formulaire pour reconfigurer } else { setMessage({ type: 'error', text: result.error || 'Erreur lors de la suppression', }); } }); }; const testConnection = async () => { try { setTestingConnection(true); setMessage(null); // Sauvegarder d'abord la config const saveResult = await saveTfsConfig(config); if (!saveResult.success) { setMessage({ type: 'error', text: saveResult.error || 'Erreur lors de la sauvegarde', }); return; } // Attendre un peu que la configuration soit prise en compte await new Promise((resolve) => setTimeout(resolve, 1000)); // Tester la connexion avec la route dédiée const response = await fetch('/api/tfs/test', { method: 'GET', headers: { 'Content-Type': 'application/json', }, }); const result = await response.json(); console.log('Test TFS - Réponse:', { status: response.status, result }); if (response.ok && result.connected) { setMessage({ type: 'success', text: `Connexion Azure DevOps réussie ! ${result.message || ''}`, }); } else { const errorMessage = result.error || result.details || 'Erreur de connexion inconnue'; setMessage({ type: 'error', text: `Connexion échouée: ${errorMessage}`, }); console.error('Test TFS échoué:', result); } } catch (error) { console.error('Erreur test connexion TFS:', error); setMessage({ type: 'error', text: `Erreur réseau: ${error instanceof Error ? error.message : 'Erreur inconnue'}`, }); } finally { setTestingConnection(false); } }; const handleDeleteAllTasks = async () => { const confirmation = confirm( 'Êtes-vous sûr de vouloir supprimer TOUTES les tâches TFS de la base locale ?\n\n' + 'Cette action est irréversible et supprimera définitivement toutes les tâches ' + 'synchronisées depuis Azure DevOps/TFS.\n\n' + 'Cliquez sur OK pour confirmer la suppression.' ); if (!confirmation) { return; } try { setDeletingTasks(true); setMessage(null); const result = await deleteAllTfsTasks(); if (result.success) { setMessage({ type: 'success', text: result.message || 'Toutes les tâches TFS ont été supprimées avec succès', }); } else { setMessage({ type: 'error', text: result.error || 'Erreur lors de la suppression des tâches TFS', }); } } catch (error) { console.error('Erreur suppression tâches TFS:', error); setMessage({ type: 'error', text: `Erreur réseau: ${error instanceof Error ? error.message : 'Erreur inconnue'}`, }); } finally { setDeletingTasks(false); } }; const updateConfig = ( field: keyof TfsConfig, value: string | boolean | string[] ) => { setConfig((prev) => ({ ...prev, [field]: value })); }; const updateArrayField = ( field: 'repositories' | 'ignoredRepositories', value: string ) => { const array = value .split(',') .map((item) => item.trim()) .filter((item) => item); updateConfig(field, array); }; const isTfsConfigured = config?.enabled && config?.organizationUrl && config?.personalAccessToken; const isLoadingState = isLoading || isPending || deletingTasks; if (isLoading) { return (
Chargement...
); } return (
{/* Statut actuel */}

Statut de l'intégration

{isTfsConfigured ? 'Azure DevOps est configuré et prêt à être utilisé' : "Azure DevOps n'est pas configuré"}

{isTfsConfigured ? '✓ Configuré' : '✗ Non configuré'}
{isTfsConfigured && (

Configuration actuelle

URL d'organisation: {' '} {config?.organizationUrl || 'Non définie'}
Projet:{' '} {config?.projectName || "Toute l'organisation"}
Token PAT:{' '} {config?.personalAccessToken ? '••••••••' : 'Non défini'}
Repositories surveillés: {' '} {config?.repositories && config.repositories.length > 0 ? (
{config.repositories.map((repo) => ( {repo} ))}
) : ( Tous les repositories )}
Repositories ignorés: {' '} {config?.ignoredRepositories && config.ignoredRepositories.length > 0 ? (
{config.ignoredRepositories.map((repo) => ( {repo} ))}
) : ( Aucun )}
)} {/* Actions de gestion des données TFS */} {isTfsConfigured && (

⚠️ Gestion des données

Supprimez toutes les tâches TFS synchronisées de la base locale

Attention: Cette action est irréversible et supprimera définitivement toutes les tâches importées depuis Azure DevOps.

)} {/* Formulaire de configuration */} {showForm && (
{ e.preventDefault(); handleSaveConfig(); }} className="space-y-4" > {/* Toggle d'activation */}

Activer l'intégration TFS

Synchroniser les Pull Requests depuis Azure DevOps

{config.enabled && ( <>
updateConfig('organizationUrl', e.target.value) } placeholder="https://dev.azure.com/votre-organisation" className="w-full px-3 py-2 border border-[var(--border)] rounded bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] focus:border-transparent" required />

L'URL de base de votre organisation Azure DevOps (ex: https://dev.azure.com/monentreprise)

updateConfig('projectName', e.target.value)} placeholder="MonProjet (laisser vide pour toute l'organisation)" className="w-full px-3 py-2 border border-[var(--border)] rounded bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] focus:border-transparent" />

Nom du projet spécifique ou laisser vide pour synchroniser les PRs de toute l'organisation

updateConfig('personalAccessToken', e.target.value) } placeholder="Votre token d'accès personnel" className="w-full px-3 py-2 border border-[var(--border)] rounded bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] focus:border-transparent" required />

Créez un PAT depuis{' '} Azure DevOps {' '} avec les permissions Code (read) et Pull Request (read)

updateArrayField('repositories', e.target.value) } placeholder="repo1, repo2, repo3" className="w-full px-3 py-2 border border-[var(--border)] rounded bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] focus:border-transparent" />

Liste séparée par des virgules. Laisser vide pour surveiller tous les repositories.

{config.repositories && config.repositories.length > 0 && (
Repositories surveillés: {config.repositories.map((repo) => ( {repo} ))}
)}
updateArrayField('ignoredRepositories', e.target.value) } placeholder="test-repo, demo-repo" className="w-full px-3 py-2 border border-[var(--border)] rounded bg-[var(--background)] text-[var(--foreground)] focus:outline-none focus:ring-2 focus:ring-[var(--primary)] focus:border-transparent" />

Repositories à exclure de la synchronisation, séparés par des virgules (ex: test-repo, demo-repo).

{config.ignoredRepositories && config.ignoredRepositories.length > 0 && (
Repositories ignorés: {config.ignoredRepositories.map((repo) => ( {repo} ))}
)}
)}
{isTfsConfigured && ( )}
{/* Instructions */}

💡 Instructions de configuration

1. URL d'organisation: Votre domaine Azure DevOps (ex: https://dev.azure.com/monentreprise)

2. Nom du projet (optionnel): Spécifiez un projet pour limiter la synchronisation, ou laissez vide pour toute l'organisation

3. Personal Access Token: Créez un PAT depuis Azure DevOps :

  • Allez sur{' '} dev.azure.com
  • Cliquez sur votre profil » Personal access tokens
  • Cliquez sur "New Token"
  • Sélectionnez les scopes: Code (read) et Pull Request (read)
  • Copiez le token généré

🎯 Synchronisation intelligente: TowerControl récupère automatiquement toutes les Pull Requests vous concernant (créées par vous ou où vous êtes reviewer) dans l'organisation ou le projet configuré.

Note: Les PRs seront synchronisées comme tâches pour un suivi centralisé de vos activités.

)} {message && (
{message.text}
)}
); }