/** * MobileSavPage.tsx * Version mobile de la page SAV (Service Après-Vente) */ 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 { Wrench, Search, Plus, Filter, Camera, Image as ImageIcon, MoreVertical, CheckCircle, XCircle, Phone } 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, FormMessage, } from "@/components/ui/form"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; // Schema const savFormSchema = z.object({ customerName: z.string().min(1, "Nom client requis"), customerPhone: z.string().optional(), productName: z.string().min(1, "Produit requis"), issueDescription: z.string().min(1, "Description requise"), priority: z.enum(["low", "medium", "high", "critical"]).default("medium"), }); export default function MobileSavPage() { const { user } = useAuthUnified(); const { selectedStoreId } = useStore(); const [searchTerm, setSearchTerm] = useState(""); const [isCreateOpen, setIsCreateOpen] = useState(false); const { toast } = useToast(); const queryClient = useQueryClient(); // Queries const { data: tickets = [], isLoading } = useQuery({ queryKey: ["/api/sav-tickets", selectedStoreId], queryFn: async () => { if (!selectedStoreId) return []; const res = await fetch(`/api/sav-tickets?storeId=${selectedStoreId}`, { credentials: 'include' }); if (!res.ok) throw new Error("Erreur fetch SAV"); return res.json(); }, enabled: !!selectedStoreId && !!user, }); const { data: groups = [] } = useQuery({ queryKey: ['/api/groups'], queryFn: () => apiRequest('/api/groups') }); // Mutations const statusMutation = useMutation({ mutationFn: ({ id, status }: { id: number; status: string }) => apiRequest(`/api/sav-tickets/${id}/status`, 'PATCH', { status }), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/sav-tickets"] }); toast({ title: "Statut mis à jour" }); } }); const createMutation = useMutation({ mutationFn: (data: any) => apiRequest('/api/sav-tickets', 'POST', data), onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["/api/sav-tickets"] }); toast({ title: "Ticket SAV créé" }); setIsCreateOpen(false); form.reset(); }, onError: (err: any) => toast({ title: "Erreur", description: err.message, variant: "destructive" }) }); // Form const form = useForm({ resolver: zodResolver(savFormSchema), defaultValues: { customerName: "", customerPhone: "", productName: "", issueDescription: "", priority: "medium" } }); 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, status: "open" }); }; const filteredTickets = tickets.filter((t: any) => t.customerName.toLowerCase().includes(searchTerm.toLowerCase()) || t.productName.toLowerCase().includes(searchTerm.toLowerCase()) || t.ticketNumber?.toLowerCase().includes(searchTerm.toLowerCase()) ); const getStatusBadge = (status: string) => { switch (status) { case "open": return Ouvert; case "in_progress": return En cours; case "waiting_parts": return Pièces; case "resolved": return Résolu; case "closed": return Fermé; default: return {status}; } }; return (
{/* Header */}

SAV / Réparations

{/* Search */}
setSearchTerm(e.target.value)} className="pl-9 h-10" />
{/* List */}
{isLoading ? (
) : filteredTickets.length === 0 ? (

Aucun ticket SAV

) : ( filteredTickets.map((ticket: any) => (
{getStatusBadge(ticket.status)} #{ticket.ticketNumber} • {format(new Date(ticket.createdAt), "dd/MM", { locale: fr })}

{ticket.customerName}

{ticket.productName}

statusMutation.mutate({ id: ticket.id, status: 'in_progress' })}> En cours statusMutation.mutate({ id: ticket.id, status: 'waiting_parts' })}> Attente Pièces statusMutation.mutate({ id: ticket.id, status: 'resolved' })} className="text-green-600"> Résolu statusMutation.mutate({ id: ticket.id, status: 'closed' })} className="text-gray-600"> Fermer
{ticket.issueDescription}
{ticket.customerPhone && ( )}
)) )}
{/* Create Sheet */} Nouveau Ticket SAV

Client

( Nom )} /> ( Téléphone )} />

Problème

( Produit )} /> ( Priorité )} /> ( Description Panne