Files
btpxpress-frontend/app/(main)/admin/parametres/page.tsx

1701 lines
72 KiB
TypeScript

'use client';
import React, { useState, useRef, useEffect } from 'react';
import { useAuth } from '../../../../contexts/AuthContext';
import ProtectedRoute from '../../../../components/auth/ProtectedRoute';
import { Card } from 'primereact/card';
import { Button } from 'primereact/button';
import { InputText } from 'primereact/inputtext';
import { InputTextarea } from 'primereact/inputtextarea';
import { InputNumber } from 'primereact/inputnumber';
import { InputSwitch } from 'primereact/inputswitch';
import { Dropdown } from 'primereact/dropdown';
import { Toast } from 'primereact/toast';
import { TabView, TabPanel } from 'primereact/tabview';
import { FileUpload } from 'primereact/fileupload';
import { Divider } from 'primereact/divider';
import { ColorPicker } from 'primereact/colorpicker';
import { SelectButton } from 'primereact/selectbutton';
import { Calendar } from 'primereact/calendar';
import { Chip } from 'primereact/chip';
import { ConfirmDialog, confirmDialog } from 'primereact/confirmdialog';
import { Toolbar } from 'primereact/toolbar';
import { SpeedDial } from 'primereact/speeddial';
import { Panel } from 'primereact/panel';
import { Accordion, AccordionTab } from 'primereact/accordion';
import { Badge } from 'primereact/badge';
import { Tag } from 'primereact/tag';
import { Avatar } from 'primereact/avatar';
import { AvatarGroup } from 'primereact/avatargroup';
import { Knob } from 'primereact/knob';
import { ProgressBar } from 'primereact/progressbar';
import { Skeleton } from 'primereact/skeleton';
import { Message } from 'primereact/message';
import { Messages } from 'primereact/messages';
import { Fieldset } from 'primereact/fieldset';
import { ScrollPanel } from 'primereact/scrollpanel';
import { Splitter, SplitterPanel } from 'primereact/splitter';
import { DataView } from 'primereact/dataview';
import { Timeline } from 'primereact/timeline';
import { Steps } from 'primereact/steps';
import { MenuItem } from 'primereact/menuitem';
import { Menu } from 'primereact/menu';
import { TieredMenu } from 'primereact/tieredmenu';
import { ContextMenu } from 'primereact/contextmenu';
import { Sidebar } from 'primereact/sidebar';
import { OverlayPanel } from 'primereact/overlaypanel';
import { Dialog } from 'primereact/dialog';
import { Rating } from 'primereact/rating';
import { Slider } from 'primereact/slider';
import { ToggleButton } from 'primereact/togglebutton';
import { MultiSelect } from 'primereact/multiselect';
import { Checkbox } from 'primereact/checkbox';
import { RadioButton } from 'primereact/radiobutton';
import { ListBox } from 'primereact/listbox';
import { InputMask } from 'primereact/inputmask';
import { AutoComplete } from 'primereact/autocomplete';
import { Password } from 'primereact/password';
import { BlockUI } from 'primereact/blockui';
import { PickList } from 'primereact/picklist';
import { OrderList } from 'primereact/orderlist';
import { DataScroller } from 'primereact/datascroller';
import { VirtualScroller } from 'primereact/virtualscroller';
import { Galleria } from 'primereact/galleria';
import { Image } from 'primereact/image';
import { Carousel } from 'primereact/carousel';
import { Chart } from 'primereact/chart';
import { Terminal } from 'primereact/terminal';
import { DeferredContent } from 'primereact/deferredcontent';
import { ScrollTop } from 'primereact/scrolltop';
import { CascadeSelect } from 'primereact/cascadeselect';
import { TreeSelect } from 'primereact/treeselect';
import { Mention } from 'primereact/mention';
import { Editor } from 'primereact/editor';
import { Tree } from 'primereact/tree';
import { TreeTable } from 'primereact/treetable';
import { Column } from 'primereact/column';
interface CompanySettings {
nom: string;
adresse: string;
telephone: string;
email: string;
siteWeb: string;
numeroTVA: string;
numeroSiret: string;
capitalSocial: number;
formeJuridique: string;
logo: string;
}
interface InvoiceSettings {
prefixFacture: string;
prefixDevis: string;
prochainNumeroFacture: number;
prochainNumeroDevis: number;
tauxTVADefaut: number;
conditionsPaiement: string;
mentionsLegales: string;
delaiPaiementDefaut: number;
penalitesRetard: number;
}
interface EmailSettings {
serveurSMTP: string;
portSMTP: number;
utilisateurSMTP: string;
motDePasseSMTP: string;
expediteurDefaut: string;
useTLS: boolean;
emailsNotification: string[];
}
interface SystemSettings {
langue: string;
fuseau: string;
deviseDefaut: string;
formatDate: string;
formatHeure: string;
theme: string;
couleurPrimaire: string;
activerNotifications: boolean;
activerRappels: boolean;
delaiRappelFacture: number;
delaiRappelDevis: number;
}
interface BackupSettings {
sauvegardeAuto: boolean;
frequenceSauvegarde: string;
heureSauvegarde: Date;
retention: number;
destination: string;
chiffrementActif: boolean;
}
const ParametresPage = () => {
const [activeIndex, setActiveIndex] = useState(0);
const [loading, setLoading] = useState(false);
const [blocked, setBlocked] = useState(false);
const [darkMode, setDarkMode] = useState(false);
const [compactMode, setCompactMode] = useState(false);
const [expertMode, setExpertMode] = useState(false);
const [autoSave, setAutoSave] = useState(true);
const [settingsHistory, setSettingsHistory] = useState([]);
const [lastSaved, setLastSaved] = useState<Date | null>(null);
const [unsavedChanges, setUnsavedChanges] = useState(false);
const [configProfile, setConfigProfile] = useState('standard');
const [performanceLevel, setPerformanceLevel] = useState(75);
const [securityScore, setSecurityScore] = useState(85);
const [systemHealth, setSystemHealth] = useState(92);
const [backupProgress, setBackupProgress] = useState(0);
const [exportProgress, setExportProgress] = useState(0);
const [sidebarVisible, setSidebarVisible] = useState(false);
const [helpVisible, setHelpVisible] = useState(false);
const [historyVisible, setHistoryVisible] = useState(false);
const [configWizard, setConfigWizard] = useState(false);
const [wizardStep, setWizardStep] = useState(0);
const [autoRefresh, setAutoRefresh] = useState(true);
const [refreshInterval, setRefreshInterval] = useState(30);
const [notifications, setNotifications] = useState([]);
const [searchTerm, setSearchTerm] = useState('');
const [filteredSettings, setFilteredSettings] = useState([]);
const [settingsComparison, setSettingsComparison] = useState(false);
const [recommendedSettings, setRecommendedSettings] = useState([]);
const [quickActions, setQuickActions] = useState([]);
const toast = useRef<Toast>(null);
const messages = useRef<Messages>(null);
const overlayPanel = useRef<OverlayPanel>(null);
const contextMenu = useRef<ContextMenu>(null);
const tieredMenu = useRef<TieredMenu>(null);
const terminal = useRef<Terminal>(null);
// TODO: Charger les paramètres de l'entreprise depuis l'API
// Exemple: useEffect(() => { fetch('/api/admin/company-settings').then(res => res.json()).then(setCompanySettings); }, []);
const [companySettings, setCompanySettings] = useState<CompanySettings>({
nom: '',
adresse: '',
telephone: '',
email: '',
siteWeb: '',
numeroTVA: '',
numeroSiret: '',
capitalSocial: 0,
formeJuridique: '',
logo: ''
});
// TODO: Charger les paramètres de facturation depuis l'API
const [invoiceSettings, setInvoiceSettings] = useState<InvoiceSettings>({
prefixFacture: '',
prefixDevis: '',
prochainNumeroFacture: 0,
prochainNumeroDevis: 0,
tauxTVADefaut: 0,
conditionsPaiement: '',
mentionsLegales: '',
delaiPaiementDefaut: 0,
penalitesRetard: 0
});
// TODO: Charger les paramètres email depuis l'API
const [emailSettings, setEmailSettings] = useState<EmailSettings>({
serveurSMTP: '',
portSMTP: 0,
utilisateurSMTP: '',
motDePasseSMTP: '',
expediteurDefaut: '',
useTLS: false,
emailsNotification: []
});
// TODO: Charger les paramètres système depuis l'API
const [systemSettings, setSystemSettings] = useState<SystemSettings>({
langue: '',
fuseau: '',
deviseDefaut: '',
formatDate: '',
formatHeure: '',
theme: '',
couleurPrimaire: '',
activerNotifications: false,
activerRappels: false,
delaiRappelFacture: 0,
delaiRappelDevis: 0
});
// TODO: Charger les paramètres de sauvegarde depuis l'API
const [backupSettings, setBackupSettings] = useState<BackupSettings>({
sauvegardeAuto: false,
frequenceSauvegarde: '',
heureSauvegarde: new Date(),
retention: 0,
destination: '',
chiffrementActif: false
});
const formesJuridiques = [
{ label: 'SARL', value: 'SARL' },
{ label: 'SAS', value: 'SAS' },
{ label: 'SA', value: 'SA' },
{ label: 'EURL', value: 'EURL' },
{ label: 'Auto-entrepreneur', value: 'AUTO' },
{ label: 'Autre', value: 'AUTRE' }
];
const langues = [
{ label: 'Français', value: 'fr' },
{ label: 'English', value: 'en' },
{ label: 'Español', value: 'es' },
{ label: 'Deutsch', value: 'de' }
];
const fuseaux = [
{ label: 'Europe/Paris', value: 'Europe/Paris' },
{ label: 'Europe/London', value: 'Europe/London' },
{ label: 'America/New_York', value: 'America/New_York' },
{ label: 'Asia/Tokyo', value: 'Asia/Tokyo' }
];
const devises = [
{ label: 'Euro (EUR)', value: 'EUR' },
{ label: 'Dollar US (USD)', value: 'USD' },
{ label: 'Livre Sterling (GBP)', value: 'GBP' },
{ label: 'Franc CFA (XOF)', value: 'XOF' }
];
const themes = [
{ label: 'Clair', value: 'light', icon: 'sun' },
{ label: 'Sombre', value: 'dark', icon: 'moon' },
{ label: 'Auto', value: 'auto', icon: 'adjust' },
{ label: 'Contraste élevé', value: 'contrast', icon: 'eye' },
{ label: 'Mode nuit', value: 'night', icon: 'moon' }
];
const configProfiles = [
{ label: 'Basique', value: 'basic', description: 'Configuration simplifiée' },
{ label: 'Standard', value: 'standard', description: 'Configuration recommandée' },
{ label: 'Avancé', value: 'advanced', description: 'Toutes les options' },
{ label: 'Expert', value: 'expert', description: 'Configuration complète' },
{ label: 'Personnalisé', value: 'custom', description: 'Configuration sur mesure' }
];
const wizardSteps = [
{ label: 'Entreprise', icon: 'pi pi-building' },
{ label: 'Utilisateurs', icon: 'pi pi-users' },
{ label: 'Sécurité', icon: 'pi pi-shield' },
{ label: 'Intégrations', icon: 'pi pi-link' },
{ label: 'Finalisation', icon: 'pi pi-check' }
];
const quickActionsData = [
{ label: 'Sauvegarde rapide', icon: 'pi pi-save', command: () => quickBackup() },
{ label: 'Mode maintenance', icon: 'pi pi-wrench', command: () => toggleMaintenance() },
{ label: 'Réinitialiser cache', icon: 'pi pi-refresh', command: () => clearCache() },
{ label: 'Export complet', icon: 'pi pi-download', command: () => exportAll() },
{ label: 'Test connectivité', icon: 'pi pi-wifi', command: () => testConnections() },
{ label: 'Optimiser DB', icon: 'pi pi-database', command: () => optimizeDatabase() }
];
const performanceMetrics = [
{ label: 'CPU', value: 45, color: '#10B981' },
{ label: 'Mémoire', value: 67, color: '#F59E0B' },
{ label: 'Disque', value: 23, color: '#3B82F6' },
{ label: 'Réseau', value: 89, color: '#8B5CF6' }
];
const securityAudits = [
{ date: new Date(), action: 'Connexion admin', status: 'success', ip: '192.168.1.100' },
{ date: new Date(), action: 'Modification paramètres', status: 'success', ip: '192.168.1.100' },
{ date: new Date(), action: 'Tentative accès', status: 'warning', ip: '192.168.1.205' },
{ date: new Date(), action: 'Sauvegarde auto', status: 'success', ip: 'system' }
];
const integrationsList = [
{ name: 'QuickBooks', status: 'connected', icon: 'pi pi-calculator' },
{ name: 'Slack', status: 'connected', icon: 'pi pi-slack' },
{ name: 'Google Drive', status: 'disconnected', icon: 'pi pi-google' },
{ name: 'Dropbox', status: 'error', icon: 'pi pi-cloud' },
{ name: 'Microsoft 365', status: 'connected', icon: 'pi pi-microsoft' },
{ name: 'Zapier', status: 'pending', icon: 'pi pi-bolt' }
];
const frequenceOptions = [
{ label: 'Horaire', value: 'horaire' },
{ label: 'Quotidien', value: 'quotidien' },
{ label: 'Hebdomadaire', value: 'hebdomadaire' },
{ label: 'Mensuel', value: 'mensuel' }
];
const destinationOptions = [
{ label: 'Stockage local', value: 'local' },
{ label: 'Cloud (Google Drive)', value: 'google' },
{ label: 'Cloud (Dropbox)', value: 'dropbox' },
{ label: 'Serveur FTP', value: 'ftp' }
];
const saveSettings = (section: string) => {
setLoading(true);
// Simuler la sauvegarde
setTimeout(() => {
setLoading(false);
toast.current?.show({
severity: 'success',
summary: 'Succès',
detail: `Les paramètres ${section} ont été sauvegardés`,
life: 3000
});
}, 1000);
};
const resetSettings = (section: string) => {
confirmDialog({
message: `Êtes-vous sûr de vouloir réinitialiser les paramètres ${section} ?`,
header: 'Confirmation',
icon: 'pi pi-exclamation-triangle',
acceptLabel: 'Oui',
rejectLabel: 'Non',
accept: () => {
toast.current?.show({
severity: 'info',
summary: 'Réinitialisation',
detail: `Les paramètres ${section} ont été réinitialisés`,
life: 3000
});
}
});
};
const testEmailConnection = () => {
setLoading(true);
setTimeout(() => {
setLoading(false);
toast.current?.show({
severity: 'success',
summary: 'Test réussi',
detail: 'La connexion au serveur SMTP a été établie avec succès',
life: 3000
});
}, 2000);
};
useEffect(() => {
if (autoRefresh) {
const interval = setInterval(() => {
updateSystemMetrics();
}, refreshInterval * 1000);
return () => clearInterval(interval);
}
}, [autoRefresh, refreshInterval]);
const updateSystemMetrics = () => {
setPerformanceLevel(Math.floor(Math.random() * 20) + 70);
setSecurityScore(Math.floor(Math.random() * 15) + 80);
setSystemHealth(Math.floor(Math.random() * 10) + 85);
};
const quickBackup = () => {
setBackupProgress(0);
const interval = setInterval(() => {
setBackupProgress(prev => {
if (prev >= 100) {
clearInterval(interval);
toast.current?.show({
severity: 'success',
summary: 'Sauvegarde terminée',
detail: 'Sauvegarde rapide effectuée avec succès',
life: 3000
});
return 100;
}
return prev + 10;
});
}, 200);
};
const toggleMaintenance = () => {
confirmDialog({
message: 'Activer le mode maintenance ?',
header: 'Mode Maintenance',
icon: 'pi pi-wrench',
acceptLabel: 'Activer',
rejectLabel: 'Annuler',
accept: () => {
toast.current?.show({
severity: 'info',
summary: 'Mode maintenance',
detail: 'Le mode maintenance a été activé',
life: 3000
});
}
});
};
const clearCache = () => {
toast.current?.show({
severity: 'success',
summary: 'Cache vidé',
detail: 'Le cache système a été réinitialisé',
life: 3000
});
};
const exportAll = () => {
setExportProgress(0);
const interval = setInterval(() => {
setExportProgress(prev => {
if (prev >= 100) {
clearInterval(interval);
exportSettings();
return 100;
}
return prev + 5;
});
}, 100);
};
const testConnections = () => {
toast.current?.show({
severity: 'info',
summary: 'Test en cours',
detail: 'Test de connectivité en cours...',
life: 3000
});
setTimeout(() => {
toast.current?.show({
severity: 'success',
summary: 'Connectivité OK',
detail: 'Toutes les connexions sont opérationnelles',
life: 3000
});
}, 2000);
};
const optimizeDatabase = () => {
toast.current?.show({
severity: 'info',
summary: 'Optimisation DB',
detail: 'Optimisation de la base de données en cours...',
life: 5000
});
};
const exportSettings = () => {
const allSettings = {
company: companySettings,
invoice: invoiceSettings,
email: emailSettings,
system: systemSettings,
backup: backupSettings,
metadata: {
exportDate: new Date().toISOString(),
version: '2.1.0',
profile: configProfile
}
};
const blob = new Blob([JSON.stringify(allSettings, null, 2)], { type: 'application/json' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `btpxpress-settings-${new Date().toISOString().split('T')[0]}.json`;
link.click();
toast.current?.show({
severity: 'success',
summary: 'Export réussi',
detail: 'Les paramètres ont été exportés',
life: 3000
});
setLastSaved(new Date());
setUnsavedChanges(false);
};
const renderCompanySettings = () => (
<Card>
<div className="formgrid grid">
<div className="field col-12">
<h5>Informations de l'entreprise</h5>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="companyName">Nom de l'entreprise</label>
<InputText
id="companyName"
value={companySettings.nom}
onChange={(e) => setCompanySettings({...companySettings, nom: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="formeJuridique">Forme juridique</label>
<Dropdown
id="formeJuridique"
value={companySettings.formeJuridique}
options={formesJuridiques}
onChange={(e) => setCompanySettings({...companySettings, formeJuridique: e.value})}
className="w-full"
/>
</div>
<div className="field col-12">
<label htmlFor="address">Adresse</label>
<InputTextarea
id="address"
value={companySettings.adresse}
onChange={(e) => setCompanySettings({...companySettings, adresse: e.target.value})}
rows={3}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="phone">Téléphone</label>
<InputText
id="phone"
value={companySettings.telephone}
onChange={(e) => setCompanySettings({...companySettings, telephone: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="email">Email</label>
<InputText
id="email"
value={companySettings.email}
onChange={(e) => setCompanySettings({...companySettings, email: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="website">Site web</label>
<InputText
id="website"
value={companySettings.siteWeb}
onChange={(e) => setCompanySettings({...companySettings, siteWeb: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="tva">Numéro TVA</label>
<InputText
id="tva"
value={companySettings.numeroTVA}
onChange={(e) => setCompanySettings({...companySettings, numeroTVA: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="siret">Numéro SIRET</label>
<InputText
id="siret"
value={companySettings.numeroSiret}
onChange={(e) => setCompanySettings({...companySettings, numeroSiret: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="capital">Capital social ()</label>
<InputNumber
id="capital"
value={companySettings.capitalSocial}
onValueChange={(e) => setCompanySettings({...companySettings, capitalSocial: e.value || 0})}
mode="currency"
currency="EUR"
locale="fr-FR"
className="w-full"
/>
</div>
<div className="field col-12">
<label htmlFor="logo">Logo de l'entreprise</label>
<FileUpload
name="logo"
accept="image/*"
maxFileSize={1000000}
emptyTemplate={
<p className="m-0">Glissez et déposez le logo ici ou cliquez pour sélectionner.</p>
}
/>
</div>
<div className="field col-12">
<div className="flex gap-2">
<Button
label="Sauvegarder"
icon="pi pi-save"
onClick={() => saveSettings('entreprise')}
loading={loading}
/>
<Button
label="Réinitialiser"
icon="pi pi-refresh"
severity={"secondary" as any}
onClick={() => resetSettings('entreprise')}
/>
</div>
</div>
</div>
</Card>
);
const renderInvoiceSettings = () => (
<Card>
<div className="formgrid grid">
<div className="field col-12">
<h5>Paramètres de facturation</h5>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="prefixFacture">Préfixe factures</label>
<InputText
id="prefixFacture"
value={invoiceSettings.prefixFacture}
onChange={(e) => setInvoiceSettings({...invoiceSettings, prefixFacture: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="nextFacture">Prochain n° facture</label>
<InputNumber
id="nextFacture"
value={invoiceSettings.prochainNumeroFacture}
onValueChange={(e) => setInvoiceSettings({...invoiceSettings, prochainNumeroFacture: e.value || 1})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="prefixDevis">Préfixe devis</label>
<InputText
id="prefixDevis"
value={invoiceSettings.prefixDevis}
onChange={(e) => setInvoiceSettings({...invoiceSettings, prefixDevis: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="nextDevis">Prochain n° devis</label>
<InputNumber
id="nextDevis"
value={invoiceSettings.prochainNumeroDevis}
onValueChange={(e) => setInvoiceSettings({...invoiceSettings, prochainNumeroDevis: e.value || 1})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="tauxTVA">Taux TVA par défaut (%)</label>
<InputNumber
id="tauxTVA"
value={invoiceSettings.tauxTVADefaut}
onValueChange={(e) => setInvoiceSettings({...invoiceSettings, tauxTVADefaut: e.value || 0})}
suffix="%"
min={0}
max={100}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="delaiPaiement">Délai de paiement (jours)</label>
<InputNumber
id="delaiPaiement"
value={invoiceSettings.delaiPaiementDefaut}
onValueChange={(e) => setInvoiceSettings({...invoiceSettings, delaiPaiementDefaut: e.value || 30})}
suffix=" jours"
min={0}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="penalites">Pénalités de retard (%)</label>
<InputNumber
id="penalites"
value={invoiceSettings.penalitesRetard}
onValueChange={(e) => setInvoiceSettings({...invoiceSettings, penalitesRetard: e.value || 0})}
suffix="%"
min={0}
max={100}
className="w-full"
/>
</div>
<div className="field col-12">
<label htmlFor="conditionsPaiement">Conditions de paiement</label>
<InputTextarea
id="conditionsPaiement"
value={invoiceSettings.conditionsPaiement}
onChange={(e) => setInvoiceSettings({...invoiceSettings, conditionsPaiement: e.target.value})}
rows={3}
className="w-full"
/>
</div>
<div className="field col-12">
<label htmlFor="mentionsLegales">Mentions légales</label>
<InputTextarea
id="mentionsLegales"
value={invoiceSettings.mentionsLegales}
onChange={(e) => setInvoiceSettings({...invoiceSettings, mentionsLegales: e.target.value})}
rows={4}
className="w-full"
/>
</div>
<div className="field col-12">
<div className="flex gap-2">
<Button
label="Sauvegarder"
icon="pi pi-save"
onClick={() => saveSettings('facturation')}
loading={loading}
/>
<Button
label="Réinitialiser"
icon="pi pi-refresh"
severity={"secondary" as any}
onClick={() => resetSettings('facturation')}
/>
</div>
</div>
</div>
</Card>
);
const renderEmailSettings = () => (
<Card>
<div className="formgrid grid">
<div className="field col-12">
<h5>Configuration email</h5>
</div>
<div className="field col-12 md:col-8">
<label htmlFor="smtpServer">Serveur SMTP</label>
<InputText
id="smtpServer"
value={emailSettings.serveurSMTP}
onChange={(e) => setEmailSettings({...emailSettings, serveurSMTP: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="smtpPort">Port SMTP</label>
<InputNumber
id="smtpPort"
value={emailSettings.portSMTP}
onValueChange={(e) => setEmailSettings({...emailSettings, portSMTP: e.value || 587})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="smtpUser">Utilisateur SMTP</label>
<InputText
id="smtpUser"
value={emailSettings.utilisateurSMTP}
onChange={(e) => setEmailSettings({...emailSettings, utilisateurSMTP: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-6">
<label htmlFor="smtpPassword">Mot de passe SMTP</label>
<InputText
id="smtpPassword"
type="password"
value={emailSettings.motDePasseSMTP}
onChange={(e) => setEmailSettings({...emailSettings, motDePasseSMTP: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-8">
<label htmlFor="fromEmail">Email expéditeur par défaut</label>
<InputText
id="fromEmail"
value={emailSettings.expediteurDefaut}
onChange={(e) => setEmailSettings({...emailSettings, expediteurDefaut: e.target.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-4">
<label htmlFor="useTLS">Utiliser TLS/SSL</label>
<div className="flex align-items-center h-3rem">
<InputSwitch
id="useTLS"
checked={emailSettings.useTLS}
onChange={(e) => setEmailSettings({...emailSettings, useTLS: e.value})}
/>
</div>
</div>
<div className="field col-12">
<label>Emails de notification</label>
<div className="flex flex-wrap gap-2 mb-2">
{emailSettings.emailsNotification.map((email, index) => (
<Chip
key={index}
label={email}
removable
onRemove={() => {
const newEmails = [...emailSettings.emailsNotification];
newEmails.splice(index, 1);
setEmailSettings({...emailSettings, emailsNotification: newEmails});
}}
/>
))}
</div>
<div className="p-inputgroup">
<InputText
placeholder="Ajouter un email de notification"
onKeyPress={(e) => {
if (e.key === 'Enter') {
const input = e.currentTarget;
if (input.value && input.value.includes('@')) {
setEmailSettings({
...emailSettings,
emailsNotification: [...emailSettings.emailsNotification, input.value]
});
input.value = '';
}
}
}}
/>
<Button icon="pi pi-plus" />
</div>
</div>
<div className="field col-12">
<div className="flex gap-2">
<Button
label="Tester la connexion"
icon="pi pi-wifi"
severity="info"
onClick={testEmailConnection}
loading={loading}
/>
<Button
label="Sauvegarder"
icon="pi pi-save"
onClick={() => saveSettings('email')}
loading={loading}
/>
<Button
label="Réinitialiser"
icon="pi pi-refresh"
severity={"secondary" as any}
onClick={() => resetSettings('email')}
/>
</div>
</div>
</div>
</Card>
);
const renderSystemSettings = () => (
<Card>
<div className="formgrid grid">
<div className="field col-12">
<h5>Paramètres système</h5>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="langue">Langue</label>
<Dropdown
id="langue"
value={systemSettings.langue}
options={langues}
onChange={(e) => setSystemSettings({...systemSettings, langue: e.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="fuseau">Fuseau horaire</label>
<Dropdown
id="fuseau"
value={systemSettings.fuseau}
options={fuseaux}
onChange={(e) => setSystemSettings({...systemSettings, fuseau: e.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="devise">Devise par défaut</label>
<Dropdown
id="devise"
value={systemSettings.deviseDefaut}
options={devises}
onChange={(e) => setSystemSettings({...systemSettings, deviseDefaut: e.value})}
className="w-full"
/>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="theme">Thème</label>
<SelectButton
value={systemSettings.theme}
options={themes}
onChange={(e) => setSystemSettings({...systemSettings, theme: e.value})}
/>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="couleurPrimaire">Couleur primaire</label>
<div className="flex align-items-center gap-2">
<ColorPicker
value={systemSettings.couleurPrimaire}
onChange={(e) => setSystemSettings({...systemSettings, couleurPrimaire: `#${e.value}`})}
/>
<InputText
value={systemSettings.couleurPrimaire}
onChange={(e) => setSystemSettings({...systemSettings, couleurPrimaire: e.target.value})}
className="flex-1"
/>
</div>
</div>
<Divider />
<div className="field col-12">
<h6>Notifications et rappels</h6>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="notifications">Activer les notifications</label>
<div className="flex align-items-center h-3rem">
<InputSwitch
id="notifications"
checked={systemSettings.activerNotifications}
onChange={(e) => setSystemSettings({...systemSettings, activerNotifications: e.value})}
/>
</div>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="rappels">Activer les rappels</label>
<div className="flex align-items-center h-3rem">
<InputSwitch
id="rappels"
checked={systemSettings.activerRappels}
onChange={(e) => setSystemSettings({...systemSettings, activerRappels: e.value})}
/>
</div>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="rappelFacture">Rappel factures (jours)</label>
<InputNumber
id="rappelFacture"
value={systemSettings.delaiRappelFacture}
onValueChange={(e) => setSystemSettings({...systemSettings, delaiRappelFacture: e.value || 7})}
suffix=" jours"
min={1}
className="w-full"
disabled={!systemSettings.activerRappels}
/>
</div>
<div className="field col-12 md:col-3">
<label htmlFor="rappelDevis">Rappel devis (jours)</label>
<InputNumber
id="rappelDevis"
value={systemSettings.delaiRappelDevis}
onValueChange={(e) => setSystemSettings({...systemSettings, delaiRappelDevis: e.value || 3})}
suffix=" jours"
min={1}
className="w-full"
disabled={!systemSettings.activerRappels}
/>
</div>
<div className="field col-12">
<div className="flex gap-2">
<Button
label="Sauvegarder"
icon="pi pi-save"
onClick={() => saveSettings('système')}
loading={loading}
/>
<Button
label="Réinitialiser"
icon="pi pi-refresh"
severity={"secondary" as any}
onClick={() => resetSettings('système')}
/>
<Button
label="Exporter tous les paramètres"
icon="pi pi-download"
severity="help"
onClick={exportSettings}
/>
</div>
</div>
</div>
</Card>
);
const renderSystemOverview = () => (
<div className="grid">
<div className="col-12 md:col-4">
<Card className="bg-gradient-to-r from-blue-500 to-purple-600 text-white">
<div className="text-center">
<Knob
value={performanceLevel}
readOnly
size={80}
strokeWidth={8}
valueColor="white"
rangeColor="rgba(255,255,255,0.3)"
/>
<h6 className="mt-3 mb-0">Performance Système</h6>
<p className="text-sm opacity-90">Optimal</p>
</div>
</Card>
</div>
<div className="col-12 md:col-4">
<Card className="bg-gradient-to-r from-green-500 to-teal-600 text-white">
<div className="text-center">
<Knob
value={securityScore}
readOnly
size={80}
strokeWidth={8}
valueColor="white"
rangeColor="rgba(255,255,255,0.3)"
/>
<h6 className="mt-3 mb-0">Score Sécurité</h6>
<p className="text-sm opacity-90">Excellent</p>
</div>
</Card>
</div>
<div className="col-12 md:col-4">
<Card className="bg-gradient-to-r from-orange-500 to-pink-600 text-white">
<div className="text-center">
<Knob
value={systemHealth}
readOnly
size={80}
strokeWidth={8}
valueColor="white"
rangeColor="rgba(255,255,255,0.3)"
/>
<h6 className="mt-3 mb-0">Santé Système</h6>
<p className="text-sm opacity-90">Très bon</p>
</div>
</Card>
</div>
<div className="col-12">
<Card title="Métriques en temps réel">
<div className="mb-4">
{performanceMetrics.map((metric, index) => (
<div key={index} className="mb-3">
<div className="flex justify-content-between mb-2">
<span>{metric.label}</span>
<span>{metric.value}%</span>
</div>
<ProgressBar value={metric.value} color={metric.color} />
</div>
))}
</div>
<div className="grid">
<div className="col-12 md:col-3">
<div className="text-center p-3 border-round bg-blue-50">
<i className="pi pi-server text-blue-500 text-2xl mb-2" />
<div className="text-lg font-bold text-blue-700">24h</div>
<div className="text-sm text-600">Uptime</div>
</div>
</div>
<div className="col-12 md:col-3">
<div className="text-center p-3 border-round bg-green-50">
<i className="pi pi-users text-green-500 text-2xl mb-2" />
<div className="text-lg font-bold text-green-700">1,247</div>
<div className="text-sm text-600">Utilisateurs actifs</div>
</div>
</div>
<div className="col-12 md:col-3">
<div className="text-center p-3 border-round bg-purple-50">
<i className="pi pi-database text-purple-500 text-2xl mb-2" />
<div className="text-lg font-bold text-purple-700">2.4GB</div>
<div className="text-sm text-600">Données traitées</div>
</div>
</div>
<div className="col-12 md:col-3">
<div className="text-center p-3 border-round bg-orange-50">
<i className="pi pi-bolt text-orange-500 text-2xl mb-2" />
<div className="text-lg font-bold text-orange-700">156ms</div>
<div className="text-sm text-600">Temps réponse</div>
</div>
</div>
</div>
</Card>
</div>
</div>
);
const renderIntegrationsTab = () => (
<div className="grid">
<div className="col-12">
<Card title="Intégrations tierces">
<DataView
value={integrationsList}
itemTemplate={(item) => (
<div className="col-12 md:col-6 lg:col-4 p-2">
<Card className="h-full">
<div className="flex align-items-center justify-content-between mb-3">
<div className="flex align-items-center">
<Avatar
icon={item.icon}
className="mr-2"
style={{ backgroundColor: '#DEE2E6', color: '#495057' }}
/>
<span className="font-bold">{item.name}</span>
</div>
<Tag
value={item.status}
severity={
item.status === 'connected' ? 'success' :
item.status === 'error' ? 'danger' :
item.status === 'pending' ? 'warning' : 'info'
}
/>
</div>
<div className="flex gap-2">
<Button
label="Configure"
size="small"
className="flex-1"
/>
<Button
icon="pi pi-cog"
size="small"
outlined
/>
</div>
</Card>
</div>
)}
layout="grid"
/>
</Card>
</div>
</div>
);
const renderSecurityTab = () => (
<div className="grid">
<div className="col-12 md:col-6">
<Card title="Audit de sécurité">
<Timeline
value={securityAudits}
content={(item) => (
<div className="flex align-items-center">
<Badge
value={item.status}
severity={
item.status === 'success' ? 'success' :
item.status === 'warning' ? 'warning' : 'danger'
}
className="mr-2"
/>
<div>
<div className="font-bold">{item.action}</div>
<div className="text-sm text-600">{item.ip} - {item.date.toLocaleTimeString()}</div>
</div>
</div>
)}
className="w-full"
/>
</Card>
</div>
<div className="col-12 md:col-6">
<Card title="Paramètres de sécurité avancés">
<Accordion multiple>
<AccordionTab header="Authentification">
<div className="field">
<label>Authentification à deux facteurs</label>
<InputSwitch checked={false} onChange={() => {}} className="ml-2" />
</div>
<div className="field">
<label>Durée de session (minutes)</label>
<Slider value={30} min={5} max={480} step={5} className="w-full mt-2" />
</div>
</AccordionTab>
<AccordionTab header="Mots de passe">
<div className="field">
<label>Longueur minimale</label>
<InputNumber value={8} min={6} max={32} className="w-full" />
</div>
<div className="field">
<label>Complexité requise</label>
<div className="flex flex-column gap-2 mt-2">
<div className="flex align-items-center">
<Checkbox checked className="mr-2" />
<label>Majuscules</label>
</div>
<div className="flex align-items-center">
<Checkbox checked className="mr-2" />
<label>Caractères spéciaux</label>
</div>
<div className="flex align-items-center">
<Checkbox checked className="mr-2" />
<label>Chiffres</label>
</div>
</div>
</div>
</AccordionTab>
<AccordionTab header="Chiffrement">
<div className="field">
<label>Algorithme de chiffrement</label>
<Dropdown
value="AES-256"
options={[
{ label: 'AES-128', value: 'AES-128' },
{ label: 'AES-256', value: 'AES-256' },
{ label: 'RSA-2048', value: 'RSA-2048' }
]}
className="w-full"
/>
</div>
</AccordionTab>
</Accordion>
</Card>
</div>
</div>
);
const renderAdvancedTab = () => (
<Splitter style={{ height: '600px' }}>
<SplitterPanel size={30}>
<ScrollPanel style={{ width: '100%', height: '100%' }}>
<div className="p-3">
<h6>Navigation rapide</h6>
<Menu
model={[
{ label: 'API & Webhooks', icon: 'pi pi-link' },
{ label: 'Variables d\'environnement', icon: 'pi pi-code' },
{ label: 'Logs système', icon: 'pi pi-file' },
{ label: 'Performance', icon: 'pi pi-chart-line' },
{ label: 'Base de données', icon: 'pi pi-database' },
{ label: 'Terminal', icon: 'pi pi-desktop' }
]}
/>
</div>
</ScrollPanel>
</SplitterPanel>
<SplitterPanel size={70}>
<ScrollPanel style={{ width: '100%', height: '100%' }}>
<div className="p-3">
<Card title="Configuration avancée">
<Fieldset legend="API Configuration" toggleable>
<div className="grid">
<div className="col-12 md:col-6">
<div className="field">
<label>Rate Limiting (req/min)</label>
<InputNumber value={1000} className="w-full" />
</div>
</div>
<div className="col-12 md:col-6">
<div className="field">
<label>Timeout (ms)</label>
<InputNumber value={30000} className="w-full" />
</div>
</div>
</div>
</Fieldset>
<Fieldset legend="Terminal système" toggleable className="mt-3">
<Terminal
ref={terminal}
welcomeMessage="Bienvenue dans le terminal BTpXpress. Tapez 'help' pour les commandes disponibles."
prompt="btpxpress $"
className="bg-gray-900 text-white"
/>
</Fieldset>
<Fieldset legend="Monitoring avancé" toggleable className="mt-3">
<Chart
type="line"
data={{
labels: ['00:00', '04:00', '08:00', '12:00', '16:00', '20:00'],
datasets: [{
label: 'CPU Usage',
data: [20, 35, 60, 45, 70, 30],
borderColor: '#42A5F5',
backgroundColor: 'rgba(66, 165, 245, 0.1)'
}]
}}
options={{
responsive: true,
plugins: {
legend: { position: 'top' }
}
}}
/>
</Fieldset>
</Card>
</div>
</ScrollPanel>
</SplitterPanel>
</Splitter>
);
const leftToolbarTemplate = () => (
<div className="flex align-items-center gap-2">
<Avatar
icon="pi pi-cog"
className="mr-2"
style={{ backgroundColor: '#3B82F6', color: 'white' }}
/>
<div>
<div className="font-bold text-lg">Paramètres Système</div>
<div className="text-sm text-600">
{lastSaved ? `Dernière sauvegarde: ${lastSaved.toLocaleString()}` : 'Aucune sauvegarde'}
{unsavedChanges && <Badge value="Non sauvé" severity="warning" className="ml-2" />}
</div>
</div>
</div>
);
const rightToolbarTemplate = () => (
<div className="flex align-items-center gap-2">
<ToggleButton
checked={autoRefresh}
onChange={(e) => setAutoRefresh(e.value)}
onIcon="pi pi-pause"
offIcon="pi pi-play"
onLabel="Auto-refresh"
offLabel="Manual"
/>
<Button
icon="pi pi-question-circle"
rounded
text
onClick={() => setHelpVisible(true)}
tooltip="Aide"
/>
<Button
icon="pi pi-history"
rounded
text
onClick={() => setHistoryVisible(true)}
tooltip="Historique"
/>
<Button
icon="pi pi-bars"
rounded
text
onClick={() => setSidebarVisible(true)}
tooltip="Menu"
/>
</div>
);
return (
<div className="grid">
<div className="col-12">
<BlockUI blocked={blocked}>
<Toast ref={toast} />
<Messages ref={messages} />
<ConfirmDialog />
<Toolbar
left={leftToolbarTemplate}
right={rightToolbarTemplate}
className="mb-4 bg-gradient-to-r from-blue-500 to-purple-600 text-white border-round"
/>
{exportProgress > 0 && exportProgress < 100 && (
<Card className="mb-4">
<div className="flex align-items-center gap-3">
<i className="pi pi-spin pi-spinner text-blue-500" />
<div className="flex-1">
<p className="m-0 font-bold">Export en cours...</p>
<ProgressBar value={exportProgress} className="mt-2" />
</div>
</div>
</Card>
)}
{backupProgress > 0 && backupProgress < 100 && (
<Card className="mb-4">
<div className="flex align-items-center gap-3">
<i className="pi pi-spin pi-save text-green-500" />
<div className="flex-1">
<p className="m-0 font-bold">Sauvegarde en cours...</p>
<ProgressBar value={backupProgress} className="mt-2" />
</div>
</div>
</Card>
)}
<TabView
activeIndex={activeIndex}
onTabChange={(e) => setActiveIndex(e.index)}
className="tabview-custom"
>
<TabPanel header="Vue d'ensemble" leftIcon="pi pi-home mr-2">
{renderSystemOverview()}
</TabPanel>
<TabPanel header="Entreprise" leftIcon="pi pi-building mr-2">
{renderCompanySettings()}
</TabPanel>
<TabPanel header="Facturation" leftIcon="pi pi-file-edit mr-2">
{renderInvoiceSettings()}
</TabPanel>
<TabPanel header="Email" leftIcon="pi pi-envelope mr-2">
{renderEmailSettings()}
</TabPanel>
<TabPanel header="Système" leftIcon="pi pi-cog mr-2">
{renderSystemSettings()}
</TabPanel>
<TabPanel header="Sécurité" leftIcon="pi pi-shield mr-2">
{renderSecurityTab()}
</TabPanel>
<TabPanel header="Intégrations" leftIcon="pi pi-link mr-2">
{renderIntegrationsTab()}
</TabPanel>
<TabPanel header="Avancé" leftIcon="pi pi-code mr-2">
{renderAdvancedTab()}
</TabPanel>
</TabView>
<SpeedDial
model={quickActionsData}
radius={80}
type="semi-circle"
direction="up-left"
style={{ left: 'calc(50% - 2rem)', bottom: 0 }}
buttonClassName="p-button-help"
maskClassName="bg-black-alpha-30"
/>
<Sidebar
visible={sidebarVisible}
onHide={() => setSidebarVisible(false)}
position="right"
className="w-20rem"
>
<h3>Actions rapides</h3>
<div className="flex flex-column gap-2">
{quickActionsData.map((action, index) => (
<Button
key={index}
label={action.label}
icon={action.icon}
outlined
className="justify-content-start"
onClick={action.command}
/>
))}
</div>
<Divider />
<h4>Profil de configuration</h4>
<Dropdown
value={configProfile}
options={configProfiles}
onChange={(e) => setConfigProfile(e.value)}
optionLabel="label"
optionValue="value"
className="w-full"
/>
<div className="mt-3">
<h5>Options d'affichage</h5>
<div className="flex align-items-center gap-2 mb-2">
<InputSwitch
checked={darkMode}
onChange={(e) => setDarkMode(e.value)}
/>
<label>Mode sombre</label>
</div>
<div className="flex align-items-center gap-2 mb-2">
<InputSwitch
checked={compactMode}
onChange={(e) => setCompactMode(e.value)}
/>
<label>Mode compact</label>
</div>
<div className="flex align-items-center gap-2">
<InputSwitch
checked={expertMode}
onChange={(e) => setExpertMode(e.value)}
/>
<label>Mode expert</label>
</div>
</div>
</Sidebar>
<Dialog
header="Assistant de configuration"
visible={configWizard}
onHide={() => setConfigWizard(false)}
style={{ width: '50vw' }}
modal
>
<Steps
model={wizardSteps}
activeIndex={wizardStep}
onSelect={(e) => setWizardStep(e.index)}
readOnly={false}
className="mb-4"
/>
<div className="wizard-content">
{wizardStep === 0 && (
<div>
<h4>Configuration de l'entreprise</h4>
<p>Configurons d'abord les informations de base de votre entreprise.</p>
{renderCompanySettings()}
</div>
)}
{wizardStep === 1 && (
<div>
<h4>Gestion des utilisateurs</h4>
<p>Définissez les paramètres d'accès et de sécurité.</p>
<Message
severity="info"
text="Configuration des rôles et permissions utilisateurs"
className="w-full"
/>
</div>
)}
{wizardStep === 4 && (
<div className="text-center">
<i className="pi pi-check-circle text-green-500 text-6xl mb-3" />
<h4>Configuration terminée !</h4>
<p>Votre système est maintenant configuré et prêt à l'emploi.</p>
</div>
)}
</div>
<div className="flex justify-content-between mt-4">
<Button
label="Précédent"
icon="pi pi-chevron-left"
outlined
disabled={wizardStep === 0}
onClick={() => setWizardStep(wizardStep - 1)}
/>
<Button
label={wizardStep === wizardSteps.length - 1 ? 'Terminer' : 'Suivant'}
icon={wizardStep === wizardSteps.length - 1 ? 'pi pi-check' : 'pi pi-chevron-right'}
iconPos="right"
onClick={() => {
if (wizardStep === wizardSteps.length - 1) {
setConfigWizard(false);
toast.current?.show({
severity: 'success',
summary: 'Configuration terminée',
detail: 'Votre système a é configuré avec succès',
life: 5000
});
} else {
setWizardStep(wizardStep + 1);
}
}}
/>
</div>
</Dialog>
<ScrollTop threshold={600} className="w-3rem h-3rem border-round bg-primary" />
</BlockUI>
</div>
</div>
);
};
const ProtectedParametresPage = () => {
return (
<ProtectedRoute requiredRoles={['SUPER_ADMIN', 'ADMIN']}>
<ParametresPage />
</ProtectedRoute>
);
};
export default ProtectedParametresPage;
// Style CSS personnalisé pour améliorer l'apparence
const customStyles = `
.tabview-custom .p-tabview-nav {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
border-radius: 10px 10px 0 0;
}
.tabview-custom .p-tabview-nav li .p-tabview-nav-link {
color: white;
border: none;
}
.tabview-custom .p-tabview-nav li.p-highlight .p-tabview-nav-link {
background: rgba(255, 255, 255, 0.2);
border-radius: 8px;
margin: 4px;
}
.bg-gradient-to-r {
background: linear-gradient(90deg, var(--tw-gradient-stops));
}
.from-blue-500 {
--tw-gradient-from: #3b82f6;
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(59, 130, 246, 0));
}
.to-purple-600 {
--tw-gradient-to: #9333ea;
}
.wizard-content {
min-height: 300px;
}
`;
// Injection du style dans le document
if (typeof document !== 'undefined') {
const style = document.createElement('style');
style.textContent = customStyles;
document.head.appendChild(style);
}