diff --git a/.replit b/.replit index 233e6b9..2122ba9 100644 --- a/.replit +++ b/.replit @@ -16,7 +16,7 @@ localPort = 5000 externalPort = 80 [[ports]] -localPort = 38145 +localPort = 35077 externalPort = 3003 [[ports]] diff --git a/client/src/components/modals/ClientCallsModal.tsx b/client/src/components/modals/ClientCallsModal.tsx index ef931ec..43090e5 100644 --- a/client/src/components/modals/ClientCallsModal.tsx +++ b/client/src/components/modals/ClientCallsModal.tsx @@ -33,8 +33,12 @@ export default function ClientCallsModal({ isOpen, onClose, pendingCalls }: Clie description: "L'appel a été enregistré avec succès", }); - // Invalider les caches - queryClient.invalidateQueries({ queryKey: ['/api/customer-orders/pending-calls'] }); + // Invalider les caches avec la bonne stratégie + queryClient.invalidateQueries({ + predicate: (query) => { + return query.queryKey[0] === '/api/customer-orders/pending-calls'; + } + }); queryClient.invalidateQueries({ queryKey: ['/api/customer-orders'] }); }, onError: (error, customerOrderId) => { diff --git a/client/src/pages/CustomerOrders.tsx b/client/src/pages/CustomerOrders.tsx index 60a2f0e..bdd9512 100644 --- a/client/src/pages/CustomerOrders.tsx +++ b/client/src/pages/CustomerOrders.tsx @@ -5,6 +5,7 @@ import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; +import { Alert, AlertDescription } from "@/components/ui/alert"; import { Dialog, DialogContent, @@ -28,7 +29,7 @@ import { SelectValue, } from "@/components/ui/select"; import { Pagination, usePagination } from "@/components/ui/pagination"; -import { Plus, Edit, Trash2, Phone, PhoneCall, Printer, Eye, Package } from "lucide-react"; +import { Plus, Edit, Trash2, Phone, PhoneCall, Printer, Eye, Package, AlertCircle } from "lucide-react"; import JsBarcode from 'jsbarcode'; import { safeFormat, safeDate } from "@/lib/dateUtils"; import { format } from "date-fns"; @@ -39,6 +40,7 @@ import type { CustomerOrderWithRelations, Group } from "@shared/schema"; import { CustomerOrderForm } from "@/components/CustomerOrderForm"; import { CustomerOrderDetails } from "@/components/CustomerOrderDetails"; import { ConfirmationModal } from "@/components/ConfirmationModal"; +import ClientCallsModal from "@/components/modals/ClientCallsModal"; import { useStore } from "@/contexts/StoreContext"; import { usePermissions } from "@shared/permissions"; @@ -52,6 +54,7 @@ export default function CustomerOrders() { const [showDetailsModal, setShowDetailsModal] = useState(false); const [showDeleteModal, setShowDeleteModal] = useState(false); const [showStatusModal, setShowStatusModal] = useState(false); + const [showClientCallsModal, setShowClientCallsModal] = useState(false); const [selectedOrder, setSelectedOrder] = useState(null); const [searchTerm, setSearchTerm] = useState(""); const [sortBy, setSortBy] = useState<"date" | "status" | "supplier">("date"); @@ -64,6 +67,22 @@ export default function CustomerOrders() { queryKey: ['/api/groups'], }); + // Query pour les appels clients en attente + const { data: pendingCalls = [], isLoading: isPendingCallsLoading } = useQuery({ + queryKey: ['/api/customer-orders/pending-calls', selectedStoreId], + queryFn: async () => { + const pendingCallsUrl = `/api/customer-orders/pending-calls${selectedStoreId ? `?storeId=${selectedStoreId}` : ''}`; + const response = await fetch(pendingCallsUrl, { credentials: 'include' }); + if (!response.ok) { + throw new Error('Failed to fetch pending calls'); + } + const data = await response.json(); + console.log('📞 Customer Orders - Pending calls received:', Array.isArray(data) ? data.length : 'NOT_ARRAY', 'items'); + return data; + }, + refetchInterval: 30000, // Refetch every 30 seconds + }); + // Fetch suppliers for filter const { data: suppliers = [] } = useQuery({ queryKey: ['/api/suppliers'], @@ -688,6 +707,23 @@ export default function CustomerOrders() { return (
+ {/* Alerte pour les appels clients en attente */} + {!isPendingCallsLoading && pendingCalls.length > 0 && ( + setShowClientCallsModal(true)} + data-testid="alert-client-calls" + > + + + 📞 {pendingCalls.length} client{pendingCalls.length > 1 ? 's' : ''} à appeler + + Produits disponibles - Cliquez pour voir la liste des clients à contacter + + + + )} + {/* Header */}
@@ -996,6 +1032,13 @@ export default function CustomerOrders() { confirmText="Supprimer" isLoading={deleteMutation.isPending} /> + + {/* Modal pour les appels clients */} + setShowClientCallsModal(false)} + pendingCalls={pendingCalls} + />
); } \ No newline at end of file diff --git a/server/storage.ts b/server/storage.ts index 651d9c7..307f307 100644 --- a/server/storage.ts +++ b/server/storage.ts @@ -1682,8 +1682,8 @@ export class DatabaseStorage implements IStorage { async getPendingClientCalls(groupIds?: number[]): Promise { const conditions = [ - eq(dlcProducts.status, 'valides'), - eq(customerOrders.customerNotified, false) + eq(customerOrders.customerNotified, false), + inArray(customerOrders.status, ['Disponible', 'disponible', 'Arrivé', 'arrivé', 'Prêt', 'prêt']) ]; if (groupIds && groupIds.length > 0) { @@ -1697,7 +1697,6 @@ export class DatabaseStorage implements IStorage { group: groups }) .from(customerOrders) - .innerJoin(dlcProducts, eq(customerOrders.gencode, dlcProducts.gencode)) .leftJoin(suppliers, eq(customerOrders.supplierId, suppliers.id)) .leftJoin(groups, eq(customerOrders.groupId, groups.id)) .where(and(...conditions)) @@ -3477,15 +3476,12 @@ export class MemStorage implements IStorage { orders = orders.filter(order => groupIds.includes(order.groupId)); } - // Find orders where client not notified and product is available + // Find orders where client not notified and status is 'Disponible' const pendingOrders = orders.filter(order => { - if (order.customerNotified) return false; - - // Check if there's a DLC product with status 'valides' matching this gencode - const matchingDlcProduct = Array.from(this.dlcProducts.values()) - .find(dlc => dlc.gencode === order.gencode && dlc.status === 'valides'); - - return !!matchingDlcProduct; + return !order.customerNotified && + (order.status === 'Disponible' || order.status === 'disponible' || + order.status === 'Arrivé' || order.status === 'arrivé' || + order.status === 'Prêt' || order.status === 'prêt'); }); return pendingOrders.map(order => ({