'use client'; import React, { useState, useRef, useEffect } from 'react'; import { DataTable } from 'primereact/datatable'; import { Column } from 'primereact/column'; import { Button } from 'primereact/button'; import { Tag } from 'primereact/tag'; import { Dialog } from 'primereact/dialog'; import { InputTextarea } from 'primereact/inputtextarea'; import { Toast } from 'primereact/toast'; import { Toolbar } from 'primereact/toolbar'; import { Card } from 'primereact/card'; import { ProgressBar } from 'primereact/progressbar'; import { Badge } from 'primereact/badge'; import { Dropdown } from 'primereact/dropdown'; import { Calendar } from 'primereact/calendar'; import { InputText } from 'primereact/inputtext'; import { Panel } from 'primereact/panel'; import { TabView, TabPanel } from 'primereact/tabview'; import { Timeline } from 'primereact/timeline'; import { Steps } from 'primereact/steps'; import { MenuItem } from 'primereact/menuitem'; import { Accordion, AccordionTab } from 'primereact/accordion'; import { Divider } from 'primereact/divider'; import { Avatar } from 'primereact/avatar'; import { AvatarGroup } from 'primereact/avatargroup'; import { Chip } from 'primereact/chip'; import { Message } from 'primereact/message'; import { Messages } from 'primereact/messages'; import { Skeleton } from 'primereact/skeleton'; import { SpeedDial } from 'primereact/speeddial'; import { ScrollPanel } from 'primereact/scrollpanel'; import { Splitter, SplitterPanel } from 'primereact/splitter'; import { FileUpload } from 'primereact/fileupload'; import { Image } from 'primereact/image'; import { Galleria } from 'primereact/galleria'; import { DataView } from 'primereact/dataview'; import { OrderList } from 'primereact/orderlist'; import { PickList } from 'primereact/picklist'; import { OverlayPanel } from 'primereact/overlaypanel'; import { Sidebar } from 'primereact/sidebar'; import { ContextMenu } from 'primereact/contextmenu'; import { Menu } from 'primereact/menu'; import { TieredMenu } from 'primereact/tieredmenu'; import { ConfirmDialog, confirmDialog } from 'primereact/confirmdialog'; import { BlockUI } from 'primereact/blockui'; import { Chart } from 'primereact/chart'; import { Rating } from 'primereact/rating'; import { Knob } from 'primereact/knob'; import { InputSwitch } from 'primereact/inputswitch'; import { MultiSelect } from 'primereact/multiselect'; import { Checkbox } from 'primereact/checkbox'; import { RadioButton } from 'primereact/radiobutton'; import { Slider } from 'primereact/slider'; import { ToggleButton } from 'primereact/togglebutton'; import { SelectButton } from 'primereact/selectbutton'; import { InputNumber } from 'primereact/inputnumber'; import { ColorPicker } from 'primereact/colorpicker'; import { ListBox } from 'primereact/listbox'; import { Fieldset } from 'primereact/fieldset'; // import { ProgressBar } from 'primereact/progressbar'; // Module not available import { TreeTable } from 'primereact/treetable'; import { Tree } from 'primereact/tree'; import { ScrollTop } from 'primereact/scrolltop'; import { VirtualScroller } from 'primereact/virtualscroller'; import { DeferredContent } from 'primereact/deferredcontent'; import { Carousel } from 'primereact/carousel'; import { Terminal } from 'primereact/terminal'; interface DemandeAcces { id: string; userId: string; nom: string; prenom: string; email: string; telephone: string; entreprise: string; siret: string; secteurActivite: string; effectif?: number; role: string; status: 'PENDING' | 'APPROVED' | 'REJECTED' | 'SUSPENDED'; dateCreation: string; dateModification?: string; dateTraitement?: string; commentaireDemandeur?: string; commentaireAdmin?: string; kbisUploade?: boolean; assuranceRCUploade?: boolean; assuranceDecennaleUploade?: boolean; attestationUrssafUploade?: boolean; pourcentageCompletion?: number; validateurNom?: string; } const DemandesAccesAdmin = () => { const [demandes, setDemandes] = useState([]); const [selectedDemande, setSelectedDemande] = useState(null); const [detailDialog, setDetailDialog] = useState(false); const [validationDialog, setValidationDialog] = useState(false); const [bulkDialog, setBulkDialog] = useState(false); const [workflowDialog, setWorkflowDialog] = useState(false); const [analyticsDialog, setAnalyticsDialog] = useState(false); const [actionType, setActionType] = useState<'APPROVE' | 'REJECT'>('APPROVE'); const [commentaire, setCommentaire] = useState(''); const [loading, setLoading] = useState(false); const [blocked, setBlocked] = useState(false); const [globalFilter, setGlobalFilter] = useState(''); const [statusFilter, setStatusFilter] = useState(''); const [dateFilter, setDateFilter] = useState(null); const [sectorFilter, setSectorFilter] = useState(''); const [selectedDemandes, setSelectedDemandes] = useState([]); const [viewMode, setViewMode] = useState<'table' | 'grid' | 'timeline'>('table'); const [autoRefresh, setAutoRefresh] = useState(true); const [refreshInterval, setRefreshInterval] = useState(30); const [sidebarVisible, setSidebarVisible] = useState(false); const [activeTab, setActiveTab] = useState(0); const [processingQueue, setProcessingQueue] = useState([]); const [approvalWorkflow, setApprovalWorkflow] = useState([]); const [auditLog, setAuditLog] = useState([]); const [kpiData, setKpiData] = useState({ totalDemandes: 0, enAttente: 0, approuvees: 0, rejetees: 0, tauxApprobation: 0, delaiMoyenTraitement: 0 }); const [chartData, setChartData] = useState({}); const [timelineData, setTimelineData] = useState([]); const [validationRules, setValidationRules] = useState([]); const [notifications, setNotifications] = useState([]); const [documentPreview, setDocumentPreview] = useState(null); const [compareMode, setCompareMode] = useState(false); const [selectedForComparison, setSelectedForComparison] = useState([]); const [exportProgress, setExportProgress] = useState(0); const [aiRecommendations, setAiRecommendations] = useState([]); const [riskScore, setRiskScore] = useState(0); const [complianceCheck, setComplianceCheck] = useState(true); const toast = useRef(null); const messages = useRef(null); const overlayPanel = useRef(null); const contextMenu = useRef(null); const terminal = useRef(null); const dt = useRef>(null); const statusOptions = [ { label: 'Tous', value: '', icon: 'pi pi-list' }, { label: 'En attente', value: 'PENDING', icon: 'pi pi-clock', color: '#f59e0b' }, { label: 'Approuvé', value: 'APPROVED', icon: 'pi pi-check', color: '#10b981' }, { label: 'Rejeté', value: 'REJECTED', icon: 'pi pi-times', color: '#ef4444' }, { label: 'Suspendu', value: 'SUSPENDED', icon: 'pi pi-pause', color: '#6b7280' }, { label: 'En révision', value: 'UNDER_REVIEW', icon: 'pi pi-eye', color: '#3b82f6' }, { label: 'Incomplet', value: 'INCOMPLETE', icon: 'pi pi-exclamation-triangle', color: '#f97316' } ]; const sectorOptions = [ { label: 'Tous les secteurs', value: '' }, { label: 'BTP Général', value: 'BTP_GENERAL' }, { label: 'Électricité', value: 'ELECTRICITE' }, { label: 'Plomberie', value: 'PLOMBERIE' }, { label: 'Maçonnerie', value: 'MACONNERIE' }, { label: 'Menuiserie', value: 'MENUISERIE' }, { label: 'Peinture', value: 'PEINTURE' }, { label: 'Couverture', value: 'COUVERTURE' }, { label: 'Terrassement', value: 'TERRASSEMENT' } ]; const viewModeOptions = [ { label: 'Tableau', value: 'table', icon: 'pi pi-table' }, { label: 'Grille', value: 'grid', icon: 'pi pi-th-large' }, { label: 'Timeline', value: 'timeline', icon: 'pi pi-calendar' } ]; const bulkActions = [ { label: 'Approuver sélection', icon: 'pi pi-check', command: () => processBulkAction('APPROVE') }, { label: 'Rejeter sélection', icon: 'pi pi-times', command: () => processBulkAction('REJECT') }, { label: 'Marquer comme incomplet', icon: 'pi pi-exclamation-triangle', command: () => processBulkAction('INCOMPLETE') }, { label: 'Exporter sélection', icon: 'pi pi-download', command: () => exportSelected() }, { label: 'Assigner à un validateur', icon: 'pi pi-user', command: () => assignValidator() }, { label: 'Envoyer rappel', icon: 'pi pi-send', command: () => sendReminder() } ]; const quickFilters = [ { label: 'Nouvelles demandes (24h)', filter: () => filterByDate(1), icon: 'pi pi-clock' }, { label: 'En attente > 7 jours', filter: () => filterByDelay(7), icon: 'pi pi-exclamation-triangle' }, { label: 'Documents manquants', filter: () => filterByDocuments(), icon: 'pi pi-file' }, { label: 'Score de risque élevé', filter: () => filterByRisk(), icon: 'pi pi-shield' }, { label: 'Grandes entreprises', filter: () => filterBySize('large'), icon: 'pi pi-building' } ]; const workflowSteps = [ { label: 'Soumission', icon: 'pi pi-send' }, { label: 'Vérification automatique', icon: 'pi pi-cog' }, { label: 'Contrôle documents', icon: 'pi pi-file-check' }, { label: 'Validation métier', icon: 'pi pi-user-check' }, { label: 'Approbation finale', icon: 'pi pi-check-circle' } ]; const validationCriteria = [ { name: 'Informations entreprise', weight: 25, status: 'complete' }, { name: 'Documents légaux', weight: 30, status: 'incomplete' }, { name: 'Assurances', weight: 20, status: 'complete' }, { name: 'Références clients', weight: 15, status: 'pending' }, { name: 'Capacité financière', weight: 10, status: 'complete' } ]; useEffect(() => { loadDemandes(); }, []); const loadDemandes = async () => { setLoading(true); try { // TODO: Remplacer par un appel API réel pour charger les demandes d'accès // Exemple: const response = await fetch('/api/admin/demandes-acces'); // const demandesData = await response.json(); const mockData: DemandeAcces[] = []; setDemandes(mockData); } catch (error) { toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Impossible de charger les demandes', life: 3000 }); } finally { setLoading(false); } }; const getStatusSeverity = (status: string) => { switch (status) { case 'PENDING': return 'warning'; case 'APPROVED': return 'success'; case 'REJECTED': return 'danger'; case 'SUSPENDED': return 'info'; default: return 'info'; } }; const getStatusLabel = (status: string) => { switch (status) { case 'PENDING': return 'En attente'; case 'APPROVED': return 'Approuvé'; case 'REJECTED': return 'Rejeté'; case 'SUSPENDED': return 'Suspendu'; default: return status; } }; // Filtrage des demandes const filteredDemandes = demandes.filter(demande => { const matchesGlobal = !globalFilter || demande.nom.toLowerCase().includes(globalFilter.toLowerCase()) || demande.prenom.toLowerCase().includes(globalFilter.toLowerCase()) || demande.email.toLowerCase().includes(globalFilter.toLowerCase()) || demande.entreprise.toLowerCase().includes(globalFilter.toLowerCase()); const matchesStatus = !statusFilter || demande.status === statusFilter; return matchesGlobal && matchesStatus; }); const statusBodyTemplate = (rowData: DemandeAcces) => { return ; }; const entrepriseBodyTemplate = (rowData: DemandeAcces) => { return (
{rowData.entreprise}
SIRET: {rowData.siret}
); }; const contactBodyTemplate = (rowData: DemandeAcces) => { return (
{rowData.prenom} {rowData.nom}
{rowData.email}
); }; const documentsBodyTemplate = (rowData: DemandeAcces) => { return (
{rowData.pourcentageCompletion || 0}%
); }; const dateBodyTemplate = (rowData: DemandeAcces) => { return new Date(rowData.dateCreation).toLocaleDateString('fr-FR'); }; const actionsBodyTemplate = (rowData: DemandeAcces) => { return (
); }; const openValidationDialog = (demande: DemandeAcces, action: 'APPROVE' | 'REJECT') => { setSelectedDemande(demande); setActionType(action); setCommentaire(''); setValidationDialog(true); }; const handleValidation = async () => { if (!selectedDemande || !commentaire.trim()) { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'Veuillez saisir un commentaire', life: 3000 }); return; } try { setLoading(true); // Simuler l'appel API const updatedDemandes = demandes.map(d => d.id === selectedDemande.id ? { ...d, status: actionType === 'APPROVE' ? 'APPROVED' as const : 'REJECTED' as const, commentaireAdmin: commentaire, dateTraitement: new Date().toISOString() } : d ); setDemandes(updatedDemandes); toast.current?.show({ severity: 'success', summary: 'Succès', detail: `Demande ${actionType === 'APPROVE' ? 'approuvée' : 'rejetée'} avec succès`, life: 3000 }); setValidationDialog(false); } catch (error) { toast.current?.show({ severity: 'error', summary: 'Erreur', detail: 'Erreur lors de la validation', life: 3000 }); } finally { setLoading(false); } }; useEffect(() => { loadDemandes(); loadKPIData(); loadAnalytics(); if (autoRefresh) { const interval = setInterval(() => { loadDemandes(); loadKPIData(); }, refreshInterval * 1000); return () => clearInterval(interval); } }, [autoRefresh, refreshInterval]); const loadKPIData = async () => { // TODO: Remplacer par un appel API réel const mockKPI = { totalDemandes: 156, enAttente: 23, approuvees: 98, rejetees: 35, tauxApprobation: 73.6, delaiMoyenTraitement: 4.2 }; setKpiData(mockKPI); }; const loadAnalytics = async () => { // TODO: Remplacer par un appel API réel pour les analytics const mockChartData = { labels: ['Jan', 'Fév', 'Mar', 'Avr', 'Mai', 'Jun'], datasets: [ { label: 'Demandes reçues', data: [25, 32, 28, 45, 38, 42], backgroundColor: '#3B82F6', borderColor: '#1D4ED8' }, { label: 'Demandes approuvées', data: [18, 24, 21, 32, 28, 31], backgroundColor: '#10B981', borderColor: '#047857' } ] }; setChartData(mockChartData); const mockTimeline = [ { date: new Date(), event: 'Nouvelle demande reçue', type: 'info', user: 'Système' }, { date: new Date(), event: 'Demande approuvée', type: 'success', user: 'Admin' }, { date: new Date(), event: 'Documents manquants signalés', type: 'warning', user: 'Validateur' } ]; setTimelineData(mockTimeline); }; const processBulkAction = (action: string) => { if (selectedDemandes.length === 0) { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'Veuillez sélectionner au moins une demande', life: 3000 }); return; } confirmDialog({ message: `Voulez-vous ${action === 'APPROVE' ? 'approuver' : 'rejeter'} ${selectedDemandes.length} demande(s) ?`, header: 'Action groupée', icon: 'pi pi-question-circle', acceptLabel: 'Confirmer', rejectLabel: 'Annuler', accept: () => { setBlocked(true); setTimeout(() => { setBlocked(false); setSelectedDemandes([]); toast.current?.show({ severity: 'success', summary: 'Action terminée', detail: `${selectedDemandes.length} demandes traitées avec succès`, life: 3000 }); }, 2000); } }); }; const exportSelected = () => { if (selectedDemandes.length === 0) { toast.current?.show({ severity: 'warn', summary: 'Attention', detail: 'Veuillez sélectionner au moins une demande', life: 3000 }); return; } setExportProgress(0); const interval = setInterval(() => { setExportProgress(prev => { if (prev >= 100) { clearInterval(interval); toast.current?.show({ severity: 'success', summary: 'Export terminé', detail: `${selectedDemandes.length} demandes exportées`, life: 3000 }); return 100; } return prev + 10; }); }, 200); }; const assignValidator = () => { toast.current?.show({ severity: 'info', summary: 'Assignation', detail: 'Fonction d\'assignation en cours de développement', life: 3000 }); }; const sendReminder = () => { toast.current?.show({ severity: 'success', summary: 'Rappels envoyés', detail: `Rappels envoyés pour ${selectedDemandes.length} demandes`, life: 3000 }); }; const filterByDate = (days: number) => { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - days); // Implémentation du filtre toast.current?.show({ severity: 'info', summary: 'Filtre appliqué', detail: `Affichage des demandes des ${days} derniers jours`, life: 3000 }); }; const filterByDelay = (days: number) => { toast.current?.show({ severity: 'info', summary: 'Filtre appliqué', detail: `Affichage des demandes en attente depuis plus de ${days} jours`, life: 3000 }); }; const filterByDocuments = () => { toast.current?.show({ severity: 'info', summary: 'Filtre appliqué', detail: 'Affichage des demandes avec documents manquants', life: 3000 }); }; const filterByRisk = () => { toast.current?.show({ severity: 'info', summary: 'Filtre appliqué', detail: 'Affichage des demandes à risque élevé', life: 3000 }); }; const filterBySize = (size: string) => { toast.current?.show({ severity: 'info', summary: 'Filtre appliqué', detail: `Affichage des ${size === 'large' ? 'grandes' : 'petites'} entreprises`, life: 3000 }); }; const advancedToolbar = (
Gestion des Demandes d'Accès
{kpiData.totalDemandes} demandes | {kpiData.enAttente} en attente | Taux d'approbation: {kpiData.tauxApprobation}%
setViewMode(e.value)} optionLabel="label" optionValue="value" className="view-mode-selector" /> setAutoRefresh(e.value)} onIcon="pi pi-pause" offIcon="pi pi-play" onLabel="" offLabel="" className="bg-white-alpha-20" tooltip="Auto-refresh" />
); const searchToolbar = (
setGlobalFilter(e.target.value)} className="w-20rem" /> setStatusFilter(e.value)} placeholder="Statut" className="w-12rem" showClear /> setSectorFilter(e.value)} placeholder="Secteur" className="w-12rem" showClear /> setDateFilter(e.value)} placeholder="Date création" showIcon className="w-12rem" />
résultats
); const renderKPIDashboard = () => (
{kpiData.totalDemandes}
Total demandes
{kpiData.enAttente}
En attente
{kpiData.tauxApprobation}%
Taux approbation
{kpiData.delaiMoyenTraitement}j
Délai moyen
); const renderQuickFilters = () => (
Filtres rapides
{quickFilters.map((filter, index) => ( ))}
); const renderTableView = () => ( {selectedDemandes.length > 0 && (
{selectedDemandes.length} demande(s) sélectionnée(s)
)} setSelectedDemandes(e.value)} paginator rows={10} rowsPerPageOptions={[10, 25, 50, 100]} loading={loading} globalFilter={globalFilter} emptyMessage="Aucune demande trouvée" responsiveLayout="stack" breakpoint="768px" className="p-datatable-striped" showGridlines sortMode="multiple" removableSort reorderableColumns resizableColumns columnResizeMode="expand" contextMenuSelection={selectedDemande} onContextMenuSelectionChange={(e) => setSelectedDemande(e.value)} onContextMenu={(e) => contextMenu.current?.show(e.originalEvent)} >
); const renderGridView = () => (
{filteredDemandes.map((demande) => (
{demande.prenom} {demande.nom}

{demande.email}

{demande.entreprise}

{demande.secteurActivite}

{demande.pourcentageCompletion || 0}%
)}
))} ); const renderTimelineView = () => ( (
{item.prenom} {item.nom}
{item.entreprise}
{new Date(item.dateCreation).toLocaleDateString('fr-FR')}
)} className="w-full" />
); const sectorFilterTemplate = (options: any) => ( options.filterCallback(e.value)} placeholder="Tous" className="p-column-filter" showClear /> ); const statusFilterTemplate = (options: any) => ( options.filterCallback(e.value)} placeholder="Tous" className="p-column-filter" showClear /> ); const dateFilterTemplate = (options: any) => ( options.filterCallback(e.value)} placeholder="Sélectionner" className="p-column-filter" showIcon /> ); const riskScoreTemplate = (rowData: DemandeAcces) => { const score = Math.floor(Math.random() * 40) + 30; // Score entre 30-70 const getSeverity = () => { if (score >= 60) return 'danger'; if (score >= 40) return 'warning'; return 'success'; }; return (
); }; return (
{advancedToolbar} {exportProgress > 0 && exportProgress < 100 && (

Export en cours...

)} setActiveTab(e.index)}> {renderKPIDashboard()} {renderQuickFilters()} {searchToolbar} {viewMode === 'table' && renderTableView()} {viewMode === 'grid' && renderGridView()} {viewMode === 'timeline' && renderTimelineView()}
File d'attente de traitement
(
{item.entreprise}
{item.prenom} {item.nom}
)} emptyMessage="Aucune demande en cours de traitement" />
Règles de validation
{validationCriteria.map((criteria, index) => (
{criteria.name}
))}
(
{item.event}
{item.user} - {item.date.toLocaleTimeString()}
)} />
0} />
{bulkActions.map((action, index) => (
setDetailDialog(true) }, { label: 'Approuver', icon: 'pi pi-check', command: () => openValidationDialog(selectedDemande, 'APPROVE') }, { label: 'Rejeter', icon: 'pi pi-times', command: () => openValidationDialog(selectedDemande, 'REJECT') }, { separator: true }, { label: 'Dupliquer', icon: 'pi pi-copy' }, { label: 'Exporter', icon: 'pi pi-download' } ]} /> setSidebarVisible(false)} position="right" className="w-25rem" >

