diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx new file mode 100644 index 0000000..ef64497 --- /dev/null +++ b/src/pages/DashboardPage.tsx @@ -0,0 +1,442 @@ + +import React, { useState, useMemo, useEffect } from 'react'; +import { Helmet } from 'react-helmet'; +import { motion } from 'framer-motion'; +import { useNavigate } from 'react-router-dom'; +import { TrendingUp, TrendingDown, Euro, FileText, ShoppingCart, Receipt, Target, Activity, Users, CreditCard, Clock, RotateCcw } from 'lucide-react'; +import SegmentedControl from '@/components/SegmentedControl'; +import KPIBar, { PeriodType } from '@/components/KPIBar'; +import ChartCard from '@/components/ChartCard'; +import { mockStats, mockQuotes, mockInvoices, mockClients, calculateKPIs, CompanyInfo } from '@/data/mockData'; +import { useAppDispatch, useAppSelector } from '@/store/hooks'; +import { factureStatus, getAllfacture } from '@/store/features/factures/selectors'; +import { Facture } from '@/types/factureType'; +import { getFactures } from '@/store/features/factures/thunk'; +import { filterItemByPeriod, getPreviousPeriodItems } from '@/components/filter/ItemsFilter'; +import { devisStatus, getAllDevis } from '@/store/features/devis/selectors'; +import { DevisListItem } from '@/types/devisType'; +import { getDevisList } from '@/store/features/devis/thunk'; +import { clientStatus, getAllClients } from '@/store/features/client/selectors'; +import { Client } from '@/types/clientType'; +import { commandeStatus, getAllcommandes } from '@/store/features/commande/selectors'; +import { Commande } from '@/types/commandeTypes'; +import { getClients } from '@/store/features/client/thunk'; +import { getCommandes } from '@/store/features/commande/thunk'; +import { ChartByStatusChart, ChartDataven, TopClientsChart } from '@/components/chart/Chart'; +import PeriodSelector from '@/components/common/PeriodSelector'; +import { getArticles } from '@/store/features/article/thunk'; +import { articleStatus } from '@/store/features/article/selectors'; +import { avoirStatus, getAllavoir } from '@/store/features/avoir/selectors'; +import { Avoir } from '@/types/avoirType'; +import { getAvoirs } from '@/store/features/avoir/thunk'; +import { gatewaysStatus } from '@/store/features/gateways/selectors'; +import { getGateways } from '@/store/features/gateways/thunk'; +import { commercialsStatus } from '@/store/features/commercial/selectors'; +import { getCommercials } from '@/store/features/commercial/thunk'; +import { universignStatus } from '@/store/features/universign/selectors'; +import { getUniversigns } from '@/store/features/universign/thunk'; +import { useDashboardData } from '@/store/hooks/useAppData'; +import { getuserConnected } from '@/store/features/user/selectors'; + +const DashboardPage = () => { + const [activeSegment, setActiveSegment] = useState('ventes'); + const [period, setPeriod] = useState('all'); + const navigate = useNavigate(); + const dispatch = useAppDispatch() + + const segments = [ + { id: 'ventes', label: 'Ventes & CRM' }, + // { id: 'achats', label: 'Achats' }, + // { id: 'sav', label: 'SAV & Qualité' }, + ]; + + // ✅ Récupération des données + + const statusCommercial = useAppSelector(commercialsStatus) ; + + const statusUniversign = useAppSelector(universignStatus) ; + + const factures = useAppSelector(getAllfacture) as Facture[]; + const statusFacture = useAppSelector(factureStatus); + + const devis = useAppSelector(getAllDevis) as DevisListItem[]; + const statusDevis = useAppSelector(devisStatus); + + const clients = useAppSelector(getAllClients) as Client[]; + const statusClient = useAppSelector(clientStatus); + + const commandes = useAppSelector(getAllcommandes) as Commande[]; + const statusCommande = useAppSelector(commandeStatus); + + const avoirs = useAppSelector(getAllavoir) as Avoir[]; + const statusAvoir = useAppSelector(avoirStatus); + + const statusArticle = useAppSelector(articleStatus) + + const userConnected = useAppSelector(getuserConnected); + + const { refresh } = useDashboardData(); + + + + // ✅ Charger les données au montage + useEffect(() => { + const load = async () => { + if (statusDevis === "idle") await dispatch(getDevisList()).unwrap(); + if (statusCommercial === "idle") await dispatch(getCommercials()).unwrap(); + if (statusUniversign === "idle") await dispatch(getUniversigns()).unwrap(); + if (statusArticle === "idle") await dispatch(getArticles()).unwrap(); + if (statusFacture === "idle") await dispatch(getFactures()).unwrap(); + if (statusClient === "idle") await dispatch(getClients()).unwrap(); + if (statusCommande === "idle") await dispatch(getCommandes()).unwrap(); + if (statusAvoir === "idle") await dispatch(getAvoirs()).unwrap(); + }; + load(); + }, [statusFacture, statusDevis, statusArticle, statusAvoir, statusCommercial, statusUniversign, dispatch]); + + // ✅ Filtrer par période + const filteredFactures = useMemo(() => { + return filterItemByPeriod(factures, period); + }, [factures, period]); + + const filteredDevis = useMemo(() => { + return filterItemByPeriod(devis, period); + }, [devis, period]); + + const filteredCommandes = useMemo(() => { + return filterItemByPeriod(commandes, period); + }, [commandes, period]); + + const filteredAvoir = useMemo(() => { + return filterItemByPeriod(avoirs, period); + }, [avoirs, period]); + + + + const isLoadingFacture = useMemo(() => { + return statusFacture === 'loading' || statusFacture === 'idle' + }, [statusFacture]); + + const isLoadingDevis = useMemo(() => { + return statusDevis === 'loading' || statusDevis === 'idle' + }, [statusDevis]); + + const isLoadingClient = useMemo(() => { + return statusClient === 'loading' || statusClient === 'idle' + }, [statusClient]); + + const isLoadingCommande = useMemo(() => { + return statusCommande === 'loading' || statusCommande === 'idle' + }, [statusCommande]); + + const isLoadingAvoir = useMemo(() => { + return statusAvoir === 'loading' || statusAvoir === 'idle' + }, [statusAvoir]); + + // ✅ Calculer les KPIs + const kpis = useMemo(() => { + if (activeSegment === 'ventes') { + const caFacture = Math.round( + filteredFactures + .filter(f => f.statut === 2) + .reduce((sum, f) => sum + f.total_ht, 0) + ); + + + const previousFactures = getPreviousPeriodItems(factures, period); + const previousCAFacture = previousFactures + .filter(f => f.statut === 2) + .reduce((sum, f) => sum + f.total_ht, 0); + + const caChange = previousCAFacture > 0 + ? ((caFacture - previousCAFacture) / previousCAFacture * 100).toFixed(1) + : '0'; + const caTrend = caFacture >= previousCAFacture ? 'up' : 'down'; + + // ✅ PIPELINE (Devis en attente ou acceptés) + const pipeline = Math.round( + filteredDevis + .filter(d => d.statut === 1 || d.statut === 2) // En attente ou Acceptés + .reduce((sum, d) => sum + d.total_ht, 0)) + + const previousDevis = getPreviousPeriodItems(devis, period); + const previousPipeline = previousDevis + .filter(d => d.statut === 1 || d.statut === 2) + .reduce((sum, d) => sum + d.total_ht, 0); + + const pipelineChange = previousPipeline > 0 + ? ((pipeline - previousPipeline) / previousPipeline * 100).toFixed(1) + : '0'; + const pipelineTrend = pipeline >= previousPipeline ? 'up' : 'down'; + + const nouveauxClients = clients.length; + const clientsChange = '+0'; + const clientsTrend = 'neutral'; + + const nbCommandes = filteredCommandes.filter(c => c.statut === 2).length; + const commandesChange = '+0'; + const commandesTrend = 'neutral'; + + const nbAvoirs = filteredAvoir.filter(c => c.statut === 2).length; + + return [ + { + title: 'CA Facturé', + value: isLoadingFacture ? '...' : `${caFacture.toLocaleString('fr-FR')}€`, + change: isLoadingFacture ? '' : `${caTrend === 'up' ? '+' : ''}${caChange}%`, + trend: caTrend, + icon: Euro, + onClick: () => navigate('/home/factures'), + loading: isLoadingFacture, + tooltip: { + content: "Montant total des factures validées sur la période.", + calculation: "Σ(Factures HT) - Σ(Avoirs HT)", + source: "Module Ventes > Factures" + } + }, + { + title: 'Pipeline', + value: isLoadingDevis ? '...' : `${pipeline.toLocaleString('fr-FR')}€`, + change: isLoadingDevis ? '' : `${pipelineTrend === 'up' ? '+' : ''}${pipelineChange}%`, + trend: pipelineTrend, + icon: Target, + onClick: () => navigate('/home/devis'), + loading: isLoadingDevis, + tooltip: { + content: "Montant total des devis en attente et acceptés", + calculation: "Σ(Montant HT des devis en attente ou acceptés)", + source: "CRM > Devis" + } + }, + { + title: 'Clients', + value: isLoadingClient ? '...' : nouveauxClients, + change: isLoadingClient ? '' : clientsChange, + trend: clientsTrend, + icon: Users, + onClick: () => navigate('/home/clients'), + loading: isLoadingClient, + tooltip: { + content: "Nombre de nouveaux comptes clients créés.", + source: "CRM > Clients" + } + }, + { + title: 'Commandes', + value: isLoadingCommande ? '...' : nbCommandes, + change: isLoadingCommande ? '' : commandesChange, + trend: commandesTrend, + icon: ShoppingCart, + onClick: () => navigate('/home/commandes'), + loading: isLoadingCommande, + tooltip: { + content: "Nombre de commandes validées non livrées.", + source: "Ventes > Commandes" + } + }, + { + title: 'Avoir', + value: isLoadingAvoir ? '...' : nbAvoirs, + change: isLoadingAvoir ? '' : commandesChange, + trend: commandesTrend, + icon: RotateCcw, + onClick: () => navigate('/home/avoirs'), + loading: isLoadingAvoir, + tooltip: { + content: "Nombre d'avoir validées non validés.", + source: "Ventes > Avoirs" + } + }, + ]; + } else if (activeSegment === 'achats') { + // TODO: Implémenter les KPIs achats + return [ + { + title: 'Dépenses', + value: '...', + change: '', + trend: 'neutral', + icon: Euro, + loading: true, + }, + { + title: 'Commandes Four.', + value: '...', + change: '', + trend: 'neutral', + icon: ShoppingCart, + loading: true, + }, + { + title: 'Factures à payer', + value: '...', + change: '', + trend: 'neutral', + icon: Receipt, + loading: true, + }, + ]; + } else { + // TODO: Implémenter les KPIs support + return [ + { + title: 'Tickets Ouverts', + value: '...', + change: '', + trend: 'neutral', + icon: Activity, + onClick: () => navigate('/home/tickets'), + loading: true, + }, + { + title: 'SLA Global', + value: '...', + change: '', + trend: 'neutral', + icon: TrendingUp, + loading: true, + }, + { + title: 'Satisfaction', + value: '...', + change: '', + trend: 'neutral', + icon: Users, + loading: true, + }, + { + title: 'Temps Moyen', + value: '...', + change: '', + trend: 'neutral', + icon: Clock, + loading: true, + }, + ]; + } + }, [ + activeSegment, + period, + navigate, + isLoadingCommande, + isLoadingClient, + isLoadingDevis, + isLoadingFacture, + filteredFactures, + filteredDevis, + factures, + devis, + ]); + + return ( + <> + + Tableau de bord - {CompanyInfo.name} + + + + {/*
+
+

Vue d'ensemble

+

Bienvenue, Jean Dupont

+
+ +
*/} +
+
+

Vue d'ensemble

+

Bienvenue, {userConnected?.nom} {userConnected?.prenom}

+
+
+ + +
+
+ + + + + {activeSegment === 'ventes' && ( +
+ + + + {/* */} + {/* */} +
+ )} + + {activeSegment === 'achats' && ( +
+ {/* + */} +
+ )} + + {activeSegment === 'sav' && ( +
+ {/* + */} +
+ )} +
+
+ + ); +}; + +export default DashboardPage; diff --git a/src/pages/DocumentsPage.jsx b/src/pages/DocumentsPage.jsx new file mode 100644 index 0000000..8bbbce5 --- /dev/null +++ b/src/pages/DocumentsPage.jsx @@ -0,0 +1,25 @@ + +import React from 'react'; +import { Helmet } from 'react-helmet'; + + +const DocumentsPage = () => { + return ( + <> + + Documents - Bijou ERP + + +
+
+

GED - Documents

+
+
+

Page Documents - Contenu à venir

+
+
+ + ); +}; + +export default DocumentsPage; diff --git a/src/pages/SageBuilderPage.tsx b/src/pages/SageBuilderPage.tsx new file mode 100644 index 0000000..f9dd9c1 --- /dev/null +++ b/src/pages/SageBuilderPage.tsx @@ -0,0 +1,91 @@ +import { ModalLoading } from '@/components/modal/ModalLoading'; +import { getSageBuilderHtmlContent, sageBuilderError, sageBuilderStatus } from '@/store/features/sage-builder/selectors'; +import { getSageBuilderDashboard } from '@/store/features/sage-builder/thunk'; +import { useAppDispatch, useAppSelector } from '@/store/hooks'; +import React, { useEffect, useRef } from 'react'; + +const SageBuilderPage = () => { + const dispatch = useAppDispatch(); + const iframeRef = useRef(null); + + const htmlContent = useAppSelector(getSageBuilderHtmlContent); + const status = useAppSelector(sageBuilderStatus); + const error = useAppSelector(sageBuilderError); + + useEffect(() => { + // Charger le dashboard au montage du composant + if (status === 'idle') { + dispatch(getSageBuilderDashboard()); + } + }, [dispatch, status]); + + useEffect(() => { + // Injecter le HTML dans l'iframe quand il est disponible + if (htmlContent && iframeRef.current && status === 'succeeded') { + const iframeDoc = iframeRef.current.contentDocument || iframeRef.current.contentWindow?.document; + if (iframeDoc) { + iframeDoc.open(); + iframeDoc.write(htmlContent); + iframeDoc.close(); + } + } + }, [htmlContent, status]); + + const handleRetry = () => { + dispatch(getSageBuilderDashboard()); + }; + + return ( +
+ {/* Loading Overlay */} + {status === 'loading' && } + + {/* Error State */} + {status === 'failed' && error && ( +
+
+
+ + + +
+

+ Erreur de chargement +

+

+ {error} +

+ +
+
+ )} + + {/* Iframe */} +