({
+ queryKey: contactsQueryKey,
+ queryFn: async () => {
+ const url = effectiveGroupId
+ ? `/api/contacts?groupId=${effectiveGroupId}`
+ : "/api/contacts";
+ return await apiRequest(url, "GET");
+ },
+ });
+
+ const createMutation = useMutation({
+ mutationFn: (data: any) => apiRequest("/api/contacts", "POST", data),
+ onSuccess: () => {
+ toast({ title: "Succès", description: "Contact créé avec succès" });
+ queryClient.invalidateQueries({ queryKey: ["/api/contacts"] });
+ closeModal();
+ },
+ onError: () => {
+ toast({ title: "Erreur", description: "Impossible de créer le contact", variant: "destructive" });
+ },
+ });
+
+ const updateMutation = useMutation({
+ mutationFn: (data: any) => apiRequest(`/api/contacts/${selectedContact?.id}`, "PUT", data),
+ onSuccess: () => {
+ toast({ title: "Succès", description: "Contact modifié avec succès" });
+ queryClient.invalidateQueries({ queryKey: ["/api/contacts"] });
+ closeModal();
+ },
+ onError: () => {
+ toast({ title: "Erreur", description: "Impossible de modifier le contact", variant: "destructive" });
+ },
+ });
+
+ const deleteMutation = useMutation({
+ mutationFn: (id: number) => apiRequest(`/api/contacts/${id}`, "DELETE"),
+ onSuccess: () => {
+ toast({ title: "Succès", description: "Contact supprimé" });
+ queryClient.invalidateQueries({ queryKey: ["/api/contacts"] });
+ },
+ onError: () => {
+ toast({ title: "Erreur", description: "Impossible de supprimer le contact", variant: "destructive" });
+ },
+ });
+
+ const openCreate = () => {
+ setSelectedContact(null);
+ setFormData({
+ groupId: effectiveGroupId ?? 0,
+ name: "",
+ role: "",
+ phone: "",
+ email: "",
+ notes: "",
+ });
+ setShowModal(true);
+ };
+
+ const openEdit = (contact: Contact) => {
+ setSelectedContact(contact);
+ setFormData({
+ groupId: contact.groupId,
+ name: contact.name,
+ role: contact.role || "",
+ phone: contact.phone || "",
+ email: contact.email || "",
+ notes: contact.notes || "",
+ });
+ setShowModal(true);
+ };
+
+ const closeModal = () => {
+ setShowModal(false);
+ setSelectedContact(null);
+ };
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ const payload = { ...formData, groupId: effectiveGroupId ?? formData.groupId };
+ if (selectedContact) {
+ updateMutation.mutate(payload);
+ } else {
+ createMutation.mutate(payload);
+ }
+ };
+
+ const handleDelete = (contact: Contact) => {
+ if (confirm(`Supprimer le contact "${contact.name}" ?`)) {
+ deleteMutation.mutate(contact.id);
+ }
+ };
+
+ const filteredSuppliers = suppliers.filter((s) =>
+ s.name.toLowerCase().includes(supplierSearch.toLowerCase()) ||
+ (s.contact || "").toLowerCase().includes(supplierSearch.toLowerCase())
+ );
+
+ const filteredContacts = contacts.filter((c) =>
+ c.name.toLowerCase().includes(contactSearch.toLowerCase()) ||
+ (c.role || "").toLowerCase().includes(contactSearch.toLowerCase())
+ );
+
+ const groupName = (groupId: number) =>
+ stores.find((s) => s.id === groupId)?.name ?? `Magasin #${groupId}`;
+
+ return (
+
+ {/* Header */}
+
+
+
+
+ Contacts
+
+
+ Fournisseurs et contacts par magasin
+
+
+
+ {canEdit && effectiveGroupId && (
+
+ )}
+
+
+ {/* Sélecteur de magasin (admin uniquement) */}
+ {isAdmin && (
+
+
+
+
+ )}
+
+ {/* Deux colonnes */}
+
+ {/* Colonne Fournisseurs */}
+
+
+
+
+ Fournisseurs ({filteredSuppliers.length})
+
+
+
+
+
+ setSupplierSearch(e.target.value)}
+ className="pl-10"
+ />
+
+
+
+ {filteredSuppliers.length === 0 ? (
+
+
+ Aucun fournisseur trouvé
+
+ ) : (
+ filteredSuppliers.map((supplier) => (
+
+
+
+
+
+
+
+
{supplier.name}
+ {supplier.contact && (
+
+
+ {supplier.contact}
+
+ )}
+
+
+
+
+
+ ))
+ )}
+
+
+
+ {/* Colonne Autres contacts */}
+
+
+
+
+ Autres contacts ({filteredContacts.length})
+
+
+
+
+
+ setContactSearch(e.target.value)}
+ className="pl-10"
+ />
+
+
+ {!effectiveGroupId && !isAdmin && (
+
+
+ Sélectionnez un magasin pour voir les contacts
+
+ )}
+
+ {(effectiveGroupId || isAdmin) && (
+
+ {filteredContacts.length === 0 ? (
+
+
+ Aucun contact
+ {canEdit && effectiveGroupId && (
+
+
+
+ )}
+
+ ) : (
+ filteredContacts.map((contact) => (
+
+
+
+
+
+
+
+
{contact.name}
+ {contact.role && (
+
{contact.role}
+ )}
+ {isAdmin && (
+
{groupName(contact.groupId)}
+ )}
+
+
+ {canEdit && (
+
+
+
+
+ )}
+
+
+
+ ))
+ )}
+
+ )}
+
+
+
+ {/* Modal créer / modifier contact */}
+
+
+ );
+}
diff --git a/client/src/pages/Suppliers.tsx b/client/src/pages/Suppliers.tsx
index d4fb951..9b48437 100644
--- a/client/src/pages/Suppliers.tsx
+++ b/client/src/pages/Suppliers.tsx
@@ -16,11 +16,12 @@ import { useAuthUnified } from "@/hooks/useAuthUnified";
import { useToast } from "@/hooks/use-toast";
import { apiRequest } from "@/lib/queryClient";
import { isUnauthorizedError } from "@/lib/authUtils";
-import {
- Plus,
- Search,
- Building,
- Phone,
+import {
+ Plus,
+ Search,
+ Building,
+ Phone,
+ Mail,
Edit,
Trash2,
Package,
@@ -43,6 +44,7 @@ export default function Suppliers() {
name: "",
contact: "",
phone: "",
+ email: "",
hasDlc: false,
automaticReconciliation: false,
requiresControl: false,
@@ -198,6 +200,7 @@ export default function Suppliers() {
name: "",
contact: "",
phone: "",
+ email: "",
hasDlc: false,
automaticReconciliation: false,
requiresControl: false,
@@ -236,6 +239,7 @@ export default function Suppliers() {
name: supplier.name || "",
contact: supplier.contact || "",
phone: supplier.phone || "",
+ email: (supplier as any).email || "",
hasDlc: supplier.hasDlc || false,
automaticReconciliation: supplier.automaticReconciliation || false,
requiresControl: supplier.requiresControl || false,
@@ -409,6 +413,12 @@ export default function Suppliers() {
{supplier.phone}
)}
+ {(supplier as any).email && (
+
+
+ {(supplier as any).email}
+
+ )}
{supplier.paymentMethod && (
@@ -528,6 +538,17 @@ export default function Suppliers() {
/>
+
+
+ handleChange('email', e.target.value)}
+ placeholder="Email du contact fournisseur"
+ />
+
+