/** * MobileAvoirsPage.tsx * Version mobile de la page Gestion des Avoirs */ import { useState } 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 { Checkbox } from "@/components/ui/checkbox"; import { FileText, Search, Plus, Filter, CheckCircle, AlertCircle, Clock, MoreVertical, Trash2, Ban } from "lucide-react"; import { format } 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, } from "@/components/ui/form"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Textarea } from "@/components/ui/textarea"; const avoirSchema = z.object({ supplierId: z.coerce.number().min(1, "Fournisseur requis"), invoiceReference: z.string().optional(), amount: z.coerce.number().optional(), comment: z.string().optional(), commercialProcessed: z.boolean().default(false), status: z.enum(["En attente de demande", "Demandé", "Reçu"]).default("En attente de demande"), }); export default function MobileAvoirsPage() { const { user } = useAuthUnified(); const { selectedStoreId } = useStore(); const [searchTerm, setSearchTerm] = useState(""); const [isCreateOpen, setIsCreateOpen] = useState(false); const { toast } = useToast(); const queryClient = useQueryClient(); // Queries const { data: avoirs = [], isLoading } = useQuery({ queryKey: ["/api/avoirs", selectedStoreId], queryFn: async () => { if (!selectedStoreId) return []; const res = await fetch(`/api/avoirs?storeId=${selectedStoreId}`, { credentials: 'include' }); if (!res.ok) throw new Error("Erreur fetch Avoirs"); return res.json(); }, enabled: !!selectedStoreId && !!user, }); const { data: suppliers = [] } = useQuery({ queryKey: ['/api/suppliers'], queryFn: () => apiRequest('/api/suppliers') }); const { data: groups = [] } = useQuery({ queryKey: ['/api/groups'], queryFn: () => apiRequest('/api/groups') }); // Mutations const createMutation = useMutation({ mutationFn: (data: any) => apiRequest('/api/avoirs', 'POST', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/avoirs"] }); toast({ title: "Avoir créé avec succès" }); setIsCreateOpen(false); form.reset(); }, onError: (err: any) => toast({ title: "Erreur", description: err.message, variant: "destructive" }) }); const updateStatusMutation = useMutation({ mutationFn: ({ id, data }: { id: number, data: any }) => apiRequest(`/api/avoirs/${id}`, 'PUT', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/avoirs"] }); toast({ title: "Statut mis à jour" }); } }); const deleteMutation = useMutation({ mutationFn: (id: number) => apiRequest(`/api/avoirs/${id}`, 'DELETE'), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/avoirs"] }); toast({ title: "Avoir supprimé" }); } }); // Form const form = useForm({ resolver: zodResolver(avoirSchema), defaultValues: { supplierId: 0, invoiceReference: "", amount: 0, comment: "", commercialProcessed: false, status: "En attente de demande" } }); 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: "Aucun magasin sélectionné", variant: "destructive" }); return; } createMutation.mutate({ ...data, groupId }); }; const sortedAvoirs = [...avoirs].sort((a: any, b: any) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); const filteredAvoirs = sortedAvoirs.filter((a: any) => (a.supplier?.name?.toLowerCase().includes(searchTerm.toLowerCase()) || "") || (a.invoiceReference?.toLowerCase().includes(searchTerm.toLowerCase()) || "") ); const getStatusBadge = (status: string) => { switch (status) { case "En attente de demande": return En attente; case "Demandé": return Demandé; case "Reçu": return Reçu; default: return {status}; } }; return ( {/* Header */} Suivi des Avoirs {/* Search */} setSearchTerm(e.target.value)} className="pl-9 h-10" /> {/* List */} {isLoading ? ( ) : filteredAvoirs.length === 0 ? ( Aucun avoir trouvé ) : ( filteredAvoirs.map((avoir: any) => ( {getStatusBadge(avoir.status)} {format(new Date(avoir.createdAt), "dd/MM", { locale: fr })} {avoir.supplier?.name} {avoir.invoiceReference && ( {avoir.invoiceReference} )} {avoir.amount && ( {parseFloat(avoir.amount).toFixed(2)} € )} updateStatusMutation.mutate({ id: avoir.id, data: { ...avoir, status: 'En attente de demande' } })}> Marquer En attente updateStatusMutation.mutate({ id: avoir.id, data: { ...avoir, status: 'Demandé' } })}> Marquer Demandé updateStatusMutation.mutate({ id: avoir.id, data: { ...avoir, status: 'Reçu' } })}> Marquer Reçu deleteMutation.mutate(avoir.id)} className="text-red-600" > Supprimer {avoir.comment && ( "{avoir.comment}" )} {avoir.nocodbVerified && ( Vérifié Compta )} )) )} {/* Create Sheet */} Nouvel Avoir ( Fournisseur field.onChange(parseInt(val))} defaultValue={field.value ? field.value.toString() : undefined} > {suppliers.map((s: any) => ( {s.name} ))} )} /> ( Réf. Facture )} /> ( Montant )} /> ( Commentaire / Raison )} /> ( Déjà traité par commercial )} /> {createMutation.isPending ? "Création..." : "Créer la demande"} {/* FAB */} setIsCreateOpen(true)} > ); }
Aucun avoir trouvé