import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useToast } from "@/hooks/use-toast"; import { apiRequest } from "@/lib/queryClient"; import { isUnauthorizedError } from "@/lib/authUtils"; import { Plus, Search, Users, Edit, Trash2, Package, Truck, Palette } from "lucide-react"; import type { Group, NocodbConfig } from "@shared/schema"; const colorOptions = [ { value: '#1976D2', label: 'Bleu' }, { value: '#388E3C', label: 'Vert' }, { value: '#FF6F00', label: 'Orange' }, { value: '#D32F2F', label: 'Rouge' }, { value: '#7B1FA2', label: 'Violet' }, { value: '#F57C00', label: 'Ambre' }, { value: '#5D4037', label: 'Marron' }, { value: '#455A64', label: 'Bleu gris' }, { value: '#E91E63', label: 'Rose' }, { value: '#00796B', label: 'Sarcelle' }, ]; export default function Groups() { const { user } = useAuthUnified(); const { toast } = useToast(); const queryClient = useQueryClient(); const [searchTerm, setSearchTerm] = useState(""); const [showCreateModal, setShowCreateModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false); const [selectedGroup, setSelectedGroup] = useState(null); const [formData, setFormData] = useState({ name: "", color: "#1976D2", nocodbConfigId: "", nocodbTableName: "", invoiceColumnName: "", nocodbBlColumnName: "", nocodbAmountColumnName: "", nocodbSupplierColumnName: "", webhookUrl: "", }); const { data: groups = [], isLoading } = useQuery({ queryKey: ['/api/groups'], }); const { data: rawNocodbConfigs = [] } = useQuery({ queryKey: ['/api/nocodb-config'], }); // Protection renforcée contre les erreurs TypeError const nocodbConfigs = Array.isArray(rawNocodbConfigs) ? rawNocodbConfigs : []; console.log('🔍 Groups NocoDB Debug:', { rawNocodbConfigs, rawType: typeof rawNocodbConfigs, nocodbConfigs, configsType: typeof nocodbConfigs, isArray: Array.isArray(rawNocodbConfigs), length: nocodbConfigs.length, environment: window.location.hostname }); const { data: orders = [] } = useQuery({ queryKey: ['/api/orders'], }); const { data: deliveries = [] } = useQuery({ queryKey: ['/api/deliveries'], }); const createMutation = useMutation({ mutationFn: async (data: any) => { console.log('🏪 Frontend: Creating group with data:', data); const result = await apiRequest("/api/groups", "POST", data); console.log('🏪 Frontend: Group creation result:', result); return result; }, onSuccess: () => { toast({ title: "Succès", description: "Groupe créé avec succès", }); queryClient.invalidateQueries({ queryKey: ['/api/groups'] }); setShowCreateModal(false); setFormData({ name: "", color: "#1976D2", nocodbConfigId: "", nocodbTableId: "", nocodbTableName: "", invoiceColumnName: "Ref Facture", }); }, onError: (error) => { if (isUnauthorizedError(error)) { toast({ title: "Non autorisé", description: "Vous êtes déconnecté. Reconnexion...", variant: "destructive", }); setTimeout(() => { window.location.href = "/api/login"; }, 500); return; } toast({ title: "Erreur", description: "Impossible de créer le groupe", variant: "destructive", }); }, }); const updateMutation = useMutation({ mutationFn: async (data: any) => { await apiRequest(`/api/groups/${selectedGroup?.id}`, "PUT", data); }, onSuccess: () => { toast({ title: "Succès", description: "Groupe modifié avec succès", }); queryClient.invalidateQueries({ queryKey: ['/api/groups'] }); setShowEditModal(false); setSelectedGroup(null); setFormData({ name: "", color: "#1976D2", nocodbConfigId: "", nocodbTableId: "", nocodbTableName: "", invoiceColumnName: "Ref Facture", }); }, onError: (error) => { if (isUnauthorizedError(error)) { toast({ title: "Non autorisé", description: "Vous êtes déconnecté. Reconnexion...", variant: "destructive", }); setTimeout(() => { window.location.href = "/api/login"; }, 500); return; } toast({ title: "Erreur", description: "Impossible de modifier le groupe", variant: "destructive", }); }, }); const deleteMutation = useMutation({ mutationFn: async (id: number) => { await apiRequest(`/api/groups/${id}`, "DELETE"); }, onSuccess: () => { toast({ title: "Succès", description: "Groupe supprimé avec succès", }); queryClient.invalidateQueries({ queryKey: ['/api/groups'] }); }, onError: (error) => { if (isUnauthorizedError(error)) { toast({ title: "Non autorisé", description: "Vous êtes déconnecté. Reconnexion...", variant: "destructive", }); setTimeout(() => { window.location.href = "/api/login"; }, 500); return; } toast({ title: "Erreur", description: "Impossible de supprimer le groupe", variant: "destructive", }); }, }); const filteredGroups = Array.isArray(groups) ? groups.filter(group => group.name.toLowerCase().includes(searchTerm.toLowerCase()) ) : []; console.log('🏪 Groups Debug:', { groups: Array.isArray(groups) ? groups.length : 'NOT_ARRAY', orders: Array.isArray(orders) ? orders.length : 'NOT_ARRAY', deliveries: Array.isArray(deliveries) ? deliveries.length : 'NOT_ARRAY', groupsData: groups?.slice(0, 2), ordersData: orders?.slice(0, 2), deliveriesData: deliveries?.slice(0, 2) }); const getGroupStats = (groupId: number) => { const groupOrders = Array.isArray(orders) ? orders.filter(order => { const match = order.groupId === groupId; if (match) console.log(`🎯 Order ${order.id} matches group ${groupId}`, order); return match; }) : []; const groupDeliveries = Array.isArray(deliveries) ? deliveries.filter(delivery => { const match = delivery.groupId === groupId; if (match) console.log(`🎯 Delivery ${delivery.id} matches group ${groupId}`, delivery); return match; }) : []; console.log(`🏪 Group ${groupId} stats:`, { groupOrders: groupOrders.length, groupDeliveries: groupDeliveries.length, delivered: groupDeliveries.filter(d => d.status === 'delivered').length, ordersSample: groupOrders.slice(0, 2), deliveriesSample: groupDeliveries.slice(0, 2), allOrdersCount: orders?.length, allDeliveriesCount: deliveries?.length }); return { orders: groupOrders.length, deliveries: groupDeliveries.length, delivered: groupDeliveries.filter(d => d.status === 'delivered').length, }; }; const handleCreate = () => { setFormData({ name: "", color: "#1976D2", nocodbConfigId: "", nocodbTableName: "", invoiceColumnName: "", nocodbBlColumnName: "", nocodbAmountColumnName: "", nocodbSupplierColumnName: "", webhookUrl: "", }); setShowCreateModal(true); }; const handleEdit = (group: Group) => { setSelectedGroup(group); setFormData({ name: group.name, color: group.color || "#1976D2", nocodbConfigId: group.nocodbConfigId?.toString() || "", nocodbTableName: group.nocodbTableName || "", invoiceColumnName: group.invoiceColumnName || "", nocodbBlColumnName: group.nocodbBlColumnName || "", nocodbAmountColumnName: group.nocodbAmountColumnName || "", nocodbSupplierColumnName: group.nocodbSupplierColumnName || "", webhookUrl: group.webhookUrl || "", }); setShowEditModal(true); }; const handleDelete = (group: Group) => { const stats = getGroupStats(group.id); if (stats.orders > 0 || stats.deliveries > 0) { toast({ title: "Suppression impossible", description: "Ce groupe a des commandes ou livraisons associées", variant: "destructive", }); return; } if (window.confirm(`Êtes-vous sûr de vouloir supprimer le groupe "${group.name}" ?`)) { deleteMutation.mutate(group.id); } }; const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); if (!formData.name.trim()) { toast({ title: "Erreur", description: "Le nom du groupe est requis", variant: "destructive", }); return; } // Prepare data with proper type conversion const submitData = { ...formData, nocodbConfigId: formData.nocodbConfigId ? parseInt(formData.nocodbConfigId) : null, }; if (selectedGroup) { updateMutation.mutate(submitData); } else { createMutation.mutate(submitData); } }; const handleChange = (field: string, value: string) => { setFormData(prev => ({ ...prev, [field]: value })); }; const canManage = user?.role === 'admin' || user?.role === 'manager'; return (
{/* Header */}

Gestion des Groupes/Magasins

{filteredGroups.length} groupe{filteredGroups.length !== 1 ? 's' : ''}

{canManage && ( )}
{/* Search */}
setSearchTerm(e.target.value)} className="pl-10 border border-gray-300 shadow-sm" />
{/* Groups List */}
{isLoading ? (
) : filteredGroups.length === 0 ? (

{searchTerm ? "Aucun groupe trouvé" : "Aucun groupe"}

{searchTerm ? "Aucun groupe ne correspond à votre recherche." : "Vous n'avez pas encore de groupes. Créez votre premier groupe pour commencer."}

{!searchTerm && canManage && ( )}
) : (
{filteredGroups.map((group) => { const stats = getGroupStats(group.id); return (

{group.name}

#{group.id}

{canManage && (
)}
{group.color}
{stats.orders}

Commandes

{stats.deliveries}

Livraisons

{stats.deliveries > 0 && (

{stats.delivered} livrées ({Math.round((stats.delivered / stats.deliveries) * 100)}%)

)}
); })}
)}
{/* Create/Edit Modal */} { setShowCreateModal(false); setShowEditModal(false); setSelectedGroup(null); setFormData({ name: "", color: "#1976D2", nocodbConfigId: "", nocodbTableId: "", nocodbTableName: "", invoiceColumnName: "Ref Facture", }); }}> {selectedGroup ? 'Modifier' : 'Nouveau'} Groupe

