diff --git a/client/src/components/RouterProduction.tsx b/client/src/components/RouterProduction.tsx index 7acb02c..13f4f5a 100644 --- a/client/src/components/RouterProduction.tsx +++ b/client/src/components/RouterProduction.tsx @@ -38,6 +38,9 @@ import MobileCalendarPage from "@/pages/mobile/CalendarPage"; import MobileTasksPage from "@/pages/mobile/TasksPage"; import MobilePublicitiesPage from "@/pages/mobile/PublicitiesPage"; import MobileCustomerOrdersPage from "@/pages/mobile/CustomerOrdersPage"; +import MobileDlcPage from "@/pages/mobile/DlcPage"; +import MobileSavPage from "@/pages/mobile/SavPage"; +import MobileAvoirsPage from "@/pages/mobile/AvoirsPage"; function RouterProduction() { const { isAuthenticated, isLoading, user, environment, error } = useAuthUnified(); @@ -106,10 +109,10 @@ function RouterProduction() { - + - - + + diff --git a/client/src/pages/mobile/AvoirsPage.tsx b/client/src/pages/mobile/AvoirsPage.tsx new file mode 100644 index 0000000..9ea2025 --- /dev/null +++ b/client/src/pages/mobile/AvoirsPage.tsx @@ -0,0 +1,377 @@ +/** + * 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 { + 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(), + 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: "", + 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, + commercialProcessed: false + }); + }; + + 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 + + + )} + /> + +
+ ( + + Réf. Facture + + + )} + /> + ( + + Montant + + + )} + /> +
+ + ( + + Commentaire / Raison + +