/** * MobileDlcPage.tsx * Version mobile de la page Gestion des DLC */ import { useState, useMemo } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useAuthUnified } from "@/hooks/useAuthUnified"; import { useStore } from "@/contexts/StoreContext"; import MobileLayout from "./MobileLayout"; import { Card, CardContent } from "@/components/ui/card"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { Badge } from "@/components/ui/badge"; import { Clock, Search, Plus, Filter, AlertTriangle, CheckCircle, X, Calendar, MoreVertical } from "lucide-react"; import { format, parseISO } from "date-fns"; import { fr } from "date-fns/locale"; import { apiRequest } from "@/lib/queryClient"; import { useToast } from "@/hooks/use-toast"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Sheet, SheetContent, SheetHeader, SheetTitle, } from "@/components/ui/sheet"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage, } from "@/components/ui/form"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs"; const dlcFormSchema = z.object({ productName: z.string().min(1, "Nom du produit requis"), gencode: z.string().optional(), dlcDate: z.string().min(1, "Date requise"), dateType: z.enum(["dlc", "ddm", "dluo"]).default("dlc"), supplierId: z.coerce.number().min(1, "Fournisseur requis"), status: z.string().default("en_cours"), }); export default function MobileDlcPage() { const { user } = useAuthUnified(); const { selectedStoreId } = useStore(); const [searchTerm, setSearchTerm] = useState(""); const [statusFilter, setStatusFilter] = useState("en_cours"); // en_cours, expires_soon, expires, valides const [isCreateOpen, setIsCreateOpen] = useState(false); const { toast } = useToast(); const queryClient = useQueryClient(); // Data fetching const { data: dlcProducts = [], isLoading } = useQuery({ queryKey: ["/api/dlc-products", selectedStoreId, statusFilter, searchTerm], queryFn: async () => { const params = new URLSearchParams(); if (selectedStoreId) params.append("storeId", selectedStoreId.toString()); // Simplify fetching everything client side for mobile smoothness or filter server side? // Let's filter client side for better UX given the likely smaller dataset per store if (selectedStoreId) { const res = await fetch(`/api/dlc-products?storeId=${selectedStoreId}`, { credentials: 'include' }); return res.json(); } return []; }, enabled: !!selectedStoreId && !!user, }); const { data: suppliers = [] } = useQuery({ queryKey: ["/api/suppliers", "dlc"], queryFn: () => apiRequest("/api/suppliers?dlc=true"), }); const { data: groups = [] } = useQuery({ queryKey: ["/api/groups"], queryFn: () => apiRequest("/api/groups"), }); // Mutations const createMutation = useMutation({ mutationFn: (data: any) => apiRequest("/api/dlc-products", "POST", data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/dlc-products"] }); toast({ title: "Produit ajouté" }); setIsCreateOpen(false); form.reset(); }, onError: (err: any) => toast({ title: "Erreur", description: err.message, variant: "destructive" }) }); const validateMutation = useMutation({ mutationFn: (id: number) => apiRequest(`/api/dlc-products/${id}/validate`, "POST"), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/dlc-products"] }); toast({ title: "Produit validé (retiré)" }); } }); // Form const form = useForm({ resolver: zodResolver(dlcFormSchema), defaultValues: { productName: "", gencode: "", dlcDate: format(new Date(), 'yyyy-MM-dd'), dateType: "dlc", supplierId: 1, status: "en_cours" } }); const onSubmit = (data: any) => { let groupId = selectedStoreId; if (!groupId && user?.userGroups?.[0]?.groupId) groupId = user.userGroups[0].groupId; if (!groupId && groups.length > 0 && user?.role === 'admin') groupId = groups[0].id; if (!groupId) { toast({ title: "Erreur", description: "Magasin introuvable", variant: "destructive" }); return; } const submitData = { ...data, name: data.productName, expiryDate: new Date(data.dlcDate), quantity: 1, unit: "unité", location: "Magasin", alertThreshold: 15, groupId }; createMutation.mutate(submitData); }; // Filtering logic const filteredProducts = useMemo(() => { if (!Array.isArray(dlcProducts)) return []; let filtered = dlcProducts; // Search if (searchTerm) { const lower = searchTerm.toLowerCase(); filtered = filtered.filter((p: any) => p.productName.toLowerCase().includes(lower) || (p.gencode && p.gencode.includes(lower)) ); } // Status logic const today = new Date(); filtered = filtered.filter((p: any) => { const expiry = new Date(p.expiryDate); const daysUntil = Math.ceil((expiry.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); if (p.status === 'valides') return statusFilter === 'valides'; if (statusFilter === 'expires') return daysUntil < 0; if (statusFilter === 'expires_soon') return daysUntil >= 0 && daysUntil <= 15; if (statusFilter === 'en_cours') return daysUntil > 15; return true; // 'all' }); return filtered.sort((a: any, b: any) => new Date(a.expiryDate).getTime() - new Date(b.expiryDate).getTime()); }, [dlcProducts, searchTerm, statusFilter]); const getDaysBadge = (dateStr: string) => { const today = new Date(); const expiry = new Date(dateStr); const days = Math.ceil((expiry.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); if (days < 0) return Expiré J{days}; if (days <= 3) return J-{days}; if (days <= 15) return J-{days}; return J-{days}; }; return ( {/* Header */} Gestion DLC {/* Search */} setSearchTerm(e.target.value)} className="pl-9 h-10" /> {/* Tabs for quick filtering */} OK Bientôt Expirés Validés {/* List */} {isLoading ? ( ) : filteredProducts.length === 0 ? ( Aucun produit trouvé ) : ( filteredProducts.map((p: any) => ( {getDaysBadge(p.expiryDate)} {format(new Date(p.expiryDate), 'dd/MM/yy')} {p.productName} {p.supplier?.name} {p.gencode ? `• ${p.gencode}` : ''} {statusFilter !== 'valides' && ( validateMutation.mutate(p.id)}> Valider (Sortir) )} )) )} {/* Create Sheet */} Nouveau Produit DLC ( Nom du produit )} /> ( Date Expiration )} /> ( Type DLC DDM DLUO )} /> ( Fournisseur field.onChange(parseInt(val))} defaultValue={field.value.toString()} > {suppliers.map((s: any) => ( {s.name} ))} )} /> ( Gencode (Scanner si dispo) )} /> {createMutation.isPending ? "Ajout..." : "Ajouter le produit"} {/* FAB */} setIsCreateOpen(true)} > ); }
Aucun produit trouvé
{p.supplier?.name} {p.gencode ? `• ${p.gencode}` : ''}