Files
btpxpress-frontend/app/(main)/admin/parametres/page.tsx
2025-10-01 01:39:07 +00:00

1699 lines
72 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

'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 'primереact/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 { InlineMessage } from 'primereact/inlinemessage';
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"
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"
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"
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"
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' : 'secondary'
}
/>
</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 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"
size="small"
/>
<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);
}