import { useState, useEffect } from "react"; import { trpc } from "@/lib/trpc"; import { Button } from "@/components/ui/button"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Switch } from "@/components/ui/switch"; import { Badge } from "@/components/ui/badge"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { toast } from "sonner"; import { Loader2, Save, Upload, FolderOpen, Mail, Play, Square, Download, Inbox, CheckCircle2, Monitor, FolderOutput, Wifi, AlertTriangle, Calendar, ArrowDownToLine, Share2 } from "lucide-react"; import DashboardLayout from "@/components/DashboardLayout"; export default function ImportSettings() { // Chargement différé : on ne charge les statuts de services qu'après le montage const { data: settings, isLoading } = trpc.importSettings.get.useQuery(undefined, { staleTime: 30_000, }); const { data: emailServiceStatus } = trpc.emailImportService.status.useQuery(undefined, { staleTime: 10_000, }); const { data: folderServiceStatus } = trpc.folderImportService.status.useQuery(undefined, { staleTime: 10_000, }); const updateMutation = trpc.importSettings.update.useMutation(); const startEmailServiceMutation = trpc.emailImportService.start.useMutation(); const stopEmailServiceMutation = trpc.emailImportService.stop.useMutation(); const checkNowEmailMutation = trpc.emailImportService.checkNow.useMutation(); const utils = trpc.useUtils(); const startFolderServiceMutation = trpc.folderImportService.start.useMutation(); const stopFolderServiceMutation = trpc.folderImportService.stop.useMutation(); const testAzureConnectionMutation = trpc.importSettings.testAzureConnection.useMutation(); const testSharePointUploadMutation = trpc.importSettings.testSharePointUpload.useMutation(); const testEmailConnectionMutation = trpc.importSettings.testEmailConnection.useMutation(); // Manual import const [manualImportEnabled, setManualImportEnabled] = useState(true); // Automatic import from folder const [autoImportEnabled, setAutoImportEnabled] = useState(false); const [autoImportSourcePath, setAutoImportSourcePath] = useState(""); const [autoImportFrequency, setAutoImportFrequency] = useState(60); // Email import const [emailImportEnabled, setEmailImportEnabled] = useState(false); const [emailImportAddress, setEmailImportAddress] = useState(""); const [emailImportPassword, setEmailImportPassword] = useState(""); const [emailImportAuthMode, setEmailImportAuthMode] = useState<"basic" | "oauth2">("basic"); const [emailImportHost, setEmailImportHost] = useState(""); const [emailImportPort, setEmailImportPort] = useState(993); const [emailImportFrequency, setEmailImportFrequency] = useState(30); // Date de début de lecture des emails (string ISO "YYYY-MM-DD" pour le champ ) const [emailImportSinceDate, setEmailImportSinceDate] = useState(""); // Export folder const [exportFolder, setExportFolder] = useState(""); const [bapExportBrowser, setBapExportBrowser] = useState(true); const [bapExportFolder, setBapExportFolder] = useState(false); const [exportFolderType, setExportFolderType] = useState<"local" | "teams" | "sharepoint">("local"); // Azure AD credentials for SharePoint const [azureTenantId, setAzureTenantId] = useState(""); const [azureClientId, setAzureClientId] = useState(""); const [azureClientSecret, setAzureClientSecret] = useState(""); // Date stockée en string ISO "YYYY-MM-DD" pour le champ const [azureSecretExpiresAt, setAzureSecretExpiresAt] = useState(""); // Initialize form with settings from database useEffect(() => { if (settings) { setManualImportEnabled(settings.manualImportEnabled === 1); setAutoImportEnabled(settings.autoImportEnabled === 1); setAutoImportSourcePath(settings.autoImportSourcePath || ""); setAutoImportFrequency(settings.autoImportFrequency || 60); setEmailImportEnabled(settings.emailImportEnabled === 1); setEmailImportAddress(settings.emailImportAddress || ""); setEmailImportPassword(settings.emailImportPassword || ""); setEmailImportAuthMode(((settings as any).emailImportAuthMode as "basic" | "oauth2") || "basic"); setEmailImportHost(settings.emailImportHost || ""); setEmailImportPort(settings.emailImportPort || 993); setEmailImportFrequency(settings.emailImportFrequency || 30); // Convertir le timestamp Unix (s) en string ISO pour l'input date if ((settings as any).emailImportSinceDate) { const d = new Date((settings as any).emailImportSinceDate * 1000); setEmailImportSinceDate(d.toISOString().split('T')[0]); } else { setEmailImportSinceDate(""); } setExportFolder(settings.exportFolder || ""); const savedMode = (settings.bapExportMode as string) || "browser"; setBapExportBrowser(savedMode === "browser" || savedMode === "both"); setBapExportFolder(savedMode === "folder" || savedMode === "both"); if ((settings as any).exportFolderType) { setExportFolderType((settings as any).exportFolderType as "local" | "teams" | "sharepoint"); } else { if ((settings.exportFolder || "").startsWith("teams://")) setExportFolderType("teams"); else if ((settings.exportFolder || "").startsWith("sharepoint://")) setExportFolderType("sharepoint"); else setExportFolderType("local"); } setAzureTenantId((settings as any).azureTenantId || ""); setAzureClientId((settings as any).azureClientId || ""); setAzureClientSecret((settings as any).azureClientSecret || ""); // Convertir la date DB en string "YYYY-MM-DD" const expDate = (settings as any).azureSecretExpiresAt; if (expDate) { try { const d = new Date(expDate); if (!isNaN(d.getTime())) { setAzureSecretExpiresAt(d.toISOString().split('T')[0]); } } catch { setAzureSecretExpiresAt(""); } } else { setAzureSecretExpiresAt(""); } } }, [settings]); const computedExportMode = (): "browser" | "folder" | "both" => { if (bapExportBrowser && bapExportFolder) return "both"; if (bapExportFolder) return "folder"; return "browser"; }; const handleSave = async () => { try { // Convertir la date string en objet Date ou null — envoyé en ISO string via superjson let expiresAtValue: Date | null = null; if (azureSecretExpiresAt) { const parsed = new Date(azureSecretExpiresAt + "T12:00:00.000Z"); if (!isNaN(parsed.getTime())) { expiresAtValue = parsed; } } await updateMutation.mutateAsync({ manualImportEnabled: manualImportEnabled ? 1 : 0, autoImportEnabled: autoImportEnabled ? 1 : 0, autoImportSourcePath: autoImportSourcePath || null, autoImportFrequency: autoImportFrequency, emailImportEnabled: emailImportEnabled ? 1 : 0, emailImportAddress: emailImportAddress || null, emailImportPassword: emailImportPassword || null, emailImportHost: emailImportHost || null, emailImportPort: emailImportPort, emailImportFrequency: emailImportFrequency, emailImportSinceDate: emailImportSinceDate ? Math.floor(new Date(emailImportSinceDate + 'T00:00:00Z').getTime() / 1000) : null, emailImportAuthMode: emailImportAuthMode, exportFolder: exportFolder || null, exportFolderType: exportFolderType, bapExportMode: computedExportMode() as "browser" | "folder" | "both", azureTenantId: azureTenantId || null, azureClientId: azureClientId || null, azureClientSecret: azureClientSecret || null, azureSecretExpiresAt: expiresAtValue, }); toast.success("Paramètres enregistrés avec succès"); } catch (error: any) { console.error("Erreur enregistrement paramètres:", error); toast.error("Impossible d'enregistrer les paramètres : " + (error?.message || "erreur inconnue")); } }; if (isLoading) { return (

Chargement des paramètres...

); } // Calcul jours restants pour l'alerte expiration const expirationAlert = (() => { if (!azureSecretExpiresAt) return null; const expDate = new Date(azureSecretExpiresAt + "T12:00:00.000Z"); const daysLeft = Math.ceil((expDate.getTime() - Date.now()) / (1000 * 60 * 60 * 24)); return { daysLeft, expDate }; })(); return (
{/* Header */}

