diff --git a/src/pages/sales/CreditNotesPage.tsx b/src/pages/sales/CreditNotesPage.tsx index 6d5e5e7..89dbf9a 100644 --- a/src/pages/sales/CreditNotesPage.tsx +++ b/src/pages/sales/CreditNotesPage.tsx @@ -124,11 +124,11 @@ const KPI_CONFIG: KPIConfig[] = [ const previousPeriodAvoirs = getPreviousPeriodItems(allAvoirs, period); return Number(value) <= previousPeriodAvoirs.length ? 'up' : 'down'; }, - filter: (avoirs) => avoirs.filter(a => a.statut === 1), + filter: (avoirs) => avoirs }, { id: 'validated', - title: 'Avoirs Validés', + title: 'Avoirs à Facturer', icon: TrendingDown, color: 'green', getValue: (avoirs) => avoirs.filter(a => a.statut === 2).length, diff --git a/src/pages/sales/DeliveryNotesPage.tsx b/src/pages/sales/DeliveryNotesPage.tsx index 73718f9..0d6a061 100644 --- a/src/pages/sales/DeliveryNotesPage.tsx +++ b/src/pages/sales/DeliveryNotesPage.tsx @@ -54,9 +54,9 @@ const COLORS = { const BL_STATUS_LABELS = { 0: { label: 'Saisi', color: COLORS.gray }, 1: { label: 'Confirmé', color: COLORS.yellow }, - 2: { label: 'A préparer', color: COLORS.blue }, - 3: { label: 'Préparé', color: COLORS.green }, - 4: { label: 'Soldé', color: COLORS.purple }, + 2: { label: 'A Facturer', color: COLORS.blue }, + // 3: { label: 'Facturé', color: COLORS.green }, + // 4: { label: 'Soldé', color: COLORS.purple }, } as const; export type StatusCode = keyof typeof BL_STATUS_LABELS; @@ -137,14 +137,14 @@ const KPI_CONFIG: KPIConfig[] = [ const previousPeriodBL = getPreviousPeriodItems(allBLs, period); return Number(value) >= previousPeriodBL.length ? 'up' : 'down'; }, - filter: (bls) => bls.filter(b => b.statut === 1), + filter: (bls) => bls, }, { id: 'delivered', - title: 'BL Livrés', + title: 'BL à facturer', icon: CheckCircle, color: 'green', - getValue: (bls) => bls.filter(b => b.statut === 3 || b.statut === 4).length, + getValue: (bls) => bls.filter(b => b.statut === 2).length, getSubtitle: (bls) => { const delivered = bls.filter(b => b.statut === 3 || b.statut === 4); const deliveredAmount = delivered.reduce((sum, item) => sum + item.total_ht, 0); @@ -161,29 +161,7 @@ const KPI_CONFIG: KPIConfig[] = [ const previousDelivered = previousPeriodBL.filter(b => b.statut === 3 || b.statut === 4); return Number(value) >= previousDelivered.length ? 'up' : 'down'; }, - filter: (bls) => bls.filter(b => b.statut === 3 || b.statut === 4), - }, - { - id: 'toInvoice', - title: 'À Facturer', - icon: Clock, - color: 'purple', - getValue: (bls) => bls.filter(b => b.statut === 3).length, - getSubtitle: (bls) => { - const toInvoice = bls.filter(b => b.statut === 3); - const toInvoiceAmount = toInvoice.reduce((sum, item) => sum + item.total_ht, 0); - return `${toInvoiceAmount.toLocaleString('fr-FR', { maximumFractionDigits: 0 })}€`; - }, - getChange: (bls) => { - const toInvoice = bls.filter(b => b.statut === 3); - const toInvoiceAmount = toInvoice.reduce((sum, item) => sum + item.total_ht, 0); - return `${toInvoiceAmount.toLocaleString('fr-FR', { maximumFractionDigits: 0 })}€`; - }, - getTrend: (bls) => { - const toInvoice = bls.filter(b => b.statut === 3); - return toInvoice.length > 0 ? 'neutral' : 'up'; - }, - filter: (bls) => bls.filter(b => b.statut === 3), + filter: (bls) => bls.filter(b => b.statut === 2), }, { id: 'invoiced', @@ -191,16 +169,16 @@ const KPI_CONFIG: KPIConfig[] = [ icon: TrendingUp, color: 'blue', getValue: (bls) => { - const invoiced = bls.filter(b => b.statut === 4); + const invoiced = bls.filter(b => b.statut === 2); return Math.round(invoiced.reduce((sum, item) => sum + item.total_ht, 0)); }, getSubtitle: (bls) => { - const invoiced = bls.filter(b => b.statut === 4); + const invoiced = bls.filter(b => b.statut === 2); return `${invoiced.length} BL facturé${invoiced.length > 1 ? 's' : ''}`; }, getChange: (bls, value, period, allBLs) => { const previousPeriodBL = getPreviousPeriodItems(allBLs, period); - const previousInvoiced = previousPeriodBL.filter(b => b.statut === 4); + const previousInvoiced = previousPeriodBL.filter(b => b.statut === 2); const previousInvoicedAmount = previousInvoiced.reduce((sum, item) => sum + item.total_ht, 0); return previousInvoicedAmount > 0 ? (((Number(value) - previousInvoicedAmount) / previousInvoicedAmount) * 100).toFixed(1) @@ -208,11 +186,11 @@ const KPI_CONFIG: KPIConfig[] = [ }, getTrend: (bls, value, period, allBLs) => { const previousPeriodBL = getPreviousPeriodItems(allBLs, period); - const previousInvoiced = previousPeriodBL.filter(b => b.statut === 4); + const previousInvoiced = previousPeriodBL.filter(b => b.statut === 2); const previousInvoicedAmount = previousInvoiced.reduce((sum, item) => sum + item.total_ht, 0); return Number(value) >= previousInvoicedAmount ? 'up' : 'down'; }, - filter: (bls) => bls.filter(b => b.statut === 4), + filter: (bls) => bls.filter(b => b.statut === 2), tooltip: { content: 'Total des BL facturés sur la période.', source: 'Ventes > Bon de livraisons' }, }, ]; diff --git a/src/pages/sales/OrdersPage.tsx b/src/pages/sales/OrdersPage.tsx index ab3c1a5..d396386 100644 --- a/src/pages/sales/OrdersPage.tsx +++ b/src/pages/sales/OrdersPage.tsx @@ -1,6 +1,6 @@ import React, { ReactNode, useEffect, useMemo, useState } from 'react'; import { Helmet } from 'react-helmet'; -import { Plus, ShoppingCart, CheckSquare, AlertCircle, Euro, Eye, Copy } from 'lucide-react'; +import { Plus, ShoppingCart, CheckSquare, AlertCircle, Euro, Eye, Copy, X } from 'lucide-react'; import KPIBar, { PeriodType } from '@/components/KPIBar'; import PrimaryButton_v2 from '@/components/PrimaryButton_v2'; import DataTable from '@/components/DataTable'; @@ -43,6 +43,25 @@ export const STATUS_LABELS_COMMANDE = { export type StatusCodeCommande = keyof typeof STATUS_LABELS_COMMANDE; +// ============================================ +// TYPES +// ============================================ + +type FilterType = 'all' | 'count' | 'delivered' | 'pending' | 'deliveredAmount'; + +interface KPIConfig { + id: FilterType; + title: string; + icon: React.ElementType; + color: string; + getValue: (commandes: Commande[]) => number; + getSubtitle: (commandes: Commande[], value: number) => string; + getChange: (commandes: Commande[], value: number, period: PeriodType, allCommandes: Commande[]) => string; + getTrend: (commandes: Commande[], value: number, period: PeriodType, allCommandes: Commande[]) => 'up' | 'down' | 'neutral'; + filter: (commandes: Commande[]) => Commande[]; + tooltip?: { content: string; source: string }; +} + // ============================================ // CONFIGURATION DES COLONNES // ============================================ @@ -57,6 +76,140 @@ const DEFAULT_COLUMNS: ColumnConfig[] = [ { key: 'statut', label: 'Statut', visible: true }, ]; +// ============================================ +// CONFIGURATION DES KPIs +// ============================================ + +const KPI_CONFIG: KPIConfig[] = [ + { + id: 'all', + title: 'Total HT', + icon: Euro, + color: 'blue', + getValue: (commandes) => Math.round(commandes.reduce((sum, item) => sum + item.total_ht, 0)), + getSubtitle: (commandes) => { + const totalTTC = commandes.reduce((sum, item) => sum + item.total_ttc, 0); + return `${totalTTC.toLocaleString('fr-FR')}€ TTC`; + }, + getChange: (commandes, value, period, allCommandes) => { + const previousPeriodCommandes = getPreviousPeriodItems(allCommandes, period); + const previousTotalHT = previousPeriodCommandes.reduce((sum, item) => sum + item.total_ht, 0); + return previousTotalHT > 0 ? (((value - previousTotalHT) / previousTotalHT) * 100).toFixed(1) : '0'; + }, + getTrend: (commandes, value, period, allCommandes) => { + const previousPeriodCommandes = getPreviousPeriodItems(allCommandes, period); + const previousTotalHT = previousPeriodCommandes.reduce((sum, item) => sum + item.total_ht, 0); + return value >= previousTotalHT ? 'up' : 'down'; + }, + filter: (commandes) => commandes, + tooltip: { content: "Total des commandes sur la période.", source: "Ventes > Commandes" } + }, + { + id: 'count', + title: 'Nombre Commandes', + icon: ShoppingCart, + color: 'purple', + getValue: (commandes) => commandes.length, + getSubtitle: (commandes) => { + const avgCommande = commandes.length > 0 + ? commandes.reduce((sum, item) => sum + item.total_ht, 0) / commandes.length + : 0; + return `${avgCommande.toLocaleString('fr-FR', { maximumFractionDigits: 0 })}€ moyen`; + }, + getChange: (commandes, value, period, allCommandes) => { + const previousPeriodCommandes = getPreviousPeriodItems(allCommandes, period); + const countChange = value - previousPeriodCommandes.length; + return countChange !== 0 ? `${countChange > 0 ? '+' : ''}${countChange}` : '0'; + }, + getTrend: (commandes, value, period, allCommandes) => { + const previousPeriodCommandes = getPreviousPeriodItems(allCommandes, period); + return value >= previousPeriodCommandes.length ? 'up' : 'down'; + }, + filter: (commandes) => commandes, + }, + { + id: 'delivered', + title: 'Commandes à préparer', + icon: CheckSquare, + color: 'green', + getValue: (commandes) => commandes.filter(c => c.statut === 2).length, + getSubtitle: (commandes) => { + const delivered = commandes.filter(c => c.statut === 2); + const deliveredAmount = delivered.reduce((sum, item) => sum + item.total_ht, 0); + return `${deliveredAmount.toLocaleString('fr-FR', { maximumFractionDigits: 0 })}€`; + }, + getChange: (commandes, value, period, allCommandes) => { + const previousPeriodCommandes = getPreviousPeriodItems(allCommandes, period); + const previousDelivered = previousPeriodCommandes.filter(c => c.statut === 2); + const deliveredChange = value - previousDelivered.length; + return deliveredChange !== 0 ? `${deliveredChange > 0 ? '+' : ''}${deliveredChange}` : ''; + }, + getTrend: (commandes, value, period, allCommandes) => { + const previousPeriodCommandes = getPreviousPeriodItems(allCommandes, period); + const previousDelivered = previousPeriodCommandes.filter(c => c.statut === 2); + return value >= previousDelivered.length ? 'up' : 'down'; + }, + filter: (commandes) => commandes.filter(c => c.statut === 2), + }, + { + id: 'pending', + title: 'Commandes à confirmer', + icon: AlertCircle, + color: 'orange', + getValue: (commandes) => commandes.filter(c => c.statut === 1).length, + getSubtitle: (commandes) => { + const pending = commandes.filter(c => c.statut === 1); + const pendingAmount = pending.reduce((sum, item) => sum + item.total_ht, 0); + return `${pendingAmount.toLocaleString('fr-FR', { maximumFractionDigits: 0 })}€`; + }, + getChange: (commandes, value, period, allCommandes) => { + const previousPeriodCommandes = getPreviousPeriodItems(allCommandes, period); + const previousPending = previousPeriodCommandes.filter(c => c.statut === 1); + const pendingChange = value - previousPending.length; + return pendingChange !== 0 ? `${pendingChange > 0 ? '+' : ''}${pendingChange}` : ''; + }, + getTrend: (commandes, value, period, allCommandes) => { + const previousPeriodCommandes = getPreviousPeriodItems(allCommandes, period); + const previousPending = previousPeriodCommandes.filter(c => c.statut === 1); + // Pour les en cours, moins c'est mieux + return value <= previousPending.length ? 'up' : 'down'; + }, + filter: (commandes) => commandes.filter(c => c.statut === 1), + }, + { + id: 'deliveredAmount', + title: 'Montant Livré', + icon: CheckSquare, + color: 'green', + getValue: (commandes) => { + const delivered = commandes.filter(c => c.statut === 2); + return Math.round(delivered.reduce((sum, item) => sum + item.total_ht, 0)); + }, + getSubtitle: (commandes) => { + const delivered = commandes.filter(c => c.statut === 2); + return `${delivered.length} commande${delivered.length > 1 ? 's' : ''}`; + }, + getChange: (commandes, value, period, allCommandes) => { + const previousPeriodCommandes = getPreviousPeriodItems(allCommandes, period); + const previousDelivered = previousPeriodCommandes.filter(c => c.statut === 2); + const previousDeliveredValue = previousDelivered.reduce((sum, item) => sum + item.total_ht, 0); + return previousDeliveredValue > 0 ? (((value - previousDeliveredValue) / previousDeliveredValue) * 100).toFixed(1) : '0'; + }, + getTrend: (commandes, value, period, allCommandes) => { + const previousPeriodCommandes = getPreviousPeriodItems(allCommandes, period); + const previousDelivered = previousPeriodCommandes.filter(c => c.statut === 2); + const previousDeliveredValue = previousDelivered.reduce((sum, item) => sum + item.total_ht, 0); + return value >= previousDeliveredValue ? 'up' : 'down'; + }, + filter: (commandes) => commandes.filter(c => c.statut === 2), + tooltip: { content: "Total des commandes livrées sur la période.", source: "Ventes > Commandes" } + }, +]; + +// ============================================ +// COMPOSANT PRINCIPAL +// ============================================ + const OrdersPage = () => { const navigate = useNavigate(); const dispatch = useAppDispatch(); @@ -64,8 +217,8 @@ const OrdersPage = () => { const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [openStatus, setOpenStatus] = useState(false); const [activeFilters, setActiveFilters] = useState>({}); + const [activeFilter, setActiveFilter] = useState('all'); - // État des colonnes visibles const [columnConfig, setColumnConfig] = useState(DEFAULT_COLUMNS); const commandes = useAppSelector(getAllcommandes) as Commande[]; @@ -80,18 +233,14 @@ const OrdersPage = () => { const clients = useAppSelector(getAllClients) as Client[]; const statusClient = useAppSelector(clientStatus); const statusArticle = useAppSelector(articleStatus); - const statusCommercial = useAppSelector(commercialsStatus) ; + const statusCommercial = useAppSelector(commercialsStatus); useEffect(() => { const load = async () => { try { - if (statusArticle === 'idle') { - await dispatch(getArticles()).unwrap(); - } - if (statusClient === 'idle' || statusClient === 'failed') { - await dispatch(getClients()).unwrap(); - } - if (statusCommercial === "idle") await dispatch(getCommercials()).unwrap(); + if (statusArticle === 'idle') await dispatch(getArticles()).unwrap(); + if (statusClient === 'idle' || statusClient === 'failed') await dispatch(getClients()).unwrap(); + if (statusCommercial === 'idle') await dispatch(getCommercials()).unwrap(); } catch (error) { console.error(error); } @@ -108,8 +257,7 @@ const OrdersPage = () => { load(); }, [statusCommande, dispatch]); - const { refresh } = useDashboardData(); - + const { refresh } = useDashboardData(); const commercialOptions = useMemo(() => { return commercials.map(c => ({ @@ -137,29 +285,61 @@ const OrdersPage = () => { }, ]; - // ============================================ - // 1. CRÉER UNE MAP CLIENT -> COMMERCIAL (dans le composant) - // ============================================ - - // Map pour retrouver le commercial d'un client rapidement - const clientCommercialMap = useMemo(() => { - const map = new Map(); - clients.forEach(client => { - if (client.commercial?.numero) { - map.set(client.numero, client.commercial.numero.toString()); - } - }); - return map; - }, [clients]); + const clientCommercialMap = useMemo(() => { + const map = new Map(); + clients.forEach(client => { + if (client.commercial?.numero) { + map.set(client.numero, client.commercial.numero.toString()); + } + }); + return map; + }, [clients]); + // ============================================ + // KPIs avec onClick + // ============================================ + + const kpis = useMemo(() => { + const periodFilteredCommandes = filterItemByPeriod(commandes, period, 'date'); + + return KPI_CONFIG.map(config => { + const value = config.getValue(periodFilteredCommandes); + return { + id: config.id, + title: config.title, + value: config.id === 'all' || config.id === 'deliveredAmount' ? `${value.toLocaleString('fr-FR')}€` : value, + change: config.getChange(periodFilteredCommandes, value, period, commandes), + trend: config.getTrend(periodFilteredCommandes, value, period, commandes), + icon: config.icon, + subtitle: config.getSubtitle(periodFilteredCommandes, value), + color: config.color, + tooltip: config.tooltip, + isActive: activeFilter === config.id, + onClick: () => setActiveFilter(prev => (prev === config.id ? 'all' : config.id)), + }; + }); + }, [commandes, period, activeFilter]); + + // ============================================ + // Filtrage combiné : Période + KPI + Filtres avancés + // ============================================ const filteredCommandes = useMemo(() => { + // 1. Filtrer par période let result = filterItemByPeriod(commandes, period, 'date'); + // 2. Filtrer par KPI actif + const kpiConfig = KPI_CONFIG.find(k => k.id === activeFilter); + if (kpiConfig && activeFilter !== 'all') { + result = kpiConfig.filter(result); + } + + // 3. Filtres avancés (statut) if (activeFilters.status && activeFilters.status.length > 0) { result = result.filter(item => activeFilters.status!.includes(item.statut.toString())); } + // 4. Filtre par commercial if (activeFilters.rep && activeFilters.rep.length > 0) { result = result.filter(item => { const commercialCode = clientCommercialMap.get(item.client_code); @@ -167,82 +347,21 @@ const OrdersPage = () => { }); } + // 5. Tri par date décroissante return [...result].sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime()); - }, [commandes, period, activeFilters, clientCommercialMap]); - - const kpis = useMemo(() => { - const totalHT = Math.round(filteredCommandes.reduce((sum, item) => sum + item.total_ht, 0)) - const validated = filteredCommandes.filter((item: Commande) => item.statut === 2); - const pending = filteredCommandes.filter((item: Commande) => item.statut === 1); - const validatedValue = Math.round(validated.reduce((sum, item) => sum + item.total_ht, 0)) - - const previousPeriodCommandes = getPreviousPeriodItems(commandes, period); - const previousTotalHT = previousPeriodCommandes.reduce((sum, item) => sum + item.total_ht, 0); - const previousValidated = previousPeriodCommandes.filter((item: Commande) => item.statut === 2); - const previousValidatedValue = previousValidated.reduce((sum, item) => sum + item.total_ht, 0); - - const htChange = previousTotalHT > 0 ? (((totalHT - previousTotalHT) / previousTotalHT) * 100).toFixed(1) : '0'; - const htTrend = totalHT >= previousTotalHT ? 'up' : 'down'; - - const countChange = filteredCommandes.length - previousPeriodCommandes.length; - const countTrend = countChange >= 0 ? 'up' : 'down'; - - const validatedChange = previousValidated.length > 0 ? validated.length - previousValidated.length : 0; - const validatedTrend = validatedChange >= 0 ? 'up' : 'down'; - - const pendingChange = previousPeriodCommandes.filter((item: Commande) => item.statut === 1).length; - const pendingDiff = pending.length - pendingChange; - const pendingTrend = pendingDiff >= 0 ? 'up' : 'down'; - - const validatedValueChange = - previousValidatedValue > 0 ? (((validatedValue - previousValidatedValue) / previousValidatedValue) * 100).toFixed(1) : '0'; - const validatedValueTrend = validatedValue >= previousValidatedValue ? 'up' : 'down'; - - return [ - { - title: 'Total HT', - value: `${totalHT.toLocaleString('fr-FR')}€`, - change: `${htTrend === 'up' ? '+' : ''}${htChange}%`, - trend: htTrend, - icon: Euro, - tooltip: { content: "Total des commandes sur la période.", source: "Ventes > Commandes" } - }, - { - title: 'Nombre Commandes', - value: filteredCommandes.length, - change: countChange !== 0 ? `${countChange > 0 ? '+' : ''}${countChange}` : '0', - trend: countTrend, - icon: ShoppingCart, - }, - { - title: 'Livrées', - value: validated.length, - change: validatedChange !== 0 ? `${validatedChange > 0 ? '+' : ''}${validatedChange}` : '', - trend: validatedTrend, - icon: CheckSquare, - }, - { - title: 'En Cours', - value: pending.length, - change: pendingDiff !== 0 ? `${pendingDiff > 0 ? '+' : ''}${pendingDiff}` : '', - trend: pendingTrend, - icon: AlertCircle, - }, - { - title: 'Montant Livré', - value: `${validatedValue.toLocaleString('fr-FR')}€`, - change: `${validatedValueTrend === 'up' ? '+' : ''}${validatedValueChange}%`, - trend: validatedValueTrend, - icon: CheckSquare, - tooltip: { content: "Total des commandes livrées sur la période.", source: "Ventes > Commandes" } - }, - ]; - }, [filteredCommandes, commandes, period]); + }, [commandes, period, activeFilter, activeFilters, clientCommercialMap]); // ============================================ - // COLONNES DYNAMIQUES + // Label du filtre actif // ============================================ + const activeFilterLabel = useMemo(() => { + const config = KPI_CONFIG.find(k => k.id === activeFilter); + return config?.title || 'Tous'; + }, [activeFilter]); + + // ... reste du code inchangé (allColumnsDefinition, visibleColumns, columnFormatters, onDuplicate, actions, etc.) + const allColumnsDefinition = useMemo(() => { const clientsMap = new Map(clients.map(c => [c.numero, c])); @@ -302,7 +421,6 @@ const OrdersPage = () => { }; }, [clients]); - // Colonnes filtrées selon la config const visibleColumns = useMemo(() => { return columnConfig .filter(col => col.visible) @@ -310,44 +428,18 @@ const OrdersPage = () => { .filter(Boolean); }, [columnConfig, allColumnsDefinition]); - - // ============================================ - // DÉFINIR LES FORMATEURS POUR L'EXPORT - // ============================================ - - // Formateurs personnalisés pour transformer les valeurs à l'export const columnFormatters: Record string> = { - date: (value) => { - if (!value) return ''; - return formatDateFRCourt(value); - }, - total_ht_calcule: (value) => { - if (value === null || value === undefined) return '0 €'; - return `${value.toLocaleString('fr-FR', { minimumFractionDigits: 2 })} €`; - }, - total_taxes_calcule: (value) => { - if (value === null || value === undefined) return '0 €'; - return `${value.toLocaleString('fr-FR', { minimumFractionDigits: 2 })} €`; - }, - total_ttc_calcule: (value) => { - if (value === null || value === undefined) return '0 €'; - return `${value.toLocaleString('fr-FR', { minimumFractionDigits: 2 })} €`; - }, - statut: (value) => { - return STATUS_LABELS_COMMANDE[value as StatusCodeCommande]?.label || 'Inconnu'; - }, - client_intitule: (value, row) => { - // Si client_intitule n'existe pas, on peut le récupérer depuis la map des clients - return value || row.client_code || ''; - }, + date: value => (value ? formatDateFRCourt(value) : ''), + total_ht_calcule: value => (value === null || value === undefined ? '0 €' : `${value.toLocaleString('fr-FR', { minimumFractionDigits: 2 })} €`), + total_taxes_calcule: value => (value === null || value === undefined ? '0 €' : `${value.toLocaleString('fr-FR', { minimumFractionDigits: 2 })} €`), + total_ttc_calcule: value => (value === null || value === undefined ? '0 €' : `${value.toLocaleString('fr-FR', { minimumFractionDigits: 2 })} €`), + statut: value => STATUS_LABELS_COMMANDE[value as StatusCodeCommande]?.label || 'Inconnu', + client_intitule: (value, row) => value || row.client_code || '', }; - - - + const onDuplicate = async () => { try { setLoading(true); - const payloadCreate: CommandeRequest = { client_id: commandeSelected.client_code, date_commande: (() => { @@ -361,16 +453,12 @@ const OrdersPage = () => { })), reference: commandeSelected.reference, }; - const result = (await dispatch(createCommande(payloadCreate)).unwrap()) as CommandeResponse; const data = result.data; - await new Promise(resolve => setTimeout(resolve, 1500)); - const itemCreated = (await dispatch(getCommande(data.numero_commande)).unwrap()) as any; const res = itemCreated as Commande; dispatch(selectcommande(res)); - toast({ title: 'Commande dupliquée avec succès !', description: `La commande ${commandeSelected.numero} a été dupliquée avec succès.`, @@ -441,28 +529,37 @@ const OrdersPage = () => {
-

Commandes

-

{filteredCommandes.length} commandes

+
+

Commandes

+ {/* Badge filtre actif */} + {activeFilter !== 'all' && ( + + )} +
+

+ {filteredCommandes.length} commande{filteredCommandes.length > 1 ? 's' : ''} + {activeFilter !== 'all' && ` (${activeFilterLabel.toLowerCase()})`} +

- {/* Sélecteur de colonnes */} - + { - setActiveFilters(prev => ({ - ...prev, - [key]: values, - })); + setActiveFilters(prev => ({ ...prev, [key]: values })); }} onReset={() => setActiveFilters({})} /> diff --git a/src/pages/sales/QuotesPage.tsx b/src/pages/sales/QuotesPage.tsx index b5590c7..b210588 100644 --- a/src/pages/sales/QuotesPage.tsx +++ b/src/pages/sales/QuotesPage.tsx @@ -137,10 +137,20 @@ const KPI_CONFIG: KPIConfig[] = [ }, { id: 'pending', - title: 'Nombre de Devis', + title: 'Nombre de confirmé', icon: FileText, color: 'orange', - getValue: (devis) => devis.length, + // getValue: (devis) => { + // const avgDevis = devis.length > 0 + // ? devis.reduce((sum, item) => sum + item.total_ht, 0) / devis.length + // : 0; + // return avgDevis.toString().length; + + // }, + getValue: (devis) => { + const devisConfirmed = devis.filter(d => d.statut === 1); + return devisConfirmed.length + }, getSubtitle: (devis) => { const avgDevis = devis.length > 0 ? devis.reduce((sum, item) => sum + item.total_ht, 0) / devis.length @@ -158,34 +168,41 @@ const KPI_CONFIG: KPIConfig[] = [ }, filter: (devis) => devis.filter(d => d.statut === 1), }, - { - id: 'signed', - title: 'Devis Signés', - icon: Percent, - color: 'purple', - getValue: (devis, universign) => { - return universign.filter(u => u.local_status === "SIGNE").length; - }, - getSubtitle: (devis, value, universign) => { - const pending = devis.filter(d => d.statut === 1).length; - return `${pending} en attente`; - }, - getChange: (devis, value) => { - return `${value}/${devis.length}`; - }, - getTrend: (devis, value) => { - const acceptanceRate = devis.length > 0 ? (value / devis.length) * 100 : 0; - return acceptanceRate >= 50 ? 'up' : 'down'; - }, - filter: (devis, universign) => { - const signedDocIds = new Set( - universign - .filter(u => u.local_status === "SIGNE") - .map(u => u.sage_document_id) - ); - return devis.filter(d => signedDocIds.has(d.numero)); - }, + { + id: 'signed', + title: 'Devis Signés', + icon: Percent, + color: 'purple', + getValue: (devis, universign) => { + // Créer un Set des IDs de documents signés + const signedDocIds = new Set( + universign + .filter(u => u.local_status === "SIGNE") + .map(u => u.sage_document_id) + ); + // Compter uniquement les devis de la période qui sont signés + return devis.filter(d => signedDocIds.has(d.numero)).length; }, + getSubtitle: (devis, value, universign) => { + const pending = devis.filter(d => d.statut === 1).length; + return `${pending} en attente`; + }, + getChange: (devis, value) => { + return `${value}/${devis.length}`; + }, + getTrend: (devis, value) => { + const acceptanceRate = devis.length > 0 ? (value / devis.length) * 100 : 0; + return acceptanceRate >= 50 ? 'up' : 'down'; + }, + filter: (devis, universign) => { + const signedDocIds = new Set( + universign + .filter(u => u.local_status === "SIGNE") + .map(u => u.sage_document_id) + ); + return devis.filter(d => signedDocIds.has(d.numero)); + }, +}, ]; // ============================================ @@ -318,12 +335,13 @@ const QuotesPage = () => { let result = filterItemByPeriod(devis, period, 'date'); result = result.filter(item => item.numero !== 'DE00126'); + // 2. Filtrer par KPI actif const kpiConfig = KPI_CONFIG.find(k => k.id === activeFilter); if (kpiConfig && activeFilter !== 'all') { result = kpiConfig.filter(result, universign); } - + // 3. Filtres avancés (statut) if (activeFilters.status && activeFilters.status.length > 0) { result = result.filter(item => @@ -345,6 +363,7 @@ const QuotesPage = () => { ); }, [devis, period, activeFilter, activeFilters, clientCommercialMap, universign]); + // ============================================ // Label du filtre actif // ============================================ diff --git a/src/service/api.ts b/src/service/api.ts index 3319dfe..91c4183 100644 --- a/src/service/api.ts +++ b/src/service/api.ts @@ -14,6 +14,7 @@ const apiService = axios.create({ baseURL: BackUrl, headers: { Authorization: access_token ? `Bearer ${access_token}` : "dsqd", + "X-API-Key": "sdk_live_TeeZGyvg34H-nIR7tBhyiemAfIV0MbT2HQ1BzG9bofE" } })