{selectedGroup ? 'Modifier les informations du groupe' : 'Créer un nouveau groupe/magasin'}

handleChange('name', e.target.value)} placeholder="Nom du groupe/magasin" required />
handleChange('color', e.target.value)} className="w-20 h-8" /> {formData.color}
{colorOptions.map(option => (
{/* Section Configuration NocoDB */}

Configuration NocoDB (Optionnel)

Configurez la vérification automatique des références de facture dans une table NocoDB.

{formData.nocodbConfigId && ( <>
handleChange('nocodbTableName', e.target.value)} placeholder="ex: Factures_Paris_2025" required />

Nom exact de la table dans votre base NocoDB

Mapping des colonnes

Configurez les noms exacts des colonnes dans votre table NocoDB

handleChange('invoiceColumnName', e.target.value)} placeholder="ex: Ref_Facture" required />
handleChange('nocodbBlColumnName', e.target.value)} placeholder="ex: Num_BL" />
handleChange('nocodbAmountColumnName', e.target.value)} placeholder="ex: Montant_HT" />
handleChange('nocodbSupplierColumnName', e.target.value)} placeholder="ex: Fournisseur" />

Notifications

handleChange('webhookUrl', e.target.value)} placeholder="https://votre-site.com/webhook" type="url" />

URL pour recevoir les notifications d'événements

{(formData.nocodbTableName && formData.invoiceColumnName) && (

Vérifie que la table et les colonnes existent

)} )}
); }