Paramètres import / export

Configurez les méthodes d'importation et les options d'export des factures BAP

{/* Onglets Import / Export */} Paramètres d'import Paramètres d'export {/* ===== ONGLET IMPORT ===== */} {/* Import manuel */}
Import manuel Autoriser l'upload manuel de fichiers PDF via l'interface
{manualImportEnabled ? "Activé" : "Désactivé"}
{/* Import automatique depuis dossier */}
Import automatique depuis dossier Surveiller un dossier et importer automatiquement les nouveaux fichiers PDF
{autoImportEnabled ? "Activé" : "Désactivé"}
{autoImportEnabled && (
setAutoImportSourcePath(e.target.value)} className="h-11" />

Chemin absolu du dossier à surveiller pour les nouveaux fichiers PDF

setAutoImportFrequency(parseInt(e.target.value) || 60)} className="h-11" />
{folderServiceStatus?.isRunning ? ( <> Service actif ) : ( )}
)}
{/* Import par email */}
Import par email Récupérer automatiquement les factures reçues par email
{emailImportEnabled ? "Activé" : "Désactivé"}
{emailImportEnabled && (
{/* Mode d'authentification */}
{emailImportAuthMode === "oauth2" && (
Office 365 : Microsoft a désactivé l'auth basique pour Exchange Online. Utilisez ce mode avec les credentials Azure AD déjà configurés dans l'onglet Export.
)}
setEmailImportAddress(e.target.value)} className="h-11" />
{emailImportAuthMode === "basic" && (
setEmailImportPassword(e.target.value)} className="h-11" />
)} {emailImportAuthMode === "oauth2" && (
En mode OAuth2, les credentials Azure AD (Tenant ID, Client ID, Client Secret) configurés dans l'onglet Export > SharePoint seront utilisés automatiquement.
)}
setEmailImportHost(e.target.value)} className="h-11" />
setEmailImportPort(parseInt(e.target.value) || 993)} className="h-11" />
setEmailImportFrequency(parseInt(e.target.value) || 30)} className="h-11" />

