import { useState } from "react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { Plus, Search, FileText, CheckCircle, AlertCircle, Clock, Edit, Trash2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle, DialogTrigger } from "@/components/ui/dialog"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle } from "@/components/ui/alert-dialog"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Checkbox } from "@/components/ui/checkbox"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import { z } from "zod"; import { format } from "date-fns"; import { fr } from "date-fns/locale"; import { useToast } from "@/hooks/use-toast"; import { apiRequest } from "@/lib/queryClient"; // Types interface Avoir { id: number; supplierId: number; groupId: number; invoiceReference?: string; amount?: number; comment?: string; commercialProcessed: boolean; status: 'En attente de demande' | 'Demandé' | 'Reçu'; webhookSent: boolean; nocodbVerified: boolean; nocodbVerifiedAt?: Date; processedAt?: Date; createdBy: string; createdAt: Date; updatedAt: Date; supplier: { id: number; name: string; contact?: string; }; group: { id: number; name: string; color: string; }; creator: { id: string; firstName?: string; lastName?: string; username: string; }; } interface Supplier { id: number; name: string; } interface Group { id: number; name: string; color: string; } // Schema for form validation const avoirSchema = z.object({ supplierId: z.coerce.number().min(1, "Veuillez sélectionner un fournisseur"), groupId: z.coerce.number().min(1, "Veuillez sélectionner un magasin"), invoiceReference: z.string().optional(), amount: z.union([ z.null().transform(() => undefined), z.undefined().transform(() => undefined), z.literal("").transform(() => undefined), z.string().transform((val) => { const num = parseFloat(val); if (isNaN(num)) { throw new Error("Montant invalide"); } if (num <= 0) { throw new Error("Le montant doit être supérieur à 0"); } return num; }) ]).optional(), comment: z.string().optional(), commercialProcessed: z.boolean().optional(), status: z.enum(["En attente de demande", "Demandé", "Reçu"]).optional(), }); type AvoirFormData = z.infer; export default function Avoirs() { const [searchTerm, setSearchTerm] = useState(""); const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false); const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); const [selectedAvoir, setSelectedAvoir] = useState(null); const [selectedStoreId, setSelectedStoreId] = useState("all"); const { toast } = useToast(); const queryClient = useQueryClient(); // Fetch user profile const { data: user } = useQuery({ queryKey: ['/api/user'], }); // Fetch groups for store filter const { data: groups = [] } = useQuery({ queryKey: ['/api/groups'], enabled: !!user, }); // Fetch suppliers const { data: suppliers = [] } = useQuery({ queryKey: ['/api/suppliers'], enabled: !!user, }); // Fetch avoirs const { data: avoirs = [], isLoading } = useQuery({ queryKey: ['/api/avoirs', selectedStoreId], queryFn: () => apiRequest(`/api/avoirs${selectedStoreId !== 'all' ? `?storeId=${selectedStoreId}` : ''}`), enabled: !!user, }); // Create avoir mutation const createAvoirMutation = useMutation({ mutationFn: async (data: AvoirFormData) => { const response = await fetch('/api/avoirs', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), credentials: 'include', }); if (!response.ok) { throw new Error('Failed to create avoir'); } return response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/avoirs'] }); setIsCreateDialogOpen(false); toast({ title: "Avoir créé", description: "L'avoir a été créé avec succès.", }); }, onError: (error: any) => { toast({ title: "Erreur", description: error.message || "Erreur lors de la création de l'avoir", variant: "destructive", }); } }); // Edit avoir mutation const editAvoirMutation = useMutation({ mutationFn: async ({ id, data }: { id: number, data: AvoirFormData }) => { const response = await fetch(`/api/avoirs/${id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(data), credentials: 'include', }); if (!response.ok) { throw new Error('Failed to edit avoir'); } return response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/avoirs'] }); setIsEditDialogOpen(false); setSelectedAvoir(null); toast({ title: "Avoir modifié", description: "L'avoir a été modifié avec succès.", }); }, onError: (error: any) => { toast({ title: "Erreur", description: error.message || "Erreur lors de la modification", variant: "destructive", }); } }); // Delete avoir mutation const deleteAvoirMutation = useMutation({ mutationFn: async (id: number) => { const response = await fetch(`/api/avoirs/${id}`, { method: 'DELETE', credentials: 'include', }); if (!response.ok) { throw new Error('Failed to delete avoir'); } return response.json(); }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['/api/avoirs'] }); setIsDeleteDialogOpen(false); setSelectedAvoir(null); toast({ title: "Avoir supprimé", description: "L'avoir a été supprimé avec succès.", }); }, onError: (error: any) => { toast({ title: "Erreur", description: error.message || "Erreur lors de la suppression", variant: "destructive", }); } }); // Initialize create form const form = useForm({ resolver: zodResolver(avoirSchema), defaultValues: { supplierId: 0, groupId: 0, invoiceReference: "", amount: undefined, comment: "", commercialProcessed: false, status: "En attente de demande", }, }); // Initialize edit form const editForm = useForm({ resolver: zodResolver(avoirSchema), defaultValues: { supplierId: 0, groupId: 0, invoiceReference: "", amount: undefined, comment: "", commercialProcessed: false, status: "En attente de demande", }, }); // Handle form submission const onSubmit = (data: AvoirFormData) => { createAvoirMutation.mutate(data); }; // Handle edit form submission const onEditSubmit = (data: AvoirFormData) => { if (selectedAvoir) { editAvoirMutation.mutate({ id: selectedAvoir.id, data }); } }; // Handle edit action const handleEdit = (avoir: Avoir) => { setSelectedAvoir(avoir); editForm.reset({ supplierId: avoir.supplierId, groupId: avoir.groupId, invoiceReference: avoir.invoiceReference || "", amount: avoir.amount, comment: avoir.comment || "", commercialProcessed: avoir.commercialProcessed, status: avoir.status as "En attente de demande" | "Demandé" | "Reçu", }); setIsEditDialogOpen(true); }; // Handle delete action const handleDelete = (avoir: Avoir) => { setSelectedAvoir(avoir); setIsDeleteDialogOpen(true); }; // Confirm delete const confirmDelete = () => { if (selectedAvoir) { deleteAvoirMutation.mutate(selectedAvoir.id); } }; // Filter avoirs based on search term const filteredAvoirs = avoirs.filter(avoir => avoir.invoiceReference?.toLowerCase().includes(searchTerm.toLowerCase()) || avoir.supplier.name.toLowerCase().includes(searchTerm.toLowerCase()) || avoir.comment?.toLowerCase().includes(searchTerm.toLowerCase()) || (avoir.creator.firstName && avoir.creator.lastName ? `${avoir.creator.firstName} ${avoir.creator.lastName}`.toLowerCase().includes(searchTerm.toLowerCase()) : avoir.creator.username.toLowerCase().includes(searchTerm.toLowerCase()) ) ); // Status badge variant const getStatusVariant = (status: string) => { switch (status) { case 'Reçu': return 'default'; case 'Demandé': return 'secondary'; case 'En attente de demande': return 'outline'; default: return 'outline'; } }; // Status icon const getStatusIcon = (status: string) => { switch (status) { case 'Reçu': return ; case 'Demandé': return ; case 'En attente de demande': return ; default: return ; } }; if (!user) { return
Chargement...
; } return (

Avoirs

Gestion des demandes d'avoirs et de remboursements

Créer un nouvel avoir Remplissez les informations pour créer une nouvelle demande d'avoir.
( Fournisseur )} /> ( Magasin )} /> ( Référence facture (optionnel) )} /> ( Montant (€) (optionnel) field.onChange(e.target.value ? parseFloat(e.target.value) : undefined)} /> )} /> ( Commentaire