Configuration avancée

setViewMode(e.value)} optionLabel="label" optionValue="value" className="w-full mt-2" />
setAutoRefresh(e.value)} />
setRefreshInterval(e.value as number)} min={10} max={300} step={10} className="w-full mt-2" />
{}} />
setComplianceCheck(e.value)} />
setRiskScore(e.value)} min={0} max={100} size={80} className="mt-2" />
{/* Dialog de détails ultra-avancé */} setDetailDialog(false)} header={(
Détails de la demande d'accès
{selectedDemande?.entreprise} - {selectedDemande?.secteurActivite}
)} style={{ width: '95vw', maxWidth: '1200px', height: '90vh' }} modal maximizable > {selectedDemande && (
KBIS
Assurance RC
Décennale
URSSAF
Complétude du dossier
item.date)} content={(item) => (
{item.event}
{new Date(item.date).toLocaleString('fr-FR')}
)} />
Excellent
Toutes les exigences sont respectées
Actions rapides
Informations système
ID: {selectedDemande.id}
Créée le: {new Date(selectedDemande.dateCreation).toLocaleDateString('fr-FR')}
Statut: {getStatusLabel(selectedDemande.status)}
{selectedDemande.validateurNom && (
Validateur: {selectedDemande.validateurNom}
)}
{selectedDemande.commentaireDemandeur && (
Commentaire demandeur

{selectedDemande.commentaireDemandeur}

)} {selectedDemande.commentaireAdmin && (
Commentaire admin

{selectedDemande.commentaireAdmin}

{selectedDemande.validateurNom && (
Par: {selectedDemande.validateurNom}
)}
)}
)}
{/* Dialog de validation amélioré */} setValidationDialog(false)} header={(
{actionType === 'APPROVE' ? 'Approuver' : 'Rejeter'} la demande
)} style={{ width: '500px' }} modal >
setCommentaire(e.target.value)} rows={4} placeholder={actionType === 'APPROVE' ? 'Commentaire d\'approbation...' : 'Motif du rejet...' } className="w-full" autoResize />
{actionType === 'APPROVE' && (
{}} />
)}
); }; export default DemandesAccesAdmin; // Styles CSS personnalisés pour l'interface ultra-professionnelle const customStyles = ` .access-requests-management .p-tabview-nav { background: linear-gradient(90deg, #667eea 0%, #764ba2 100%); border-radius: 10px 10px 0 0; } .access-requests-management .p-tabview-nav li .p-tabview-nav-link { color: white; border: none; } .access-requests-management .p-tabview-nav li.p-highlight .p-tabview-nav-link { background: rgba(255, 255, 255, 0.2); border-radius: 8px; margin: 4px; } .view-mode-selector .p-button { background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; } .view-mode-selector .p-button.p-highlight { background: rgba(255, 255, 255, 0.3); } .p-datatable-striped .p-datatable-tbody > tr:nth-child(odd) { background: rgba(0, 0, 0, 0.02); } .p-datatable .p-datatable-tbody > tr:hover { background: rgba(59, 130, 246, 0.1); } .hover\\:shadow-lg:hover { box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); transition: box-shadow 0.3s ease; } .transition-shadow { transition: box-shadow 0.3s ease; } .cursor-pointer { cursor: pointer; } .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; } .from-orange-500 { --tw-gradient-from: #f97316; --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(249, 115, 22, 0)); } .to-orange-600 { --tw-gradient-to: #ea580c; } .from-green-500 { --tw-gradient-from: #22c55e; --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(34, 197, 94, 0)); } .to-green-600 { --tw-gradient-to: #16a34a; } .from-purple-500 { --tw-gradient-from: #a855f7; --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to, rgba(168, 85, 247, 0)); } .to-purple-600 { --tw-gradient-to: #9333ea; } `; // Injection du style dans le document if (typeof document !== 'undefined') { const style = document.createElement('style'); style.textContent = customStyles; document.head.appendChild(style); }