102 lines
7.0 KiB
TypeScript
102 lines
7.0 KiB
TypeScript
import { useEffect, useMemo, useState } from "react";
|
||
import { Calendar, CheckCircle2, Cloud, Loader2, Save, Upload, Wifi } from "lucide-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 { toast } from "sonner";
|
||
|
||
/** Les identifiants Azure restent centralisés dans Paramètres, hors des règles métier. */
|
||
export default function AzureAdExportSettingsCard() {
|
||
const utils = trpc.useUtils();
|
||
const { data: settings } = trpc.importSettings.get.useQuery();
|
||
const [sharepointUrl, setSharepointUrl] = useState("");
|
||
const [tenantId, setTenantId] = useState("");
|
||
const [clientId, setClientId] = useState("");
|
||
const [clientSecret, setClientSecret] = useState("");
|
||
const [secretExpiresAt, setSecretExpiresAt] = useState("");
|
||
|
||
useEffect(() => {
|
||
if (!settings) return;
|
||
setSharepointUrl(settings.exportFolder || "");
|
||
setTenantId(settings.azureTenantId || "");
|
||
setClientId(settings.azureClientId || "");
|
||
setSecretExpiresAt(settings.azureSecretExpiresAt ? new Date(settings.azureSecretExpiresAt).toISOString().slice(0, 10) : "");
|
||
}, [settings]);
|
||
|
||
const updateMutation = trpc.importSettings.update.useMutation({
|
||
onSuccess: () => {
|
||
toast.success("Configuration Microsoft Azure AD enregistrée");
|
||
setClientSecret("");
|
||
utils.importSettings.get.invalidate();
|
||
},
|
||
onError: (error) => toast.error(error.message || "Impossible d’enregistrer la configuration Azure AD"),
|
||
});
|
||
const testConnectionMutation = trpc.importSettings.testAzureConnection.useMutation();
|
||
const testUploadMutation = trpc.importSettings.testSharePointUpload.useMutation();
|
||
|
||
const expiration = useMemo(() => {
|
||
if (!secretExpiresAt) return null;
|
||
const date = new Date(`${secretExpiresAt}T12:00:00Z`);
|
||
if (Number.isNaN(date.getTime())) return null;
|
||
return Math.ceil((date.getTime() - Date.now()) / 86_400_000);
|
||
}, [secretExpiresAt]);
|
||
|
||
const save = () => {
|
||
if (!settings) return;
|
||
if (!sharepointUrl.trim() || !tenantId.trim() || !clientId.trim()) {
|
||
toast.error("URL SharePoint, Tenant ID et Client ID sont obligatoires");
|
||
return;
|
||
}
|
||
updateMutation.mutate({
|
||
manualImportEnabled: settings.manualImportEnabled,
|
||
autoImportEnabled: settings.autoImportEnabled,
|
||
emailImportEnabled: settings.emailImportEnabled,
|
||
exportFolder: sharepointUrl.trim(),
|
||
exportFolderType: "sharepoint",
|
||
bapExportMode: settings.bapExportMode,
|
||
azureTenantId: tenantId.trim(),
|
||
azureClientId: clientId.trim(),
|
||
azureSecretExpiresAt: secretExpiresAt || null,
|
||
...(clientSecret ? { azureClientSecret: clientSecret } : {}),
|
||
});
|
||
};
|
||
|
||
const testConnection = async () => {
|
||
try {
|
||
const result = await testConnectionMutation.mutateAsync();
|
||
result.success ? toast.success(result.message || "Connexion Azure AD réussie") : toast.error(result.error || "Connexion Azure AD impossible");
|
||
} catch (error: any) {
|
||
toast.error(error.message || "Connexion Azure AD impossible");
|
||
}
|
||
};
|
||
|
||
const testUpload = async () => {
|
||
try {
|
||
const result = await testUploadMutation.mutateAsync();
|
||
if (!result.success) {
|
||
toast.error(result.error || "Upload SharePoint impossible");
|
||
return;
|
||
}
|
||
toast.success("Fichier test déposé dans SharePoint");
|
||
if (result.webUrl) window.open(result.webUrl, "_blank", "noopener,noreferrer");
|
||
} catch (error: any) {
|
||
toast.error(error.message || "Upload SharePoint impossible");
|
||
}
|
||
};
|
||
|
||
const canTest = Boolean(sharepointUrl && tenantId && clientId);
|
||
|
||
return <Card className="border-2 border-cyan-200">
|
||
<CardHeader className="border-b bg-gradient-to-r from-cyan-50 to-sky-50"><div className="flex items-center gap-3"><div className="rounded-lg bg-cyan-600 p-2"><Cloud className="h-6 w-6 text-white" /></div><div><CardTitle>Configuration Microsoft Azure AD</CardTitle><CardDescription>Accès partagé utilisé par les destinations SharePoint et Teams des automatismes d’export.</CardDescription></div></div></CardHeader>
|
||
<CardContent className="space-y-4 pt-6">
|
||
<div className="space-y-2"><Label htmlFor="sharepoint-url">URL SharePoint / chemin</Label><Input id="sharepoint-url" value={sharepointUrl} onChange={(event) => setSharepointUrl(event.target.value)} placeholder="https://…sharepoint.com/:f:/r/sites/…/Documents/Factures" /><p className="text-xs text-muted-foreground">Pour un canal Teams, utilisez l’URL SharePoint du dossier Fichiers concerné.</p></div>
|
||
<div className="grid gap-4 md:grid-cols-2"><div className="space-y-2"><Label htmlFor="azure-tenant-id">ID de l’annuaire (Tenant ID)</Label><Input id="azure-tenant-id" value={tenantId} onChange={(event) => setTenantId(event.target.value)} /></div><div className="space-y-2"><Label htmlFor="azure-client-id">ID d’application (Client ID)</Label><Input id="azure-client-id" value={clientId} onChange={(event) => setClientId(event.target.value)} /></div></div>
|
||
<div className="grid gap-4 md:grid-cols-2"><div className="space-y-2"><Label htmlFor="azure-client-secret">Secret client</Label><Input id="azure-client-secret" type="password" value={clientSecret} onChange={(event) => setClientSecret(event.target.value)} placeholder="Conserver le secret configuré" /><p className="text-xs text-muted-foreground">Laissez vide pour conserver le secret enregistré. Permissions requises : Files.ReadWrite.All et Sites.ReadWrite.All.</p></div><div className="space-y-2"><Label htmlFor="azure-secret-expiration">Date d’expiration du secret</Label><Input id="azure-secret-expiration" type="date" value={secretExpiresAt} onChange={(event) => setSecretExpiresAt(event.target.value)} />{expiration !== null && <p className={expiration <= 30 ? "text-xs text-amber-700" : "text-xs text-emerald-700"}><Calendar className="mr-1 inline h-3 w-3" />{expiration < 0 ? "Secret expiré" : `Secret valide encore ${expiration} jours`}</p>}</div></div>
|
||
<div className="flex flex-wrap gap-2 border-t pt-4"><Button onClick={save} disabled={updateMutation.isPending}><Save className="mr-2 h-4 w-4" />{updateMutation.isPending ? "Enregistrement…" : "Enregistrer Azure AD"}</Button><Button variant="outline" onClick={testConnection} disabled={testConnectionMutation.isPending || !canTest}>{testConnectionMutation.isPending ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Wifi className="mr-2 h-4 w-4" />}Tester la connexion Azure AD</Button><Button variant="outline" onClick={testUpload} disabled={testUploadMutation.isPending || !canTest}>{testUploadMutation.isPending ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <Upload className="mr-2 h-4 w-4" />}Tester l’upload SharePoint</Button></div>
|
||
{settings?.azureClientSecret && <p className="flex items-center gap-1 text-xs text-emerald-700"><CheckCircle2 className="h-3.5 w-3.5" />Un secret client est déjà enregistré de manière masquée.</p>}
|
||
</CardContent>
|
||
</Card>;
|
||
}
|