From fa25518e6ba5207181ee0cdbf3f0e7ea42dc151d Mon Sep 17 00:00:00 2001 From: mickael Date: Tue, 20 Jan 2026 11:06:52 +0300 Subject: [PATCH] push all page --- .gitignore | 1 - src/App.jsx | 44 ++ src/config/apiConfig.ts | 135 +++++ src/config/moduleConfig.js | 132 +++++ src/context/BannerContext.jsx | 63 +++ src/context/CompanyContext.jsx | 46 ++ src/context/DisplayModeContext.jsx | 43 ++ src/context/ModuleContext.jsx | 59 +++ src/contexts/GlobalMessageContext.jsx | 82 +++ src/contexts/SignatureContext.jsx | 86 ++++ src/contexts/ThemeContext.jsx | 49 ++ src/data/ItemsFilter.js | 231 +++++++++ src/data/mockData.js | 373 ++++++++++++++ src/index.css | 110 ++++ src/lib/data.ts | 2 + src/lib/utils.js | 47 ++ src/main.jsx | 13 + src/providers/StoreProvider.tsx | 19 + src/routes/DatavenRoute.jsx | 179 +++++++ src/routes/ProtectedRoute.tsx | 19 + src/service/api.ts | 86 ++++ src/service/sageService.ts | 509 +++++++++++++++++++ src/service/signature/universignService.js | 122 +++++ src/store/features/article/selectors.ts | 10 + src/store/features/article/slice.ts | 162 ++++++ src/store/features/article/thunk.ts | 92 ++++ src/store/features/article/type.ts | 10 + src/store/features/avoir/selectors.ts | 10 + src/store/features/avoir/slice.ts | 133 +++++ src/store/features/avoir/thunk.ts | 94 ++++ src/store/features/avoir/type.ts | 10 + src/store/features/bl/selectors.ts | 10 + src/store/features/bl/slice.ts | 197 +++++++ src/store/features/bl/thunk.ts | 128 +++++ src/store/features/bl/type.ts | 9 + src/store/features/client/selectors.ts | 9 + src/store/features/client/slice.ts | 259 ++++++++++ src/store/features/client/thunk.ts | 90 ++++ src/store/features/client/type.ts | 11 + src/store/features/commande/selectors.ts | 10 + src/store/features/commande/slice.ts | 222 ++++++++ src/store/features/commande/thunk.ts | 140 +++++ src/store/features/commande/type.ts | 9 + src/store/features/commercial/selectors.ts | 10 + src/store/features/commercial/slice.ts | 80 +++ src/store/features/commercial/thunk.ts | 73 +++ src/store/features/commercial/type.ts | 10 + src/store/features/contact/selectors.ts | 10 + src/store/features/contact/slice.ts | 100 ++++ src/store/features/contact/thunk.ts | 94 ++++ src/store/features/contact/type.ts | 10 + src/store/features/devis/selectors.ts | 10 + src/store/features/devis/slice.ts | 230 +++++++++ src/store/features/devis/thunk.ts | 158 ++++++ src/store/features/devis/type.ts | 10 + src/store/features/entreprise/selectors.ts | 8 + src/store/features/entreprise/slice.ts | 30 ++ src/store/features/entreprise/thunk.ts | 24 + src/store/features/entreprise/type.ts | 9 + src/store/features/factures/selectors.ts | 10 + src/store/features/factures/slice.ts | 195 +++++++ src/store/features/factures/thunk.ts | 146 ++++++ src/store/features/factures/type.ts | 10 + src/store/features/famille/selectors.ts | 10 + src/store/features/famille/slice.ts | 74 +++ src/store/features/famille/thunk.ts | 39 ++ src/store/features/famille/type.ts | 10 + src/store/features/fournisseur/selectors.ts | 9 + src/store/features/fournisseur/slice.ts | 185 +++++++ src/store/features/fournisseur/thunk.ts | 58 +++ src/store/features/fournisseur/type.ts | 11 + src/store/features/gateways/selectors.ts | 10 + src/store/features/gateways/slice.ts | 123 +++++ src/store/features/gateways/thunk.ts | 71 +++ src/store/features/gateways/type.ts | 12 + src/store/features/reglement/selectors.ts | 60 +++ src/store/features/reglement/slice.ts | 226 ++++++++ src/store/features/reglement/thunk.ts | 177 +++++++ src/store/features/reglement/type.ts | 29 ++ src/store/features/sage-builder/selectors.ts | 6 + src/store/features/sage-builder/slice.ts | 45 ++ src/store/features/sage-builder/thunk.ts | 28 + src/store/features/sage-builder/type.ts | 5 + src/store/features/universign/selectors.ts | 10 + src/store/features/universign/slice.ts | 84 +++ src/store/features/universign/thunk.ts | 52 ++ src/store/features/universign/type.ts | 9 + src/store/features/user/selectors.ts | 10 + src/store/features/user/slice.ts | 54 ++ src/store/features/user/thunk.ts | 23 + src/store/features/user/type.ts | 10 + src/store/hooks.ts | 18 + src/store/hooks/articles.ts | 29 ++ src/store/hooks/client.ts | 29 ++ src/store/hooks/commande.ts | 29 ++ src/store/hooks/devis.ts | 29 ++ src/store/hooks/useAppData.ts | 252 +++++++++ src/store/resetAction.ts | 2 + src/store/store.ts | 87 ++++ src/types.d.ts | 4 + src/types/BL_Types.ts | 82 +++ src/types/articleType.ts | 102 ++++ src/types/avoirType.ts | 85 ++++ src/types/clientType.ts | 350 +++++++++++++ src/types/commandeTypes.ts | 79 +++ src/types/commercialType.ts | 57 +++ src/types/devisType.ts | 114 +++++ src/types/entreprise.ts | 17 + src/types/factureType.ts | 118 +++++ src/types/familleType.ts | 36 ++ src/types/fournisseurType.ts | 91 ++++ src/types/gateways.ts | 50 ++ src/types/reglementType.ts | 226 ++++++++ src/types/sageTypes.ts | 214 ++++++++ src/types/societeType.ts | 35 ++ src/types/userInterface.ts | 30 ++ 116 files changed, 8896 insertions(+), 1 deletion(-) create mode 100644 src/App.jsx create mode 100644 src/config/apiConfig.ts create mode 100644 src/config/moduleConfig.js create mode 100644 src/context/BannerContext.jsx create mode 100644 src/context/CompanyContext.jsx create mode 100644 src/context/DisplayModeContext.jsx create mode 100644 src/context/ModuleContext.jsx create mode 100644 src/contexts/GlobalMessageContext.jsx create mode 100644 src/contexts/SignatureContext.jsx create mode 100644 src/contexts/ThemeContext.jsx create mode 100644 src/data/ItemsFilter.js create mode 100644 src/data/mockData.js create mode 100644 src/index.css create mode 100644 src/lib/data.ts create mode 100644 src/lib/utils.js create mode 100644 src/main.jsx create mode 100644 src/providers/StoreProvider.tsx create mode 100644 src/routes/DatavenRoute.jsx create mode 100644 src/routes/ProtectedRoute.tsx create mode 100644 src/service/api.ts create mode 100644 src/service/sageService.ts create mode 100644 src/service/signature/universignService.js create mode 100644 src/store/features/article/selectors.ts create mode 100644 src/store/features/article/slice.ts create mode 100644 src/store/features/article/thunk.ts create mode 100644 src/store/features/article/type.ts create mode 100644 src/store/features/avoir/selectors.ts create mode 100644 src/store/features/avoir/slice.ts create mode 100644 src/store/features/avoir/thunk.ts create mode 100644 src/store/features/avoir/type.ts create mode 100644 src/store/features/bl/selectors.ts create mode 100644 src/store/features/bl/slice.ts create mode 100644 src/store/features/bl/thunk.ts create mode 100644 src/store/features/bl/type.ts create mode 100644 src/store/features/client/selectors.ts create mode 100644 src/store/features/client/slice.ts create mode 100644 src/store/features/client/thunk.ts create mode 100644 src/store/features/client/type.ts create mode 100644 src/store/features/commande/selectors.ts create mode 100644 src/store/features/commande/slice.ts create mode 100644 src/store/features/commande/thunk.ts create mode 100644 src/store/features/commande/type.ts create mode 100644 src/store/features/commercial/selectors.ts create mode 100644 src/store/features/commercial/slice.ts create mode 100644 src/store/features/commercial/thunk.ts create mode 100644 src/store/features/commercial/type.ts create mode 100644 src/store/features/contact/selectors.ts create mode 100644 src/store/features/contact/slice.ts create mode 100644 src/store/features/contact/thunk.ts create mode 100644 src/store/features/contact/type.ts create mode 100644 src/store/features/devis/selectors.ts create mode 100644 src/store/features/devis/slice.ts create mode 100644 src/store/features/devis/thunk.ts create mode 100644 src/store/features/devis/type.ts create mode 100644 src/store/features/entreprise/selectors.ts create mode 100644 src/store/features/entreprise/slice.ts create mode 100644 src/store/features/entreprise/thunk.ts create mode 100644 src/store/features/entreprise/type.ts create mode 100644 src/store/features/factures/selectors.ts create mode 100644 src/store/features/factures/slice.ts create mode 100644 src/store/features/factures/thunk.ts create mode 100644 src/store/features/factures/type.ts create mode 100644 src/store/features/famille/selectors.ts create mode 100644 src/store/features/famille/slice.ts create mode 100644 src/store/features/famille/thunk.ts create mode 100644 src/store/features/famille/type.ts create mode 100644 src/store/features/fournisseur/selectors.ts create mode 100644 src/store/features/fournisseur/slice.ts create mode 100644 src/store/features/fournisseur/thunk.ts create mode 100644 src/store/features/fournisseur/type.ts create mode 100644 src/store/features/gateways/selectors.ts create mode 100644 src/store/features/gateways/slice.ts create mode 100644 src/store/features/gateways/thunk.ts create mode 100644 src/store/features/gateways/type.ts create mode 100644 src/store/features/reglement/selectors.ts create mode 100644 src/store/features/reglement/slice.ts create mode 100644 src/store/features/reglement/thunk.ts create mode 100644 src/store/features/reglement/type.ts create mode 100644 src/store/features/sage-builder/selectors.ts create mode 100644 src/store/features/sage-builder/slice.ts create mode 100644 src/store/features/sage-builder/thunk.ts create mode 100644 src/store/features/sage-builder/type.ts create mode 100644 src/store/features/universign/selectors.ts create mode 100644 src/store/features/universign/slice.ts create mode 100644 src/store/features/universign/thunk.ts create mode 100644 src/store/features/universign/type.ts create mode 100644 src/store/features/user/selectors.ts create mode 100644 src/store/features/user/slice.ts create mode 100644 src/store/features/user/thunk.ts create mode 100644 src/store/features/user/type.ts create mode 100644 src/store/hooks.ts create mode 100644 src/store/hooks/articles.ts create mode 100644 src/store/hooks/client.ts create mode 100644 src/store/hooks/commande.ts create mode 100644 src/store/hooks/devis.ts create mode 100644 src/store/hooks/useAppData.ts create mode 100644 src/store/resetAction.ts create mode 100644 src/store/store.ts create mode 100644 src/types.d.ts create mode 100644 src/types/BL_Types.ts create mode 100644 src/types/articleType.ts create mode 100644 src/types/avoirType.ts create mode 100644 src/types/clientType.ts create mode 100644 src/types/commandeTypes.ts create mode 100644 src/types/commercialType.ts create mode 100644 src/types/devisType.ts create mode 100644 src/types/entreprise.ts create mode 100644 src/types/factureType.ts create mode 100644 src/types/familleType.ts create mode 100644 src/types/fournisseurType.ts create mode 100644 src/types/gateways.ts create mode 100644 src/types/reglementType.ts create mode 100644 src/types/sageTypes.ts create mode 100644 src/types/societeType.ts create mode 100644 src/types/userInterface.ts 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