Les emails reçus avant cette date ne seront pas importés, même s'ils sont non lus. Laissez vide pour lire tous les emails non lus.

setEmailImportSinceDate(e.target.value)} className="h-11" /> {emailImportSinceDate && ( )}
{/* Bouton test connexion IMAP */}

Vérifie que la connexion au serveur IMAP est fonctionnelle (sans importer d'emails)

{emailServiceStatus?.isRunning ? ( <> Service actif ) : ( )}
)}
{/* Bouton Enregistrer onglet Import */}
{/* ===== ONGLET EXPORT ===== */} {/* Export des factures BAP */}
Export des factures BAP Configurez les modes d'export pour les factures validées BAP (les deux modes peuvent être actifs simultanément)
{/* Switch : Ouvrir dans le navigateur */}

Le PDF s'ouvre directement dans un nouvel onglet

{/* Switch : Enregistrer dans un dossier */}

Le PDF est copié dans le dossier configuré ci-dessous

{/* Configuration du dossier */} {bapExportFolder && (
{/* Type de dossier */}
{/* Chemin du dossier */}
setExportFolder(e.target.value)} className="h-11" />

{exportFolderType === "local" && "Chemin absolu du dossier local où les PDF BAP seront enregistrés (exécuté côté serveur)"} {exportFolderType === "teams" && "Format : teams://NomEquipe/Chemin/Dossier — le PDF sera copié dans le canal Teams correspondant"} {exportFolderType === "sharepoint" && "URL complète du dossier SharePoint de destination"}

{/* Champs Azure AD — visibles uniquement pour SharePoint */} {exportFolderType === "sharepoint" && (
Configuration Microsoft Azure AD
setAzureTenantId(e.target.value)} className="h-10 font-mono text-sm" />
setAzureClientId(e.target.value)} className="h-10 font-mono text-sm" />
setAzureClientSecret(e.target.value)} className="h-10 font-mono text-sm" />

Stocké de façon sécurisée. Permissions requises : Files.ReadWrite.All et Sites.ReadWrite.All

{/* Date d'expiration du secret */}
setAzureSecretExpiresAt(e.target.value)} className="h-10" />

Renseignez la date d'expiration pour recevoir une alerte avant renouvellement

{/* Alerte expiration */} {expirationAlert && ( expirationAlert.daysLeft <= 0 ? (
Secret expiré — renouvelez-le immédiatement dans le portail Azure AD
) : expirationAlert.daysLeft <= 30 ? (
Secret expire dans {expirationAlert.daysLeft} jours (le {expirationAlert.expDate.toLocaleDateString('fr-FR')}) — pensez à le renouveler
) : (
Secret valide encore {expirationAlert.daysLeft} jours (expire le {expirationAlert.expDate.toLocaleDateString('fr-FR')})
) )} {/* Bouton test connexion */}

Vérifie le token OAuth2 et l'accès au site SharePoint

{/* Bouton test upload réel */}

Dépose un fichier texte de 1 Ko dans le dossier SharePoint pour valider les permissions d'upload

)}
)} {/* Résumé de la configuration */}
Configuration active : {!bapExportBrowser && !bapExportFolder ? Aucun mode sélectionné — le téléchargement direct restera disponible : [ bapExportBrowser && "Ouverture navigateur", bapExportFolder && `Enregistrement ${exportFolderType === "local" ? "dossier local" : exportFolderType === "teams" ? "Teams" : "SharePoint"}`, ].filter(Boolean).join(" + ") }
{/* Bouton Enregistrer onglet Export */}
); }