diff --git a/.gitignore b/.gitignore
index 143933b..15f2459 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,4 +27,3 @@ dist-ssr
.env
.env.staging
.env.production
-src/
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
new file mode 100644
index 0000000..c4e4bc8
--- /dev/null
+++ b/src/App.jsx
@@ -0,0 +1,44 @@
+
+
+import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
+
+import { Toaster } from '@/components/ui/toaster';
+import { ThemeProvider } from '@/contexts/ThemeContext';
+import DatavenRoute from '@/routes/DatavenRoute';
+import ProtectedRoute from '@/routes/ProtectedRoute';
+import Login from '@/pages/auth/Login';
+import Forgot from '@/pages/auth/forgot';
+import Reset from '@/pages/auth/reset';
+import { SignatureProvider } from './contexts/SignatureContext';
+
+
+function App() {
+ return (
+
+
+
+
+ } />
+ } />
+ } />
+
+
+
+
+ }
+ />
+ } />
+
+
+
+
+
+
+
+ );
+}
+
+export default App;
diff --git a/src/config/apiConfig.ts b/src/config/apiConfig.ts
new file mode 100644
index 0000000..7364e2c
--- /dev/null
+++ b/src/config/apiConfig.ts
@@ -0,0 +1,135 @@
+import { pdfInterface } from "@/types/sageTypes"
+
+export const API_CONFIG = {
+ baseURL: import.meta.env.VITE_API_URL,
+ timeout: 30000,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+}
+
+export const API_ENDPOINTS = {
+
+ // ========= Authentification ==========
+ auth: '/auth/me',
+ login: '/auth/login',
+ logout: '/auth/logout',
+ refreshToken: '/auth/refresh',
+ forgotPassword: '/auth/forgot-password',
+ resetPassword: '/auth/reset-password',
+
+ // entreprise
+ searchEntreprise : (query: string) => `/entreprises/search?q=${query}`,
+
+ // client
+ clients: '/clients',
+ clientById: (numero: string) => `/clients/${numero}`,
+
+
+ // societe
+ societe: '/societe/info',
+
+ // gateways
+ gateways: '/sage-gateways',
+ gatewaysById: (id: string) => `/sage-gateways/${id}`,
+
+ // contacts
+ contactsClient: (numero: string) => `/tiers/${numero}/contacts`,
+ contactId: (numero: string, contact_numero: number) => `/tiers/${numero}/contacts/${contact_numero}`,
+
+ // commercial
+ commercials: '/collaborateurs',
+ commercialsById: (numero: string) => `/collaborateurs/${numero}`,
+
+ // Articles
+ articles: '/articles',
+ articleById: (reference: string) => `/articles/${reference}`,
+
+ // stock
+ addStock: '/stock/entree',
+
+ // devis
+ devis: '/devis',
+ devisById: (id: string) => `/devis/${id}`,
+ devisPDF: (id: string) => `/devis/${id}/pdf`,
+ devisEnvoyer: (id: string) => `/devis/${id}/envoyer`,
+ devisSigner: (id: string) => `/devis/${id}/signer`,
+ devisStatut: (id: string) => `/devis/${id}/statut`,
+ devisContact: (id: string) => `/devis/${id}/contact`,
+
+ // fournisseur
+ fournisseurs: '/fournisseurs',
+ fournisseursById: (numero: string) => `/fournisseurs/${numero}`,
+
+ // famille
+ familles: '/familles',
+ familleId: (code: string) => `/familles/${code}`,
+
+ // uniersign
+ downloadSignedDocument: (transactionId: string) => `/universign/transactions/${transactionId}/document/download`,
+ getTransaction: (transactionId: string) => `/universign/transactions/${transactionId}`,
+
+
+ // Bon de Livraison
+ bl: '/livraisons',
+ blById: (id: string) => `/livraisons/${id}`,
+
+ // client
+ avoirs: '/avoirs',
+ avoirsById: (numero: string) => `/avoirs/${numero}`,
+
+ // reglements
+ reglement: "/reglements",
+ reglementById: (id: string) => `/reglements/facture/${id}`,
+ reglerFacture : '/reglements/multiple',
+ mode: '/reglements/modes',
+ devises: '/devises',
+ tresorerie: '/journaux/tresorerie',
+ compte_generaux: '/comptes-generaux',
+ taux: '/tva/taux',
+ encaissement : '/parametres/encaissement',
+
+ // US-A2 - Workflow
+ devisToFacture: (id: string) => `/workflow/devis/${id}/to-facture`,
+ devisToCommande: (id: string) => `/workflow/devis/${id}/to-commande`,
+ commandeToBL: (id: string) => `/workflow/commande/${id}/to-livraison`,
+ commandeToFacture: (id: string) => `/workflow/commande/${id}/to-facture`,
+ blToFacture: (id: string) => `/workflow/livraison/${id}/to-facture`,
+ commandes: '/commandes',
+ commandeById: (id: string) => `/commandes/${id}`,
+
+ // factures
+ factures: '/factures',
+ factureValider: (numero_facture: string) => `/factures/${numero_facture}/valider`,
+ factureById: (id: string) => `/factures/${id}`,
+
+ // US-A3 - Signature
+ signatureSend: '/signature/universign/send',
+ signatureStatus: '/signature/universign/status',
+
+ // US-A4/A8 - Emails
+ emailBatch: '/emails/send-batch',
+ emailLogs: '/emails/logs',
+ emailLogsExport: '/emails/logs/export',
+
+ // US-A5 - Remises
+ devisValiderRemise: '/devis/valider-remise',
+
+ // US-A6 - Relance Devis
+ devisRelancerSignature: (id: string) => `/devis/${id}/relancer-signature`,
+
+ // US-A7 - Relance Facture
+ factureRelancer: (id: string) => `/factures/${id}/relancer`,
+
+ // US-A10 - Templates
+ templates: '/templates/emails',
+ templateById: (id: string) => `/templates/emails/${id}`,
+ templatePreview: '/templates/emails/preview',
+
+ // Admin
+ cacheInfo: '/admin/cache/info',
+ cacheRefresh: '/admin/cache/refresh',
+ queueStatus: '/admin/queue/status',
+ health: '/health',
+}
+
diff --git a/src/config/moduleConfig.js b/src/config/moduleConfig.js
new file mode 100644
index 0000000..dc80dad
--- /dev/null
+++ b/src/config/moduleConfig.js
@@ -0,0 +1,132 @@
+
+import {
+ LayoutDashboard,
+ Users,
+ UserPlus,
+ ShoppingCart,
+ FileText,
+ CreditCard,
+ Settings,
+ PieChart,
+ Wallet,
+ Briefcase,
+ FileBarChart,
+ CheckSquare,
+ Upload,
+ Package,
+ Truck,
+ FolderOpen,
+ PenTool,
+ History,
+ BarChart3,
+ Scale,
+ BookOpen,
+ ScrollText,
+ Tags,
+ Building2,
+ Calculator,
+ ListTodo,
+ ShieldCheck,
+ PanelLeft
+} from 'lucide-react';
+
+export const MODULE_GESTION = 'gestion';
+export const MODULE_COMPTABILITE = 'compta';
+
+export const getMenuForModule = (currentModule) => {
+ if (currentModule === MODULE_COMPTABILITE) {
+ return [
+ {
+ title: "Pilotage",
+ items: [
+ { to: "/comptabilite/dashboard", icon: LayoutDashboard, label: "Tableau de bord" },
+ { to: "/comptabilite/transactions", icon: Wallet, label: "Banque & Transactions" },
+ ]
+ },
+ {
+ title: "Saisie & Journaux",
+ items: [
+ { to: "/comptabilite/ecritures", icon: PenTool, label: "Saisie d'écritures" },
+ { to: "/comptabilite/journaux", icon: BookOpen, label: "Journaux" },
+ { to: "/comptabilite/rapprochement", icon: Scale, label: "Rapprochement" },
+ ]
+ },
+ {
+ title: "États & Déclarations",
+ items: [
+ { to: "/comptabilite/grand-livre", icon: ScrollText, label: "Grand Livre" },
+ { to: "/comptabilite/balance-generale", icon: Scale, label: "Balance Générale" },
+ { to: "/comptabilite/balance-clients", icon: Users, label: "Balance Clients" },
+ { to: "/comptabilite/balance-fournisseurs", icon: Building2, label: "Balance Fournisseurs" },
+ { to: "/comptabilite/tva", icon: FileText, label: "Déclaration TVA" },
+ ]
+ },
+ {
+ title: "Paramètres",
+ items: [
+ { to: "/comptabilite/plan-comptable", icon: Tags, label: "Plan Comptable" },
+ { to: "/comptabilite/analytique", icon: PieChart, label: "Analytique" },
+ { to: "/comptabilite/imports-exports", icon: Upload, label: "Imports / Exports" },
+ ]
+ }
+ ];
+ }
+
+ // Default to MODULE_GESTION
+ return [
+ {
+ title: "Pilotage",
+ items: [
+ { to: "/home/", icon: LayoutDashboard, label: "Tableau de bord", end: true },
+ ]
+ },
+ {
+ title: "Relations Tiers",
+ items: [
+ // { to: "/home/prospects", icon: UserPlus, label: "Prospects" },
+ { to: "/home/clients", icon: Users, label: "Clients" },
+ { to: "/home/fournisseurs", icon: Building2, label: "Fournisseurs" },
+ ]
+ },
+ {
+ title: "Ventes",
+ items: [
+ { to: "/home/devis", icon: FileText, label: "Devis" },
+ { to: "/home/commandes", icon: ShoppingCart, label: "Commandes" },
+ { to: "/home/bons-livraison", icon: Truck, label: "Bons de livraison" },
+ { to: "/home/factures", icon: FileBarChart, label: "Factures" },
+ { to: "/home/avoirs", icon: FileText, label: "Avoirs" },
+ // { to: "/home/relances", icon: History, label: "Relances" },
+ { to: "/home/reglements", icon: CreditCard, label: "Règlements" },
+ ]
+ },
+ {
+ title: "Catalogue",
+ items: [
+ { to: "/home/articles", icon: Package, label: "Articles" },
+ { to: "/home/familles-articles", icon: Tags, label: "Familles" },
+ ]
+ },
+ {
+ title: "Vues personnalisées",
+ items: [
+ { to: "/home/sage-builder", icon: PanelLeft, label: "Tableau des ventes" },
+ ]
+ },
+ // {
+ // title: "Signature",
+ // items: [
+ // { to: "/home/signature/dashboard", icon: PenTool, label: "Tableau de bord" },
+ // { to: "/home/signature/tracking", icon: History, label: "Suivi" },
+ // { to: "/home/signature/purchase", icon: ShoppingCart, label: "Acheter des crédits" },
+ // ]
+ // },
+ // {
+ // title: "Administration",
+ // items: [
+ // { to: "/home/utilisateurs", icon: Users, label: "Utilisateurs" },
+ // { to: "/home/parametres", icon: Settings, label: "Paramètres" },
+ // ]
+ // }
+ ];
+};
diff --git a/src/context/BannerContext.jsx b/src/context/BannerContext.jsx
new file mode 100644
index 0000000..49f6c02
--- /dev/null
+++ b/src/context/BannerContext.jsx
@@ -0,0 +1,63 @@
+
+import React, { createContext, useContext, useState, useEffect } from 'react';
+
+const BannerContext = createContext();
+
+export const useBanners = () => useContext(BannerContext);
+
+export const BannerProvider = ({ children }) => {
+ const [priorityBanner, setPriorityBanner] = useState({
+ visible: true,
+ message: "Plateforme agréée facture électronique – 2026",
+ type: "info", // info, warning
+ dismissible: true,
+ id: "priority-2026-mandate"
+ });
+
+ const [secondaryBanner, setSecondaryBanner] = useState({
+ visible: true,
+ message: "Bienvenue sur la nouvelle version de Dataven. Découvrez les nouvelles fonctionnalités.",
+ dismissible: true,
+ id: "secondary-welcome-v3"
+ });
+
+ // Load dismissed state from localStorage
+ useEffect(() => {
+ const dismissedPriority = localStorage.getItem(`banner-dismissed-${priorityBanner.id}`);
+ const dismissedSecondary = localStorage.getItem(`banner-dismissed-${secondaryBanner.id}`);
+
+ if (dismissedPriority) {
+ setPriorityBanner(prev => ({ ...prev, visible: false }));
+ }
+ if (dismissedSecondary) {
+ setSecondaryBanner(prev => ({ ...prev, visible: false }));
+ }
+ }, []);
+
+ const dismissPriority = () => {
+ if (priorityBanner.dismissible) {
+ setPriorityBanner(prev => ({ ...prev, visible: false }));
+ localStorage.setItem(`banner-dismissed-${priorityBanner.id}`, 'true');
+ }
+ };
+
+ const dismissSecondary = () => {
+ if (secondaryBanner.dismissible) {
+ setSecondaryBanner(prev => ({ ...prev, visible: false }));
+ localStorage.setItem(`banner-dismissed-${secondaryBanner.id}`, 'true');
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/context/CompanyContext.jsx b/src/context/CompanyContext.jsx
new file mode 100644
index 0000000..6e5c181
--- /dev/null
+++ b/src/context/CompanyContext.jsx
@@ -0,0 +1,46 @@
+
+import React, { createContext, useContext, useState, useEffect } from 'react';
+
+const CompanyContext = createContext();
+
+export const useCompany = () => {
+ const context = useContext(CompanyContext);
+ if (!context) {
+ throw new Error('useCompany must be used within a CompanyProvider');
+ }
+ return context;
+};
+
+const AVAILABLE_COMPANIES = [
+ { id: 'c1', name: 'Acme Corp' },
+ { id: 'c2', name: 'Tech Solutions' },
+ { id: 'c3', name: 'Global Services' }
+];
+
+export const CompanyProvider = ({ children }) => {
+ const [currentCompany, setCurrentCompany] = useState(() => {
+ const saved = localStorage.getItem('currentCompany');
+ return saved ? JSON.parse(saved) : AVAILABLE_COMPANIES[0];
+ });
+
+ useEffect(() => {
+ localStorage.setItem('currentCompany', JSON.stringify(currentCompany));
+ }, [currentCompany]);
+
+ const switchCompany = (companyId) => {
+ const company = AVAILABLE_COMPANIES.find(c => c.id === companyId);
+ if (company) {
+ setCurrentCompany(company);
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/context/DisplayModeContext.jsx b/src/context/DisplayModeContext.jsx
new file mode 100644
index 0000000..096dbdf
--- /dev/null
+++ b/src/context/DisplayModeContext.jsx
@@ -0,0 +1,43 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+
+const DisplayModeContext = createContext();
+
+export const DisplayModeProvider = ({ children }) => {
+ const [displayMode, setDisplayMode] = useState(() => {
+ return localStorage.getItem('displayMode') || 'comfortable';
+ });
+
+ const [isPdfPreviewVisible, setIsPdfPreviewVisible] = useState(() => {
+ return localStorage.getItem('pdfPreviewVisible') === 'true';
+ });
+
+ useEffect(() => {
+ localStorage.setItem('displayMode', displayMode);
+ }, [displayMode]);
+
+ useEffect(() => {
+ localStorage.setItem('pdfPreviewVisible', isPdfPreviewVisible);
+ }, [isPdfPreviewVisible]);
+
+ const toggleDisplayMode = () => {
+ setDisplayMode(prev => prev === 'compact' ? 'comfortable' : 'compact');
+ };
+
+ const togglePdfPreview = () => {
+ setIsPdfPreviewVisible(prev => !prev);
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useDisplayMode = () => useContext(DisplayModeContext);
\ No newline at end of file
diff --git a/src/context/ModuleContext.jsx b/src/context/ModuleContext.jsx
new file mode 100644
index 0000000..9c01d43
--- /dev/null
+++ b/src/context/ModuleContext.jsx
@@ -0,0 +1,59 @@
+
+import React, { createContext, useContext, useState, useEffect } from 'react';
+import { useNavigate, useLocation } from 'react-router-dom';
+
+const ModuleContext = createContext();
+
+export const MODULE_GESTION = 'gestion';
+export const MODULE_COMPTABILITE = 'comptabilite';
+
+export const ModuleProvider = ({ children }) => {
+ // Initialize from localStorage or default to 'gestion'
+ const [currentModule, setCurrentModule] = useState(() => {
+ const saved = localStorage.getItem('dataven_current_module');
+ return saved || MODULE_GESTION;
+ });
+
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ // Persist to localStorage whenever module changes
+ useEffect(() => {
+ localStorage.setItem('dataven_current_module', currentModule);
+ }, [currentModule]);
+
+ const changeModule = (newModule) => {
+ if (newModule === currentModule) return;
+
+ setCurrentModule(newModule);
+
+ // Smart Navigation Logic
+ if (newModule === MODULE_COMPTABILITE) {
+ // If switching TO Compta, go to Compta Dashboard unless already deep in a compta page (unlikely if we are in Gesco mode)
+ if (!location.pathname.startsWith('/comptabilite')) {
+ // navigate('/comptabilite/dashboard');
+ navigate('/home');
+ }
+ }
+ else if (newModule === MODULE_GESTION) {
+ // If switching TO Gesco, go to Main Dashboard if currently in a Compta page
+ if (location.pathname.startsWith('/comptabilite')) {
+ navigate('/home');
+ }
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useModule = () => {
+ const context = useContext(ModuleContext);
+ if (!context) {
+ throw new Error('useModule must be used within a ModuleProvider');
+ }
+ return context;
+};
diff --git a/src/contexts/GlobalMessageContext.jsx b/src/contexts/GlobalMessageContext.jsx
new file mode 100644
index 0000000..2844ac6
--- /dev/null
+++ b/src/contexts/GlobalMessageContext.jsx
@@ -0,0 +1,82 @@
+
+import React, { createContext, useContext, useState, useCallback } from 'react';
+
+const GlobalMessageContext = createContext(null);
+
+export const GlobalMessageProvider = ({ children }) => {
+ const [messageState, setMessageState] = useState({
+ isVisible: false,
+ text: '',
+ type: 'info', // info, warning, success, error
+ dismissible: true,
+ scrolling: false,
+ icon: 'info',
+ actionLabel: null,
+ onAction: null,
+ });
+
+ const showMessage = useCallback((text, options = {}) => {
+ // Hard blocklist for legacy placeholders / prompt-related messaging.
+ // If a legacy message tries to render (toast-like banners, announcements), do nothing.
+ const msg = (text ?? '').toString().replace(/\s+/g, ' ').trim();
+ const BLOCKLIST = [
+ /prochain\s+demande/i,
+ /prochaine\s+demande/i,
+ /prochain\s+prompt/i,
+ /\bprompt\b/i,
+ /\bllm\b/i,
+ /\bopenai\b/i,
+ /\bgpt\b/i,
+ /\bclaude\b/i,
+ /pas\s+encore\s+impl[ée]ment/i,
+ /sera\s+impl[ée]ment/i,
+ /\bcoming\s+soon\b/i,
+ /\bnot\s+implemented\b/i,
+ ];
+ if (msg && BLOCKLIST.some((re) => re.test(msg))) {
+ return;
+ }
+
+ setMessageState({
+ isVisible: true,
+ text,
+ type: options.type || 'info',
+ dismissible: options.dismissible !== undefined ? options.dismissible : true,
+ scrolling: options.scrolling || false,
+ icon: options.icon || 'info',
+ actionLabel: options.actionLabel || null,
+ onAction: options.onAction || null,
+ });
+ }, []);
+
+ const hideMessage = useCallback(() => {
+ setMessageState(prev => ({ ...prev, isVisible: false }));
+ }, []);
+
+ const clearMessage = useCallback(() => {
+ setMessageState({
+ isVisible: false,
+ text: '',
+ type: 'info',
+ dismissible: true,
+ scrolling: false,
+ icon: 'info',
+ actionLabel: null,
+ onAction: null,
+ });
+ }, []);
+
+ return (
+
+ {children}
+
+ );
+};
+
+export const useGlobalMessage = () => {
+ const context = useContext(GlobalMessageContext);
+ if (!context) {
+ throw new Error('useGlobalMessage must be used within a GlobalMessageProvider');
+ }
+ return context;
+};
diff --git a/src/contexts/SignatureContext.jsx b/src/contexts/SignatureContext.jsx
new file mode 100644
index 0000000..1683574
--- /dev/null
+++ b/src/contexts/SignatureContext.jsx
@@ -0,0 +1,86 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+import { toast } from '@/components/ui/use-toast';
+import { universignService } from '@/service/signature/universignService';
+
+const SignatureContext = createContext();
+
+export const useSignature = () => {
+ const context = useContext(SignatureContext);
+ if (!context) {
+ throw new Error('useSignature must be used within a SignatureProvider');
+ }
+ return context;
+};
+
+export const SignatureProvider = ({ children }) => {
+ const [loading, setLoading] = useState(true);
+ const [stats, setStats] = useState(null);
+ const [signatures, setSignatures] = useState([]);
+ const [history, setHistory] = useState([]);
+ const [refreshTrigger, setRefreshTrigger] = useState(0);
+
+ // Initial load
+ useEffect(() => {
+ const loadData = async () => {
+ setLoading(true);
+ try {
+ const [statsData, sigsData, historyData] = await Promise.all([
+ universignService.getStats(),
+ universignService.getSignatures(),
+ universignService.getConsumptionHistory()
+ ]);
+
+ setStats(statsData);
+ setSignatures(sigsData);
+ setHistory(historyData);
+ } catch (error) {
+ console.error("Error loading signature data:", error);
+ toast({
+ title: "Erreur",
+ description: "Impossible de charger les données de signature.",
+ variant: "destructive"
+ });
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ loadData();
+ }, [refreshTrigger]);
+
+ const refreshData = () => setRefreshTrigger(prev => prev + 1);
+
+ const purchaseCredits = async (amount) => {
+ try {
+ await universignService.purchaseCredits(amount);
+ refreshData();
+ return true;
+ } catch (error) {
+ return false;
+ }
+ };
+
+ const sendReminder = async (signatureId) => {
+ try {
+ await universignService.sendReminder(signatureId);
+ refreshData();
+ return true;
+ } catch (error) {
+ return false;
+ }
+ };
+
+ return (
+
+ {children}
+
+ );
+};
\ No newline at end of file
diff --git a/src/contexts/ThemeContext.jsx b/src/contexts/ThemeContext.jsx
new file mode 100644
index 0000000..cd4c289
--- /dev/null
+++ b/src/contexts/ThemeContext.jsx
@@ -0,0 +1,49 @@
+import React, { createContext, useContext, useState, useEffect } from 'react';
+
+// Création du contexte
+const ThemeContext = createContext();
+
+// Hook pour l'utiliser facilement
+export const useTheme = () => {
+ const context = useContext(ThemeContext);
+ if (!context) {
+ throw new Error('useTheme must be used within ThemeProvider');
+ }
+ return context;
+};
+
+// Provider
+export const ThemeProvider = ({ children }) => {
+ const [isDark, setIsDark] = useState(() => {
+ const saved = localStorage.getItem('theme');
+ if (saved) return saved === 'dark';
+ return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
+ });
+
+ useEffect(() => {
+ localStorage.setItem('theme', isDark ? 'dark' : 'light');
+
+ if (isDark) {
+ document.documentElement.classList.add('dark');
+ } else {
+ document.documentElement.classList.remove('dark');
+ }
+ }, [isDark]);
+
+ useEffect(() => {
+ const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
+ const handleChange = (e) => setIsDark(e.matches);
+
+ mediaQuery.addEventListener('change', handleChange);
+
+ return () => mediaQuery.removeEventListener('change', handleChange);
+ }, []);
+
+ const toggleTheme = () => setIsDark(!isDark);
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/data/ItemsFilter.js b/src/data/ItemsFilter.js
new file mode 100644
index 0000000..bbf05f8
--- /dev/null
+++ b/src/data/ItemsFilter.js
@@ -0,0 +1,231 @@
+// components/filter/ItemsFilter.js
+
+/**
+ * Filtre les items par période
+ * @param {Array} items - Liste des items à filtrer
+ * @param {string|object} period - Période de filtre ('today', 'week', 'month', etc. ou { id: 'custom', start, end })
+ * @param {string} dateField - Nom du champ date (default: 'date')
+ * @returns {Array} Items filtrés
+ */
+export const filterItemByPeriod = (items, period, dateField = 'date') => {
+ if (!items || items.length === 0) return [];
+
+ const now = new Date();
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+
+ // Période personnalisée
+ if (typeof period === 'object' && period.id === 'custom') {
+ const startDate = new Date(period.start);
+ startDate.setHours(0, 0, 0, 0);
+
+ const endDate = new Date(period.end);
+ endDate.setHours(23, 59, 59, 999);
+
+ return items.filter(item => {
+ const itemDate = new Date(item[dateField]);
+ return itemDate >= startDate && itemDate <= endDate;
+ });
+ }
+
+ // Périodes prédéfinies
+ let startDate, endDate;
+
+ switch (period) {
+ case 'today':
+ startDate = today;
+ endDate = new Date(today);
+ endDate.setHours(23, 59, 59, 999);
+ break;
+
+ case 'week':
+ // Début de la semaine (lundi)
+ const dayOfWeek = today.getDay();
+ const diffToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
+ startDate = new Date(today);
+ startDate.setDate(today.getDate() - diffToMonday);
+ endDate = new Date(startDate);
+ endDate.setDate(startDate.getDate() + 6);
+ endDate.setHours(23, 59, 59, 999);
+ break;
+
+ case 'month':
+ startDate = new Date(now.getFullYear(), now.getMonth(), 1);
+ endDate = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
+ break;
+
+ case 'last_month':
+ startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
+ endDate = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
+ break;
+
+ case 'quarter':
+ const currentQuarter = Math.floor(now.getMonth() / 3);
+ startDate = new Date(now.getFullYear(), currentQuarter * 3, 1);
+ endDate = new Date(now.getFullYear(), currentQuarter * 3 + 3, 0, 23, 59, 59, 999);
+ break;
+
+ case 'year':
+ startDate = new Date(now.getFullYear(), 0, 1);
+ endDate = new Date(now.getFullYear(), 11, 31, 23, 59, 59, 999);
+ break;
+
+ case '30days':
+ startDate = new Date(today);
+ startDate.setDate(today.getDate() - 30);
+ endDate = new Date(today);
+ endDate.setHours(23, 59, 59, 999);
+ break;
+
+ case '90days':
+ startDate = new Date(today);
+ startDate.setDate(today.getDate() - 90);
+ endDate = new Date(today);
+ endDate.setHours(23, 59, 59, 999);
+ break;
+
+ case 'all':
+ default:
+ return items;
+ }
+
+ return items.filter(item => {
+ const itemDate = new Date(item[dateField]);
+ return itemDate >= startDate && itemDate <= endDate;
+ });
+};
+
+/**
+ * Récupère les items de la période précédente (pour comparaison)
+ * @param {Array} items - Liste des items
+ * @param {string|object} period - Période actuelle
+ * @param {string} dateField - Nom du champ date
+ * @returns {Array} Items de la période précédente
+ */
+export const getPreviousPeriodItems = (items, period, dateField = 'date') => {
+ if (!items || items.length === 0) return [];
+
+ const now = new Date();
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+
+ // Période personnalisée - calculer la période précédente de même durée
+ if (typeof period === 'object' && period.id === 'custom') {
+ const startDate = new Date(period.start);
+ const endDate = new Date(period.end);
+ const duration = endDate.getTime() - startDate.getTime();
+
+ const previousEnd = new Date(startDate.getTime() - 1); // Jour avant le début
+ previousEnd.setHours(23, 59, 59, 999);
+
+ const previousStart = new Date(previousEnd.getTime() - duration);
+ previousStart.setHours(0, 0, 0, 0);
+
+ return items.filter(item => {
+ const itemDate = new Date(item[dateField]);
+ return itemDate >= previousStart && itemDate <= previousEnd;
+ });
+ }
+
+ let startDate, endDate;
+
+ switch (period) {
+ case 'today':
+ // Hier
+ startDate = new Date(today);
+ startDate.setDate(today.getDate() - 1);
+ endDate = new Date(startDate);
+ endDate.setHours(23, 59, 59, 999);
+ break;
+
+ case 'week':
+ // Semaine précédente
+ const dayOfWeek = today.getDay();
+ const diffToMonday = dayOfWeek === 0 ? 6 : dayOfWeek - 1;
+ startDate = new Date(today);
+ startDate.setDate(today.getDate() - diffToMonday - 7);
+ endDate = new Date(startDate);
+ endDate.setDate(startDate.getDate() + 6);
+ endDate.setHours(23, 59, 59, 999);
+ break;
+
+ case 'month':
+ // Mois précédent
+ startDate = new Date(now.getFullYear(), now.getMonth() - 1, 1);
+ endDate = new Date(now.getFullYear(), now.getMonth(), 0, 23, 59, 59, 999);
+ break;
+
+ case 'last_month':
+ // Mois d'avant le mois dernier
+ startDate = new Date(now.getFullYear(), now.getMonth() - 2, 1);
+ endDate = new Date(now.getFullYear(), now.getMonth() - 1, 0, 23, 59, 59, 999);
+ break;
+
+ case 'quarter':
+ // Trimestre précédent
+ const currentQuarter = Math.floor(now.getMonth() / 3);
+ startDate = new Date(now.getFullYear(), (currentQuarter - 1) * 3, 1);
+ endDate = new Date(now.getFullYear(), currentQuarter * 3, 0, 23, 59, 59, 999);
+ break;
+
+ case 'year':
+ // Année précédente
+ startDate = new Date(now.getFullYear() - 1, 0, 1);
+ endDate = new Date(now.getFullYear() - 1, 11, 31, 23, 59, 59, 999);
+ break;
+
+ case '30days':
+ // 30 jours avant les 30 derniers jours
+ startDate = new Date(today);
+ startDate.setDate(today.getDate() - 60);
+ endDate = new Date(today);
+ endDate.setDate(today.getDate() - 31);
+ endDate.setHours(23, 59, 59, 999);
+ break;
+
+ case '90days':
+ // 90 jours avant les 90 derniers jours
+ startDate = new Date(today);
+ startDate.setDate(today.getDate() - 180);
+ endDate = new Date(today);
+ endDate.setDate(today.getDate() - 91);
+ endDate.setHours(23, 59, 59, 999);
+ break;
+
+ case 'all':
+ default:
+ return [];
+ }
+
+ return items.filter(item => {
+ const itemDate = new Date(item[dateField]);
+ return itemDate >= startDate && itemDate <= endDate;
+ });
+};
+
+/**
+ * Obtient le label de la période
+ * @param {string|object} period - Période
+ * @returns {string} Label formaté
+ */
+export const getPeriodLabel = (period) => {
+ if (typeof period === 'object' && period.id === 'custom') {
+ const formatDate = (dateStr) => {
+ const d = new Date(dateStr);
+ return d.toLocaleDateString('fr-FR', { day: 'numeric', month: 'short' });
+ };
+ return `${formatDate(period.start)} - ${formatDate(period.end)}`;
+ }
+
+ const labels = {
+ today: "Aujourd'hui",
+ week: 'Cette semaine',
+ month: 'Ce mois-ci',
+ last_month: 'Mois dernier',
+ quarter: 'Ce trimestre',
+ year: 'Cette année',
+ '30days': '30 derniers jours',
+ '90days': '90 derniers jours',
+ all: 'Tout',
+ };
+
+ return labels[period] || 'Période';
+};
\ No newline at end of file
diff --git a/src/data/mockData.js b/src/data/mockData.js
new file mode 100644
index 0000000..3f713a1
--- /dev/null
+++ b/src/data/mockData.js
@@ -0,0 +1,373 @@
+
+// Comprehensive Mock Data Expansion
+
+export const CompanyInfo = {
+ name: "Sage",
+ email: "sage@gmail.com",
+ adresse: "12 Avenue de l'Innovation",
+ adresse2: "75011 Paris, France",
+ code_postal: "75008",
+ ville: "Paris",
+ siret: "123 456 789 00012",
+ tva_intra: "FR12345678901",
+ telephone: "01 23 45 67 89",
+}
+export const currentUser = {
+ id: 1,
+ name: 'Jean Dupont',
+ email: 'jean.dupont@bijou.com',
+ role: 'Administrateur',
+ department: 'Direction',
+ phone: '+33 6 12 34 56 78',
+ avatar: 'JD',
+ preferences: {
+ language: 'fr',
+ currency: 'EUR',
+ theme: 'light',
+ notifications: { email: true, sms: false, inApp: true }
+ }
+};
+
+export const mockUsers = [
+ { id: 1, name: 'Jean Dupont', email: 'jean.dupont@bijou.com', role: 'Administrateur', department: 'Direction', avatar: 'JD', status: 'active' },
+ { id: 2, name: 'Marie Dubois', email: 'marie.dubois@bijou.com', role: 'Commercial', department: 'Ventes', avatar: 'MD', status: 'active' },
+ { id: 3, name: 'Thomas Robert', email: 'thomas.robert@bijou.com', role: 'Commercial', department: 'Ventes', avatar: 'TR', status: 'active' },
+ { id: 4, name: 'Sophie Martin', email: 'sophie.martin@bijou.com', role: 'Support', department: 'SAV', avatar: 'SM', status: 'active' },
+ { id: 5, name: 'Pierre Durand', email: 'pierre.durand@bijou.com', role: 'Comptabilité', department: 'Finance', avatar: 'PD', status: 'active' },
+ { id: 6, name: 'Julie Moreau', email: 'julie.moreau@bijou.com', role: 'Logistique', department: 'Opérations', avatar: 'JM', status: 'active' },
+];
+
+export const mockProductFamilies = [
+ { id: 1, code: 'FAM-001', name: 'Ordinateurs Portables', color: 'bg-blue-100 text-blue-800', articleCount: 12, category: 'Matériel', status: 'active' },
+ { id: 2, code: 'FAM-002', name: 'Écrans & Moniteurs', color: 'bg-purple-100 text-purple-800', articleCount: 8, category: 'Matériel', status: 'active' },
+ { id: 3, code: 'FAM-003', name: 'Logiciels & Licences', color: 'bg-green-100 text-green-800', articleCount: 25, category: 'Software', status: 'active' },
+ { id: 4, code: 'FAM-004', name: 'Services & Maintenance', color: 'bg-orange-100 text-orange-800', articleCount: 5, category: 'Service', status: 'active' },
+ { id: 5, code: 'FAM-005', name: 'Accessoires', color: 'bg-gray-100 text-gray-800', articleCount: 45, category: 'Accessoire', status: 'inactive' },
+];
+
+export const mockArticles = Array.from({ length: 50 }, (_, i) => {
+ const family = mockProductFamilies[i % mockProductFamilies.length];
+ const costPrice = Math.floor(Math.random() * 1000) + 50;
+ const margin = 1.3 + (Math.random() * 0.4); // 30% to 70% margin
+
+ return {
+ id: i + 1,
+ reference: `ART-${(i + 1000).toString()}`,
+ name: `${family.name.split(' ')[0]} Pro ${2025 + (i % 3)} - Modèle ${String.fromCharCode(65 + (i % 26))}`,
+ familyId: family.id,
+ familyName: family.name,
+ familyColor: family.color,
+ costPrice: costPrice,
+ sellingPrice: Math.floor(costPrice * margin),
+ vatRate: 20,
+ unit: 'Pcs',
+ stock: Math.floor(Math.random() * 100),
+ minStock: 10,
+ status: i % 10 === 0 ? 'inactive' : 'active',
+ description: "Description détaillée de l'article pour les devis et factures.",
+ createdAt: new Date(Date.now() - Math.floor(Math.random() * 10000000000)).toISOString().split('T')[0],
+ };
+});
+
+export const mockClients = Array.from({ length: 25 }, (_, i) => ({
+ id: i + 1,
+ name: [`Sophie Martin`, `Pierre Durand`, `Marie Lambert`, `Lucas Bernard`, `Emma Petit`][i % 5],
+ company: [`ACME Corp`, `TechStart SAS`, `Digital Plus`, `InnovCo`, `WebAgency`, `LogiTrans`, `BuildIt`, `GreenEnergy`, `FoodProc`, `HealthCare`][i % 10],
+ email: `contact${i}@company.com`,
+ phone: `+33 1 ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)}`,
+ status: ['active', 'inactive', 'prospect', 'vip'][Math.floor(Math.random() * 4)],
+ owner: mockUsers[Math.floor(Math.random() * 3)].name,
+ industry: ['Technology', 'Finance', 'Retail', 'Manufacturing'][Math.floor(Math.random() * 4)],
+ city: ['Paris', 'Lyon', 'Marseille', 'Bordeaux', 'Lille'][Math.floor(Math.random() * 5)],
+ address: `${Math.floor(Math.random() * 100) + 1} Rue de l'Innovation, 75001 Paris`,
+ createdAt: new Date(Date.now() - Math.floor(Math.random() * 10000000000)).toISOString().split('T')[0],
+ lastActivity: new Date(Date.now() - Math.floor(Math.random() * 1000000000)).toISOString().split('T')[0]
+}));
+
+export const mockContacts = Array.from({ length: 30 }, (_, i) => ({
+ id: i + 1,
+ firstName: ['Jean', 'Marie', 'Paul', 'Sophie', 'Luc'][i % 5],
+ lastName: ['Dupont', 'Durand', 'Martin', 'Bernard', 'Petit'][i % 5],
+ email: `contact${i}@example.com`,
+ phone: `+33 6 ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)} ${Math.floor(Math.random() * 90 + 10)}`,
+ role: ['CEO', 'CTO', 'CFO', 'Manager', 'Buyer'][i % 5],
+ clientId: (i % mockClients.length) + 1
+}));
+
+export const mockSuppliers = Array.from({ length: 15 }, (_, i) => ({
+ id: i + 1,
+ name: [`Office Depot`, `Dell`, `Microsoft`, `Amazon AWS`, `Google`, `Lyreco`, `Metro`, `Rexel`, `Manutan`, `Orange`][i % 10],
+ contact: [`Jean Supply`, `Marie Vente`, `Paul Achat`][i % 3],
+ email: `supplier${i}@partner.com`,
+ phone: `+33 1 99 88 77 ${i.toString().padStart(2, '0')}`,
+ status: ['active', 'inactive', 'pending'][Math.floor(Math.random() * 3)],
+ category: ['Hardware', 'Software', 'Office', 'Services'][Math.floor(Math.random() * 4)],
+ paymentTerms: '30 jours fin de mois'
+}));
+
+const stages = ['new', 'qualification', 'discovery', 'proposal', 'negotiation', 'won', 'lost'];
+export const mockOpportunities = Array.from({ length: 25 }, (_, i) => ({
+ id: i + 1,
+ name: `Projet ${['Digital', 'Cloud', 'Migration', 'Audit', 'Formation', 'Maintenance'][i % 6]} ${2025 + i}`,
+ client: mockClients[i % mockClients.length].company,
+ clientId: (i % mockClients.length) + 1,
+ contactId: (i % mockContacts.length) + 1,
+ amount: Math.floor(Math.random() * 50000) + 5000,
+ probability: Math.floor(Math.random() * 10) * 10,
+ stage: stages[Math.floor(Math.random() * 7)],
+ priority: ['low', 'medium', 'high'][Math.floor(Math.random() * 3)],
+ owner: mockUsers[Math.floor(Math.random() * 3)].name,
+ createdAt: new Date(Date.now() - Math.floor(Math.random() * 8000000000)).toISOString().split('T')[0],
+ closeDate: new Date(Date.now() + Math.floor(Math.random() * 8000000000)).toISOString().split('T')[0],
+ nextAction: 'Relance client',
+ description: 'Projet stratégique pour le développement de la nouvelle infrastructure cloud du client.',
+ quotes: []
+}));
+
+export const mockQuotes = Array.from({ length: 15 }, (_, i) => ({
+ id: i + 1,
+ number: `DEV-2025-${(i + 1).toString().padStart(3, '0')}`,
+ client: mockClients[i % mockClients.length].company,
+ clientId: (i % mockClients.length) + 1,
+ opportunityId: (i % mockOpportunities.length) + 1,
+ date: new Date(Date.now() - Math.floor(Math.random() * 5000000000)).toISOString().split('T')[0],
+ amountHT: Math.floor(Math.random() * 20000) + 1000,
+ status: ['draft', 'sent', 'accepted', 'refused', 'expired'][Math.floor(Math.random() * 5)],
+ owner: mockUsers[Math.floor(Math.random() * 3)].name,
+ items: [
+ { description: "Consulting Senior", quantity: 2, unitPrice: 1200, vat: 20 },
+ { description: "Licence Logiciel", quantity: 1, unitPrice: 5000, vat: 20 }
+ ]
+})).map(q => ({ ...q, vat: q.amountHT * 0.2, amountTTC: q.amountHT * 1.2 }));
+
+export const mockOrders = mockQuotes.filter(q => q.status === 'accepted').map((q, i) => ({
+ id: i + 1,
+ number: `CMD-2025-${(i + 1).toString().padStart(3, '0')}`,
+ sourceQuote: q.number,
+ client: q.client,
+ clientId: q.clientId,
+ date: new Date(new Date(q.date).getTime() + 86400000 * 2).toISOString().split('T')[0],
+ deliveryDate: new Date(new Date(q.date).getTime() + 86400000 * 15).toISOString().split('T')[0],
+ amountHT: q.amountHT,
+ amountTTC: q.amountTTC,
+ status: ['pending', 'validated', 'shipped', 'delivered'][Math.floor(Math.random() * 4)],
+ items: q.items
+}));
+
+// ENRICHED INVOICES MOCK DATA
+const invoiceStatuses = ['draft', 'sent', 'paid', 'partial', 'overdue', 'cancelled'];
+
+export const mockInvoices = Array.from({ length: 25 }, (_, i) => {
+ const client = mockClients[i % mockClients.length];
+ const status = invoiceStatuses[Math.floor(Math.random() * invoiceStatuses.length)];
+ const amountHT = Math.floor(Math.random() * 15000) + 500;
+ const vat = amountHT * 0.2;
+ const amountTTC = amountHT + vat;
+ const date = new Date(Date.now() - Math.floor(Math.random() * 4000000000)).toISOString().split('T')[0];
+ const dueDate = new Date(new Date(date).getTime() + 30 * 24 * 60 * 60 * 1000).toISOString().split('T')[0];
+
+ let balanceDue = amountTTC;
+ let payments = [];
+
+ if (status === 'paid') {
+ balanceDue = 0;
+ payments.push({ id: 1, date: dueDate, amount: amountTTC, method: 'Virement', reference: 'VIR-12345' });
+ } else if (status === 'partial') {
+ const partialAmount = Math.floor(amountTTC * 0.4);
+ balanceDue = amountTTC - partialAmount;
+ payments.push({ id: 1, date: new Date(date).toISOString().split('T')[0], amount: partialAmount, method: 'Virement', reference: 'ACCOMPTE' });
+ }
+
+ return {
+ id: i + 1,
+ number: `FAC-2025-${(i + 100).toString().padStart(3, '0')}`,
+ client: client.company,
+ clientId: client.id,
+ clientAvatar: client.name.charAt(0),
+ sourceDocument: i % 3 === 0 ? `CMD-2025-00${(i % 5) + 1}` : null,
+ date: date,
+ dueDate: dueDate,
+ amountHT: amountHT,
+ vat: vat,
+ amountTTC: amountTTC,
+ balanceDue: balanceDue,
+ status: status,
+ items: [
+ { id: 1, description: "Prestation de service informatique", quantity: 1, unitPrice: amountHT * 0.6, vat: 20, total: amountHT * 0.6 },
+ { id: 2, description: "Matériel divers", quantity: 2, unitPrice: amountHT * 0.2, vat: 20, total: amountHT * 0.4 }
+ ],
+ payments: payments,
+ timeline: [
+ { date: date, title: 'Facture créée', type: 'creation', user: 'Jean Dupont' },
+ ...(status !== 'draft' ? [{ date: date, title: 'Facture envoyée', type: 'email', user: 'Jean Dupont' }] : []),
+ ...(status === 'paid' ? [{ date: dueDate, title: 'Paiement reçu', type: 'payment', user: 'Système' }] : [])
+ ]
+ };
+});
+
+export const mockPurchaseOrders = Array.from({ length: 12 }, (_, i) => ({
+ id: i + 1,
+ number: `BC-2025-${(i + 100).toString()}`,
+ supplier: mockSuppliers[i % mockSuppliers.length].name,
+ supplierId: (i % mockSuppliers.length) + 1,
+ date: new Date(Date.now() - Math.floor(Math.random() * 5000000000)).toISOString().split('T')[0],
+ deliveryDate: new Date(Date.now() + Math.floor(Math.random() * 2000000000)).toISOString().split('T')[0],
+ amountHT: Math.floor(Math.random() * 10000) + 500,
+ status: ['draft', 'sent', 'received', 'invoiced'][Math.floor(Math.random() * 4)],
+ items: [
+ { description: 'MacBook Pro 16"', quantity: 2, unitPrice: 2400 },
+ { description: 'Dell Monitor 27"', quantity: 5, unitPrice: 350 }
+ ]
+})).map(o => ({ ...o, amountTTC: o.amountHT * 1.2 }));
+
+export const mockReceptionNotes = mockPurchaseOrders.filter(p => p.status === 'received' || p.status === 'invoiced').map((p, i) => ({
+ id: i + 1,
+ number: `BR-2025-${(i + 100).toString()}`,
+ purchaseOrder: p.number,
+ supplier: p.supplier,
+ date: new Date(new Date(p.date).getTime() + 86400000 * 5).toISOString().split('T')[0],
+ status: 'validated',
+ items: p.items
+}));
+
+export const mockPurchaseInvoices = mockReceptionNotes.slice(0, 8).map((br, i) => ({
+ id: i + 1,
+ number: `FA-2025-${(i + 500).toString()}`,
+ supplier: br.supplier,
+ receptionNote: br.number,
+ date: new Date(new Date(br.date).getTime() + 86400000 * 2).toISOString().split('T')[0],
+ dueDate: new Date(new Date(br.date).getTime() + 86400000 * 32).toISOString().split('T')[0],
+ amountTTC: Math.floor(Math.random() * 10000) + 500,
+ status: ['pending', 'paid', 'overdue'][Math.floor(Math.random() * 3)]
+}));
+
+export const mockTickets = Array.from({ length: 15 }, (_, i) => ({
+ id: i + 1,
+ number: `TKT-${(1000 + i)}`,
+ client: mockClients[i % mockClients.length].company,
+ clientId: (i % mockClients.length) + 1,
+ subject: ['Erreur connexion', 'Demande évolution', 'Question facture', 'Bug critique', 'Formation', 'Accès perdu'][i % 6],
+ priority: ['low', 'normal', 'high', 'critical'][Math.floor(Math.random() * 4)],
+ status: ['open', 'in-progress', 'pending', 'resolved', 'closed'][Math.floor(Math.random() * 5)],
+ assignedTo: mockUsers[3 + Math.floor(Math.random() * 3)].name,
+ openDate: new Date(Date.now() - Math.floor(Math.random() * 1000000000)).toISOString().split('T')[0],
+ category: ['Technique', 'Commercial', 'Administratif'][Math.floor(Math.random() * 3)],
+ description: "Le client rencontre des difficultés pour accéder à la plateforme depuis la mise à jour de lundi.",
+ comments: [
+ { user: 'Jean Dupont', date: '2025-12-01', text: 'J\'ai pris en compte la demande.' },
+ { user: 'Sophie Martin', date: '2025-12-02', text: 'Le problème semble venir du cache navigateur.' }
+ ]
+}));
+
+export const mockTimeline = Array.from({ length: 20 }, (_, i) => ({
+ id: i,
+ type: ['email', 'call', 'meeting', 'quote', 'task'][Math.floor(Math.random() * 5)],
+ title: ['Email envoyé', 'Appel client', 'Réunion projet', 'Devis créé', 'Tâche terminée'][Math.floor(Math.random() * 5)],
+ description: 'Détails de l\'activité simulée pour la démonstration.',
+ date: new Date(Date.now() - Math.floor(Math.random() * 1000000000)).toISOString().split('T')[0],
+ user: mockUsers[Math.floor(Math.random() * mockUsers.length)].name
+}));
+
+export const mockStats = {
+ revenue: [
+ { name: 'Jan', value: 145000 }, { name: 'Fév', value: 168000 }, { name: 'Mar', value: 156000 },
+ { name: 'Avr', value: 178000 }, { name: 'Mai', value: 192000 }, { name: 'Juin', value: 215000 },
+ { name: 'Juil', value: 198000 }, { name: 'Août', value: 185000 }, { name: 'Sep', value: 225000 },
+ { name: 'Oct', value: 238000 }, { name: 'Nov', value: 245000 }, { name: 'Déc', value: 260000 }
+ ],
+ expenses: [
+ { name: 'Jan', value: 110000 }, { name: 'Fév', value: 115000 }, { name: 'Mar', value: 112000 },
+ { name: 'Avr', value: 125000 }, { name: 'Mai', value: 128000 }, { name: 'Juin', value: 135000 },
+ { name: 'Juil', value: 130000 }, { name: 'Août', value: 128000 }, { name: 'Sep', value: 140000 },
+ { name: 'Oct', value: 145000 }, { name: 'Nov', value: 150000 }, { name: 'Déc', value: 155000 }
+ ],
+ ticketPriority: [
+ { name: 'Basse', value: 25, fill: '#3b82f6' },
+ { name: 'Normale', value: 45, fill: '#9ca3af' },
+ { name: 'Haute', value: 20, fill: '#f97316' },
+ { name: 'Critique', value: 10, fill: '#ef4444' }
+ ],
+ ticketStatus: [
+ { name: 'Ouvert', value: 15, fill: '#ef4444' },
+ { name: 'En cours', value: 25, fill: '#f97316' },
+ { name: 'Attente', value: 10, fill: '#eab308' },
+ { name: 'Résolu', value: 40, fill: '#22c55e' },
+ { name: 'Fermé', value: 10, fill: '#9ca3af' }
+ ],
+ quoteStatus: [
+ { name: 'Brouillon', value: 15, fill: '#9ca3af' },
+ { name: 'Envoyé', value: 30, fill: '#3b82f6' },
+ { name: 'Accepté', value: 45, fill: '#22c55e' },
+ { name: 'Refusé', value: 10, fill: '#ef4444' }
+ ],
+ salesByRep: [
+ { name: 'Jean', value: 450000 },
+ { name: 'Marie', value: 380000 },
+ { name: 'Thomas', value: 320000 }
+ ]
+};
+
+// --- KPI HELPERS ---
+
+export const filterDataByPeriod = (data, dateField, period) => {
+ const now = new Date();
+ const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
+
+ // Handle Custom Range Object
+ if (typeof period === 'object' && period?.id === 'custom') {
+ if (!period.start || !period.end) return data;
+ const startDate = new Date(period.start);
+ const endDate = new Date(period.end);
+ // Set end date to end of day for inclusive comparison
+ endDate.setHours(23, 59, 59, 999);
+
+ return data.filter(item => {
+ const itemDate = new Date(item[dateField]);
+ if (isNaN(itemDate)) return false;
+ return itemDate >= startDate && itemDate <= endDate;
+ });
+ }
+
+ // Handle string identifiers
+ return data.filter(item => {
+ const itemDate = new Date(item[dateField]);
+ if (isNaN(itemDate)) return false;
+
+ switch (period) {
+ case 'today':
+ return itemDate >= today;
+ case 'week':
+ const startOfWeek = new Date(today);
+ startOfWeek.setDate(today.getDate() - today.getDay()); // Sunday
+ return itemDate >= startOfWeek;
+ case 'month':
+ return itemDate.getMonth() === today.getMonth() && itemDate.getFullYear() === today.getFullYear();
+ case 'quarter':
+ const currentQuarter = Math.floor(today.getMonth() / 3);
+ const itemQuarter = Math.floor(itemDate.getMonth() / 3);
+ return itemQuarter === currentQuarter && itemDate.getFullYear() === today.getFullYear();
+ case 'year':
+ return itemDate.getFullYear() === today.getFullYear();
+ case '30days':
+ const thirtyDaysAgo = new Date(today);
+ thirtyDaysAgo.setDate(today.getDate() - 30);
+ return itemDate >= thirtyDaysAgo;
+ default: // 'all'
+ return true;
+ }
+ });
+};
+
+export const calculateKPIs = (data, period, config) => {
+ const filtered = filterDataByPeriod(data, config.dateField || 'date', period);
+
+ // Determine trend seed
+ const seed = typeof period === 'object' ? (period.start?.length || 0) : period.length;
+
+ return {
+ filteredCount: filtered.length,
+ totalAmount: filtered.reduce((acc, item) => acc + (item[config.amountField] || 0), 0),
+ trend: seed % 2 === 0 ? 'up' : 'down',
+ items: filtered
+ };
+};
diff --git a/src/index.css b/src/index.css
new file mode 100644
index 0000000..1486ab5
--- /dev/null
+++ b/src/index.css
@@ -0,0 +1,110 @@
+@tailwind base;
+@tailwind components;
+@tailwind utilities;
+
+@layer base {
+ :root {
+ --background: 0 0% 100%;
+ --foreground: 240 10% 3.9%;
+ --card: 0 0% 100%;
+ --card-foreground: 240 10% 3.9%;
+ --popover: 0 0% 100%;
+ --popover-foreground: 240 10% 3.9%;
+ --primary: 346 87% 30%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 240 4.8% 95.9%;
+ --secondary-foreground: 240 5.9% 10%;
+ --muted: 240 4.8% 95.9%;
+ --muted-foreground: 240 3.8% 46.1%;
+ --accent: 240 4.8% 95.9%;
+ --accent-foreground: 240 5.9% 10%;
+ --destructive: 0 84.2% 60.2%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 240 5.9% 90%;
+ --input: 240 5.9% 90%;
+ --ring: 346 87% 30%;
+ --radius: 0.5rem;
+ --primary-color: #000000;
+ --primary-color-dark: #007E45;
+ /* --primary-color: #C94635;
+ --primary-color-dark: #941403; */
+ --error-color: #C94635;
+ --background-color: #ffffff;
+ --text-color: #6f6f6f;
+ }
+
+ .dark {
+ --primary-color: #941403;
+ --background-color: #1a1a1a;
+ --text-color: #ffffff;
+ --background: 240 10% 3.9%;
+ --foreground: 0 0% 98%;
+ --card: 240 10% 3.9%;
+ --card-foreground: 0 0% 98%;
+ --popover: 240 10% 3.9%;
+ --popover-foreground: 0 0% 98%;
+ --primary: 346 87% 30%;
+ --primary-foreground: 0 0% 98%;
+ --secondary: 240 3.7% 15.9%;
+ --secondary-foreground: 0 0% 98%;
+ --muted: 240 3.7% 15.9%;
+ --muted-foreground: 240 5% 64.9%;
+ --accent: 240 3.7% 15.9%;
+ --accent-foreground: 0 0% 98%;
+ --destructive: 0 62.8% 30.6%;
+ --destructive-foreground: 0 0% 98%;
+ --border: 240 3.7% 15.9%;
+ --input: 240 3.7% 15.9%;
+ --ring: 346 87% 30%;
+ }
+}
+
+@layer base {
+ * {
+ @apply border-border;
+ }
+ body {
+ @apply bg-background text-foreground;
+ font-feature-settings: "rlig" 1, "calt" 1;
+ }
+
+ /* Unified Color Utilities - All mapped to #338660 */
+ .text-\[\#941403\], .text-red-700, .text-\[\#A71810\], .text-\[\#00DB00\] {
+ @apply text-[#338660];
+ }
+
+ .bg-\[\#941403\], .bg-red-700, .bg-\[\#A71810\], .bg-\[\#00DB00\] {
+ @apply bg-[#338660];
+ }
+
+ .border-\[\#941403\], .border-red-700, .border-\[\#A71810\], .border-\[\#00DB00\] {
+ @apply border-[#338660];
+ }
+
+ .ring-\[\#941403\], .ring-\[\#A71810\], .ring-\[\#00DB00\] {
+ @apply ring-[#338660];
+ }
+}
+
+body {
+ font-family: 'Sage Text', sans-serif;
+}
+
+h1, h2, h3 {
+ font-family: 'Sage Headline', sans-serif;
+}
+
+/* Custom Scrollbar */
+.custom-scrollbar::-webkit-scrollbar {
+ width: 6px;
+}
+.custom-scrollbar::-webkit-scrollbar-track {
+ background: transparent;
+}
+.custom-scrollbar::-webkit-scrollbar-thumb {
+ background-color: rgba(156, 163, 175, 0.5);
+ border-radius: 20px;
+}
+.custom-scrollbar::-webkit-scrollbar-thumb:hover {
+ background-color: rgba(107, 114, 128, 0.8);
+}
\ No newline at end of file
diff --git a/src/lib/data.ts b/src/lib/data.ts
new file mode 100644
index 0000000..1d64d22
--- /dev/null
+++ b/src/lib/data.ts
@@ -0,0 +1,2 @@
+export const ACCESS_TOKEN = "access_token";
+export const REFRESH_TOKEN = "refresh_token";
\ No newline at end of file
diff --git a/src/lib/utils.js b/src/lib/utils.js
new file mode 100644
index 0000000..8b21cd5
--- /dev/null
+++ b/src/lib/utils.js
@@ -0,0 +1,47 @@
+import { clsx } from 'clsx';
+import { twMerge } from 'tailwind-merge';
+
+export function cn(...inputs) {
+ return twMerge(clsx(inputs));
+}
+
+export const formatDateFR = (dateString) => {
+ if (!dateString) return "";
+
+ const date = new Date(dateString);
+
+ return date.toLocaleDateString("fr-FR", {
+ day: "numeric",
+ month: "long",
+ year: "numeric",
+ });
+};
+
+
+
+export const formatCurrency = (amount) => {
+ return new Intl.NumberFormat('fr-FR', {
+ style: 'currency',
+ currency: 'EUR',
+ minimumFractionDigits: 2
+ }).format(amount);
+};
+
+
+export const formatDateFRCourt = (dateString) => {
+ if (!dateString) return "";
+
+ const date = new Date(dateString);
+
+ const year = date.getFullYear();
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+
+ return `${day}/${month}/${year}`;
+};
+
+
+export const formatForDateInput = (dateString) => {
+ if (!dateString) return ''
+ return dateString.split(' ')[0]
+}
diff --git a/src/main.jsx b/src/main.jsx
new file mode 100644
index 0000000..ed2d272
--- /dev/null
+++ b/src/main.jsx
@@ -0,0 +1,13 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import App from '@/App';
+import '@/index.css';
+import StoreProvider from './providers/StoreProvider';
+import './assets/fonts/fonts.css';
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+
+);
diff --git a/src/providers/StoreProvider.tsx b/src/providers/StoreProvider.tsx
new file mode 100644
index 0000000..34c758e
--- /dev/null
+++ b/src/providers/StoreProvider.tsx
@@ -0,0 +1,19 @@
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+/* eslint-disable import/no-unresolved */
+
+
+import { store } from '@/store/store';
+import { useRef } from 'react';
+
+import { Provider } from 'react-redux';
+
+
+export default function StoreProvider({
+ children,
+}: {
+ children: React.ReactNode;
+}) {
+
+
+ return {children};
+}
diff --git a/src/routes/DatavenRoute.jsx b/src/routes/DatavenRoute.jsx
new file mode 100644
index 0000000..0784575
--- /dev/null
+++ b/src/routes/DatavenRoute.jsx
@@ -0,0 +1,179 @@
+
+import React from 'react';
+import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
+import { Helmet } from 'react-helmet';
+import { Toaster } from '@/components/ui/toaster';
+import { ThemeProvider } from '@/contexts/ThemeContext';
+import AppLayout from '@/components/layout/AppLayout';
+
+// Dashboard
+import DashboardPage from '@/pages/DashboardPage';
+
+// CRM
+import ProspectsPage from '@/pages/crm/ProspectsPage';
+import CreateProspectPage from '@/pages/crm/CreateProspectPage';
+import ProspectDetailPage from '@/pages/crm/ProspectDetailPage';
+import ClientsPage from '@/pages/crm/ClientsPage';
+import ClientDetailPage from '@/pages/crm/ClientDetailPage';
+import SuppliersPage from '@/pages/crm/SuppliersPage';
+import OpportunitiesPipelinePage from '@/pages/crm/OpportunitiesPipelinePage';
+import OpportunityDetailPage from '@/pages/crm/OpportunityDetailPage';
+import ActivitiesPage from '@/pages/crm/ActivitiesPage';
+import TasksPage from '@/pages/crm/TasksPage';
+
+// Tiers (Articles & Families)
+import ArticlesPage from '@/pages/tiers/ArticlesPage';
+import ArticleDetailPage from '@/pages/tiers/ArticleDetailPage';
+import ProductFamiliesPage from '@/pages/tiers/ProductFamiliesPage';
+import ProductFamilyDetailPage from '@/pages/tiers/ProductFamilyDetailPage';
+
+// Sales
+import QuotesPage from '@/pages/sales/QuotesPage';
+import QuoteDetailPage from '@/pages/sales/QuoteDetailPage';
+import OrdersPage from '@/pages/sales/OrdersPage';
+import OrderDetailPage from '@/pages/sales/OrderDetailPage';
+import DeliveryNotesPage from '@/pages/sales/DeliveryNotesPage';
+import InvoicesPage from '@/pages/sales/InvoicesPage';
+import InvoiceDetailPage from '@/pages/sales/InvoiceDetailPage';
+import CreditNotesPage from '@/pages/sales/CreditNotesPage';
+
+// Purchases
+import PurchaseOrdersPage from '@/pages/purchases/PurchaseOrdersPage';
+import PurchaseOrderDetailPage from '@/pages/purchases/PurchaseOrderDetailPage';
+import ReceptionNotesPage from '@/pages/purchases/ReceptionNotesPage';
+import ReceptionNoteDetailPage from '@/pages/purchases/ReceptionNoteDetailPage';
+import PurchaseInvoicesPage from '@/pages/purchases/PurchaseInvoicesPage';
+import PurchaseInvoiceDetailPage from '@/pages/purchases/PurchaseInvoiceDetailPage';
+
+// Support
+import SupportDashboardPage from '@/pages/support/SupportDashboardPage';
+import TicketsPage from '@/pages/support/TicketsPage';
+import TicketDetailPage from '@/pages/support/TicketDetailPage';
+
+// Documents
+import DocumentsPage from '@/pages/DocumentsPage';
+
+// Admin & User
+import SettingsPage from '@/pages/admin/SettingsPage';
+import UsersPage from '@/pages/admin/UsersPage';
+import RolesPage from '@/pages/admin/RolesPage';
+import ActivityLogPage from '@/pages/admin/ActivityLogPage';
+import UserProfilePage from '@/pages/UserProfilePage';
+
+// UI Kit
+import UIKitPage from '@/pages/UIKitPage';
+import SuppliersDetailPage from '@/pages/crm/SuppliersDetailPage';
+import CreditNotesDetailPage from '@/pages/sales/CreditNotesDetailPage';
+import DeliveryNotesDetailPage from '@/pages/sales/DeliveryNotesDetailPage';
+import CreateClientPage from '@/pages/crm/CreateClientPage';
+import SignatureDashboard from '@/pages/signature/SignatureDashboard';
+import SignatureTracking from '@/pages/signature/SignatureTracking';
+import SignatureCreditPurchase from '@/pages/signature/SignatureCreditPurchase';
+import CommercialPage from '@/pages/tiers/CommercialPage';
+import QuoteCreatePage from '@/pages/sales/QuoteCreate';
+import InvoiceCreatePage from '@/pages/sales/InvoiceCreatePage';
+import SageBuilderPage from '@/pages/SageBuilderPage';
+import PaymentsPage from '@/pages/sales/PaymentsPage';
+
+const DatavenRoute = () => {
+ return (
+
+
+
+ {/* Dashboard */}
+ } />
+
+ {/* CRM */}
+ } />
+ } />
+ } />
+
+ } />
+ } />
+ } />
+ } />
+
+ } />
+ } />
+
+ } />
+
+ } />
+ {/* } /> */}
+
+ {/* Tiers - Articles & Familles */}
+ } />
+ } />
+ } />
+ } />
+
+ {/* iframe */}
+ } />
+
+ {/* Sales */}
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ } />
+ } />
+ } />
+
+ } />
+ } />
+
+ } />
+ } />
+
+ } />
+ } />
+ } />
+
+ } />
+ } />
+
+ {/* Signature Électronique */}
+ } />
+ } />
+ } />
+
+ {/* Purchases */}
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+ {/* Support */}
+ } />
+ } />
+ } />
+
+ {/* Documents */}
+ } />
+
+ {/* Admin */}
+ } />
+ } />
+ } />
+ } />
+
+ {/* User */}
+ } />
+
+ {/* Dev */}
+ } />
+
+ {/* Catch all */}
+ } />
+
+
+
+
+ );
+}
+
+export default DatavenRoute;
diff --git a/src/routes/ProtectedRoute.tsx b/src/routes/ProtectedRoute.tsx
new file mode 100644
index 0000000..ab5a3d8
--- /dev/null
+++ b/src/routes/ProtectedRoute.tsx
@@ -0,0 +1,19 @@
+import { Navigate } from 'react-router-dom'
+import Cookies from "js-cookie";
+import { ACCESS_TOKEN, REFRESH_TOKEN } from '@/lib/data';
+
+
+type Props = {
+ children: React.ReactNode
+}
+
+const ProtectedRoute: React.FC = ({ children }) => {
+
+ const access_token = Cookies.get(ACCESS_TOKEN);
+ const refresh_token = Cookies.get(REFRESH_TOKEN)
+
+ if (!access_token || ! refresh_token) return
+ return <>{children}>
+}
+
+export default ProtectedRoute
diff --git a/src/service/api.ts b/src/service/api.ts
new file mode 100644
index 0000000..3319dfe
--- /dev/null
+++ b/src/service/api.ts
@@ -0,0 +1,86 @@
+/* eslint-disable import/no-unresolved */
+
+import { ACCESS_TOKEN, REFRESH_TOKEN } from "@/lib/data";
+
+import axios from 'axios'
+import Cookies from 'js-cookie';
+
+
+export const BackUrl = import.meta.env.VITE_API_URL
+
+const access_token: string = Cookies.get(ACCESS_TOKEN) || "";
+
+const apiService = axios.create({
+ baseURL: BackUrl,
+ headers: {
+ Authorization: access_token ? `Bearer ${access_token}` : "dsqd",
+ }
+})
+
+
+// Interceptor pour capturer les erreurs et gérer le refresh token
+apiService.interceptors.response.use(
+ response => response,
+ async error => {
+ if (!error.response) {
+ throw new Error('Network Error');
+ }
+ // Si le token est expiré (401), tente de le rafraîchir
+ if (error.response.status === 401) {
+ const refreshToken = Cookies.get(REFRESH_TOKEN);
+ if (refreshToken) {
+ try {
+ const res = await axios.post(
+ `${BackUrl}/auth/refresh`,
+ { refresh_token: refreshToken }
+ );
+
+ if (res.data && res.data.success && res.data.data.access_token) {
+ Cookies.set(ACCESS_TOKEN, res.data.data.access_token);
+ error.config.headers.Authorization = `Bearer ${res.data.data.access_token}`;
+ return apiService.request(error.config);
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ } catch (refreshErr) {
+ Cookies.remove(ACCESS_TOKEN);
+ Cookies.remove(REFRESH_TOKEN);
+ }
+ }
+ }
+ return Promise.reject(error);
+ }
+);
+
+
+
+
+// Interceptor pour capturer les erreurs
+apiService.interceptors.response.use(
+ response => response,
+ error => {
+ if (!error.response) {
+ throw new Error('Network Error');
+ }
+ return Promise.reject(error);
+ }
+)
+
+apiService.interceptors.request.use(
+ request => {
+ const access_token: string = Cookies.get(ACCESS_TOKEN) || "";
+
+ // Assurer que le token est présent et valide
+ if (access_token) {
+ request.headers.Authorization = `Bearer ${access_token}`;
+ }
+ return request;
+ },
+ error => {
+ // Gérer les erreurs d'intercepteur
+ return Promise.reject(error);
+ }
+);
+
+
+export default apiService
+
diff --git a/src/service/sageService.ts b/src/service/sageService.ts
new file mode 100644
index 0000000..d053cc1
--- /dev/null
+++ b/src/service/sageService.ts
@@ -0,0 +1,509 @@
+/* eslint-disable import/no-unresolved */
+import { loginInterface, loginResponseInterface, resetPassInterface, UserInterface } from '@/types/userInterface';
+import { API_ENDPOINTS } from '@/config/apiConfig';
+import { pdfInterface, responseDefault, SageDocumentType, SignatureRequest, StatusRequest, StatusResponse, TransformationResponse, TypeDocument, UniversignType } from '@/types/sageTypes';
+import { Client, ClientCreateResponse, ClientRequest, ClientUpdateRequest, ContactRequest, Contacts } from '@/types/clientType';
+import apiService, { BackUrl } from './api';
+import { Article, ArticleRequest, ArticleUpdateRequest, StockRequest, StockResponse } from '@/types/articleType';
+import { DevisDetail, DevisListItem, DevisRequest, DevisResponse, DevisUpdateRequest } from '@/types/devisType';
+import { Commande, CommandeRequest, CommandeResponse, CommandeUpdateRequest } from '@/types/commandeTypes';
+import { Facture, FactureRequest, FactureUpdateRequest, FactureValideResponse, RelanceRequest, RelanceResponse } from '@/types/factureType';
+import { BL, BLRequest, BLUpdateRequest } from '@/types/BL_Types';
+import { Fournisseur, FournisseurRequest, FournisseurUpdateRequest } from '@/types/fournisseurType';
+import { Avoir, AvoirRequest, AvoirUpdateRequest } from '@/types/avoirType';
+import { Famille, FamilleRequest } from '@/types/familleType';
+import { Gateways, GatewaysRequest, GatewaysResponse } from '@/types/gateways';
+import { Commercial, CommercialRequest } from '@/types/commercialType';
+import { Societe } from '@/types/societeType';
+import { EntrepriseResponse } from '@/types/entreprise';
+import { ComptesResponse, DeviseResponse, EncaissementResponse, ModeResponse, ReglementPostResponse, ReglementRequest, ReglementsIdResponse, reglementsResponse, TauxResponse, TresorerieResponse } from '@/types/reglementType';
+
+
+
+export const sageService = {
+
+ // ========== Authentification ==========
+ async authMe(): Promise {
+ return (await apiService.get(`${API_ENDPOINTS.auth}`)).data
+ },
+
+ async login(data: loginInterface): Promise {
+ const rep = await apiService.post(`${API_ENDPOINTS.login}`, data)
+ return rep.data
+ },
+
+
+
+ async forgotPass(email: string): Promise {
+ return (await apiService.post(`${API_ENDPOINTS.forgotPassword}`, { email: email })).data
+ },
+
+ async resetPass(data: resetPassInterface): Promise {
+ return (await apiService.post(`${API_ENDPOINTS.resetPassword}`, data)).data
+ },
+
+ // ========== contacts ==========
+ async contacts(numero: string): Promise {
+ return (await apiService.get(`${API_ENDPOINTS.contactsClient(numero)}`)).data
+ },
+
+ async addContact(data: ContactRequest): Promise{
+ return (await apiService.post(API_ENDPOINTS.contactsClient(data.numero!), data)).data
+ },
+
+ async updateContact(contact_numero: number,data: ContactRequest): Promise {
+ return (await apiService.put(API_ENDPOINTS.contactId(data.numero!, contact_numero), data)).data;
+ },
+
+
+ // ========== contacts ==========
+ async searchEntreprise(query: string): Promise {
+ return (await apiService.get(`${API_ENDPOINTS.searchEntreprise(query)}`)).data
+ },
+
+
+
+
+ // ========== contacts ==========
+ async commercials(): Promise {
+ return (await apiService.get(`${API_ENDPOINTS.commercials}`)).data
+ },
+
+ async commercialsById(numero: string): Promise {
+ return (await apiService.get(`${API_ENDPOINTS.commercialsById(numero)}`)).data
+ },
+
+ async addCommercial(data: CommercialRequest): Promise {
+ return (await apiService.post(API_ENDPOINTS.commercials, data)).data
+ },
+
+ // async updateCommercial(contact_numero: number,data: ContactRequest): Promise {
+ // return (await apiService.put(API_ENDPOINTS.contactId(data.numero!, contact_numero), data)).data;
+ // },
+
+
+
+
+ // ========== clients ==========
+
+ async getClients(query?: string): Promise {
+ const params = query ? `?query=${encodeURIComponent(query)}` : ''
+ return (await apiService.get(`${API_ENDPOINTS.clients}${params}`)).data
+ },
+
+ async getClient(id: string): Promise {
+ return (await apiService.get(API_ENDPOINTS.clientById(id))).data
+ },
+
+ async addClient(data: ClientRequest): Promise {
+ return (await apiService.post(API_ENDPOINTS.clients, data)).data
+ },
+
+ async updateClient(numero: string, data: ClientUpdateRequest): Promise {
+ return (await apiService.put(API_ENDPOINTS.clientById(numero), data)).data
+ },
+
+ async updateStatusClient(numero: string, est_actif: boolean): Promise {
+ const data = {
+ est_actif
+ }
+ return (await apiService.put(API_ENDPOINTS.clientById(numero), data)).data
+ },
+
+
+
+
+ // ========== societe ==========
+ async getSociete(): Promise {
+ return (await apiService.get(`${API_ENDPOINTS.societe}`)).data
+ },
+
+
+ // ========== gateways ==========
+
+ async getGateways(): Promise {
+ return (await apiService.get(`${API_ENDPOINTS.gateways}`)).data
+ },
+
+ async getGatewaysById(id: string): Promise {
+ return (await apiService.get(API_ENDPOINTS.gatewaysById(id))).data
+ },
+
+ async addGateways(data: GatewaysRequest): Promise {
+ return (await apiService.post(API_ENDPOINTS.gateways, data)).data
+ },
+
+
+
+ // ========== Fournisseurs ==========
+
+ async getFournisseurs(query?: string): Promise {
+ const params = query ? `?query=${encodeURIComponent(query)}` : ''
+ return (await apiService.get(`${API_ENDPOINTS.fournisseurs}${params}`)).data
+ },
+
+ async addFournisseur(data: FournisseurRequest): Promise {
+ return (await apiService.post(API_ENDPOINTS.fournisseurs, data)).data
+ },
+
+ async updateFournisseur(numero: string, data: FournisseurUpdateRequest): Promise {
+ return (await apiService.put(API_ENDPOINTS.fournisseursById(numero), data)).data
+ },
+
+
+
+ // ========== article ==========
+ async getArticles(query?: string): Promise {
+ const params = query ? `?query=${encodeURIComponent(query)}` : ''
+ return (await apiService.get(`${API_ENDPOINTS.articles}${params}`)).data
+ },
+
+ async getArticleId(id: string): Promise {
+ return (await apiService.get(API_ENDPOINTS.articleById(id))).data
+ },
+
+ async createArticle(data: ArticleRequest): Promise {
+ const rep = await apiService.post(API_ENDPOINTS.articles, data)
+ return rep.data
+ },
+
+ async updateArticle(reference: string, data: ArticleUpdateRequest) {
+ return (await apiService.put(API_ENDPOINTS.articleById(reference), data)).data
+ },
+
+
+
+ // ========== add stock ==========
+ async addStock(data: StockRequest): Promise {
+ const rep = await apiService.post(API_ENDPOINTS.addStock, data)
+ return rep.data
+ },
+
+
+
+ // ========== famille-article ==========
+
+ async getFamilles(query?: string): Promise {
+ const params = query ? `?query=${encodeURIComponent(query)}` : ''
+ return (await apiService.get(`${API_ENDPOINTS.familles}${params}`)).data
+ },
+
+ async createFamille(data: FamilleRequest): Promise {
+ return (await apiService.post(API_ENDPOINTS.familles, data)).data
+ },
+
+
+
+
+ // ========== devis ==========
+ async getDevis(id: string): Promise {
+ return (await apiService.get(API_ENDPOINTS.devisById(id))).data
+ },
+
+ async createDevis(data: DevisRequest): Promise {
+ const rep = await apiService.post(API_ENDPOINTS.devis, data)
+ return rep.data
+ },
+
+ async updateDevis(numero: string, data: DevisUpdateRequest) {
+ return (await apiService.put(API_ENDPOINTS.devisById(numero), data)).data
+ },
+
+ async getDevisList(statut?: number, limit?: number): Promise {
+ let params = ''
+
+ if (statut !== undefined || limit) {
+ const queryParams = new URLSearchParams()
+
+ if (statut !== undefined) queryParams.append('statut', statut.toString())
+ if (limit) queryParams.append('limit', limit.toString())
+ params = `?${queryParams.toString()}`
+ }
+
+ return (await apiService.get(`${API_ENDPOINTS.devis}${params}`)).data
+ },
+
+ async changerStatut(type_doc: SageDocumentType, data: StatusRequest): Promise {
+ return (await apiService.put(`/document/${type_doc}/${data.numero}/statut?nouveau_statut=${data.status}`)).data
+ },
+
+
+
+
+ async devisToPDF(id: string) {
+ const response = await fetch(`${BackUrl}/devis/${id}/pdf`, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/pdf',
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error('Erreur lors du téléchargement');
+ }
+
+ // ✅ Récupérer directement en tant que Blob
+ const blob = await response.blob();
+ return blob;
+
+ },
+
+
+ // ========== commandes ==========
+ async getCommandes(statut?: number, limit?: number): Promise {
+ let params = ''
+
+ if (statut !== undefined || limit) {
+ const queryParams = new URLSearchParams()
+
+ if (statut !== undefined) queryParams.append('statut', statut.toString())
+ if (limit) queryParams.append('limit', limit.toString())
+ params = `?${queryParams.toString()}`
+ }
+
+ return (await apiService.get(`/commandes${params}`)).data
+ },
+
+ async getCommande(id: string): Promise {
+ return (await apiService.get(`/commandes/${id}`)).data
+ },
+
+ async createCommande(data: CommandeRequest): Promise {
+ const rep = await apiService.post(API_ENDPOINTS.commandes, data)
+ return rep.data
+ },
+
+ async updateCommande(numero: string, data: CommandeUpdateRequest) {
+ return (await apiService.put(API_ENDPOINTS.commandeById(numero), data)).data
+ },
+
+
+
+
+ // ========== bon de livraison ==========
+ async getAllBL(statut?: number, limit?: number): Promise {
+ let params = ''
+
+ if (statut !== undefined || limit) {
+ const queryParams = new URLSearchParams()
+
+ if (statut !== undefined) queryParams.append('statut', statut.toString())
+ if (limit) queryParams.append('limit', limit.toString())
+ params = `?${queryParams.toString()}`
+ }
+ const rep = await apiService.get(`${API_ENDPOINTS.bl}${params}`)
+ return rep.data
+ },
+
+ async getBL(id: string): Promise {
+ return (await apiService.get(`${API_ENDPOINTS.blById(id)}`)).data
+ },
+
+ async createBL(data: BLRequest) {
+ const rep = await apiService.post(API_ENDPOINTS.bl, data)
+ return rep.data
+ },
+
+ async updateBL(numero: string, data: BLUpdateRequest) {
+ return (await apiService.put(API_ENDPOINTS.blById(numero), data)).data
+ },
+
+
+
+
+
+ // ========== Facture==========
+
+ async getFactures(statut?: number, limit?: number): Promise {
+ let params = ''
+
+ if (statut !== undefined || limit) {
+ const queryParams = new URLSearchParams()
+
+ if (statut !== undefined) queryParams.append('statut', statut.toString())
+ if (limit) queryParams.append('limit', limit.toString())
+ params = `?${queryParams.toString()}`
+ }
+
+ return (await apiService.get(`${API_ENDPOINTS.factures}${params}`)).data
+ },
+
+ async getFacture(id: string): Promise {
+ return (await apiService.get(`${API_ENDPOINTS.factures}/${id}`)).data
+ },
+
+ async relancerFacture(data: RelanceRequest): Promise {
+ return apiService.post(`${API_ENDPOINTS.factureRelancer(data.factureId)}`, {
+ doc_id: data.factureId,
+ message_personnalise: data.messagePersonnalise
+ })
+ },
+
+ async createFacture(data: FactureRequest) {
+ const rep = await apiService.post(API_ENDPOINTS.factures, data)
+ return rep.data
+ },
+
+ async updateFacture(numero: string, data: FactureUpdateRequest) {
+ return (await apiService.put(API_ENDPOINTS.factureById(numero), data)).data
+ },
+
+ async validerFacture(id: string): Promise {
+ return (await apiService.post(API_ENDPOINTS.factureValider(id))).data
+ },
+
+
+
+ // ========== Avoirs ==========
+
+ async getAvoirs(statut?: number, limit?: number): Promise {
+ let params = ''
+
+ if (statut !== undefined || limit) {
+ const queryParams = new URLSearchParams()
+
+ if (statut !== undefined) queryParams.append('statut', statut.toString())
+ if (limit) queryParams.append('limit', limit.toString())
+ params = `?${queryParams.toString()}`
+ }
+
+ return (await apiService.get(`${API_ENDPOINTS.avoirs}${params}`)).data
+ },
+
+ async getAvoir(id: string): Promise {
+ return (await apiService.get(`${API_ENDPOINTS.avoirsById(id)}`)).data
+ },
+
+ async createAvoir(data: AvoirRequest) {
+ const rep = await apiService.post(API_ENDPOINTS.avoirs, data)
+ return rep.data
+ },
+
+ async updateAvoir(numero: string, data: AvoirUpdateRequest) {
+ return (await apiService.put(API_ENDPOINTS.avoirsById(numero), data)).data
+ },
+
+
+
+ // ========== workflow ==========
+ async devisToCommande(id: string): Promise {
+ return (await apiService.post(API_ENDPOINTS.devisToCommande(id))).data
+ },
+
+ async devisToFacture(id: string): Promise {
+ return (await apiService.post(API_ENDPOINTS.devisToFacture(id))).data
+ },
+
+ async commandeToBL(id: string): Promise {
+ return (await apiService.post(API_ENDPOINTS.commandeToBL(id))).data
+ },
+
+ async commandeToFacture(id: string): Promise {
+ return (await apiService.post(API_ENDPOINTS.commandeToFacture(id))).data
+ },
+
+ async blToFacture(id: string): Promise {
+ return (await apiService.post(API_ENDPOINTS.blToFacture(id))).data
+ },
+
+
+
+ // ========== trans PDF ==========
+ async transPDF(data: pdfInterface) {
+ const response = await fetch(`${BackUrl}/documents/${data.type_doc}/${data.numero}/pdf`, {
+ method: 'GET',
+ headers: {
+ 'Accept': 'application/pdf',
+ },
+ });
+
+ if (!response.ok) {
+ throw new Error('Erreur lors du téléchargement');
+ }
+
+ // ✅ Récupérer directement en tant que Blob
+ const blob = await response.blob();
+ return blob;
+
+ },
+
+
+ async envoyerDevisSignature(id: string, emailSignataire: string, nomSignataire: string): Promise {
+ const request: SignatureRequest = {
+ sage_document_id: id,
+ sage_document_type: TypeDocument.DEVIS,
+ signer_email: emailSignataire,
+ signer_name: nomSignataire,
+ document_name: "test"
+ }
+
+ // return apiService.post(`/signature/universign/send`, request)
+ return (await apiService.post(`/universign/signatures/create`, request)).data
+ },
+
+
+ async universign(): Promise {
+ return (await apiService.get(`/universign/transactions`)).data
+ },
+
+ async universignId(transaction_id: string): Promise {
+ return (await apiService.get(`/universign/transactions/${transaction_id}`)).data
+ },
+
+ async downloadSignedDocument(transactionId: string): Promise {
+ const response = await apiService.get(
+ API_ENDPOINTS.downloadSignedDocument(transactionId),
+ {
+ responseType: 'blob',
+ headers: {
+ 'Accept': 'application/pdf',
+ },
+ }
+ );
+ return response.data;
+ },
+
+
+
+
+ // ========== reglement ==========
+ async getReglement(): Promise {
+ return (await apiService.get(API_ENDPOINTS.reglement)).data
+ },
+
+ async getReglementId(id: string): Promise {
+ return (await apiService.get(API_ENDPOINTS.reglementById(id))).data
+ },
+
+
+ async reglerFacture(data: ReglementRequest): Promise {
+ const rep = await apiService.post(API_ENDPOINTS.reglerFacture, data)
+ return rep.data
+ },
+
+
+ async getMode(): Promise {
+ return (await apiService.get(API_ENDPOINTS.mode)).data
+ },
+
+ async getDevises(): Promise {
+ return (await apiService.get(API_ENDPOINTS.devises)).data
+ },
+
+ async getTresorerie(): Promise {
+ return (await apiService.get(API_ENDPOINTS.tresorerie)).data
+ },
+
+ async getCompteGeneraux(): Promise {
+ return (await apiService.get(API_ENDPOINTS.compte_generaux)).data
+ },
+
+ async getTaux(): Promise {
+ return (await apiService.get(API_ENDPOINTS.taux)).data
+ },
+
+ async getEncaissement(): Promise {
+ return (await apiService.get(API_ENDPOINTS.encaissement)).data
+ },
+
+}
diff --git a/src/service/signature/universignService.js b/src/service/signature/universignService.js
new file mode 100644
index 0000000..03843f8
--- /dev/null
+++ b/src/service/signature/universignService.js
@@ -0,0 +1,122 @@
+import { mockQuotes, mockOrders, mockInvoices } from '@/data/mockData';
+
+// Mock data store for signatures
+let mockSignatures = [
+ ...mockQuotes.filter(q => q.status !== 'Brouillon').map(q => ({
+ id: `SIG-Q-${q.id}`,
+ documentId: q.id,
+ documentType: 'quote',
+ documentNumber: q.number,
+ clientName: q.client,
+ signerEmail: q.signerEmail || 'client@email.com',
+ status: q.signatureStatus === 'signed' ? 'signed' : q.signatureStatus === 'viewed' ? 'viewed' : 'sent',
+ sentAt: q.date,
+ viewedAt: q.signatureStatus !== 'pending' ? new Date(new Date(q.date).getTime() + 3600000).toISOString() : null,
+ signedAt: q.signedDate,
+ creditsConsumed: q.signatureStatus === 'signed',
+ remindersSent: 0
+ })),
+ // Add some dummy orders/invoices for variety
+ {
+ id: 'SIG-O-1',
+ documentId: 1,
+ documentType: 'order',
+ documentNumber: 'CM000126',
+ clientName: 'TechStart SAS',
+ signerEmail: 'jean@techstart.com',
+ status: 'pending',
+ sentAt: new Date(Date.now() - 86400000 * 2).toISOString(),
+ creditsConsumed: false,
+ remindersSent: 1
+ }
+];
+
+let creditBalance = 45;
+let totalPurchased = 100;
+
+export const universignService = {
+ // Get dashboard statistics
+ getStats: async () => {
+ // Simulate API delay
+ await new Promise(resolve => setTimeout(resolve, 800));
+
+ const total = mockSignatures.length;
+ const signed = mockSignatures.filter(s => s.status === 'signed').length;
+ const pending = mockSignatures.filter(s => ['sent', 'viewed', 'pending'].includes(s.status)).length;
+ const refused = mockSignatures.filter(s => ['refused', 'expired'].includes(s.status)).length;
+ const remindable = mockSignatures.filter(s => ['sent', 'viewed'].includes(s.status)).length;
+
+ return {
+ credits: {
+ total: totalPurchased,
+ consumed: totalPurchased - creditBalance,
+ remaining: creditBalance,
+ projectionDays: Math.floor(creditBalance / 2) // Assuming 2 signatures per day
+ },
+ signatures: {
+ total,
+ signed,
+ pending,
+ refused,
+ remindable,
+ conversionRate: total > 0 ? Math.round((signed / total) * 100) : 0
+ }
+ };
+ },
+
+ // Get consumption history for chart
+ getConsumptionHistory: async (period = '30days') => {
+ await new Promise(resolve => setTimeout(resolve, 600));
+
+ // Generate mock history data
+ const days = 30;
+ const history = [];
+ let cumulative = 0;
+
+ for (let i = days; i >= 0; i--) {
+ const date = new Date();
+ date.setDate(date.getDate() - i);
+ const daily = Math.floor(Math.random() * 5);
+ cumulative += daily;
+
+ history.push({
+ date: date.toISOString().split('T')[0],
+ daily,
+ cumulative
+ });
+ }
+
+ return history;
+ },
+
+ // List all signatures
+ getSignatures: async (filters = {}) => {
+ await new Promise(resolve => setTimeout(resolve, 600));
+ return [...mockSignatures].sort((a, b) => new Date(b.sentAt) - new Date(a.sentAt));
+ },
+
+ // Purchase credits
+ purchaseCredits: async (amount) => {
+ await new Promise(resolve => setTimeout(resolve, 1500));
+ creditBalance += amount;
+ totalPurchased += amount;
+ return { success: true, newBalance: creditBalance };
+ },
+
+ // Send reminder
+ sendReminder: async (signatureId) => {
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ const sig = mockSignatures.find(s => s.id === signatureId);
+ if (sig) {
+ sig.remindersSent += 1;
+ return { success: true, remindersSent: sig.remindersSent };
+ }
+ return { success: false };
+ },
+
+ // Update settings
+ updateSettings: async (settings) => {
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ return { success: true };
+ }
+};
\ No newline at end of file
diff --git a/src/store/features/article/selectors.ts b/src/store/features/article/selectors.ts
new file mode 100644
index 0000000..6875169
--- /dev/null
+++ b/src/store/features/article/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAllArticles = (state: RootState) => state.article.articles;
+export const getArticleSelected = (state: RootState) => state.article.articleSelected;
+
+
+export const articleStatus = (state: RootState) => state.article.status;
+export const articleError = (state: RootState) => state.article.error;
diff --git a/src/store/features/article/slice.ts b/src/store/features/article/slice.ts
new file mode 100644
index 0000000..65519eb
--- /dev/null
+++ b/src/store/features/article/slice.ts
@@ -0,0 +1,162 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { articleState } from "./type";
+import { addStock, createArticle, getarticleById, getArticles, updateArticle } from "./thunk";
+import { Article } from "@/types/articleType";
+
+const initialState: articleState = {
+ status: "idle",
+ error: null,
+ articles: [],
+ articleSelected: null,
+};
+
+const articleSlice = createSlice({
+ name: "article",
+ initialState,
+ reducers: {
+ selectArticle: (state, action: PayloadAction) => {
+ state.articleSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all articles
+ */
+ builder.addCase(getArticles.fulfilled, (state, action) => {
+
+ const data = action.payload;
+
+ state.articles = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getArticles.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getArticles.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+ /**
+ * get article by id
+ */
+ builder.addCase(getarticleById.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getarticleById.fulfilled, (state, action) => {
+ const data = action.payload as Article;
+
+ const idx = state.articles.findIndex((n) => n.reference === data.reference);
+
+ if (idx !== -1) {
+ state.articles[idx] = data;
+ } else {
+ state.articles.push(data);
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(getarticleById.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * add article
+ */
+ builder.addCase(createArticle.fulfilled, (state, action) => {
+
+ const data = action.payload as any
+
+ state.articles.unshift(data);
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(createArticle.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(createArticle.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * update article
+ */
+ builder.addCase(updateArticle.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateArticle.fulfilled, (state, action) => {
+ const data = action.payload as Article
+ const idx = state.articles.findIndex((p) => p.reference === data.reference);
+ if (idx !== -1){
+ state.articles[idx] = data
+ }
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateArticle.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "Failed to update";
+ });
+
+
+
+ /**
+ * add stock
+ */
+ builder.addCase(addStock.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(addStock.fulfilled, (state, action) => {
+ const { data, quantite } = action.payload;
+
+ const idx = state.articles.findIndex(
+ (p) => p.reference === data.article_ref
+ );
+
+ if (idx !== -1) {
+ state.articles[idx].stock_reel += quantite;
+ state.articles[idx].stock_disponible! += quantite;
+ }
+
+ if (state.articleSelected) {
+ state.articleSelected.stock_reel += quantite;
+ state.articleSelected.stock_disponible! += quantite;
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+
+ builder.addCase(addStock.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "Failed to update";
+ });
+
+ },
+});
+
+
+export const { selectArticle } = articleSlice.actions
+export default articleSlice.reducer;
+
+
diff --git a/src/store/features/article/thunk.ts b/src/store/features/article/thunk.ts
new file mode 100644
index 0000000..47f4811
--- /dev/null
+++ b/src/store/features/article/thunk.ts
@@ -0,0 +1,92 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { Article, ArticleRequest, ArticleUpdateRequest, StockRequest, StockResponse } from '@/types/articleType';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all artcles
+ */
+export const getArticles = createAsyncThunk(
+ 'article/getArticles',
+ async (): Promise => {
+ try {
+ return await sageService.getArticles();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get article by id
+ */
+export const getarticleById = createAsyncThunk(
+ 'article/getarticle',
+ async (id: string): Promise => {
+ try {
+ return await sageService.getArticleId(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * add article
+ */
+export const createArticle = createAsyncThunk(
+ 'article/createArticle',
+ async (data: ArticleRequest): Promise => {
+ try {
+ return await sageService.createArticle(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * update article
+ */
+export const updateArticle = createAsyncThunk(
+ 'article/updateArticle',
+ async (
+ { reference, data }: { reference: string; data: ArticleUpdateRequest }
+ ): Promise => {
+ try {
+ return await sageService.updateArticle(reference, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * add stock
+ */
+export const addStock = createAsyncThunk(
+ 'article/addStock',
+ async (data: StockRequest) => {
+ try {
+ const res = await sageService.addStock(data);
+ return {
+ data: res,
+ quantite: data.lignes[0].quantite
+ }
+ } catch (err) {
+ throw err;
+ }
+ }
+);
diff --git a/src/store/features/article/type.ts b/src/store/features/article/type.ts
new file mode 100644
index 0000000..2424339
--- /dev/null
+++ b/src/store/features/article/type.ts
@@ -0,0 +1,10 @@
+import { Article } from "@/types/articleType";
+
+
+export interface articleState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ articles: Article[];
+ articleSelected: Article | null;
+}
+
diff --git a/src/store/features/avoir/selectors.ts b/src/store/features/avoir/selectors.ts
new file mode 100644
index 0000000..0b02b37
--- /dev/null
+++ b/src/store/features/avoir/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAllavoir = (state: RootState) => state.avoir.avoirs;
+export const getavoirSelected = (state: RootState) => state.avoir.avoirSelected;
+
+
+export const avoirStatus = (state: RootState) => state.avoir.status;
+export const avoirError = (state: RootState) => state.avoir.error;
diff --git a/src/store/features/avoir/slice.ts b/src/store/features/avoir/slice.ts
new file mode 100644
index 0000000..e1afac7
--- /dev/null
+++ b/src/store/features/avoir/slice.ts
@@ -0,0 +1,133 @@
+/* eslint-disaAvoire import/order */
+/* eslint-disaAvoire @typescript-eslint/consistent-type-imports */
+
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { avoirState } from "./type";
+import { Avoir } from "@/types/avoirType";
+import { createAvoir, getAvoir, getAvoirs, selectAvoirAsync, updateAvoir } from "./thunk";
+
+const initialState: avoirState = {
+ status: "idle",
+ error: null,
+ avoirs: [],
+ avoirSelected: null,
+};
+
+const avoirSlice = createSlice({
+ name: "avoir",
+ initialState,
+ reducers: {
+ selectavoir: (state, action: PayloadAction) => {
+ state.avoirSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all avoir
+ */
+ builder.addCase(getAvoirs.fulfilled, (state, action) => {
+ const data = action.payload;
+
+ state.avoirs = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getAvoirs.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getAvoirs.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * get avoir by id
+ */
+ builder.addCase(getAvoir.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getAvoir.fulfilled, (state, action) => {
+ const data = action.payload as Avoir;
+
+ const idx = state.avoirs.findIndex((n) => n.numero === data.numero);
+
+ if (idx !== -1) {
+ state.avoirs[idx] = data;
+ } else {
+ state.avoirs.push(data);
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(getAvoir.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * create Avoir
+ */
+ builder.addCase(createAvoir.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(createAvoir.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(createAvoir.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+ /**
+ * update Avoir
+ */
+ builder.addCase(updateAvoir.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateAvoir.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateAvoir.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * verified Avoir
+ */
+ builder.addCase(selectAvoirAsync.fulfilled, (state, action) => {
+ state.avoirSelected = {
+ ...action.payload
+ };
+ const idx = state.avoirs.findIndex(d => d.numero === action.payload.numero);
+ if (idx !== -1) {
+ state.avoirs[idx] = state.avoirSelected;
+ } else {
+ state.avoirs.push(state.avoirSelected);
+ }
+ });
+
+ },
+});
+
+
+export const { selectavoir } = avoirSlice.actions
+export default avoirSlice.reducer;
+
+
diff --git a/src/store/features/avoir/thunk.ts b/src/store/features/avoir/thunk.ts
new file mode 100644
index 0000000..89d6b29
--- /dev/null
+++ b/src/store/features/avoir/thunk.ts
@@ -0,0 +1,94 @@
+/* eslint-disaAvoire import/no-unresolved */
+/* eslint-disaAvoire import/order */
+
+import { sageService } from '@/service/sageService';
+import { Avoir, AvoirRequest, AvoirUpdateRequest } from '@/types/avoirType';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all avoir
+ */
+export const getAvoirs = createAsyncThunk(
+ 'avoir/getAvoirs',
+ async (): Promise => {
+ try {
+ return await sageService.getAvoirs();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get avoir by id
+ */
+export const getAvoir = createAsyncThunk(
+ 'avoir/getAvoir',
+ async (id: string): Promise => {
+ try {
+ return await sageService.getAvoir(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * create Avoir
+ */
+export const createAvoir = createAsyncThunk(
+ 'avoir/createAvoir',
+ async (data: AvoirRequest) => {
+ try {
+ return await sageService.createAvoir(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * update Avoir
+ */
+export const updateAvoir = createAsyncThunk(
+ 'avoir/updateAvoir',
+ async (
+ { numero, data }: { numero: string; data: AvoirUpdateRequest }
+ ) => {
+ try {
+ return await sageService.updateAvoir(numero, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * verified Avoir
+ */
+export const selectAvoirAsync = createAsyncThunk<
+ Avoir,
+ Avoir,
+ { state: any }
+>(
+ "avoir/selectAvoirAsync",
+ async (items, { dispatch }) => {
+
+ if (!items.lignes) {
+ const updateItem = await dispatch(getAvoir(items.numero)).unwrap() as any
+
+ return updateItem;
+ }
+ return items;
+ }
+);
diff --git a/src/store/features/avoir/type.ts b/src/store/features/avoir/type.ts
new file mode 100644
index 0000000..7e9d00d
--- /dev/null
+++ b/src/store/features/avoir/type.ts
@@ -0,0 +1,10 @@
+import { Avoir } from "@/types/avoirType";
+
+
+export interface avoirState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ avoirs: Avoir[];
+ avoirSelected: Avoir | null;
+}
+
diff --git a/src/store/features/bl/selectors.ts b/src/store/features/bl/selectors.ts
new file mode 100644
index 0000000..0ef9d35
--- /dev/null
+++ b/src/store/features/bl/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAllBLs = (state: RootState) => state.BL.BLs;
+export const getBLSelected = (state: RootState) => state.BL.BLSelected;
+
+
+export const BLStatus = (state: RootState) => state.BL.status;
+export const BLError = (state: RootState) => state.BL.error;
diff --git a/src/store/features/bl/slice.ts b/src/store/features/bl/slice.ts
new file mode 100644
index 0000000..182471a
--- /dev/null
+++ b/src/store/features/bl/slice.ts
@@ -0,0 +1,197 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { BLState } from "./type";
+import { BL } from "@/types/BL_Types";
+import { blToFacture, changerStatutBL, createBL, getAllBL, getBL, selectBLAsync, updateBL } from "./thunk";
+import { StatusResponse, TransformationResponse } from "@/types/sageTypes";
+
+const initialState: BLState = {
+ status: "idle",
+ error: null,
+ BLs: [],
+ BLSelected: null,
+};
+
+const BLSlice = createSlice({
+ name: "BL",
+ initialState,
+ reducers: {
+ selectBL: (state, action: PayloadAction) => {
+ state.BLSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all BLs
+ */
+ builder.addCase(getAllBL.fulfilled, (state, action) => {
+ const data = action.payload;
+
+ state.BLs = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getAllBL.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getAllBL.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+ /**
+ * get BL by id
+ */
+ builder.addCase(getBL.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getBL.fulfilled, (state, action) => {
+ const data = action.payload as BL;
+
+ const idx = state.BLs.findIndex((n) => n.numero === data.numero);
+
+ if (idx !== -1) {
+ state.BLs[idx] = data;
+ } else {
+ state.BLs.push(data);
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(getBL.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * create BL
+ */
+ builder.addCase(createBL.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(createBL.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(createBL.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+ /**
+ * update BL
+ */
+ builder.addCase(updateBL.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateBL.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateBL.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * verified bg
+ */
+ builder.addCase(selectBLAsync.fulfilled, (state, action) => {
+ state.BLSelected = {
+ ...action.payload
+ };
+ const idx = state.BLs.findIndex(d => d.numero === action.payload.numero);
+ if (idx !== -1) {
+ state.BLs[idx] = state.BLSelected;
+ } else {
+ state.BLs.push(state.BLSelected);
+ }
+ });
+
+ /**
+ * BL to facture
+ */
+ builder.addCase(blToFacture.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(blToFacture.fulfilled, (state, action) => {
+ const data = action.payload as TransformationResponse;
+ const idx = state.BLs.findIndex((n) => n.numero === data.document_source);
+ if (idx !== -1) {
+ state.BLs.splice(idx, 1)
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(blToFacture.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+ /**
+ * update BL status
+ */
+ builder.addCase(changerStatutBL.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(changerStatutBL.fulfilled, (state, action) => {
+ const data = action.payload as StatusResponse;
+
+ const idx = state.BLs.findIndex((n) => n.numero === data.document_id);
+
+ if (idx !== -1) {
+ const oldItem = state.BLs[idx];
+
+ state.BLs[idx] = {
+ ...oldItem,
+ statut: data.statut_nouveau
+ };
+ }
+
+ if (state.BLSelected?.numero === data.document_id) {
+ const oldItem = state.BLSelected;
+
+ state.BLSelected= {
+ ...oldItem,
+ statut: data.statut_nouveau
+
+ };
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(changerStatutBL.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ },
+});
+
+
+export const { selectBL } = BLSlice.actions
+export default BLSlice.reducer;
+
+
diff --git a/src/store/features/bl/thunk.ts b/src/store/features/bl/thunk.ts
new file mode 100644
index 0000000..7c4e1e0
--- /dev/null
+++ b/src/store/features/bl/thunk.ts
@@ -0,0 +1,128 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { BL, BLRequest, BLUpdateRequest } from '@/types/BL_Types';
+import { StatusRequest, StatusResponse, TransformationResponse } from '@/types/sageTypes';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all BLs
+ */
+export const getAllBL = createAsyncThunk(
+ 'BL/getAllBL',
+ async (): Promise => {
+ try {
+ return await sageService.getAllBL();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get BL by id
+ */
+export const getBL = createAsyncThunk(
+ 'BL/getBL',
+ async (id: string): Promise => {
+ try {
+ return await sageService.getBL(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * changed statut BL
+ */
+export const changerStatutBL = createAsyncThunk(
+ 'BL/changerStatutBL',
+ async (data: StatusRequest): Promise => {
+ try {
+ return await sageService.changerStatut(30, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * create BL
+ */
+export const createBL = createAsyncThunk(
+ 'BL/createBL',
+ async (data: BLRequest) => {
+ try {
+ return await sageService.createBL(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * update BL
+ */
+export const updateBL = createAsyncThunk(
+ 'BL/updateBL',
+ async (
+ { numero, data }: { numero: string; data: BLUpdateRequest }
+ ) => {
+ try {
+ return await sageService.updateBL(numero, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * verified BLs
+ */
+export const selectBLAsync = createAsyncThunk<
+ BL,
+ BL,
+ { state: any }
+>(
+ "BL/selectBLAsync",
+ async (items, { dispatch }) => {
+ if (!items.lignes) {
+ const updateItem = await dispatch(getBL(items.numero)).unwrap() as any
+ return updateItem;
+ }
+ return items;
+ }
+);
+
+
+
+
+
+/**
+ * change bl to facture
+ */
+export const blToFacture = createAsyncThunk(
+ 'BL/blToFacture',
+ async (numero: string): Promise => {
+ try {
+ return await sageService.blToFacture(numero);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
diff --git a/src/store/features/bl/type.ts b/src/store/features/bl/type.ts
new file mode 100644
index 0000000..ad35695
--- /dev/null
+++ b/src/store/features/bl/type.ts
@@ -0,0 +1,9 @@
+import { BL } from "@/types/BL_Types";
+
+
+export interface BLState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ BLs: BL[];
+ BLSelected: BL | null;}
+
diff --git a/src/store/features/client/selectors.ts b/src/store/features/client/selectors.ts
new file mode 100644
index 0000000..46210e2
--- /dev/null
+++ b/src/store/features/client/selectors.ts
@@ -0,0 +1,9 @@
+import { RootState } from "@/store/store";
+
+
+export const getAllClients = (state: RootState) => state.client.clients;
+export const getClientSelected = (state: RootState) => state.client.clientSelected;
+
+
+export const clientStatus = (state: RootState) => state.client.status;
+export const clientError = (state: RootState) => state.client.error;
diff --git a/src/store/features/client/slice.ts b/src/store/features/client/slice.ts
new file mode 100644
index 0000000..8683c0a
--- /dev/null
+++ b/src/store/features/client/slice.ts
@@ -0,0 +1,259 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { clientState } from "./type";
+import { addClient, getClient, getClients, updateClient, updateStatus } from "./thunk";
+import { Client, Contacts } from "@/types/clientType";
+import { addContactClient, updateContactClient } from "../contact/thunk";
+
+const initialState: clientState = {
+ status: "idle",
+ error: null,
+ clients: [],
+ clientSelected: null,
+};
+
+const clientSlice = createSlice({
+ name: "client",
+ initialState,
+ reducers: {
+ selectClient: (state, action: PayloadAction) => {
+ state.clientSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all clients
+ */
+ builder.addCase(getClients.fulfilled, (state, action) => {
+ const data = action.payload;
+
+ state.clients = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getClients.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getClients.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * get client by id
+ */
+ builder.addCase(getClient.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getClient.fulfilled, (state, action) => {
+ const payload = action.payload as Client;
+
+ const idx = state.clients.findIndex((n) => n.numero === payload.numero);
+
+ if (idx !== -1) {
+ state.clients[idx] = payload;
+ } else {
+ state.clients.unshift(payload);
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(getClient.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * create contact client
+ */
+ builder.addCase(addContactClient.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(addContactClient.fulfilled, (state, action) => {
+ const payload = action.payload as Contacts;
+ const idx = state.clients.findIndex((n) => n.numero === payload.numero);
+ if (idx !== -1) {
+ state.clients[idx].contacts?.unshift(payload);
+ }
+
+ if (state.clientSelected?.numero === payload.numero) {
+ state.clientSelected.contacts?.unshift(payload)
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(addContactClient.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * update contact client
+ */
+ builder.addCase(updateContactClient.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateContactClient.fulfilled, (state, action) => {
+ const payload = action.payload as Contacts;
+ const idx = state.clients.findIndex((n) => n.numero === payload.numero);
+ if (idx !== -1) {
+ const idxContact = state.clients[idx].contacts?.findIndex((n) => n.contact_numero === payload.contact_numero);
+ if (idxContact !== -1) {
+ state.clients[idx].contacts![idxContact || 0] = payload
+ }
+ }
+
+ if (state.clientSelected?.numero === payload.numero) {
+ const idxContact = state.clientSelected.contacts?.findIndex((n) => n.contact_numero === payload.contact_numero);
+ if (idxContact !== -1) {
+ state.clientSelected.contacts![idxContact || 0] = payload
+ }
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateContactClient.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+
+ /**
+ * add client
+ */
+ builder.addCase(addClient.fulfilled, (state, action) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(addClient.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(addClient.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * update client
+ */
+ builder.addCase(updateClient.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateClient.fulfilled, (state, action) => {
+ const { client, success } = action.payload as unknown as { success: boolean; client: Client };
+
+ if (success && client?.numero) {
+ const idx = state.clients.findIndex((p) => p.numero === client.numero);
+
+ if (idx !== -1) {
+ const oldItem = state.clients[idx];
+
+ state.clients[idx] = {
+ ...oldItem,
+ intitule: client.intitule,
+ adresse: client.adresse,
+ code_postal: client.code_postal,
+ ville: client.ville,
+ telephone: client.telephone,
+ email: client.email,
+ siret: client.siret,
+ tva_intra: client.tva_intra,
+ code_naf: client.code_naf,
+ encours_autorise: client.encours_autorise,
+ assurance_credit: client.assurance_credit,
+ pays: client.pays,
+ est_actif: client.est_actif,
+ est_prospect: client.est_prospect,
+ type_tiers: client.type_tiers
+ };
+ }
+
+ }
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateClient.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "Failed to update";
+ });
+
+ /**
+ * update status client
+ */
+ builder.addCase(updateStatus.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateStatus.fulfilled, (state, action) => {
+ const { client, success } = action.payload as unknown as { success: boolean; client: Client };
+
+ if (success && client?.numero) {
+ const idx = state.clients.findIndex((p) => p.numero === client.numero);
+
+ if (idx !== -1) {
+ const oldItem = state.clients[idx];
+
+ state.clients[idx] = {
+ ...oldItem,
+ intitule: client.intitule,
+ adresse: client.adresse,
+ code_postal: client.code_postal,
+ ville: client.ville,
+ telephone: client.telephone,
+ email: client.email,
+ siret: client.siret,
+ tva_intra: client.tva_intra,
+ code_naf: client.code_naf,
+ encours_autorise: client.encours_autorise,
+ assurance_credit: client.assurance_credit,
+ pays: client.pays,
+ est_actif: client.est_actif,
+ est_prospect: client.est_prospect,
+ type_tiers: client.type_tiers
+ };
+ }
+
+ }
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateStatus.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "Failed to update";
+ });
+
+ },
+});
+
+
+export const { selectClient } = clientSlice.actions
+export default clientSlice.reducer;
+
+
diff --git a/src/store/features/client/thunk.ts b/src/store/features/client/thunk.ts
new file mode 100644
index 0000000..05290c1
--- /dev/null
+++ b/src/store/features/client/thunk.ts
@@ -0,0 +1,90 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { Client, ClientCreateResponse, ClientRequest, ClientUpdateRequest } from '@/types/clientType';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all clients
+ */
+export const getClients = createAsyncThunk(
+ 'client/getClients',
+ async (): Promise => {
+ try {
+ return await sageService.getClients();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get client by id
+ */
+export const getClient = createAsyncThunk(
+ 'client/getClient',
+ async (id: string): Promise => {
+ try {
+ return await sageService.getClient(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * add client
+ */
+export const addClient = createAsyncThunk(
+ 'client/addClient',
+ async (data: ClientRequest): Promise => {
+ try {
+ return await sageService.addClient(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * update client
+ */
+export const updateClient = createAsyncThunk(
+ 'client/updateClient',
+ async (
+ { numero, data }: { numero: string; data: ClientUpdateRequest }
+ ): Promise => {
+ try {
+ return await sageService.updateClient(numero, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * update client status
+ */
+export const updateStatus = createAsyncThunk(
+ 'client/updateStatus',
+ async (
+ { numero, est_actif }: { numero: string; est_actif: boolean }
+ ): Promise => {
+ try {
+ return await sageService.updateStatusClient(numero, est_actif);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
\ No newline at end of file
diff --git a/src/store/features/client/type.ts b/src/store/features/client/type.ts
new file mode 100644
index 0000000..e56aec7
--- /dev/null
+++ b/src/store/features/client/type.ts
@@ -0,0 +1,11 @@
+import { Client } from "@/types/clientType";
+
+
+
+export interface clientState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ clients: Client[];
+ clientSelected: Client | null;
+}
+
diff --git a/src/store/features/commande/selectors.ts b/src/store/features/commande/selectors.ts
new file mode 100644
index 0000000..31e7bc8
--- /dev/null
+++ b/src/store/features/commande/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAllcommandes = (state: RootState) => state.commande.commandes;
+export const getcommandeSelected = (state: RootState) => state.commande.commandeSelected;
+
+
+export const commandeStatus = (state: RootState) => state.commande.status;
+export const commandeError = (state: RootState) => state.commande.error;
diff --git a/src/store/features/commande/slice.ts b/src/store/features/commande/slice.ts
new file mode 100644
index 0000000..beab9bc
--- /dev/null
+++ b/src/store/features/commande/slice.ts
@@ -0,0 +1,222 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { commandeState } from "./type";
+import { changerStatutCommande, commandeToBL, commandeToFacture, createCommande, getCommande, getCommandes, selectCommandeAsync, updateCommande } from "./thunk";
+import { Commande } from "@/types/commandeTypes";
+import { StatusResponse, TransformationResponse } from "@/types/sageTypes";
+
+const initialState: commandeState = {
+ status: "idle",
+ error: null,
+ commandes: [],
+ commandeSelected: null,
+};
+
+const commandeSlice = createSlice({
+ name: "commande",
+ initialState,
+ reducers: {
+ selectcommande: (state, action: PayloadAction) => {
+ state.commandeSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all commandes
+ */
+ builder.addCase(getCommandes.fulfilled, (state, action) => {
+ const data = action.payload;
+
+ state.commandes = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getCommandes.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getCommandes.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+ /**
+ * get commande by id
+ */
+ builder.addCase(getCommande.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getCommande.fulfilled, (state, action) => {
+ const data = action.payload as Commande;
+
+ const idx = state.commandes.findIndex((n) => n.numero === data.numero);
+
+ if (idx !== -1) {
+ state.commandes[idx] = data;
+ } else {
+ state.commandes.push(data);
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(getCommande.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+ /**
+ * create commande
+ */
+ builder.addCase(createCommande.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(createCommande.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(createCommande.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+ /**
+ * update commande
+ */
+ builder.addCase(updateCommande.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateCommande.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateCommande.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * verified commande
+ */
+ builder.addCase(selectCommandeAsync.fulfilled, (state, action) => {
+ state.commandeSelected = {
+ ...action.payload
+ };
+ const idx = state.commandes.findIndex(d => d.numero === action.payload.numero);
+ if (idx !== -1) {
+ state.commandes[idx] = state.commandeSelected;
+ } else {
+ state.commandes.push(state.commandeSelected);
+ }
+ });
+
+
+ /**
+ * devis to commande
+ */
+ builder.addCase(commandeToBL.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(commandeToBL.fulfilled, (state, action) => {
+ const data = action.payload as TransformationResponse;
+ const idx = state.commandes.findIndex((n) => n.numero === data.document_source);
+ if (idx !== -1) {
+ state.commandes.splice(idx, 1)
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(commandeToBL.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * devis to facture
+ */
+ builder.addCase(commandeToFacture.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(commandeToFacture.fulfilled, (state, action) => {
+ const data = action.payload as TransformationResponse;
+ const idx = state.commandes.findIndex((n) => n.numero === data.document_source);
+ if (idx !== -1) {
+ state.commandes.splice(idx, 1)
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(commandeToFacture.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * update commande status
+ */
+ builder.addCase(changerStatutCommande.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(changerStatutCommande.fulfilled, (state, action) => {
+ const data = action.payload as StatusResponse;
+
+ const idx = state.commandes.findIndex((n) => n.numero === data.document_id);
+
+ if (idx !== -1) {
+ const oldItem = state.commandes[idx];
+
+ state.commandes[idx] = {
+ ...oldItem,
+ statut: data.statut_nouveau
+ };
+ }
+
+ if (state.commandeSelected?.numero === data.document_id) {
+ const oldItem = state.commandeSelected;
+
+ state.commandeSelected= {
+ ...oldItem,
+ statut: data.statut_nouveau
+
+ };
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(changerStatutCommande.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ },
+});
+
+
+export const { selectcommande } = commandeSlice.actions
+export default commandeSlice.reducer;
+
+
diff --git a/src/store/features/commande/thunk.ts b/src/store/features/commande/thunk.ts
new file mode 100644
index 0000000..784f0aa
--- /dev/null
+++ b/src/store/features/commande/thunk.ts
@@ -0,0 +1,140 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { Commande, CommandeRequest, CommandeResponse, CommandeUpdateRequest } from '@/types/commandeTypes';
+import { StatusRequest, StatusResponse, TransformationResponse } from '@/types/sageTypes';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all commandes
+ */
+export const getCommandes = createAsyncThunk(
+ 'commande/getCommandes',
+ async (): Promise => {
+ try {
+ return await sageService.getCommandes();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get commande by id
+ */
+export const getCommande = createAsyncThunk(
+ 'commande/getCommande',
+ async (id: string): Promise => {
+ try {
+ return await sageService.getCommande(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * changed statut commande
+ */
+export const changerStatutCommande = createAsyncThunk(
+ 'commande/changerStatutCommande',
+ async (data: StatusRequest): Promise => {
+ try {
+ return await sageService.changerStatut(10, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * commande to facture
+ */
+export const commandeToFacture = createAsyncThunk(
+ 'commande/commandeToFacture',
+ async (id: string): Promise => {
+ try {
+ return await sageService.commandeToFacture(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * commande to BL
+ */
+export const commandeToBL = createAsyncThunk(
+ 'commande/commandeToBL',
+ async (id: string): Promise => {
+ try {
+ return await sageService.commandeToBL(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * create commande
+ */
+export const createCommande = createAsyncThunk(
+ 'commande/createCommande',
+ async (data: CommandeRequest): Promise => {
+ try {
+ return await sageService.createCommande(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * update commande
+ */
+export const updateCommande = createAsyncThunk(
+ 'commande/updateCommande',
+ async (
+ { numero, data }: { numero: string; data: CommandeUpdateRequest }
+ ) => {
+ try {
+ return await sageService.updateCommande(numero, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * verified commandes
+ */
+export const selectCommandeAsync = createAsyncThunk<
+ Commande,
+ Commande,
+ { state: any }
+>(
+ "commande/selectCommandeAsync",
+ async (devis, { dispatch }) => {
+ if (!devis.lignes) {
+ const updateItem = await dispatch(getCommande(devis.numero)).unwrap() as any
+ return updateItem;
+ }
+ return devis;
+ }
+);
\ No newline at end of file
diff --git a/src/store/features/commande/type.ts b/src/store/features/commande/type.ts
new file mode 100644
index 0000000..b0c522a
--- /dev/null
+++ b/src/store/features/commande/type.ts
@@ -0,0 +1,9 @@
+import { Commande } from "@/types/commandeTypes";
+
+
+export interface commandeState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ commandes: Commande[];
+ commandeSelected: Commande | null;}
+
diff --git a/src/store/features/commercial/selectors.ts b/src/store/features/commercial/selectors.ts
new file mode 100644
index 0000000..4668fd2
--- /dev/null
+++ b/src/store/features/commercial/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAllcommercials = (state: RootState) => state.commercials.commercials;
+export const getcommercialsSelected = (state: RootState) => state.commercials.commercialsSelected;
+
+
+export const commercialsStatus = (state: RootState) => state.commercials.status;
+export const commercialsError = (state: RootState) => state.commercials.error;
diff --git a/src/store/features/commercial/slice.ts b/src/store/features/commercial/slice.ts
new file mode 100644
index 0000000..d44a702
--- /dev/null
+++ b/src/store/features/commercial/slice.ts
@@ -0,0 +1,80 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { commercialsState } from "./type";
+import { Commercial } from "@/types/commercialType";
+import { getCommercials, getcommercialsById } from "./thunk";
+
+const initialState: commercialsState = {
+ status: "idle",
+ error: null,
+ commercials: [],
+ commercialsSelected: null,
+};
+
+const commercialsSlice = createSlice({
+ name: "commercials",
+ initialState,
+ reducers: {
+ selectcommercials: (state, action: PayloadAction) => {
+ state.commercialsSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all commercials
+ */
+ builder.addCase(getCommercials.fulfilled, (state, action) => {
+
+ const data = action.payload;
+
+ state.commercials = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getCommercials.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getCommercials.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+ /**
+ * get commercials by id
+ */
+ builder.addCase(getcommercialsById.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getcommercialsById.fulfilled, (state, action) => {
+ const data = action.payload as Commercial;
+
+ const idx = state.commercials.findIndex((n) => n.numero === data.numero);
+
+ if (idx !== -1) {
+ state.commercials[idx] = data;
+ } else {
+ state.commercials.push(data);
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(getcommercialsById.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+ },
+});
+
+
+export const { selectcommercials } = commercialsSlice.actions
+export default commercialsSlice.reducer;
+
+
diff --git a/src/store/features/commercial/thunk.ts b/src/store/features/commercial/thunk.ts
new file mode 100644
index 0000000..8bb4950
--- /dev/null
+++ b/src/store/features/commercial/thunk.ts
@@ -0,0 +1,73 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { Commercial, CommercialRequest } from '@/types/commercialType';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all commercial
+ */
+export const getCommercials = createAsyncThunk(
+ 'commercials/getCommercials',
+ async (): Promise => {
+ try {
+ return await sageService.commercials();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get commercials by id
+ */
+export const getcommercialsById = createAsyncThunk(
+ 'commercials/getcommercialsById',
+ async (id: string): Promise => {
+ try {
+ return await sageService.commercialsById(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * add commercials
+ */
+export const addCommercial = createAsyncThunk(
+ 'commercials/addCommercial',
+ async (data: CommercialRequest): Promise => {
+ try {
+ return await sageService.addCommercial(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * update commercials
+ */
+// export const updatecommercials = createAsyncThunk(
+// 'commercials/updatecommercials',
+// async (
+// { reference, data }: { reference: string; data: commercialsUpdateRequest }
+// ): Promise => {
+// try {
+// return await sageService.updatecommercials(reference, data);
+// } catch (err) {
+// throw err;
+// }
+// }
+// );
+
diff --git a/src/store/features/commercial/type.ts b/src/store/features/commercial/type.ts
new file mode 100644
index 0000000..2e5d84a
--- /dev/null
+++ b/src/store/features/commercial/type.ts
@@ -0,0 +1,10 @@
+import { Commercial } from "@/types/commercialType";
+
+
+export interface commercialsState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ commercials: Commercial[];
+ commercialsSelected: Commercial | null;
+}
+
diff --git a/src/store/features/contact/selectors.ts b/src/store/features/contact/selectors.ts
new file mode 100644
index 0000000..f556c7d
--- /dev/null
+++ b/src/store/features/contact/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAllcontacts = (state: RootState) => state.contact.contacts;
+export const getcontactSelected = (state: RootState) => state.contact.contactSelected;
+
+
+export const contactStatus = (state: RootState) => state.contact.status;
+export const contactError = (state: RootState) => state.contact.error;
diff --git a/src/store/features/contact/slice.ts b/src/store/features/contact/slice.ts
new file mode 100644
index 0000000..5882ee9
--- /dev/null
+++ b/src/store/features/contact/slice.ts
@@ -0,0 +1,100 @@
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { contactState } from "./type";
+import { Contacts } from "@/types/clientType";
+import { addContactClient, addContactFournisseur, updateContactClient, updateContactFournisseur } from "./thunk";
+
+const initialState: contactState = {
+ status: "idle",
+ error: null,
+ contacts: [],
+ contactSelected: null,
+};
+
+const contactSlice = createSlice({
+ name: "contact",
+ initialState,
+ reducers: {
+ selectcontact: (state, action: PayloadAction) => {
+ state.contactSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+ /**
+ * create contact client
+ */
+ builder.addCase(addContactClient.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(addContactClient.fulfilled, (state, action) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(addContactClient.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * update contact client
+ */
+ builder.addCase(updateContactClient.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateContactClient.fulfilled, (state, action) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateContactClient.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * create contact fournisseur
+ */
+ builder.addCase(addContactFournisseur.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(addContactFournisseur.fulfilled, (state, action) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(addContactFournisseur.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * update contact fournisseur
+ */
+ builder.addCase(updateContactFournisseur.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateContactFournisseur.fulfilled, (state, action) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateContactFournisseur.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+ }
+});
+
+
+export const { selectcontact } = contactSlice.actions
+export default contactSlice.reducer;
+
+
diff --git a/src/store/features/contact/thunk.ts b/src/store/features/contact/thunk.ts
new file mode 100644
index 0000000..e03e22d
--- /dev/null
+++ b/src/store/features/contact/thunk.ts
@@ -0,0 +1,94 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { ContactRequest, Contacts } from '@/types/clientType';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all contacts clients
+ */
+export const contactsClient = createAsyncThunk(
+ 'contact/contactsClient',
+ async (numero: string): Promise => {
+ try {
+ return await sageService.contacts(numero);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * add contact
+ */
+export const addContactClient = createAsyncThunk(
+ 'contact/addContactClient',
+ async (data: ContactRequest): Promise => {
+ try {
+ return await sageService.addContact(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+
+/**
+ * update contact
+ */
+export const updateContactClient = createAsyncThunk(
+ 'contact/updateContactClient',
+ async (
+ {
+ contact_numero,
+ data,
+ }: { contact_numero: number; data: ContactRequest }
+ ): Promise => {
+ return await sageService.updateContact(contact_numero, data);
+ }
+);
+
+
+
+
+/**
+ * add contact fournisseur
+ */
+export const addContactFournisseur = createAsyncThunk(
+ 'contact/addContactFournisseur',
+ async (data: ContactRequest): Promise => {
+ try {
+ return await sageService.addContact(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+
+/**
+ * update contact fournisseur
+ */
+export const updateContactFournisseur = createAsyncThunk(
+ 'contact/updateContactFournisseur',
+ async (
+ {
+ contact_numero,
+ data,
+ }: { contact_numero: number; data: ContactRequest }
+ ): Promise => {
+ return await sageService.updateContact(contact_numero, data);
+ }
+);
diff --git a/src/store/features/contact/type.ts b/src/store/features/contact/type.ts
new file mode 100644
index 0000000..e750be4
--- /dev/null
+++ b/src/store/features/contact/type.ts
@@ -0,0 +1,10 @@
+import { Contacts } from "@/types/clientType";
+
+
+export interface contactState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ contacts: Contacts[];
+ contactSelected: Contacts | null;
+}
+
diff --git a/src/store/features/devis/selectors.ts b/src/store/features/devis/selectors.ts
new file mode 100644
index 0000000..faa6621
--- /dev/null
+++ b/src/store/features/devis/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAllDevis = (state: RootState) => state.devis.devis;
+export const getDevisSelected = (state: RootState) => state.devis.devisSelected;
+
+
+export const devisStatus = (state: RootState) => state.devis.status;
+export const devisError = (state: RootState) => state.devis.error;
diff --git a/src/store/features/devis/slice.ts b/src/store/features/devis/slice.ts
new file mode 100644
index 0000000..b38e03c
--- /dev/null
+++ b/src/store/features/devis/slice.ts
@@ -0,0 +1,230 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { devisState } from "./type";
+import { changerStatutDevis, createDevis, devisToCommande, devisToFacture, getDevisById, getDevisList, selectDevisAsync, updateDevis } from "./thunk";
+import { DevisListItem, DevisStatusResponse } from "@/types/devisType";
+import { StatusResponse, TransformationResponse } from "@/types/sageTypes";
+
+const initialState: devisState = {
+ status: "idle",
+ error: null,
+ devis: [],
+ devisSelected: null,
+};
+
+const devisSlice = createSlice({
+ name: "devis",
+ initialState,
+ reducers: {
+ selectDevis: (state, action: PayloadAction) => {
+ state.devisSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all devis
+ */
+ builder.addCase(getDevisList.fulfilled, (state, action) => {
+ const data = action.payload.map((d: DevisListItem) => ({
+ ...d,
+ a_deja_ete_transforme: d.a_deja_ete_transforme ?? false,
+ isVerified: d.isVerified ?? false,
+ }));
+
+ state.devis = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getDevisList.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getDevisList.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * get devis by id
+ */
+ builder.addCase(getDevisById.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getDevisById.fulfilled, (state, action) => {
+ const payload = action.payload as any;
+ const data = payload.data as DevisListItem
+
+ const idx = state.devis.findIndex((n) => n.numero === data.numero);
+
+ if (idx !== -1) {
+ state.devis[idx] = data;
+ } else {
+ state.devis.push(data);
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(getDevisById.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * verified status devis
+ */
+ builder.addCase(selectDevisAsync.fulfilled, (state, action) => {
+ state.devisSelected = {
+ ...action.payload,
+ isVerified: true
+ };
+ const idx = state.devis.findIndex(d => d.numero === action.payload.numero);
+ if (idx !== -1) {
+ state.devis[idx] = state.devisSelected;
+ } else {
+ state.devis.push(state.devisSelected);
+ }
+ });
+
+
+ /**
+ * create devis
+ */
+ builder.addCase(createDevis.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(createDevis.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(createDevis.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+ /**
+ * update devis
+ */
+ builder.addCase(updateDevis.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateDevis.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateDevis.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * update devis status
+ */
+ builder.addCase(changerStatutDevis.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(changerStatutDevis.fulfilled, (state, action) => {
+ const data = action.payload as StatusResponse;
+
+ const idx = state.devis.findIndex((n) => n.numero === data.document_id);
+
+ if (idx !== -1) {
+ const oldItem = state.devis[idx];
+
+ state.devis[idx] = {
+ ...oldItem,
+ statut: data.statut_nouveau
+ };
+ }
+
+ if (state.devisSelected?.numero === data.document_id) {
+ const oldItem = state.devisSelected;
+
+ state.devisSelected= {
+ ...oldItem,
+ statut: data.statut_nouveau
+
+ };
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(changerStatutDevis.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * devis to commande
+ */
+ builder.addCase(devisToCommande.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(devisToCommande.fulfilled, (state, action) => {
+ const data = action.payload as TransformationResponse;
+ const idx = state.devis.findIndex((n) => n.numero === data.document_source);
+ if (idx !== -1) {
+ state.devis.splice(idx, 1)
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(devisToCommande.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * devis to facture
+ */
+ builder.addCase(devisToFacture.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(devisToFacture.fulfilled, (state, action) => {
+ const data = action.payload as TransformationResponse;
+ const idx = state.devis.findIndex((n) => n.numero === data.document_source);
+ if (idx !== -1) {
+ state.devis.splice(idx, 1)
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(devisToFacture.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+ },
+});
+
+
+export const { selectDevis } = devisSlice.actions
+export default devisSlice.reducer;
+
+
diff --git a/src/store/features/devis/thunk.ts b/src/store/features/devis/thunk.ts
new file mode 100644
index 0000000..eaaa2e4
--- /dev/null
+++ b/src/store/features/devis/thunk.ts
@@ -0,0 +1,158 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+import { sageService } from '@/service/sageService';
+import { DevisListItem, DevisRequest, DevisResponse, DevisStatusRequest, DevisStatusResponse, DevisUpdateRequest } from '@/types/devisType';
+import type { StatusRequest, StatusResponse, TransformationResponse } from '@/types/sageTypes';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all devis
+ */
+export const getDevisList = createAsyncThunk(
+ 'devis/getDevisList',
+ async (): Promise => {
+ try {
+ return await sageService.getDevisList();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get devis by id
+ */
+export const getDevisById = createAsyncThunk(
+ 'devis/getDevis',
+ async (id: string): Promise => {
+ try {
+ return await sageService.getDevis(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * verified status devis
+ */
+export const selectDevisAsync = createAsyncThunk<
+ DevisListItem,
+ DevisListItem,
+ { state: any }
+>(
+ "devis/selectDevisAsync",
+ async (devis, { dispatch }) => {
+ if (!devis.isVerified && !devis.a_deja_ete_transforme) {
+ const updatedDevis = await dispatch(getDevisById(devis.numero)).unwrap() as any
+ return updatedDevis.data;
+ }
+ return devis;
+ }
+);
+
+
+
+/**
+ * create devis
+ */
+export const createDevis = createAsyncThunk(
+ 'devis/createDevis',
+ async (data: DevisRequest): Promise => {
+ try {
+ return await sageService.createDevis(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * update devis
+ */
+export const updateDevis = createAsyncThunk(
+ 'devis/updateDevis',
+ async (
+ { numero, data }: { numero: string; data: DevisUpdateRequest }
+ ) => {
+ try {
+ return await sageService.updateDevis(numero, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * changed statut devis
+ */
+export const changerStatutDevis = createAsyncThunk(
+ 'devis/changerStatutDevis',
+ async (data: StatusRequest): Promise => {
+ try {
+ return await sageService.changerStatut(0, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+
+/**
+ * change devis to commande
+ */
+export const devisToCommande = createAsyncThunk(
+ 'devis/devisToCommande',
+ async (numero: string): Promise => {
+ try {
+ return await sageService.devisToCommande(numero);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * change devis to facture
+ */
+export const devisToFacture = createAsyncThunk(
+ 'devis/devisToFacture',
+ async (numero: string): Promise => {
+ try {
+ return await sageService.devisToFacture(numero);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * devis to pdf
+ */
+export const devisToPDF = createAsyncThunk(
+ 'devis/devisToPDF',
+ async (numero: string) => {
+ try {
+ return await sageService.devisToPDF(numero);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
diff --git a/src/store/features/devis/type.ts b/src/store/features/devis/type.ts
new file mode 100644
index 0000000..9be643e
--- /dev/null
+++ b/src/store/features/devis/type.ts
@@ -0,0 +1,10 @@
+import { DevisListItem } from "@/types/devisType";
+
+
+export interface devisState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ devis: DevisListItem[];
+ devisSelected: DevisListItem | null;
+}
+
diff --git a/src/store/features/entreprise/selectors.ts b/src/store/features/entreprise/selectors.ts
new file mode 100644
index 0000000..5d35862
--- /dev/null
+++ b/src/store/features/entreprise/selectors.ts
@@ -0,0 +1,8 @@
+import { RootState } from "@/store/store";
+
+
+export const getentrepriseSelected = (state: RootState) => state.entreprise.entrepriseSelected;
+
+
+export const entrepriseStatus = (state: RootState) => state.entreprise.status;
+export const entrepriseError = (state: RootState) => state.entreprise.error;
diff --git a/src/store/features/entreprise/slice.ts b/src/store/features/entreprise/slice.ts
new file mode 100644
index 0000000..77cf434
--- /dev/null
+++ b/src/store/features/entreprise/slice.ts
@@ -0,0 +1,30 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { entrepriseState } from "./type";
+import { Entreprise } from "@/types/entreprise";
+
+const initialState: entrepriseState = {
+ status: "idle",
+ error: null,
+ entrepriseSelected: null,
+};
+
+const entrepriseSlice = createSlice({
+ name: "entreprise",
+ initialState,
+ reducers: {
+ selectentreprise: (state, action: PayloadAction) => {
+ state.entrepriseSelected = action.payload;
+ },
+ deselectentreprise: (state, action: PayloadAction) => {
+ state.entrepriseSelected = action.payload;
+ }
+ },
+});
+
+
+export const { selectentreprise, deselectentreprise } = entrepriseSlice.actions
+export default entrepriseSlice.reducer;
+
+
diff --git a/src/store/features/entreprise/thunk.ts b/src/store/features/entreprise/thunk.ts
new file mode 100644
index 0000000..ffa4075
--- /dev/null
+++ b/src/store/features/entreprise/thunk.ts
@@ -0,0 +1,24 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+import { sageService } from '@/service/sageService';
+import { EntrepriseResponse } from '@/types/entreprise';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * search entreprise
+ */
+export const searchEntreprise = createAsyncThunk(
+ 'entreprise/searchEntreprise',
+ async (query: string): Promise => {
+ try {
+ return await sageService.searchEntreprise(query);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
diff --git a/src/store/features/entreprise/type.ts b/src/store/features/entreprise/type.ts
new file mode 100644
index 0000000..8716c57
--- /dev/null
+++ b/src/store/features/entreprise/type.ts
@@ -0,0 +1,9 @@
+import { Entreprise } from "@/types/entreprise";
+
+
+export interface entrepriseState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ entrepriseSelected: Entreprise | null;
+}
+
diff --git a/src/store/features/factures/selectors.ts b/src/store/features/factures/selectors.ts
new file mode 100644
index 0000000..4d385ba
--- /dev/null
+++ b/src/store/features/factures/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAllfacture = (state: RootState) => state.facture.factures;
+export const getfactureSelected = (state: RootState) => state.facture.factureSelected;
+
+
+export const factureStatus = (state: RootState) => state.facture.status;
+export const factureError = (state: RootState) => state.facture.error;
diff --git a/src/store/features/factures/slice.ts b/src/store/features/factures/slice.ts
new file mode 100644
index 0000000..ba6e55b
--- /dev/null
+++ b/src/store/features/factures/slice.ts
@@ -0,0 +1,195 @@
+/* eslint-disaFacturee import/order */
+/* eslint-disaFacturee @typescript-eslint/consistent-type-imports */
+
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { factureState } from "./type";
+import { Facture } from "@/types/factureType";
+import { changerStatutFacture, createFacture, getFacture, getFactures, selectFactureAsync, updateFacture, validerFacture } from "./thunk";
+import { StatusResponse } from "@/types/sageTypes";
+
+const initialState: factureState = {
+ status: "idle",
+ error: null,
+ factures: [],
+ factureSelected: null,
+};
+
+const factureSlice = createSlice({
+ name: "facture",
+ initialState,
+ reducers: {
+ selectfacture: (state, action: PayloadAction) => {
+ state.factureSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all facture
+ */
+ builder.addCase(getFactures.fulfilled, (state, action) => {
+ const data = action.payload;
+
+ state.factures = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getFactures.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getFactures.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * get facture by id
+ */
+ builder.addCase(getFacture.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getFacture.fulfilled, (state, action) => {
+ const data = action.payload as Facture;
+
+
+ const idx = state.factures.findIndex((n) => n.numero === data.numero);
+
+ if (idx !== -1) {
+ state.factures[idx] = data;
+ } else {
+ state.factures.push(data);
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(getFacture.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ })
+
+ /**
+ * create Facture
+ */
+ builder.addCase(createFacture.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(createFacture.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(createFacture.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+ /**
+ * update Facture
+ */
+ builder.addCase(updateFacture.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateFacture.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateFacture.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * update Facture
+ */
+ builder.addCase(validerFacture.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(validerFacture.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(validerFacture.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * verified bg
+ */
+ builder.addCase(selectFactureAsync.fulfilled, (state, action) => {
+ state.factureSelected = {
+ ...action.payload
+ };
+ const idx = state.factures.findIndex(d => d.numero === action.payload.numero);
+ if (idx !== -1) {
+ state.factures[idx] = state.factureSelected;
+ } else {
+ state.factures.push(state.factureSelected);
+ }
+ });
+
+
+ /**
+ * update facture status
+ */
+ builder.addCase(changerStatutFacture.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(changerStatutFacture.fulfilled, (state, action) => {
+ const data = action.payload as StatusResponse;
+
+ const idx = state.factures.findIndex((n) => n.numero === data.document_id);
+
+ if (idx !== -1) {
+ const oldItem = state.factures[idx];
+
+ state.factures[idx] = {
+ ...oldItem,
+ statut: data.statut_nouveau
+ };
+ }
+
+ if (state.factureSelected?.numero === data.document_id) {
+ const oldItem = state.factureSelected;
+
+ state.factureSelected= {
+ ...oldItem,
+ statut: data.statut_nouveau
+
+ };
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(changerStatutFacture.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+ },
+});
+
+
+export const { selectfacture } = factureSlice.actions
+export default factureSlice.reducer;
+
+
diff --git a/src/store/features/factures/thunk.ts b/src/store/features/factures/thunk.ts
new file mode 100644
index 0000000..8a117ff
--- /dev/null
+++ b/src/store/features/factures/thunk.ts
@@ -0,0 +1,146 @@
+/* eslint-disaFacturee import/no-unresolved */
+/* eslint-disaFacturee import/order */
+
+import { sageService } from '@/service/sageService';
+import { Facture, FactureRequest, FactureUpdateRequest, RelanceRequest, RelanceResponse } from '@/types/factureType';
+import { StatusRequest, StatusResponse } from '@/types/sageTypes';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all facture
+ */
+export const getFactures = createAsyncThunk(
+ 'facture/getFactures',
+ async (): Promise => {
+ try {
+ return await sageService.getFactures();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get facture by id
+ */
+export const getFacture = createAsyncThunk(
+ 'facture/getfacture',
+ async (id: string) => {
+ try {
+ const rep = await sageService.getFacture(id) as any
+ return rep.data as Facture
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * valider facture
+ */
+export const validerFacture = createAsyncThunk(
+ 'facture/validerFacture',
+ async (id: string) => {
+ try {
+ return await sageService.validerFacture(id)
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * changed statut facture
+ */
+export const changerStatutFacture = createAsyncThunk(
+ 'facture/changerStatutFacture',
+ async (data: StatusRequest): Promise => {
+ try {
+ return await sageService.changerStatut(60, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * relance facture
+ */
+export const relancerFacture = createAsyncThunk(
+ 'facture/relancerFacture',
+ async (data: RelanceRequest): Promise => {
+ try {
+ return await sageService.relancerFacture(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+
+
+
+/**
+ * create Facture
+ */
+export const createFacture = createAsyncThunk(
+ 'facture/createFacture',
+ async (data: FactureRequest) => {
+ try {
+ return await sageService.createFacture(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * update Facture
+ */
+export const updateFacture = createAsyncThunk(
+ 'facture/updateFacture',
+ async (
+ { numero, data }: { numero: string; data: FactureUpdateRequest }
+ ) => {
+ try {
+ return await sageService.updateFacture(numero, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * verified Factures
+ */
+export const selectFactureAsync = createAsyncThunk<
+ Facture,
+ Facture,
+ { state: any }
+>(
+ "facture/selectFactureAsync",
+ async (items, { dispatch }) => {
+ if (!items.lignes) {
+ const updateItem = await dispatch(getFacture(items.numero)).unwrap() as any
+ return updateItem;
+ }
+ return items;
+ }
+);
diff --git a/src/store/features/factures/type.ts b/src/store/features/factures/type.ts
new file mode 100644
index 0000000..67b47af
--- /dev/null
+++ b/src/store/features/factures/type.ts
@@ -0,0 +1,10 @@
+import { Facture } from "@/types/factureType";
+
+
+export interface factureState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ factures: Facture[];
+ factureSelected: Facture | null;
+}
+
diff --git a/src/store/features/famille/selectors.ts b/src/store/features/famille/selectors.ts
new file mode 100644
index 0000000..22e7505
--- /dev/null
+++ b/src/store/features/famille/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAllfamilles = (state: RootState) => state.famille.familles;
+export const getfamilleSelected = (state: RootState) => state.famille.familleSelected;
+
+
+export const familleStatus = (state: RootState) => state.famille.status;
+export const familleError = (state: RootState) => state.famille.error;
diff --git a/src/store/features/famille/slice.ts b/src/store/features/famille/slice.ts
new file mode 100644
index 0000000..920d316
--- /dev/null
+++ b/src/store/features/famille/slice.ts
@@ -0,0 +1,74 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { Famille } from "@/types/familleType";
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { createFamille, getfamilles } from "./thunk";
+import { familleState } from "./type";
+
+const initialState: familleState = {
+ status: "idle",
+ error: null,
+ familles: [],
+ familleSelected: null,
+};
+
+const familleSlice = createSlice({
+ name: "famille",
+ initialState,
+ reducers: {
+ selectfamille: (state, action: PayloadAction) => {
+ state.familleSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all familles
+ */
+ builder.addCase(getfamilles.fulfilled, (state, action) => {
+ const data = action.payload;
+
+ state.familles = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getfamilles.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getfamilles.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+ /**
+ * add famille
+ */
+ builder.addCase(createFamille.fulfilled, (state, action) => {
+
+ const data = action.payload as any
+
+ state.familles.unshift(data);
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(createFamille.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(createFamille.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+ },
+});
+
+
+export const { selectfamille } = familleSlice.actions
+export default familleSlice.reducer;
+
+
diff --git a/src/store/features/famille/thunk.ts b/src/store/features/famille/thunk.ts
new file mode 100644
index 0000000..3e1ac54
--- /dev/null
+++ b/src/store/features/famille/thunk.ts
@@ -0,0 +1,39 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { Famille, FamilleRequest } from '@/types/familleType';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all familles
+ */
+export const getfamilles = createAsyncThunk(
+ 'famille/getFamilles',
+ async (): Promise => {
+ try {
+ return await sageService.getFamilles();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * add famille
+ */
+export const createFamille = createAsyncThunk(
+ 'famille/createFamille',
+ async (data: FamilleRequest): Promise => {
+ try {
+ return await sageService.createFamille(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
diff --git a/src/store/features/famille/type.ts b/src/store/features/famille/type.ts
new file mode 100644
index 0000000..590a815
--- /dev/null
+++ b/src/store/features/famille/type.ts
@@ -0,0 +1,10 @@
+import { Famille } from "@/types/familleType";
+
+
+export interface familleState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ familles: Famille[];
+ familleSelected: Famille | null;
+}
+
diff --git a/src/store/features/fournisseur/selectors.ts b/src/store/features/fournisseur/selectors.ts
new file mode 100644
index 0000000..3b5219c
--- /dev/null
+++ b/src/store/features/fournisseur/selectors.ts
@@ -0,0 +1,9 @@
+import { RootState } from "@/store/store";
+
+
+export const getAllfournisseurs = (state: RootState) => state.fournisseur.fournisseurs;
+export const getfournisseurSelected = (state: RootState) => state.fournisseur.fournisseurSelected;
+
+
+export const fournisseurStatus = (state: RootState) => state.fournisseur.status;
+export const fournisseurError = (state: RootState) => state.fournisseur.error;
diff --git a/src/store/features/fournisseur/slice.ts b/src/store/features/fournisseur/slice.ts
new file mode 100644
index 0000000..933e150
--- /dev/null
+++ b/src/store/features/fournisseur/slice.ts
@@ -0,0 +1,185 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { fournisseurState } from "./type";
+import { Fournisseur } from "@/types/fournisseurType";
+import { addFournisseur, getFournisseurs, updateFournisseur } from "./thunk";
+import { addContactFournisseur, updateContactFournisseur } from "../contact/thunk";
+import { Contacts } from "@/types/clientType";
+
+const initialState: fournisseurState = {
+ status: "idle",
+ error: null,
+ fournisseurs: [],
+ fournisseurSelected: null,
+};
+
+const fournisseurSlice = createSlice({
+ name: "fournisseur",
+ initialState,
+ reducers: {
+ selectfournisseur: (state, action: PayloadAction) => {
+ state.fournisseurSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all fournisseurs
+ */
+ builder.addCase(getFournisseurs.fulfilled, (state, action) => {
+ const data = action.payload;
+
+ state.fournisseurs = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getFournisseurs.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getFournisseurs.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * add fournisseur
+ */
+ builder.addCase(addFournisseur.fulfilled, (state, action) => {
+ const { data, success } = action.payload as any
+
+ if(success){
+ state.fournisseurs.push(data);
+ }
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(addFournisseur.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(addFournisseur.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * update fournisseur
+ */
+ builder.addCase(updateFournisseur.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateFournisseur.fulfilled, (state, action) => {
+ console.log("action.payload : ",action.payload);
+
+ const { fournisseur, success } = action.payload as unknown as { success: boolean; fournisseur: Fournisseur };
+
+ if (success && fournisseur?.numero) {
+ const idx = state.fournisseurs.findIndex((p) => p.numero === fournisseur.numero);
+
+ if (idx !== -1) {
+ const oldItem = state.fournisseurs[idx];
+
+ state.fournisseurs[idx] = {
+ ...oldItem,
+ intitule: fournisseur.intitule,
+ adresse: fournisseur.adresse,
+ code_postal: fournisseur.code_postal,
+ ville: fournisseur.ville,
+ telephone: fournisseur.telephone,
+ email: fournisseur.email,
+ siret: fournisseur.siret,
+ tva_intra: fournisseur.tva_intra,
+ code_naf: fournisseur.code_naf,
+ };
+ }
+
+ }
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateFournisseur.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "Failed to update";
+ });
+
+
+ /**
+ * create contact Fournisseur
+ */
+ builder.addCase(addContactFournisseur.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(addContactFournisseur.fulfilled, (state, action) => {
+ const payload = action.payload as Contacts;
+ const idx = state.fournisseurs.findIndex((n) => n.numero === payload.numero);
+ if (idx !== -1) {
+ state.fournisseurs[idx].contacts?.unshift(payload);
+ }
+
+ if (state.fournisseurSelected?.numero === payload.numero) {
+ state.fournisseurSelected.contacts?.unshift(payload)
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(addContactFournisseur.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * update contact Fournisseur
+ */
+ builder.addCase(updateContactFournisseur.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(updateContactFournisseur.fulfilled, (state, action) => {
+ const payload = action.payload as Contacts;
+ const idx = state.fournisseurs.findIndex((n) => n.numero === payload.numero);
+ if (idx !== -1) {
+ const idxContact = state.fournisseurs[idx].contacts?.findIndex((n) => n.contact_numero === payload.contact_numero);
+ if (idxContact !== -1) {
+ state.fournisseurs[idx].contacts![idxContact || 0] = payload
+ }
+ }
+
+ if (state.fournisseurSelected?.numero === payload.numero) {
+ const idxContact = state.fournisseurSelected.contacts?.findIndex((n) => n.contact_numero === payload.contact_numero);
+ if (idxContact !== -1) {
+ state.fournisseurSelected.contacts![idxContact || 0] = payload
+ }
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(updateContactFournisseur.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+ },
+});
+
+
+export const { selectfournisseur } = fournisseurSlice.actions
+export default fournisseurSlice.reducer;
+
+
diff --git a/src/store/features/fournisseur/thunk.ts b/src/store/features/fournisseur/thunk.ts
new file mode 100644
index 0000000..0df4908
--- /dev/null
+++ b/src/store/features/fournisseur/thunk.ts
@@ -0,0 +1,58 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { Fournisseur, FournisseurRequest, FournisseurUpdateRequest } from '@/types/fournisseurType';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all fournisseurs
+ */
+export const getFournisseurs = createAsyncThunk(
+ 'fournisseur/getFournisseurs',
+ async (): Promise => {
+ try {
+ return await sageService.getFournisseurs();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * add fournisseur
+ */
+export const addFournisseur = createAsyncThunk(
+ 'fournisseur/addFournisseur',
+ async (data: FournisseurRequest): Promise => {
+ try {
+ return await sageService.addFournisseur(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * update fournisseur
+ */
+export const updateFournisseur = createAsyncThunk(
+ 'fournisseur/updateFournisseur',
+ async (
+ { numero, data }: { numero: string; data: FournisseurUpdateRequest }
+ ): Promise => {
+ try {
+ return await sageService.updateFournisseur(numero, data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
diff --git a/src/store/features/fournisseur/type.ts b/src/store/features/fournisseur/type.ts
new file mode 100644
index 0000000..00c5cf2
--- /dev/null
+++ b/src/store/features/fournisseur/type.ts
@@ -0,0 +1,11 @@
+import { Fournisseur } from "@/types/fournisseurType";
+
+
+
+export interface fournisseurState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ fournisseurs: Fournisseur[];
+ fournisseurSelected: Fournisseur | null;
+}
+
diff --git a/src/store/features/gateways/selectors.ts b/src/store/features/gateways/selectors.ts
new file mode 100644
index 0000000..734b05c
--- /dev/null
+++ b/src/store/features/gateways/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+export const getSociete = (state: RootState) => state.gateways.societe;
+export const getAllGateways = (state: RootState) => state.gateways.gateways;
+export const getgatewaysSelected = (state: RootState) => state.gateways.gatewaysSelected;
+
+
+export const gatewaysStatus = (state: RootState) => state.gateways.status;
+export const gatewaysError = (state: RootState) => state.gateways.error;
diff --git a/src/store/features/gateways/slice.ts b/src/store/features/gateways/slice.ts
new file mode 100644
index 0000000..9fc9958
--- /dev/null
+++ b/src/store/features/gateways/slice.ts
@@ -0,0 +1,123 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { gatewaysState } from "./type";
+import { Gateways, GatewaysResponse } from "@/types/gateways";
+import { addGateways, getGateways, getGatewaysById, getSociete } from "./thunk";
+
+const initialState: gatewaysState = {
+ status: "idle",
+ error: null,
+ gateways: [],
+ gatewaysSelected: null,
+ societe: null
+};
+
+const gatewaysSlice = createSlice({
+ name: "gateways",
+ initialState,
+ reducers: {
+ selectgateways: (state, action: PayloadAction) => {
+ state.gatewaysSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get societe
+ */
+ builder.addCase(getSociete.fulfilled, (state, action) => {
+ const data = action.payload
+ console.log("data : ",data);
+
+ state.societe = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getSociete.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getSociete.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * get all gateways
+ */
+ builder.addCase(getGateways.fulfilled, (state, action) => {
+ const data = action.payload
+
+ state.gateways = data.items;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getGateways.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getGateways.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+ /**
+ * get devis by id
+ */
+ builder.addCase(getGatewaysById.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getGatewaysById.fulfilled, (state, action) => {
+ const data = action.payload as Gateways;
+
+ const idx = state.gateways.findIndex((n) => n.id === data.id);
+
+ if (idx !== -1) {
+ state.gateways[idx] = data;
+ } else {
+ state.gateways.push(data);
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(getGatewaysById.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+ /**
+ * add gateways
+ */
+ builder.addCase(addGateways.fulfilled, (state, action) => {
+
+ const data = action.payload as any
+
+ state.gateways.unshift(data);
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(addGateways.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(addGateways.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+ },
+});
+
+
+export const { selectgateways } = gatewaysSlice.actions
+export default gatewaysSlice.reducer;
+
+
diff --git a/src/store/features/gateways/thunk.ts b/src/store/features/gateways/thunk.ts
new file mode 100644
index 0000000..ceaaba5
--- /dev/null
+++ b/src/store/features/gateways/thunk.ts
@@ -0,0 +1,71 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { Gateways, GatewaysRequest, GatewaysResponse } from '@/types/gateways';
+import { Societe } from '@/types/societeType';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get societe
+ */
+export const getSociete = createAsyncThunk(
+ 'gateways/getSociete',
+ async (): Promise => {
+ try {
+ return await sageService.getSociete();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get all gateways
+ */
+export const getGateways = createAsyncThunk(
+ 'gateways/getGateways',
+ async (): Promise => {
+ try {
+ return await sageService.getGateways();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * get gateways by id
+ */
+export const getGatewaysById = createAsyncThunk(
+ 'gateways/gatewaysById',
+ async (id: string): Promise => {
+ try {
+ return await sageService.getGatewaysById(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * add gateways
+ */
+export const addGateways = createAsyncThunk(
+ 'gateways/addGateways',
+ async (data: GatewaysRequest): Promise => {
+ try {
+ return await sageService.addGateways(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
diff --git a/src/store/features/gateways/type.ts b/src/store/features/gateways/type.ts
new file mode 100644
index 0000000..fa5ac3d
--- /dev/null
+++ b/src/store/features/gateways/type.ts
@@ -0,0 +1,12 @@
+import { Gateways } from "@/types/gateways";
+import { Societe } from "@/types/societeType";
+
+
+export interface gatewaysState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ gateways: Gateways[];
+ gatewaysSelected: Gateways | null;
+ societe: Societe | null;
+}
+
diff --git a/src/store/features/reglement/selectors.ts b/src/store/features/reglement/selectors.ts
new file mode 100644
index 0000000..e2d5a3d
--- /dev/null
+++ b/src/store/features/reglement/selectors.ts
@@ -0,0 +1,60 @@
+import type { RootState } from "@/store/store";
+
+
+/**
+ * REGLEMENT
+ */
+export const getAllReglements = (state: RootState) => state.reglement.reglements;
+export const getreglementSelected = (state: RootState) => state.reglement.reglementSelected;
+
+
+/**
+ * STATISTIQUE
+ */
+export const getReglementStats = (state: RootState) => state.reglement.statistiques;
+
+
+/**
+ * MODES
+ */
+export const getAllModes = (state: RootState) => state.reglement.modes;
+export const getModeSelected = (state: RootState) => state.reglement.modeSelected;
+
+/**
+ * DEVISES
+ */
+export const getAllDevises = (state: RootState) => state.reglement.devises;
+export const getDeviseSelected = (state: RootState) => state.reglement.deviseSelected;
+
+/**
+ * TRÉSORERIES
+ */
+export const getAllTresoreries = (state: RootState) => state.reglement.tresoreries;
+export const getTresorerieSelected = (state: RootState) =>
+ state.reglement.tresorerieSelected;
+
+/**
+ * COMPTES GÉNÉRAUX
+ */
+export const getAllCompteGeneraux = (state: RootState) =>
+ state.reglement.CompteGeneraux;
+export const getCompteGenerauxSelected = (state: RootState) =>
+ state.reglement.CompteGenerauxSelected;
+
+
+/**
+ * TAUX
+ */
+export const getAllTaux = (state: RootState) => state.reglement.taux;
+export const getTauxSelected = (state: RootState) => state.reglement.tauxSelected;
+
+/**
+ * ENCAISSEMENTS
+ */
+export const getAllEncaissements = (state: RootState) => state.reglement.encaissements;
+
+/**
+ * GLOBAL
+ */
+export const reglementStatus = (state: RootState) => state.reglement.status;
+export const reglementError = (state: RootState) => state.reglement.error;
\ No newline at end of file
diff --git a/src/store/features/reglement/slice.ts b/src/store/features/reglement/slice.ts
new file mode 100644
index 0000000..74a33df
--- /dev/null
+++ b/src/store/features/reglement/slice.ts
@@ -0,0 +1,226 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { createSlice, isFulfilled, isPending, isRejected, type PayloadAction } from "@reduxjs/toolkit";
+import { reglementState } from "./type";
+import { Comptes, ComptesResponse, Devise, DeviseResponse, Encaissement, EncaissementResponse, FacturesReglement, Mode, ModeResponse, ReglementsIdResponse, reglementsResponse, Statistique, Taux, TauxResponse, Tresorerie, TresorerieResponse } from "@/types/reglementType";
+import { getCompteGeneraux, getDevises, getMode, getTaux, getTresorerie, getEncaissement, loadAllReglementData, getReglement, reglerFacture, getReglementId } from "./thunk";
+
+const initialState: reglementState = {
+ status: "idle",
+ error: null,
+
+ reglements: [],
+ reglementSelected: null,
+
+ statistiques: null,
+
+ modes: [],
+ modeSelected: null,
+
+ devises: [],
+ deviseSelected: null,
+
+ tresoreries: [],
+ tresorerieSelected: null,
+
+ CompteGeneraux: [],
+ CompteGenerauxSelected: null,
+
+ taux: [],
+ tauxSelected: null,
+
+ encaissements: null,
+};
+
+const reglementSlice = createSlice({
+ name: "reglement",
+ initialState,
+
+ reducers: {
+ selectReglement: (state, action: PayloadAction) => {
+ state.reglementSelected = action.payload;
+ },
+
+ selectMode: (state, action: PayloadAction) => {
+ state.modeSelected = action.payload;
+ },
+
+ selectDevise: (state, action: PayloadAction) => {
+ state.deviseSelected = action.payload;
+ },
+
+ selectTresorerie: (state, action: PayloadAction) => {
+ state.tresorerieSelected = action.payload;
+ },
+
+ selectCompteGeneraux: (state, action: PayloadAction) => {
+ state.CompteGenerauxSelected = action.payload;
+ },
+
+ selectTaux: (state, action: PayloadAction) => {
+ state.tauxSelected = action.payload;
+ },
+
+ selectEncaissement: (state, action: PayloadAction) => {
+ state.encaissements = action.payload;
+ },
+ },
+
+ extraReducers: (builder) => {
+
+ /**
+ * regler facture
+ */
+ builder.addCase(reglerFacture.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(reglerFacture.fulfilled, (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(reglerFacture.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+
+ /**
+ * get reglement by id
+ */
+ builder.addCase(getReglementId.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getReglementId.fulfilled, (state, action) => {
+ const { success, data } = action.payload as ReglementsIdResponse;
+
+ if(success) {
+ const idx = state.reglements.findIndex((n) => n.numero === data.numero);
+ if (idx !== -1) {
+ state.reglements[idx] = data;
+ } else {
+ state.reglements.unshift(data);
+ }
+ }
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(getReglementId.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+ /**
+ * ✅ LOAD ALL DATA - Handler unifié pour charger toutes les données
+ */
+ builder.addCase(loadAllReglementData.fulfilled, (state, action) => {
+ const { reglements, modes, devises, tresoreries, comptes, taux } = action.payload;
+
+ if (reglements){
+ state.reglements = reglements.factures;
+ state.statistiques = reglements.statistiques;
+ }
+ if (modes) state.modes = modes;
+ if (devises) state.devises = devises;
+ if (tresoreries) state.tresoreries = tresoreries;
+ if (comptes) state.CompteGeneraux = comptes;
+ if (taux) state.taux = taux;
+ });
+
+ /**
+ * SUCCESS HANDLERS - Handlers individuels (optionnels, pour les appels individuels)
+ */
+ builder
+ .addCase(getReglement.fulfilled, (state, action) => {
+ const { data, success } = action.payload as reglementsResponse;
+ if (success) {
+ state.reglements = data.factures;
+ state.statistiques = data.statistiques;
+ }
+ })
+ .addCase(getMode.fulfilled, (state, action) => {
+ const { data, success } = action.payload as ModeResponse;
+ if (success) state.modes = data.modes;
+ })
+ .addCase(getDevises.fulfilled, (state, action) => {
+ const { data, success } = action.payload as DeviseResponse;
+ if (success) state.devises = data.devises;
+ })
+ .addCase(getTresorerie.fulfilled, (state, action) => {
+ const { data, success } = action.payload as TresorerieResponse;
+ if (success) state.tresoreries = data.journaux;
+ })
+ .addCase(getCompteGeneraux.fulfilled, (state, action) => {
+ const { data, success } = action.payload as ComptesResponse;
+ if (success) state.CompteGeneraux = data.comptes;
+ })
+ .addCase(getTaux.fulfilled, (state, action) => {
+ const { data, success } = action.payload as TauxResponse;
+ if (success) state.taux = data.taux;
+ })
+ .addCase(getEncaissement.fulfilled, (state, action) => {
+ const { data, success } = action.payload as EncaissementResponse;
+ if (success) state.encaissements = data;
+ });
+
+ /**
+ * GLOBAL MATCHERS
+ */
+ builder
+ .addMatcher(
+ isPending(
+ loadAllReglementData,
+ getReglement,
+ getMode,
+ getDevises,
+ getTresorerie,
+ getCompteGeneraux,
+ getTaux,
+ getEncaissement
+ ),
+ (state) => {
+ state.status = "loading";
+ state.error = null;
+ }
+ )
+ .addMatcher(
+ isFulfilled(
+ loadAllReglementData,
+ getReglement,
+ getMode,
+ getDevises,
+ getTresorerie,
+ getCompteGeneraux,
+ getTaux,
+ getEncaissement
+ ),
+ (state) => {
+ state.status = "succeeded";
+ state.error = null;
+ }
+ )
+ .addMatcher(
+ isRejected(
+ loadAllReglementData,
+ getReglement,
+ getMode,
+ getDevises,
+ getTresorerie,
+ getCompteGeneraux,
+ getTaux,
+ getEncaissement
+ ),
+ (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message ?? "An error occurred";
+ }
+ );
+ },
+});
+
+export const { selectReglement, selectMode, selectDevise, selectTresorerie, selectCompteGeneraux, selectTaux, selectEncaissement } = reglementSlice.actions;
+export default reglementSlice.reducer;
\ No newline at end of file
diff --git a/src/store/features/reglement/thunk.ts b/src/store/features/reglement/thunk.ts
new file mode 100644
index 0000000..e2058ba
--- /dev/null
+++ b/src/store/features/reglement/thunk.ts
@@ -0,0 +1,177 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { ComptesResponse, DeviseResponse, EncaissementResponse, ModeResponse, ReglementPostResponse, ReglementRequest, ReglementsIdResponse, reglementsResponse, TauxResponse, TresorerieResponse } from '@/types/reglementType';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * ✅ Thunk unifié pour charger toutes les données de règlement en parallèle
+ */
+export const loadAllReglementData = createAsyncThunk(
+ 'reglement/loadAll',
+ async (_, { rejectWithValue }) => {
+ try {
+ // Lancer tous les appels en parallèle avec Promise.all
+ const [reglementsRes, modesRes, devisesRes, tresoreriesRes, comptesRes, tauxRes] = await Promise.all([
+ sageService.getReglement().catch(() => ({ success: false, data: {} })),
+ sageService.getMode().catch(() => ({ success: false, data: { modes: [] } })),
+ sageService.getDevises().catch(() => ({ success: false, data: { devises: [] } })),
+ sageService.getTresorerie().catch(() => ({ success: false, data: { journaux: [] } })),
+ sageService.getCompteGeneraux().catch(() => ({ success: false, data: { comptes: [] } })),
+ sageService.getTaux().catch(() => ({ success: false, data: { taux: [] } })),
+ ]);
+
+ // Extraire les données de chaque réponse
+ return {
+ reglements: (reglementsRes as reglementsResponse)?.data || [],
+ modes: (modesRes as ModeResponse)?.data?.modes || [],
+ devises: (devisesRes as DeviseResponse)?.data?.devises || [],
+ tresoreries: (tresoreriesRes as TresorerieResponse)?.data?.journaux || [],
+ comptes: (comptesRes as ComptesResponse)?.data?.comptes || [],
+ taux: (tauxRes as TauxResponse)?.data?.taux || [],
+ };
+ } catch (error: any) {
+ return rejectWithValue(error.message || "Erreur lors du chargement des données de règlement");
+ }
+ }
+);
+
+
+
+
+/**
+ * regler facture
+ */
+export const reglerFacture = createAsyncThunk(
+ 'reglement/reglerFacture',
+ async (data: ReglementRequest): Promise => {
+ try {
+ return await sageService.reglerFacture(data);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+
+/**
+ * get reglement by id
+ */
+export const getReglementId = createAsyncThunk(
+ 'reglement/getReglementId',
+ async (id: string): Promise => {
+ try {
+ return await sageService.getReglementId(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * get reglement
+ */
+export const getReglement = createAsyncThunk(
+ 'reglement/getReglement',
+ async (): Promise => {
+ try {
+ return await sageService.getReglement();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * get mode
+ */
+export const getMode = createAsyncThunk(
+ 'reglement/getMode',
+ async (): Promise => {
+ try {
+ return await sageService.getMode();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get devises
+ */
+export const getDevises = createAsyncThunk(
+ 'reglement/getDevises',
+ async (): Promise => {
+ try {
+ return await sageService.getDevises();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+/**
+ * get trésorerie
+ */
+export const getTresorerie = createAsyncThunk(
+ 'reglement/getTresorerie',
+ async (): Promise => {
+ try {
+ return await sageService.getTresorerie();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+/**
+ * get comptes généraux
+ */
+export const getCompteGeneraux = createAsyncThunk(
+ 'reglement/getCompteGeneraux',
+ async (): Promise => {
+ try {
+ return await sageService.getCompteGeneraux();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+/**
+ * get taux
+ */
+export const getTaux = createAsyncThunk(
+ 'reglement/getTaux',
+ async (): Promise => {
+ try {
+ return await sageService.getTaux();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+/**
+ * get encaissement
+ */
+export const getEncaissement = createAsyncThunk(
+ 'reglement/getEncaissement',
+ async (): Promise => {
+ try {
+ return await sageService.getEncaissement();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
diff --git a/src/store/features/reglement/type.ts b/src/store/features/reglement/type.ts
new file mode 100644
index 0000000..010e651
--- /dev/null
+++ b/src/store/features/reglement/type.ts
@@ -0,0 +1,29 @@
+import { Comptes, Devise, Encaissement, FacturesReglement, Mode, Statistique, Taux, Tresorerie } from "@/types/reglementType";
+
+export interface reglementState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+
+ reglements: FacturesReglement[]
+ reglementSelected: FacturesReglement | null;
+
+ statistiques: Statistique | null;
+
+ modes: Mode[];
+ modeSelected: Mode | null;
+
+ encaissements: Encaissement | null;
+
+ devises: Devise[];
+ deviseSelected: Devise | null;
+
+ tresoreries: Tresorerie[];
+ tresorerieSelected: Tresorerie | null;
+
+ CompteGeneraux: Comptes[];
+ CompteGenerauxSelected: Comptes | null;
+
+ taux: Taux[];
+ tauxSelected: Taux | null;
+}
+
diff --git a/src/store/features/sage-builder/selectors.ts b/src/store/features/sage-builder/selectors.ts
new file mode 100644
index 0000000..f3f01c7
--- /dev/null
+++ b/src/store/features/sage-builder/selectors.ts
@@ -0,0 +1,6 @@
+// store/sageBuilder/selector.ts
+import type { RootState } from "@/store/store";
+
+export const getSageBuilderHtmlContent = (state: RootState) => state.sageBuilder.htmlContent;
+export const sageBuilderStatus = (state: RootState) => state.sageBuilder.status;
+export const sageBuilderError = (state: RootState) => state.sageBuilder.error;
\ No newline at end of file
diff --git a/src/store/features/sage-builder/slice.ts b/src/store/features/sage-builder/slice.ts
new file mode 100644
index 0000000..d7003ff
--- /dev/null
+++ b/src/store/features/sage-builder/slice.ts
@@ -0,0 +1,45 @@
+// store/sageBuilder/slice.ts
+import { createSlice } from "@reduxjs/toolkit";
+import { SageBuilderState } from "./type";
+import { getSageBuilderDashboard } from "./thunk";
+
+const initialState: SageBuilderState = {
+ status: "idle",
+ error: null,
+ htmlContent: null,
+};
+
+const sageBuilderSlice = createSlice({
+ name: "sageBuilder",
+ initialState,
+ reducers: {
+ resetSageBuilder: (state) => {
+ state.status = "idle";
+ state.error = null;
+ state.htmlContent = null;
+ }
+ },
+ extraReducers: (builder) => {
+ /**
+ * Get Sage Builder Dashboard
+ */
+ builder.addCase(getSageBuilderDashboard.fulfilled, (state, action) => {
+ state.htmlContent = action.payload;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getSageBuilderDashboard.pending, (state) => {
+ state.status = "loading";
+ state.error = null;
+ });
+
+ builder.addCase(getSageBuilderDashboard.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "Une erreur est survenue";
+ });
+ },
+});
+
+export const { resetSageBuilder } = sageBuilderSlice.actions;
+export default sageBuilderSlice.reducer;
\ No newline at end of file
diff --git a/src/store/features/sage-builder/thunk.ts b/src/store/features/sage-builder/thunk.ts
new file mode 100644
index 0000000..b2e81c7
--- /dev/null
+++ b/src/store/features/sage-builder/thunk.ts
@@ -0,0 +1,28 @@
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+/**
+ * Fetch Sage Builder Dashboard HTML
+ */
+export const getSageBuilderDashboard = createAsyncThunk(
+ 'sageBuilder/getDashboard',
+ async (): Promise => {
+ try {
+ const response = await fetch(
+ 'https://api.sage-ai-studio.webexpr.dev/api/embed/artifact/968ba24a-47d4-4ac4-b838-f1f7a60c310d?mode=light&theme=dataven',
+ {
+ headers: {
+ 'Authorization': 'Bearer sk_live_bef70d4dd6bc671fd815d21104d5dce8'
+ }
+ }
+ );
+
+ if (!response.ok) {
+ throw new Error('Erreur de chargement du dashboard');
+ }
+
+ return await response.text();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
\ No newline at end of file
diff --git a/src/store/features/sage-builder/type.ts b/src/store/features/sage-builder/type.ts
new file mode 100644
index 0000000..5cd2345
--- /dev/null
+++ b/src/store/features/sage-builder/type.ts
@@ -0,0 +1,5 @@
+export interface SageBuilderState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ htmlContent: string | null;
+}
\ No newline at end of file
diff --git a/src/store/features/universign/selectors.ts b/src/store/features/universign/selectors.ts
new file mode 100644
index 0000000..5980a48
--- /dev/null
+++ b/src/store/features/universign/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAlluniversign = (state: RootState) => state.universign.universign;
+export const getuniversignSelected = (state: RootState) => state.universign.universignSelected;
+
+
+export const universignStatus = (state: RootState) => state.universign.status;
+export const universignError = (state: RootState) => state.universign.error;
diff --git a/src/store/features/universign/slice.ts b/src/store/features/universign/slice.ts
new file mode 100644
index 0000000..5a9c188
--- /dev/null
+++ b/src/store/features/universign/slice.ts
@@ -0,0 +1,84 @@
+/* eslint-disauniversigne import/order */
+/* eslint-disauniversigne @typescript-eslint/consistent-type-imports */
+
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { universignState } from "./type";
+import { UniversignType } from "@/types/sageTypes";
+import { getUniversigns, universignId } from "./thunk";
+
+const initialState: universignState = {
+ status: "idle",
+ error: null,
+ universign: [],
+ universignSelected: null,
+};
+
+const universignSlice = createSlice({
+ name: "universign",
+ initialState,
+ reducers: {
+ selectuniversign: (state, action: PayloadAction) => {
+ state.universignSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * get all universign
+ */
+ builder.addCase(getUniversigns.fulfilled, (state, action) => {
+ const data = action.payload;
+
+ state.universign = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(getUniversigns.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(getUniversigns.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+
+
+ /**
+ * get universign by id
+ */
+ builder.addCase(universignId.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(universignId.fulfilled, (state, action) => {
+ const payload = action.payload as any;
+ const data = payload.data as UniversignType
+
+ const idx = state.universign.findIndex((n) => n.transaction_id === data.transaction_id);
+
+ if (idx !== -1) {
+ state.universign[idx] = data;
+ } else {
+ state.universign.push(data);
+ }
+
+ state.status = "succeeded";
+ state.error = null;
+ });
+
+ builder.addCase(universignId.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = (action.payload as string) || action.error.message || "An error occurred";
+ });
+
+
+ },
+});
+
+
+export const { selectuniversign } = universignSlice.actions
+export default universignSlice.reducer;
+
+
diff --git a/src/store/features/universign/thunk.ts b/src/store/features/universign/thunk.ts
new file mode 100644
index 0000000..f3efe3f
--- /dev/null
+++ b/src/store/features/universign/thunk.ts
@@ -0,0 +1,52 @@
+/* eslint-disauniversigne import/no-unresolved */
+/* eslint-disauniversigne import/order */
+
+import { sageService } from '@/service/sageService';
+import { UniversignType } from '@/types/sageTypes';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * get all universign
+ */
+export const getUniversigns = createAsyncThunk(
+ 'universign/getUniversigns',
+ async (): Promise => {
+ try {
+ return await sageService.universign();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+/**
+ * get universign by id
+ */
+export const universignId = createAsyncThunk(
+ 'universign/universignId',
+ async (id: string): Promise => {
+ try {
+ return await sageService.universignId(id);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
+
+
+/**
+ * download pdf
+ */
+export const downloadSignedDocument = createAsyncThunk(
+ 'universign/downloadSignedDocument',
+ async (transactionId: string): Promise => {
+ try {
+ return await sageService.downloadSignedDocument(transactionId);
+ } catch (err) {
+ throw err;
+ }
+ }
+);
\ No newline at end of file
diff --git a/src/store/features/universign/type.ts b/src/store/features/universign/type.ts
new file mode 100644
index 0000000..d1bf6e7
--- /dev/null
+++ b/src/store/features/universign/type.ts
@@ -0,0 +1,9 @@
+import { UniversignType } from "@/types/sageTypes";
+
+export interface universignState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ universign: UniversignType[];
+ universignSelected: UniversignType | null;
+}
+
diff --git a/src/store/features/user/selectors.ts b/src/store/features/user/selectors.ts
new file mode 100644
index 0000000..c02a091
--- /dev/null
+++ b/src/store/features/user/selectors.ts
@@ -0,0 +1,10 @@
+import type { RootState } from "@/store/store";
+
+
+
+export const getAllusers = (state: RootState) => state.user.users;
+export const getuserSelected = (state: RootState) => state.user.userSelected;
+export const getuserConnected = (state: RootState) => state.user.userConnected;
+
+export const userStatus = (state: RootState) => state.user.status;
+export const userError = (state: RootState) => state.user.error;
diff --git a/src/store/features/user/slice.ts b/src/store/features/user/slice.ts
new file mode 100644
index 0000000..24fadcb
--- /dev/null
+++ b/src/store/features/user/slice.ts
@@ -0,0 +1,54 @@
+/* eslint-disable import/order */
+/* eslint-disable @typescript-eslint/consistent-type-imports */
+import { UserInterface } from "@/types/userInterface";
+import { createSlice, type PayloadAction } from "@reduxjs/toolkit";
+import { userState } from "./type";
+import { authMe } from "./thunk";
+
+const initialState: userState = {
+ status: "idle",
+ error: null,
+ users: [],
+ userSelected: null,
+ userConnected: null
+};
+
+const userSlice = createSlice({
+ name: "user",
+ initialState,
+ reducers: {
+ selectuser: (state, action: PayloadAction) => {
+ state.userSelected = action.payload;
+ }
+ },
+ extraReducers: (builder) => {
+
+
+ /**
+ * auth me
+ */
+ builder.addCase(authMe.fulfilled, (state, action) => {
+
+ const data = action.payload;
+
+ state.userConnected = data;
+ state.error = null;
+ state.status = "succeeded";
+ });
+
+ builder.addCase(authMe.pending, (state) => {
+ state.status = "loading";
+ });
+
+ builder.addCase(authMe.rejected, (state, action) => {
+ state.status = "failed";
+ state.error = action.error.message || "An error occurred";
+ });
+ },
+});
+
+
+export const { selectuser } = userSlice.actions
+export default userSlice.reducer;
+
+
diff --git a/src/store/features/user/thunk.ts b/src/store/features/user/thunk.ts
new file mode 100644
index 0000000..8a6cdff
--- /dev/null
+++ b/src/store/features/user/thunk.ts
@@ -0,0 +1,23 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/order */
+
+
+import { sageService } from '@/service/sageService';
+import { UserInterface } from '@/types/userInterface';
+import { createAsyncThunk } from '@reduxjs/toolkit';
+
+
+/**
+ * user auth
+ */
+export const authMe = createAsyncThunk(
+ 'user/authMe',
+ async (): Promise => {
+ try {
+ return await sageService.authMe();
+ } catch (err) {
+ throw err;
+ }
+ }
+);
+
diff --git a/src/store/features/user/type.ts b/src/store/features/user/type.ts
new file mode 100644
index 0000000..9acbf17
--- /dev/null
+++ b/src/store/features/user/type.ts
@@ -0,0 +1,10 @@
+import { UserInterface } from "@/types/userInterface";
+
+export interface userState {
+ status: 'idle' | 'loading' | 'succeeded' | 'failed';
+ error: string | null;
+ users: UserInterface[];
+ userSelected: UserInterface | null;
+ userConnected: UserInterface | null;
+}
+
diff --git a/src/store/hooks.ts b/src/store/hooks.ts
new file mode 100644
index 0000000..a595ba7
--- /dev/null
+++ b/src/store/hooks.ts
@@ -0,0 +1,18 @@
+
+
+// import { useDispatch, useSelector, useStore } from 'react-redux';
+
+
+// import type { TypedUseSelectorHook } from 'react-redux';
+
+// import type { RootState, AppDispatch, AppStore } from './store';
+
+// export const useAppDispatch: () => AppDispatch = useDispatch;
+// export const useAppSelector: TypedUseSelectorHook = useSelector;
+// export const useAppStore: () => AppStore = useStore;
+
+import { useDispatch, useSelector, type TypedUseSelectorHook } from 'react-redux';
+import { RootState, store } from "./store";
+
+export const useAppDispatch: () => typeof store.dispatch = useDispatch;
+export const useAppSelector: TypedUseSelectorHook = useSelector;
\ No newline at end of file
diff --git a/src/store/hooks/articles.ts b/src/store/hooks/articles.ts
new file mode 100644
index 0000000..56de8e0
--- /dev/null
+++ b/src/store/hooks/articles.ts
@@ -0,0 +1,29 @@
+/* eslint-disable import/no-unresolved */
+
+
+import { useEffect, useState } from "react";
+
+import { useDispatch, useSelector } from "react-redux";
+
+import { getArticles } from "../features/article/thunk";
+
+
+export function useArticles() {
+ const dispatch = useDispatch();
+ const status = useSelector((state: any) => state.article.status);
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const load = async () => {
+ if (status === "idle") {
+ setLoading(true);
+ await dispatch(getArticles()).unwrap();
+ setLoading(false);
+ }
+ };
+
+ load();
+ }, [dispatch, status]);
+
+ return { loading, status };
+}
diff --git a/src/store/hooks/client.ts b/src/store/hooks/client.ts
new file mode 100644
index 0000000..edba1ce
--- /dev/null
+++ b/src/store/hooks/client.ts
@@ -0,0 +1,29 @@
+/* eslint-disable import/no-unresolved */
+
+
+import { useEffect, useState } from "react";
+
+import { useDispatch, useSelector } from "react-redux";
+
+import { getClients } from "../features/client/thunk";
+
+
+export function useClients() {
+ const dispatch = useDispatch();
+ const clientsStatus = useSelector((state: any) => state.client.status);
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const load = async () => {
+ if (clientsStatus === "idle") {
+ setLoading(true);
+ await dispatch(getClients()).unwrap();
+ setLoading(false);
+ }
+ };
+
+ load();
+ }, [dispatch, clientsStatus]);
+
+ return { loading, clientsStatus };
+}
diff --git a/src/store/hooks/commande.ts b/src/store/hooks/commande.ts
new file mode 100644
index 0000000..05e35b0
--- /dev/null
+++ b/src/store/hooks/commande.ts
@@ -0,0 +1,29 @@
+/* eslint-disable import/no-unresolved */
+
+
+import { useEffect, useState } from "react";
+
+import { useDispatch, useSelector } from "react-redux";
+
+import { getCommandes } from "../features/commande/thunk";
+
+
+export function useCommande() {
+ const dispatch = useDispatch();
+ const status = useSelector((state: any) => state.commande.status);
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const load = async () => {
+ if (status === "idle") {
+ setLoading(true);
+ await dispatch(getCommandes()).unwrap();
+ setLoading(false);
+ }
+ };
+
+ load();
+ }, [dispatch, status]);
+
+ return { loading, status };
+}
diff --git a/src/store/hooks/devis.ts b/src/store/hooks/devis.ts
new file mode 100644
index 0000000..4210407
--- /dev/null
+++ b/src/store/hooks/devis.ts
@@ -0,0 +1,29 @@
+/* eslint-disable import/no-unresolved */
+
+
+import { useEffect, useState } from "react";
+
+import { useDispatch, useSelector } from "react-redux";
+
+import { getDevisList } from "../features/devis/thunk";
+
+
+export function useDevis() {
+ const dispatch = useDispatch();
+ const status = useSelector((state: any) => state.devis.status);
+ const [loading, setLoading] = useState(false);
+
+ useEffect(() => {
+ const load = async () => {
+ if (status === "idle") {
+ setLoading(true);
+ await dispatch(getDevisList()).unwrap();
+ setLoading(false);
+ }
+ };
+
+ load();
+ }, [dispatch, status]);
+
+ return { loading, status };
+}
diff --git a/src/store/hooks/useAppData.ts b/src/store/hooks/useAppData.ts
new file mode 100644
index 0000000..3f6e7c2
--- /dev/null
+++ b/src/store/hooks/useAppData.ts
@@ -0,0 +1,252 @@
+// hooks/useAppData.ts
+
+import { useCallback, useEffect, useState } from 'react';
+import { useAppDispatch, useAppSelector } from '@/store/hooks';
+
+// Thunks
+import { getClients } from '@/store/features/client/thunk';
+import { getDevisList } from '@/store/features/devis/thunk';
+import { getArticles } from '@/store/features/article/thunk';
+import { getFactures } from '@/store/features/factures/thunk';
+import { getCommercials } from '@/store/features/commercial/thunk';
+import { getCommandes } from '@/store/features/commande/thunk';
+import { getAvoirs } from '@/store/features/avoir/thunk';
+import { getfamilles } from '@/store/features/famille/thunk';
+import { getUniversigns } from '@/store/features/universign/thunk';
+
+// Selectors pour les status
+import { clientStatus } from '@/store/features/client/selectors';
+import { devisStatus } from '@/store/features/devis/selectors';
+import { articleStatus } from '@/store/features/article/selectors';
+import { factureStatus } from '@/store/features/factures/selectors';
+import { commercialsStatus } from '@/store/features/commercial/selectors';
+import { commandeStatus } from '@/store/features/commande/selectors';
+import { avoirStatus } from '@/store/features/avoir/selectors';
+import { familleStatus } from '@/store/features/famille/selectors';
+import { universignStatus } from '@/store/features/universign/selectors';
+import { loadAllReglementData } from '../features/reglement/thunk';
+import { reglementStatus } from '../features/reglement/selectors';
+
+// Types pour les données à charger
+export type DataType =
+ | 'clients'
+ | 'devis'
+ | 'articles'
+ | 'factures'
+ | 'reglement'
+ | 'commercials'
+ | 'commandes'
+ | 'avoirs'
+ | 'familles'
+ | 'universign'
+
+// Configuration des thunks par type de données
+const dataConfig: Record = {
+ clients: { thunk: getClients, statusSelector: clientStatus },
+ devis: { thunk: getDevisList, statusSelector: devisStatus },
+ articles: { thunk: getArticles, statusSelector: articleStatus },
+ factures: { thunk: getFactures, statusSelector: factureStatus },
+ reglement: { thunk: loadAllReglementData, statusSelector: reglementStatus },
+ commercials: { thunk: getCommercials, statusSelector: commercialsStatus },
+ commandes: { thunk: getCommandes, statusSelector: commandeStatus },
+ avoirs: { thunk: getAvoirs, statusSelector: avoirStatus },
+ familles: { thunk: getfamilles, statusSelector: familleStatus },
+ universign: { thunk: getUniversigns, statusSelector: universignStatus },
+};
+
+// Données par défaut à charger sur toutes les pages
+const DEFAULT_DATA: DataType[] = [
+ 'clients',
+ 'articles',
+ 'commercials',
+ 'familles',
+];
+
+interface UseAppDataOptions {
+ include?: DataType[];
+ only?: DataType[];
+ exclude?: DataType[];
+ forceRefresh?: boolean;
+ skipInitialLoad?: boolean;
+}
+
+interface UseAppDataReturn {
+ refresh: () => Promise;
+ refreshData: (...types: DataType[]) => Promise;
+ isLoading: boolean;
+ isInitialLoading: boolean;
+ error: string | null;
+ statuses: Record;
+}
+
+export const useAppData = (options: UseAppDataOptions = {}): UseAppDataReturn => {
+ const dispatch = useAppDispatch();
+ const [isLoading, setIsLoading] = useState(false);
+ const [isInitialLoading, setIsInitialLoading] = useState(true);
+ const [error, setError] = useState(null);
+
+ // Récupérer tous les status
+ const statuses: Record = {
+ clients: useAppSelector(clientStatus),
+ devis: useAppSelector(devisStatus),
+ articles: useAppSelector(articleStatus),
+ factures: useAppSelector(factureStatus),
+ reglement: useAppSelector(reglementStatus),
+ commercials: useAppSelector(commercialsStatus),
+ commandes: useAppSelector(commandeStatus),
+ avoirs: useAppSelector(avoirStatus),
+ familles: useAppSelector(familleStatus),
+ universign: useAppSelector(universignStatus),
+ };
+
+ // Déterminer les données à charger
+ const getDataToLoad = useCallback((): DataType[] => {
+ const { include = [], only, exclude = [] } = options;
+
+ if (only) {
+ return only;
+ }
+
+ const baseData = [...DEFAULT_DATA, ...include];
+ return baseData.filter((type) => !exclude.includes(type));
+ }, [options]);
+
+ // Charger des données spécifiques
+ const loadData = useCallback(
+ async (types: DataType[], force = false) => {
+ const promises = types.map(async (type) => {
+ const config = dataConfig[type];
+ if (!config) return;
+
+ // Ne charger que si status est 'idle' ou si force est true
+ const currentStatus = statuses[type];
+ if (!force && currentStatus !== 'idle') return;
+
+ try {
+ await dispatch(config.thunk()).unwrap();
+ } catch (err) {
+ console.error(`Erreur chargement ${type}:`, err);
+ throw err;
+ }
+ });
+
+ await Promise.all(promises);
+ },
+ [dispatch, statuses]
+ );
+
+ // Rafraîchir toutes les données configurées
+ const refresh = useCallback(async () => {
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ const dataToLoad = getDataToLoad();
+ await loadData(dataToLoad, true); // Force refresh
+ } catch (err: any) {
+ setError(err.message || 'Erreur lors du rafraîchissement');
+ } finally {
+ setIsLoading(false);
+ }
+ }, [getDataToLoad, loadData]);
+
+ // Rafraîchir des données spécifiques
+ const refreshData = useCallback(
+ async (...types: DataType[]) => {
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ await loadData(types, true); // Force refresh
+ } catch (err: any) {
+ setError(err.message || 'Erreur lors du rafraîchissement');
+ } finally {
+ setIsLoading(false);
+ }
+ },
+ [loadData]
+ );
+
+ // Chargement initial au montage
+ useEffect(() => {
+ if (options.skipInitialLoad) {
+ setIsInitialLoading(false);
+ return;
+ }
+
+ const initialLoad = async () => {
+ setIsInitialLoading(true);
+ setError(null);
+
+ try {
+ const dataToLoad = getDataToLoad();
+ await loadData(dataToLoad, options.forceRefresh);
+ } catch (err: any) {
+ setError(err.message || 'Erreur lors du chargement initial');
+ } finally {
+ setIsInitialLoading(false);
+ }
+ };
+
+ initialLoad();
+ }, []); // Exécuter une seule fois au montage
+
+ return {
+ refresh,
+ refreshData,
+ isLoading,
+ isInitialLoading,
+ error,
+ statuses,
+ };
+};
+
+
+// ============================================
+// HOOKS SPÉCIALISÉS PAR PAGE
+// ============================================
+
+/** Hook pour la page Devis */
+export const useDevisData = () => {
+ return useAppData({
+ include: ['devis', 'universign'],
+ });
+};
+
+/** Hook pour la page Factures */
+export const useFacturesData = () => {
+ return useAppData({
+ include: ['factures', 'avoirs'],
+ });
+};
+
+/** Hook pour la page Commandes */
+export const useCommandesData = () => {
+ return useAppData({
+ include: ['commandes'],
+ });
+};
+
+/** Hook pour la page Articles */
+export const useArticlesData = () => {
+ return useAppData({
+ include: ['articles', 'familles'],
+ });
+};
+
+/** Hook pour le Dashboard (toutes les données) */
+export const useDashboardData = () => {
+ return useAppData({
+ include: ['devis', 'factures', 'commandes', 'avoirs', 'reglement'],
+ });
+};
+
+/** Hook pour la création de documents (devis, factures, etc.) */
+export const useDocumentCreationData = () => {
+ return useAppData({
+ only: ['clients', 'articles', 'familles', 'commercials'],
+ });
+};
+
+
+export default useAppData;
\ No newline at end of file
diff --git a/src/store/resetAction.ts b/src/store/resetAction.ts
new file mode 100644
index 0000000..fc85d38
--- /dev/null
+++ b/src/store/resetAction.ts
@@ -0,0 +1,2 @@
+export const RESET_APP = 'RESET_APP';
+export const resetApp = () => ({ type: RESET_APP });
\ No newline at end of file
diff --git a/src/store/store.ts b/src/store/store.ts
new file mode 100644
index 0000000..159ef33
--- /dev/null
+++ b/src/store/store.ts
@@ -0,0 +1,87 @@
+import { configureStore } from '@reduxjs/toolkit';
+import { combineReducers } from 'redux';
+import { persistStore, persistReducer } from 'redux-persist';
+import storage from 'redux-persist/lib/storage'; // Utilisez localStorage par défaut
+
+import clientReducer from "./features/client/slice";
+import articleReducer from "./features/article/slice";
+import devisReducer from "./features/devis/slice";
+import commandeReducer from "./features/commande/slice";
+import factureReducer from "./features/factures/slice";
+import BLReducer from "./features/bl/slice";
+import fournisseurReducer from "./features/fournisseur/slice";
+import avoirReducer from "./features/avoir/slice";
+import familleReducer from "./features/famille/slice";
+import contactReducer from "./features/contact/slice";
+import commercialsReducer from "./features/commercial/slice"
+import gatewaysReducer from "./features/gateways/slice";
+import universignReducer from "./features/universign/slice";
+import userReducer from "./features/user/slice";
+import entrepriseReducer from "./features/entreprise/slice"
+import sageBuilderReducer from "./features/sage-builder/slice";
+import reglementReducer from "./features/reglement/slice";
+
+import {
+ FLUSH,
+ REHYDRATE,
+ PAUSE,
+ PERSIST,
+ PURGE,
+ REGISTER,
+} from 'redux-persist';
+
+
+const appReducer = combineReducers({
+ client: clientReducer,
+ article: articleReducer,
+ devis: devisReducer,
+ commande: commandeReducer,
+ facture: factureReducer,
+ BL: BLReducer,
+ fournisseur: fournisseurReducer,
+ avoir: avoirReducer,
+ famille: familleReducer,
+ contact: contactReducer,
+ gateways: gatewaysReducer,
+ commercials: commercialsReducer,
+ universign: universignReducer,
+ user: userReducer,
+ entreprise: entrepriseReducer,
+ sageBuilder: sageBuilderReducer,
+ reglement: reglementReducer
+});
+
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+const rootReducer = (state: any, action: any) => {
+ if (action.type === 'RESET_APP') {
+ state = undefined;
+ }
+ return appReducer(state, action);
+};
+
+
+const persistConfig = {
+ key: 'root',
+ storage,
+};
+
+const persistedReducer = persistReducer(persistConfig, rootReducer);
+
+export const store = configureStore({
+ reducer: persistedReducer,
+ middleware: (getDefaultMiddleware) =>
+ getDefaultMiddleware({
+ serializableCheck: {
+ ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
+ },
+ }),
+});
+
+export const persistor = persistStore(store);
+
+
+export type RootState = ReturnType;
+export type AppDispatch = typeof store.dispatch;
+
+
+
diff --git a/src/types.d.ts b/src/types.d.ts
new file mode 100644
index 0000000..5e52f80
--- /dev/null
+++ b/src/types.d.ts
@@ -0,0 +1,4 @@
+declare module "*.svg" {
+ const content: string;
+ export default content;
+}
diff --git a/src/types/BL_Types.ts b/src/types/BL_Types.ts
new file mode 100644
index 0000000..8cbee1c
--- /dev/null
+++ b/src/types/BL_Types.ts
@@ -0,0 +1,82 @@
+
+export interface BL {
+ reference?: string
+ numero: string
+ date: string
+ date_livraison?: string
+ date_expedition?: string
+ client_code: string
+ client_intitule: string
+ client_adresse: string
+ client_code_postal: string
+ client_ville: string
+ client_email: string
+ client_telephone: string
+ total_ht: number
+ total_ttc: number
+ total_ht_net: number
+ statut: number
+ total_taxes_calcule: number
+ total_ht_calcule:number
+ total_ttc_calcule: number
+ valeur_frais: number
+ taxes1?: number
+ lignes?: LigneBL[]
+}
+
+
+
+export interface BLRequest {
+ client_id: string
+ reference?: string
+ date_livraison?: string
+ lignes: LigneBLRequest[]
+}
+
+
+
+export interface BLUpdateRequest {
+ reference?: string
+ date_livraison?: string
+ lignes: LigneBLRequest[]
+}
+
+
+export interface LigneBL {
+ article_code: string
+ designation?: string
+ quantite: number
+ prix_unitaire_ht?: number
+ remise_pourcentage?: number
+ montant_ligne_ht?: number
+ taux_taxe1?: number
+ montant_taxe1?: number
+ taux_taxe2?: number
+ montant_taxe2?: number
+ taux_taxe3?: number
+ montant_taxe3?: number
+ total_taxes?: number
+ famille_article?: string
+ code_barre?: string
+ poids_brut?: number
+ poids_net?: number
+}
+
+export interface LigneBLRequest {
+ article_code: string
+ quantite: number
+ prix_unitaire?: number
+ designation?: string
+ montant_ht?: number
+}
+
+
+
+export interface BLResponse {
+ success: boolean
+ data: {
+ numero_livraison: string,
+ client_id: string
+ }
+ message: string
+}
\ No newline at end of file
diff --git a/src/types/articleType.ts b/src/types/articleType.ts
new file mode 100644
index 0000000..cc94e9c
--- /dev/null
+++ b/src/types/articleType.ts
@@ -0,0 +1,102 @@
+export interface Article {
+ reference: string;
+ designation: string;
+ designation_complementaire?: string;
+
+ code_ean?: string;
+ code_barre?: string;
+
+ prix_vente: number;
+ prix_achat?: number;
+ prix_revient?: number;
+
+ stock_reel: number;
+ stock_mini?: number;
+ stock_maxi?: number;
+ stock_reserve?: number;
+ stock_commande?: number;
+ stock_disponible?: number;
+
+ description?: string;
+
+ type_article?: number;
+ type_article_libelle?: string;
+
+ famille_code?: string;
+ famille_libelle?: string;
+
+ fournisseur_principal?: string;
+ fournisseur_nom?: string;
+
+ unite_vente?: string;
+ unite_achat?: string;
+
+ poids?: number;
+ volume?: number;
+
+ est_actif?: boolean;
+ en_sommeil?: boolean;
+
+ tva_code?: string;
+ tva_taux?: number;
+
+ date_creation?: string | null;
+ publie?: boolean;
+ date_modification?: string | null;
+}
+
+
+
+export interface ArticleRequest {
+ reference: string,
+ designation: string,
+ famille: string,
+ prix_vente: number,
+ prix_achat: number,
+ stock_maxi: number,
+ code_ean: string,
+ description: string,
+ stock_reel: number;
+ stock_mini?: number;
+ publie?: boolean;
+}
+
+
+
+export interface StockRequest {
+ commentaire?: string,
+ date_entree?: string,
+ depot_code?: string,
+ lignes: [
+ {
+ article_ref: string,
+ quantite: number,
+ stock_mini: number,
+ stock_maxi: number
+ }
+ ],
+ reference?: string
+}
+
+export interface StockResponse {
+ article_ref: string,
+ numero: string,
+ type: number,
+ type_libelle: string,
+ date: string,
+ reference: string,
+ nb_lignes: number
+}
+
+export interface ArticleUpdateRequest {
+ designation: string,
+ famille: string,
+ prix_vente: number,
+ prix_achat: number,
+ stock_maxi: number,
+ code_ean: string,
+ description: string;
+ stock_reel: number;
+ stock_mini?: number;
+ publie?: boolean;
+}
diff --git a/src/types/avoirType.ts b/src/types/avoirType.ts
new file mode 100644
index 0000000..3d10ada
--- /dev/null
+++ b/src/types/avoirType.ts
@@ -0,0 +1,85 @@
+
+export interface Avoir {
+ reference?: string
+ numero: string
+ date: string
+ date_livraison?: string
+ date_expedition?: string
+ client_code: string
+ client_intitule: string
+ client_adresse: string
+ client_code_postal: string
+ client_ville: string
+ client_email: string
+ client_telephone: string
+ total_ht: number
+ total_ttc: number
+ total_ht_net: number
+ statut: number
+ total_taxes_calcule: number
+ total_ht_calcule:number
+ total_ttc_calcule: number
+ valeur_frais: number
+ taxes1?: number
+ lignes?: LigneAvoir[]
+}
+
+
+export interface LigneAvoir {
+ article_code: string
+ designation?: string
+ quantite: number
+ prix_unitaire_ht?: number
+ remise_pourcentage?: number
+ montant_ligne_ht?: number
+ taux_taxe1?: number
+ montant_taxe1?: number
+ taux_taxe2?: number
+ montant_taxe2?: number
+ taux_taxe3?: number
+ montant_taxe3?: number
+ total_taxes?: number
+ famille_article?: string
+ code_barre?: string
+ poids_brut?: number
+ poids_net?: number
+}
+
+
+export interface AvoirRequest {
+ client_id: string
+ date_avoir?: string
+ // reference?: string
+ // date_livraison?: string
+ lignes: LigneAvoirRequest[]
+}
+
+
+
+export interface AvoirUpdateRequest {
+ date_avoir?: string
+ reference?: string
+ date_livraison?: string
+ lignes: LigneAvoirRequest[]
+}
+
+
+
+export interface LigneAvoirRequest {
+ article_code: string
+ quantite: number
+ prix_unitaire?: number
+ designation?: string
+ montant_ht?: number
+}
+
+
+
+export interface AvoirResponse {
+ success: boolean
+ data: {
+ numero_avoir: string,
+ client_id: string
+ }
+ message: string
+}
\ No newline at end of file
diff --git a/src/types/clientType.ts b/src/types/clientType.ts
new file mode 100644
index 0000000..a7aa8e8
--- /dev/null
+++ b/src/types/clientType.ts
@@ -0,0 +1,350 @@
+import { Commercial } from "./commercialType"
+
+export interface Client {
+ // ============================================
+ // CHAMPS OBLIGATOIRES (sans ?)
+ // ============================================
+ numero: string
+ intitule: string
+
+ // ============================================
+ // CHAMPS OPTIONNELS (avec ?)
+ // ============================================
+ type_tiers?: number
+ qualite?: string | null
+ classement?: string
+ raccourci?: string | null
+
+ siret?: string
+ tva_intra?: string
+ code_naf?: string
+
+ contact?: string
+ adresse?: string
+ complement?: string
+ code_postal?: string
+ ville?: string
+ region?: string
+ pays?: string
+
+ telephone?: string
+ telecopie?: string
+ email?: string
+ site_web?: string
+
+ facebook?: string | null
+ linkedin?: string | null
+
+ taux01?: number
+ taux02?: number
+ taux03?: number
+ taux04?: number
+
+ statistique01?: string | null
+ statistique02?: string | null
+ statistique03?: string | null
+ statistique04?: string | null
+ statistique05?: string | null
+ statistique06?: string | null
+ statistique07?: string | null
+ statistique08?: string | null
+ statistique09?: string | null
+ statistique10?: string | null
+
+ encours_autorise?: number
+ assurance_credit?: number
+
+ langue?: number
+ commercial_code?: number
+ lettrage_auto?: boolean
+ est_actif?: boolean
+ est_prospect?: boolean
+
+ type_facture?: number
+ bl_en_facture?: number
+ saut_page?: number
+ validation_echeance?: number
+ controle_encours?: number
+
+ exclure_relance?: boolean
+ exclure_penalites?: boolean
+
+ bon_a_payer?: number
+ priorite_livraison?: number
+ livraison_partielle?: number
+ delai_transport?: number
+ delai_appro?: number
+
+ commentaire?: string | null
+
+ section_analytique?: string
+ mode_reglement_code?: number
+
+ surveillance_active?: boolean
+ coface?: string | null
+
+ forme_juridique?: string | null
+ effectif?: number | null
+
+ sv_regularite?: string | null
+ sv_cotation?: string | null
+ sv_objet_maj?: string | null
+ sv_chiffre_affaires?: number
+ sv_resultat?: number
+
+ compte_general?: string
+ categorie_tarif?: number
+ categorie_compta?: number
+
+ // Contacts
+ contacts?: Contacts[]
+
+ commercial?: Commercial
+}
+
+export interface ClientRequest {
+ // ========================================
+ // INFORMATIONS OBLIGATOIRES (69 caractères max)
+ // ========================================
+ intitule: string // 69 characters max
+
+ // ========================================
+ // IDENTIFICATION (17 caractères max)
+ // ========================================
+ numero?: string // 17 characters max
+ type_tiers?: number // [0-3] enum
+ qualite?: string | null
+ classement?: string | null
+ raccourci?: string | null
+
+ // ========================================
+ // INFORMATIONS LÉGALES
+ // ========================================
+ siret?: string | null
+ tva_intra?: string | null
+ code_naf?: string | null
+
+ // ========================================
+ // CONTACT
+ // ========================================
+ contact?: string | null
+ adresse?: string | null
+ complement?: string | null
+ code_postal?: string | null
+ ville?: string | null
+ region?: string | null
+ pays?: string | null
+ telephone?: string | null
+ telecopie?: string | null
+ email?: string | null
+ site_web?: string | null
+ portable?: string | null
+ facebook?: string | null
+ linkedin?: string | null
+
+ // ========================================
+ // CATÉGORIES & TARIFICATION
+ // ========================================
+ compte_general?: string | null
+ categorie_tarifaire?: string | null
+ categorie_comptable?: string | null
+
+ // ========================================
+ // TAUX (taux01 à taux04)
+ // ========================================
+ taux01?: number | null
+ taux02?: number | null
+ taux03?: number | null
+ taux04?: number | null
+
+ // ========================================
+ // STATISTIQUES (statistique01 à statistique10)
+ // ========================================
+ statistique01?: string | null
+ statistique02?: string | null
+ statistique03?: string | null
+ statistique04?: string | null
+ statistique05?: string | null
+ statistique06?: string | null
+ statistique07?: string | null
+ statistique08?: string | null
+ statistique09?: string | null
+ statistique10?: string | null
+
+ // ========================================
+ // DONNÉES FINANCIÈRES
+ // ========================================
+ encours_autorise?: number | null
+ assurance_credit?: number | null
+
+ // ========================================
+ // PARAMÈTRES COMMERCIAUX
+ // ========================================
+ langue?: number | null
+ commercial_code?: number | null
+ lettrage_auto?: boolean | null
+ est_actif?: boolean
+ type_facture?: number | null
+ est_prospect?: boolean
+ bl_en_facture?: number | null
+ saut_page?: number | null
+ validation_echeance?: number | null
+ controle_encours?: number | null
+
+ // ========================================
+ // RELANCES & PÉNALITÉS
+ // ========================================
+ exclure_relance?: number | null
+ exclure_penalites?: number | null
+
+ // ========================================
+ // LIVRAISON
+ // ========================================
+ bon_a_payer?: number | null
+ priorite_livraison?: number | null
+ livraison_partielle?: number | null
+ delai_transport?: number | null
+ delai_appro?: number | null
+
+ // ========================================
+ // AUTRES INFORMATIONS
+ // ========================================
+ commentaire?: string | null
+ section_analytique?: string | null
+ mode_reglement_code?: number | null
+ surveillance_active?: boolean | null
+ coface?: string | null
+ forme_juridique?: string | null
+ effectif?: number | null
+
+ // ========================================
+ // DONNÉES STATISTIQUES FINANCIÈRES
+ // ========================================
+ sv_regularite?: string | null
+ sv_cotation?: string | null
+ sv_objet_maj?: string | null
+ ca_annuel?: number | null
+ sv_chiffre_affaires?: number | null
+ sv_resultat?: number | null
+
+}
+
+export interface ClientUpdateRequest {
+ // Tous les champs sont optionnels pour l'update sauf ceux que vous voulez rendre obligatoires
+ intitule?: string
+ type_tiers?: number
+ qualite?: string | null
+ classement?: string | null
+ raccourci?: string | null
+ siret?: string | null
+ tva_intra?: string | null
+ code_naf?: string | null
+ contact?: string | null
+ adresse?: string | null
+ complement?: string | null
+ code_postal?: string | null
+ ville?: string | null
+ region?: string | null
+ pays?: string | null
+ telephone?: string | null
+ telecopie?: string | null
+ email?: string | null
+ site_web?: string | null
+ portable?: string | null
+ facebook?: string | null
+ linkedin?: string | null
+ compte_general?: string | null
+ categorie_tarifaire?: string | null
+ categorie_comptable?: string | null
+ taux01?: number | null
+ taux02?: number | null
+ taux03?: number | null
+ taux04?: number | null
+ statistique01?: string | null
+ statistique02?: string | null
+ statistique03?: string | null
+ statistique04?: string | null
+ statistique05?: string | null
+ statistique06?: string | null
+ statistique07?: string | null
+ statistique08?: string | null
+ statistique09?: string | null
+ statistique10?: string | null
+ encours_autorise?: number | null
+ assurance_credit?: number | null
+ langue?: number | null
+ commercial_code?: number | null
+ lettrage_auto?: boolean | null
+ est_actif?: boolean
+ type_facture?: number | null
+ est_prospect?: boolean
+ bl_en_facture?: number | null
+ saut_page?: number | null
+ validation_echeance?: number | null
+ controle_encours?: number | null
+ exclure_relance?: number | null
+ exclure_penalites?: number | null
+ bon_a_payer?: number | null
+ priorite_livraison?: number | null
+ livraison_partielle?: number | null
+ delai_transport?: number | null
+ delai_appro?: number | null
+ commentaire?: string | null
+ section_analytique?: string | null
+ mode_reglement_code?: number | null
+ surveillance_active?: boolean | null
+ coface?: string | null
+ forme_juridique?: string | null
+ effectif?: number | null
+ sv_regularite?: string | null
+ sv_cotation?: string | null
+ sv_objet_maj?: string | null
+ ca_annuel?: number | null
+ sv_chiffre_affaires?: number | null
+ sv_resultat?: number | null
+}
+
+export interface Contacts {
+ numero: string
+ contact_numero: number
+ n_contact: number
+ civilite: string
+ nom: string
+ prenom: string
+ fonction: string
+ service_code: number
+ telephone: string
+ portable: string | null
+ telecopie: string | null
+ email: string | null
+ facebook: string | null
+ linkedin: string | null
+ skype: string | null
+ est_defaut: boolean
+}
+
+export interface ContactRequest {
+ civilite?: string
+ nom: string
+ prenom: string
+ fonction?: string
+ service_code?: number
+ telephone?: string
+ portable?: string
+ telecopie?: string
+ email?: string
+ facebook?: string
+ linkedin?: string
+ skype?: string
+ est_defaut?: boolean
+ numero?: string
+}
+
+
+export interface ClientCreateResponse {
+ data: {
+ numero: string
+ },
+ message: string,
+ success: boolean
+}
\ No newline at end of file
diff --git a/src/types/commandeTypes.ts b/src/types/commandeTypes.ts
new file mode 100644
index 0000000..25aff8b
--- /dev/null
+++ b/src/types/commandeTypes.ts
@@ -0,0 +1,79 @@
+
+
+export interface Commande {
+ reference?: string
+ numero: string
+ date: string
+ date_livraison?: string
+ date_expedition?: string
+ client_code: string
+ client_intitule: string
+ client_adresse: string
+ client_code_postal: string
+ client_ville: string
+ client_email: string
+ client_telephone: string
+ total_ht: number
+ total_ttc: number
+ total_ht_net: number
+ statut: number
+ total_taxes_calcule: number
+ total_ht_calcule:number
+ total_ttc_calcule: number
+ valeur_frais: number
+ taxes1: number
+ lignes?: LigneCommande[]
+}
+
+
+export interface CommandeRequest {
+ client_id: string
+ date_commande?: string
+ reference?: string
+ date_livraison?: string
+ lignes: LigneCommande[]
+}
+
+
+
+export interface CommandeUpdateRequest {
+ date_commande?: string
+ date_livraison?: string
+ reference?: string
+ lignes: LigneCommande[]
+}
+
+
+
+
+export interface LigneCommande {
+ article_code: string
+ designation?: string
+ quantite: number
+ prix_unitaire_ht?: number
+ remise_pourcentage?: number
+ montant_ligne_ht?: number
+ taux_taxe1?: number
+ montant_taxe1?: number
+ taux_taxe2?: number
+ montant_taxe2?: number
+ taux_taxe3?: number
+ montant_taxe3?: number
+ total_taxes?: number
+ famille_article?: string
+ code_barre?: string
+ poids_brut?: number
+ poids_net?: number
+}
+
+
+export interface CommandeResponse {
+ success: boolean
+ data: {
+ numero_commande: string,
+ client_id: string
+ }
+ message: string
+}
+
+
diff --git a/src/types/commercialType.ts b/src/types/commercialType.ts
new file mode 100644
index 0000000..cbe5de0
--- /dev/null
+++ b/src/types/commercialType.ts
@@ -0,0 +1,57 @@
+export interface Commercial {
+ numero: number;
+ nom: string;
+ prenom: string;
+ fonction: string;
+ adresse: string;
+ complement: string | null;
+ code_postal: string;
+ ville: string;
+ region: string | null;
+ pays: string | null;
+ service: string;
+ vendeur: boolean;
+ caissier: boolean;
+ acheteur: boolean;
+ telephone: string;
+ telecopie: string | null;
+ email: string | null;
+ tel_portable: string | null;
+ matricule: string | null;
+ facebook: string | null;
+ linkedin: string | null;
+ skype: string | null;
+ est_actif: boolean;
+ chef_ventes: boolean;
+ chef_ventes_numero: number;
+}
+
+
+export interface CommercialRequest {
+ numero: number;
+ nom: string;
+ prenom: string;
+ fonction: string;
+ adresse: string;
+ complement?: string | null;
+ code_postal: string;
+ ville: string;
+ region?: string | null;
+ pays?: string | null;
+ service: string;
+ vendeur: boolean;
+ caissier: boolean;
+ acheteur: boolean;
+ telephone: string;
+ telecopie?: string | null;
+ email: string | null;
+ tel_portable?: string | null;
+ matricule?: string | null;
+ facebook?: string | null;
+ linkedin?: string | null;
+ skype?: string | null;
+ est_actif: boolean;
+ chef_ventes: boolean;
+ chef_ventes_numero: number;
+}
+
diff --git a/src/types/devisType.ts b/src/types/devisType.ts
new file mode 100644
index 0000000..4ca5ef4
--- /dev/null
+++ b/src/types/devisType.ts
@@ -0,0 +1,114 @@
+
+export interface LigneDevis {
+ article_code: string
+ quantite: number
+ prix_unitaire_ht?: number
+ remise_pourcentage?: number
+}
+
+export interface DevisRequest {
+ client_id: string
+ reference?:string
+ date_devis?: string
+ date_livraison?: string
+ lignes: LigneDevis[]
+}
+
+export interface DevisUpdateRequest {
+ date_devis?: string
+ lignes: LigneDevis[]
+ date_livraison?: string
+ reference?:string
+}
+
+export interface DevisResponse {
+ id: string
+ client_id: string
+ date_devis: string
+ montant_total_ht: number
+ montant_total_ttc: number
+ nb_lignes: number
+}
+
+export interface DevisDetail {
+ reference?: string
+ numero: string
+ date: string
+ client_code: string
+ client_intitule: string
+ total_ht: number
+ total_ttc: number
+ statut: number
+ lignes: {
+ article: string
+ designation: string
+ quantite: number
+ prix_unitaire: number
+ montant_ht: number
+ }[]
+ a_deja_ete_transforme?: boolean
+ documents_cibles?: []
+}
+
+
+export interface DevisListItem {
+ reference?: string
+ numero: string
+ date: string
+ date_livraison?: string
+ date_expedition?: string
+ client_code: string
+ client_intitule: string
+ client_adresse: string
+ client_code_postal: string
+ client_ville: string
+ client_email: string
+ client_telephone: string
+ total_ht: number
+ total_ttc: number
+ total_ht_net: number
+ statut: number
+ total_taxes_calcule: number
+ total_ht_calcule:number
+ total_ttc_calcule: number
+ valeur_frais: number
+ taxes1: number
+ lignes: {
+ article_code: string
+ designation?: string
+ quantite: number
+ prix_unitaire_ht?: number
+ remise_pourcentage?: number
+ montant_ligne_ht?: number
+ remise_valeur1?: number
+ taux_taxe1: number
+ montant_taxe1: number
+ taux_taxe2: number
+ montant_taxe2: number
+ taux_taxe3: number
+ montant_taxe3: number
+ total_taxes: number
+ famille_article: string
+ code_barre: string
+ poids_brut?: number
+ poids_net?: number
+ }[]
+ a_deja_ete_transforme?: boolean
+ documents_cibles?: []
+ isVerified?: boolean
+}
+
+
+
+
+export interface DevisStatusRequest {
+ numero: string
+ status: number
+}
+export interface DevisStatusResponse {
+ success: boolean
+ devis_id: string
+ statut_ancien: number
+ statut_nouveau: number
+ message: string
+}
diff --git a/src/types/entreprise.ts b/src/types/entreprise.ts
new file mode 100644
index 0000000..1f69c63
--- /dev/null
+++ b/src/types/entreprise.ts
@@ -0,0 +1,17 @@
+export interface Entreprise {
+ company_name: string;
+ siren: string;
+ vat_number: string;
+ address: string;
+ naf_code: string;
+ is_active: boolean;
+ siret_siege: string;
+ code_postal: string;
+ ville: string;
+}
+
+export interface EntrepriseResponse {
+ total_results: number;
+ results: Entreprise[];
+ query: string;
+}
diff --git a/src/types/factureType.ts b/src/types/factureType.ts
new file mode 100644
index 0000000..b905d03
--- /dev/null
+++ b/src/types/factureType.ts
@@ -0,0 +1,118 @@
+
+export interface Facture {
+ reference?: string
+ numero: string
+ date: string
+ date_livraison?: string
+ date_expedition?: string
+ client_code: string
+ client_intitule: string
+ client_adresse: string
+ client_code_postal: string
+ client_ville: string
+ client_email: string
+ client_telephone: string
+ valide: number
+ total_ht: number
+ total_ttc: number
+ total_ht_net: number
+ statut: number
+ total_taxes_calcule: number
+ total_ht_calcule:number
+ total_ttc_calcule: number
+ valeur_frais: number
+ taxes1?: number
+ lignes?: LigneFacture[]
+}
+
+
+
+export interface LigneFacture {
+ article_code: string
+ designation?: string
+ quantite: number
+ prix_unitaire_ht?: number
+ remise_pourcentage?: number
+ remise_valeur1?: number
+ montant_ligne_ht?: number
+ taux_taxe1?: number
+ montant_taxe1?: number
+ taux_taxe2?: number
+ montant_taxe2?: number
+ taux_taxe3?: number
+ montant_taxe3?: number
+ total_taxes?: number
+ famille_article?: string
+ code_barre?: string
+ poids_brut?: number
+ poids_net?: number
+}
+
+
+export interface FactureRequest {
+ client_id: string
+ date_livraison?: string
+ reference?: string
+ date_facture?: string
+ lignes: LigneFactureRequest[]
+}
+
+
+
+export interface FactureUpdateRequest {
+ date_facture?: string
+ date_livraison?: string
+ reference?: string
+ lignes: LigneFactureRequest[]
+}
+
+
+
+export interface LigneFactureRequest {
+ article_code: string
+ quantite: number
+ prix_unitaire?: number
+ designation?: string
+ montant_ht?: number
+}
+
+
+
+export interface FactureResponse {
+ success: boolean
+ data: {
+ numero_facture: string,
+ client_id: string
+ }
+ message: string
+}
+
+
+export interface FactureValideResponse {
+ success: boolean
+ data: {
+ numero_facture: string,
+ client_id: string
+ }
+ message: string
+}
+
+
+export interface RelanceResponse {
+ success: boolean
+ devis_id?: string
+ facture_id?: string
+ email_log_id?: string
+ destinataire: string
+ message: string
+}
+
+
+
+export interface RelanceRequest {
+ factureId: string
+ messagePersonnalise?: string
+}
+
+
+export type FactureAction = "send" | "download" | "remind" | "paid";
diff --git a/src/types/familleType.ts b/src/types/familleType.ts
new file mode 100644
index 0000000..0d9fcca
--- /dev/null
+++ b/src/types/familleType.ts
@@ -0,0 +1,36 @@
+export interface Famille {
+ code: string
+ intitule: string
+ type: number
+ type_libelle: string
+ est_total: boolean
+ coef: number
+ compte_achat: string
+ compte_vente: string
+ nature: number
+}
+
+
+
+export interface FamilleRequest {
+ code: string,
+ intitule: string,
+ compte_achat: string,
+ compte_vente: string,
+ type: 0
+}
+
+
+export interface FamilleUpdateRequest {
+ reference: string,
+ designation: string,
+ famille: string,
+ prix_vente: string,
+ prix_achat: string,
+ stock_reel: string,
+ stock_mini: string,
+ code_ean: string,
+ unite_vente: string,
+ tva_code: string,
+ description: string
+}
diff --git a/src/types/fournisseurType.ts b/src/types/fournisseurType.ts
new file mode 100644
index 0000000..f3634f4
--- /dev/null
+++ b/src/types/fournisseurType.ts
@@ -0,0 +1,91 @@
+import { Contacts } from "./clientType"
+import { Commercial } from "./commercialType"
+
+export interface Fournisseur {
+ numero: string
+ intitule: string
+ compte_collectif: string
+
+ type?: number
+ est_fournisseur?: boolean
+ est_actif?: boolean
+ en_sommeil?: boolean
+
+ adresse?: string
+ complement?: string
+ code_postal?: string
+ ville?: string
+ region?: string
+ pays?: string
+ adresse_complete?: string
+
+ telephone?: string
+ portable?: string
+ telecopie?: string
+
+ email?: string
+ site_web?: string
+
+ siret?: string
+ siren?: string
+ tva_intra?: string
+ code_naf?: string
+ forme_juridique?: string
+
+ categorie_tarifaire?: number | null
+ categorie_comptable?: number | null
+
+ conditions_reglement_code?: string
+ conditions_reglement_libelle?: string
+ mode_reglement_code?: string
+ mode_reglement_libelle?: string
+
+ coordonnees_bancaires?: any[]
+ iban_principal?: string
+ bic_principal?: string
+
+ nb_contacts?: number
+ contact_principal?: any | null
+
+ encours_autorise?: number
+ ca_annuel?: number
+ compte_general?: string
+
+ date_creation?: string
+ date_modification?: string | null
+
+ contacts?: Contacts[]
+ commercial: Commercial
+}
+
+
+export interface FournisseurRequest {
+ num: string
+ compte_collectif?: string
+ intitule: string
+ adresse?: string
+ code_postal?: string
+ ville?: string
+ email?: string
+ telephone?: string
+ pays?: string
+ siret?: string
+ tva_intra?: string
+ date_creation?: string
+}
+
+
+export interface FournisseurUpdateRequest {
+ intitule: string
+ adresse?: string
+ est_actif?: boolean
+ code_postal?: string
+ ville?: string
+ email?: string
+ telephone?: string
+ pays?: string
+ siret?: string
+ tva_intra?: string
+ date_creation?: string
+}
+
diff --git a/src/types/gateways.ts b/src/types/gateways.ts
new file mode 100644
index 0000000..27d2566
--- /dev/null
+++ b/src/types/gateways.ts
@@ -0,0 +1,50 @@
+export interface Gateways {
+ id: string;
+ user_id: string;
+
+ name: string;
+ description: string | null;
+
+ gateway_url: string;
+ token_preview: string;
+
+ sage_database: string | null;
+ sage_company: string | null;
+
+ is_active: boolean;
+ is_default: boolean;
+ priority: number;
+
+ health_status: 'unknown' | 'healthy' | 'unhealthy';
+ last_health_check: string | null;
+ last_error: string | null;
+
+ total_requests: number;
+ successful_requests: number;
+ failed_requests: number;
+ success_rate: number;
+
+ last_used_at: string | null;
+
+ extra_config: Record | null;
+ allowed_ips: string[] | null;
+
+ created_at: string;
+ updated_at: string;
+}
+
+export interface GatewaysRequest {
+ name: string;
+ description: string | null;
+ gateway_url: string;
+ gateway_token: string;
+ is_active: boolean;
+ is_default: boolean;
+}
+
+export interface GatewaysResponse {
+ items: Gateways[];
+ total: number;
+ active_gateway: string;
+ using_fallback: boolean;
+}
\ No newline at end of file
diff --git a/src/types/reglementType.ts b/src/types/reglementType.ts
new file mode 100644
index 0000000..4d0f330
--- /dev/null
+++ b/src/types/reglementType.ts
@@ -0,0 +1,226 @@
+import { Client } from "./clientType"
+
+export interface Reglements{
+ statistiques: Statistique
+ filtres: Filtres
+ factures: FacturesReglement[]
+}
+
+export interface ReglementPostResponse {
+ success: boolean,
+ message: string,
+ data: {
+ client_code: string
+ reglements: ReglementPostRes[]
+ }
+}
+export interface ReglementPostRes {
+ numero_facture: string
+ numero_reglement: string
+ client_code: string
+}
+
+
+export interface reglementsResponse{
+ success: boolean
+ data: Reglements
+}
+
+export interface ReglementsIdResponse{
+ success: boolean
+ data: FacturesReglement
+}
+
+export interface Statistique {
+ nb_factures: number,
+ nb_soldees: number,
+ nb_partiellement_reglees: number,
+ nb_non_reglees: number,
+ total_factures: number,
+ total_regle: number,
+ total_reste: number,
+}
+export interface Filtres {
+ date_debut: string,
+ date_fin: string,
+ client_code: string,
+ statut_reglement: string
+}
+
+
+export interface FacturesReglement {
+ domaine: number,
+ type_code: number,
+ type_libelle: string
+ numero: string,
+ date: string,
+ date_livraison: string,
+ reference: string,
+ souche: number,
+ date_creation: string,
+ client: Client,
+ montants: {
+ total_ttc: number,
+ montant_regle: number,
+ reste_a_regler: number
+ }
+ statut_reglement: string,
+ nb_echeances: number,
+ nb_reglements: number,
+ echeances: echeances[]
+}
+
+export interface echeances {
+ dr_no: number,
+ date_echeance: string,
+ montant: number,
+ montant_regle: number,
+ reste_a_regler: number
+ est_regle: boolean,
+ mode_reglement: {
+ code: number,
+ libelle: string
+ },
+ reference_paiement: string,
+ nb_reglements: number,
+ reglements: ReglementDetail[]
+}
+
+
+export interface ReglementDetail {
+ rg_no: number;
+ numero_piece: string;
+ date: string; // format YYYY-MM-DD
+ heure: string;
+ reference: string;
+ libelle: string;
+ montant: number;
+
+ mode_reglement: {
+ code: number;
+ libelle: string;
+ };
+
+ journal: {
+ code: string;
+ intitule: string;
+ };
+
+ banque: string;
+ est_impaye: boolean;
+ est_valide: boolean;
+ date_creation: string; // format YYYY-MM-DD HH:mm:ss
+}
+
+
+export interface ReglementRequest {
+ client_id: string;
+ code_journal: string;
+ cours_devise: number;
+ date_echeance: string;
+ date_reglement: string;
+ devise_code: number;
+ libelle: string;
+ mode_reglement: number;
+ montant_total: number;
+ numeros_factures: string[];
+ reference: string;
+ tva_encaissement: boolean;
+}
+
+
+
+export interface Mode {
+ code: number;
+ intitule: string
+ code_sage: string
+ mode_paie_debit: number
+ mode_paie_credit: number
+}
+export interface ModeResponse {
+ success: boolean;
+ data: {
+ modes: Mode[]
+ }
+}
+
+
+
+export interface Devise {
+ code: number
+ intitule: string
+ sigle: string
+ cours_actuel: number
+ est_principale: boolean
+}
+export interface DeviseResponse {
+ success: boolean;
+ data: {
+ devises: Devise[]
+ }
+}
+
+
+
+export interface Tresorerie {
+ code: string
+ intitule: string
+ compte_general: string
+ type: string
+}
+export interface TresorerieResponse {
+ success: boolean;
+ data: {
+ journaux: Tresorerie[]
+ }
+}
+
+
+
+export interface Comptes {
+ numero: string
+ intitule: string
+ raccourci: string
+ type: string
+}
+export interface ComptesResponse {
+ success: boolean;
+ data: {
+ comptes: Comptes[]
+ }
+}
+
+
+
+
+export interface Taux {
+ numero: number
+ code: string
+ intitule: string
+ taux: number
+ type_taux: number
+ type: number
+ sens: number
+ compte_general: string
+ assujetti: number
+}
+export interface TauxResponse {
+ success: boolean;
+ data: {
+ taux: Taux[]
+ }
+}
+
+
+
+export interface Encaissement {
+ tva_encaissement_regime: number
+ tva_encaissement_affectation: string
+ tva_encaissement_actif: boolean
+ devise_compte: number
+}
+export interface EncaissementResponse {
+ success: boolean;
+ data: Encaissement
+}
+
diff --git a/src/types/sageTypes.ts b/src/types/sageTypes.ts
new file mode 100644
index 0000000..5392070
--- /dev/null
+++ b/src/types/sageTypes.ts
@@ -0,0 +1,214 @@
+// ✅ Nouveau modèle pour l'envoi d'email avec CC/CCI
+export interface EmailEnvoiRequest {
+ destinataire: string
+ cc?: string[]
+ cci?: string[]
+ sujet: string
+ corps_html: string
+ document_ids?: string[]
+ type_document?: TypeDocument
+}
+
+// ✅ Mise à jour du SignatureRequest pour correspondre au backend
+// export interface SignatureRequest {
+// doc_id: string
+// type_doc: TypeDocument
+// email_signataire: string
+// nom_signataire: string
+// }
+
+export interface SignatureRequest {
+ sage_document_id: string
+ sage_document_type: TypeDocument
+ signer_email: string
+ signer_name: string
+ document_name: string
+}
+
+export interface EmailBatchRequest {
+ destinataires: string[]
+ sujet: string
+ corps_html: string
+ document_ids?: string[]
+ type_document?: TypeDocument
+}
+
+// ... (reste des types inchangés)
+
+
+
+
+
+
+export interface LigneDocument {
+ article: string
+ designation: string
+ quantite: number
+ prix_unitaire: number
+ montant_ht: number
+}
+
+export enum TypeDocument {
+ DEVIS = 0,
+ BON_LIVRAISON = 1,
+ BON_RETOUR = 2,
+ COMMANDE = 3,
+ PREPARATION = 4,
+ FACTURE = 5,
+}
+
+export enum StatutSignature {
+ EN_ATTENTE = "EN_ATTENTE",
+ ENVOYE = "ENVOYE",
+ SIGNE = "SIGNE",
+ REFUSE = "REFUSE",
+ EXPIRE = "EXPIRE",
+}
+
+export enum StatutEmail {
+ EN_ATTENTE = "EN_ATTENTE",
+ EN_COURS = "EN_COURS",
+ ENVOYE = "ENVOYE",
+ OUVERT = "OUVERT",
+ ERREUR = "ERREUR",
+ BOUNCE = "BOUNCE",
+}
+
+export interface EmailLog {
+ id: string
+ destinataire: string
+ sujet: string
+ statut: StatutEmail
+ date_creation: string
+ date_envoi?: string
+ document_ids?: string
+ nb_tentatives: number
+ derniere_erreur?: string
+}
+
+export interface CacheInfo {
+ clients: {
+ count: number
+ last_update: string | null
+ age_minutes: number | null
+ }
+ articles: {
+ count: number
+ last_update: string | null
+ age_minutes: number | null
+ }
+ ttl_minutes: number
+}
+
+export interface BaremeRemiseResponse {
+ client_id: string
+ remise_max_autorisee: number
+ remise_demandee: number
+ autorisee: boolean
+ message: string
+}
+
+export interface TemplateEmail {
+ id?: string
+ nom: string
+ sujet: string
+ corps_html: string
+ variables_disponibles: string[]
+}
+
+export interface TemplatePreview {
+ template_id: string
+ document_id: string
+ sujet: string
+ corps_html: string
+ variables_utilisees: Record
+}
+
+export interface TransformationResponse {
+ success: boolean
+ document_source: string
+ document_cible: string
+ nb_lignes: number
+}
+
+
+
+export interface responseDefault {
+ success: boolean;
+ message: string;
+}
+
+
+
+
+export interface pdfInterface {
+ type_doc: number;
+ numero: string;
+}
+
+
+
+export enum SageDocumentType {
+ DEVIS = 0,
+ BON_COMMANDE = 10,
+ PREPARATION = 20,
+ BON_LIVRAISON = 30,
+ BON_RETOUR = 40,
+ BON_AVOIR = 50,
+ FACTURE = 60,
+}
+
+export interface StatusResponse {
+ success: boolean;
+ document_id: string;
+ type_document_code: SageDocumentType;
+ statut_ancien: number;
+ statut_nouveau: number;
+ message: string;
+}
+
+export interface StatusRequest {
+ numero: string
+ status: number
+}
+
+
+
+export enum StatusDocumentType {
+ DEVIS,
+ BON_COMMANDE,
+ PREPARATION,
+ BON_LIVRAISON,
+ BON_RETOUR,
+ BON_AVOIR,
+ FACTURE,
+}
+
+
+
+export interface UniversignType {
+ id: string,
+ transaction_id: string;
+ sage_document_id: string;
+ sage_document_type: StatusDocumentType;
+ universign_status: string;
+ local_status: string;
+ local_status_label: string;
+ signer_url: string;
+ document_url: string;
+ created_at: string;
+ sent_at: string;
+ signed_at: string;
+ last_synced_at: string;
+ needs_sync: boolean;
+ signers: Signer[];
+}
+
+
+export interface Signer {
+ email: string,
+ name: string;
+ status: string;
+ signed_at: string;
+}
+
diff --git a/src/types/societeType.ts b/src/types/societeType.ts
new file mode 100644
index 0000000..3cb0ef4
--- /dev/null
+++ b/src/types/societeType.ts
@@ -0,0 +1,35 @@
+export interface Exercice {
+ numero: number;
+ debut: string;
+ fin: string;
+}
+
+export interface Societe {
+ raison_sociale: string;
+ numero_dossier: string;
+ siret: string;
+ code_ape: string;
+ numero_tva: string;
+ adresse: string;
+ complement_adresse: string;
+ code_postal: string;
+ ville: string;
+ code_region: string;
+ pays: string;
+ telephone: string;
+ telecopie: string;
+ email: string;
+ email_societe: string;
+ site_web: string;
+ capital: number;
+ forme_juridique: string;
+ exercices: Exercice[];
+
+ devise_compte: number;
+ devise_equivalent: number;
+ longueur_compte_general: number;
+ longueur_compte_analytique: number;
+ regime_fec: number;
+ base_modele: string;
+ marqueur: number;
+}
diff --git a/src/types/userInterface.ts b/src/types/userInterface.ts
new file mode 100644
index 0000000..635a031
--- /dev/null
+++ b/src/types/userInterface.ts
@@ -0,0 +1,30 @@
+export interface UserInterface {
+ id: string;
+ email: string;
+ nom: string;
+ prenom: string;
+ role: string;
+ is_verified: boolean
+ created_at: string
+ last_login: string
+}
+
+export interface loginInterface {
+ email: string;
+ password: string;
+ rememberMe: boolean;
+}
+
+export interface loginResponseInterface {
+ access_token: string;
+ refresh_token: string;
+ token_type: string;
+ expires_in: number;
+}
+
+
+export interface resetPassInterface {
+ token: string;
+ new_password: string;
+ verify_password?: string;
+}
\ No newline at end of file