Checkpoint: Checkpoint saved: Ajout d'une page "Paramètres de réception" complète pour configurer les différentes méthodes d'import de factures.
Nouvelles fonctionnalités : ✅ Table importSettings dans la base de données (7 tables au total) ✅ Routes tRPC pour gérer les paramètres de réception (get, update) ✅ Page "Paramètres de réception" accessible via le menu de navigation Configuration disponible : 1. **Import manuel** : Activation/désactivation de l'upload manuel de fichiers PDF via l'interface 2. **Import automatique depuis dossier** : - Activation/désactivation - Configuration du chemin du dossier source à surveiller - Fréquence de lecture du dossier (en minutes) 3. **Import par email** : - Activation/désactivation - Configuration du compte email (adresse + mot de passe) - Serveur IMAP (host + port) - Fréquence de lecture des emails (en minutes) Interface utilisateur : - 3 cartes distinctes avec icônes (Upload, FolderOpen, Mail) - Switches pour activer/désactiver chaque méthode - Champs de configuration qui apparaissent uniquement si la méthode est activée - Bouton "Enregistrer les paramètres" avec feedback visuel - Notifications toast pour confirmer la sauvegarde ou signaler les erreurs Architecture : - Schéma de base de données : table importSettings avec 14 colonnes - Backend : Fonctions db.ts (getImportSettingsByUser, upsertImportSettings) - Routes tRPC : importSettings.get et importSettings.update - Frontend : Page ImportSettings.tsx avec gestion d'état React - Navigation : Nouveau lien "Paramètres de réception" dans le menu latéral La page est maintenant accessible via /import-settings et permet aux utilisateurs de configurer toutes les méthodes d'importation de factures de manière centralisée.
This commit is contained in:
309
client/src/pages/ImportSettings.tsx
Normal file
309
client/src/pages/ImportSettings.tsx
Normal file
@@ -0,0 +1,309 @@
|
||||
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 { toast } from "sonner";
|
||||
import { Loader2, Save, Upload, FolderOpen, Mail } from "lucide-react";
|
||||
|
||||
export default function ImportSettings() {
|
||||
const { data: settings, isLoading } = trpc.importSettings.get.useQuery();
|
||||
const updateMutation = trpc.importSettings.update.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 [emailImportHost, setEmailImportHost] = useState("");
|
||||
const [emailImportPort, setEmailImportPort] = useState(993);
|
||||
const [emailImportFrequency, setEmailImportFrequency] = useState(30);
|
||||
|
||||
// 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 || "");
|
||||
setEmailImportHost(settings.emailImportHost || "");
|
||||
setEmailImportPort(settings.emailImportPort || 993);
|
||||
setEmailImportFrequency(settings.emailImportFrequency || 30);
|
||||
}
|
||||
}, [settings]);
|
||||
|
||||
const handleSave = async () => {
|
||||
try {
|
||||
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,
|
||||
});
|
||||
|
||||
toast.success("Paramètres enregistrés", {
|
||||
description: "Vos paramètres de réception ont été mis à jour avec succès.",
|
||||
});
|
||||
} catch (error) {
|
||||
toast.error("Erreur", {
|
||||
description: "Impossible d'enregistrer les paramètres. Veuillez réessayer.",
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="flex items-center justify-center h-[calc(100vh-200px)]">
|
||||
<Loader2 className="h-8 w-8 animate-spin text-primary" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="container max-w-4xl py-8">
|
||||
<div className="mb-8">
|
||||
<h1 className="text-3xl font-bold mb-2">Paramètres de réception</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Configurez les différentes méthodes d'importation de factures
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-6">
|
||||
{/* Manual Import */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-primary/10 rounded-lg">
|
||||
<Upload className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle>Import manuel</CardTitle>
|
||||
<CardDescription>
|
||||
Autoriser l'upload manuel de fichiers PDF via l'interface
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="manual-import" className="text-base">
|
||||
Activer l'import manuel
|
||||
</Label>
|
||||
<Switch
|
||||
id="manual-import"
|
||||
checked={manualImportEnabled}
|
||||
onCheckedChange={setManualImportEnabled}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Automatic Import from Folder */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-blue-500/10 rounded-lg">
|
||||
<FolderOpen className="h-5 w-5 text-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle>Import automatique depuis dossier</CardTitle>
|
||||
<CardDescription>
|
||||
Surveiller un dossier et importer automatiquement les nouveaux fichiers PDF
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="auto-import" className="text-base">
|
||||
Activer l'import automatique
|
||||
</Label>
|
||||
<Switch
|
||||
id="auto-import"
|
||||
checked={autoImportEnabled}
|
||||
onCheckedChange={setAutoImportEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{autoImportEnabled && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="source-path">Chemin du dossier source</Label>
|
||||
<Input
|
||||
id="source-path"
|
||||
type="text"
|
||||
placeholder="/chemin/vers/dossier/factures"
|
||||
value={autoImportSourcePath}
|
||||
onChange={(e) => setAutoImportSourcePath(e.target.value)}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Chemin absolu du dossier à surveiller pour les nouveaux fichiers PDF
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="auto-frequency">Fréquence de lecture (minutes)</Label>
|
||||
<Input
|
||||
id="auto-frequency"
|
||||
type="number"
|
||||
min="1"
|
||||
value={autoImportFrequency}
|
||||
onChange={(e) => setAutoImportFrequency(parseInt(e.target.value) || 60)}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Intervalle de temps entre chaque vérification du dossier (minimum: 1 minute)
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Email Import */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="p-2 bg-green-500/10 rounded-lg">
|
||||
<Mail className="h-5 w-5 text-green-500" />
|
||||
</div>
|
||||
<div>
|
||||
<CardTitle>Import par email</CardTitle>
|
||||
<CardDescription>
|
||||
Récupérer automatiquement les factures reçues par email
|
||||
</CardDescription>
|
||||
</div>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<Label htmlFor="email-import" className="text-base">
|
||||
Activer l'import par email
|
||||
</Label>
|
||||
<Switch
|
||||
id="email-import"
|
||||
checked={emailImportEnabled}
|
||||
onCheckedChange={setEmailImportEnabled}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{emailImportEnabled && (
|
||||
<>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email-address">Adresse email</Label>
|
||||
<Input
|
||||
id="email-address"
|
||||
type="email"
|
||||
placeholder="factures@votreentreprise.com"
|
||||
value={emailImportAddress}
|
||||
onChange={(e) => setEmailImportAddress(e.target.value)}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Adresse email à surveiller pour les factures
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email-password">Mot de passe</Label>
|
||||
<Input
|
||||
id="email-password"
|
||||
type="password"
|
||||
placeholder="••••••••"
|
||||
value={emailImportPassword}
|
||||
onChange={(e) => setEmailImportPassword(e.target.value)}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Mot de passe du compte email (stocké de manière sécurisée)
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email-host">Serveur IMAP</Label>
|
||||
<Input
|
||||
id="email-host"
|
||||
type="text"
|
||||
placeholder="imap.gmail.com"
|
||||
value={emailImportHost}
|
||||
onChange={(e) => setEmailImportHost(e.target.value)}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Adresse du serveur IMAP
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email-port">Port IMAP</Label>
|
||||
<Input
|
||||
id="email-port"
|
||||
type="number"
|
||||
min="1"
|
||||
max="65535"
|
||||
value={emailImportPort}
|
||||
onChange={(e) => setEmailImportPort(parseInt(e.target.value) || 993)}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Port SSL (993 par défaut)
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email-frequency">Fréquence de lecture (minutes)</Label>
|
||||
<Input
|
||||
id="email-frequency"
|
||||
type="number"
|
||||
min="1"
|
||||
value={emailImportFrequency}
|
||||
onChange={(e) => setEmailImportFrequency(parseInt(e.target.value) || 30)}
|
||||
/>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
Intervalle de temps entre chaque vérification des emails (minimum: 1 minute)
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Save Button */}
|
||||
<div className="flex justify-end">
|
||||
<Button
|
||||
onClick={handleSave}
|
||||
disabled={updateMutation.isPending}
|
||||
size="lg"
|
||||
>
|
||||
{updateMutation.isPending ? (
|
||||
<>
|
||||
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
||||
Enregistrement...
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Save className="mr-2 h-4 w-4" />
|
||||
Enregistrer les paramètres
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user