diff --git a/client/src/components/ReconciliationComments.tsx b/client/src/components/ReconciliationComments.tsx new file mode 100644 index 0000000..9ed7b66 --- /dev/null +++ b/client/src/components/ReconciliationComments.tsx @@ -0,0 +1,396 @@ +import React, { useState } from "react"; +import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Badge } from "@/components/ui/badge"; +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; +import { Textarea } from "@/components/ui/textarea"; +import { useToast } from "@/hooks/use-toast"; +import { apiRequest } from "@/lib/queryClient"; +import { safeFormat } from "@/lib/dateUtils"; +import { MessageSquare, Plus, Edit2, Trash2, Save, X, AlertCircle, Info, CheckCircle, AlertTriangle } from "lucide-react"; + +interface ReconciliationCommentsProps { + deliveryId: number; + className?: string; +} + +interface ReconciliationComment { + id: number; + content: string; + type: 'info' | 'warning' | 'error' | 'success'; + deliveryId: number; + authorId: string; + groupId: number; + createdAt: Date; + updatedAt: Date; + author: { + id: string; + email: string; + role: string; + }; +} + +const typeColors = { + info: "bg-blue-50 border-blue-200 text-blue-800", + warning: "bg-yellow-50 border-yellow-200 text-yellow-800", + error: "bg-red-50 border-red-200 text-red-800", + success: "bg-green-50 border-green-200 text-green-800", +}; + +const typeIcons = { + info: Info, + warning: AlertTriangle, + error: AlertCircle, + success: CheckCircle, +}; + +const typeLabels = { + info: "Information", + warning: "Attention", + error: "Erreur", + success: "Succès", +}; + +export default function ReconciliationComments({ deliveryId, className = "" }: ReconciliationCommentsProps) { + const { toast } = useToast(); + const queryClient = useQueryClient(); + + const [isAdding, setIsAdding] = useState(false); + const [editingId, setEditingId] = useState(null); + const [newComment, setNewComment] = useState<{ + content: string; + type: 'info' | 'warning' | 'error' | 'success'; + }>({ + content: "", + type: "info", + }); + const [editComment, setEditComment] = useState<{ + content: string; + type: 'info' | 'warning' | 'error' | 'success'; + }>({ + content: "", + type: "info", + }); + + // Récupérer les commentaires + const { data: comments = [], isLoading } = useQuery({ + queryKey: [`/api/deliveries/${deliveryId}/reconciliation-comments`], + queryFn: () => apiRequest(`/api/deliveries/${deliveryId}/reconciliation-comments`), + enabled: !!deliveryId, + }); + + // Créer un commentaire + const createCommentMutation = useMutation({ + mutationFn: async (data: { content: string; type: string }) => { + return apiRequest(`/api/deliveries/${deliveryId}/reconciliation-comments`, "POST", data); + }, + onSuccess: () => { + toast({ + title: "Succès", + description: "Commentaire ajouté avec succès", + }); + queryClient.invalidateQueries({ queryKey: [`/api/deliveries/${deliveryId}/reconciliation-comments`] }); + setIsAdding(false); + setNewComment({ content: "", type: "info" }); + }, + onError: (error: any) => { + toast({ + title: "Erreur", + description: error?.message || "Impossible d'ajouter le commentaire", + variant: "destructive", + }); + }, + }); + + // Modifier un commentaire + const updateCommentMutation = useMutation({ + mutationFn: async ({ id, data }: { id: number; data: { content: string; type: string } }) => { + return apiRequest(`/api/reconciliation-comments/${id}`, "PUT", data); + }, + onSuccess: () => { + toast({ + title: "Succès", + description: "Commentaire modifié avec succès", + }); + queryClient.invalidateQueries({ queryKey: [`/api/deliveries/${deliveryId}/reconciliation-comments`] }); + setEditingId(null); + }, + onError: (error: any) => { + toast({ + title: "Erreur", + description: error?.message || "Impossible de modifier le commentaire", + variant: "destructive", + }); + }, + }); + + // Supprimer un commentaire + const deleteCommentMutation = useMutation({ + mutationFn: async (id: number) => { + return apiRequest(`/api/reconciliation-comments/${id}`, "DELETE"); + }, + onSuccess: () => { + toast({ + title: "Succès", + description: "Commentaire supprimé avec succès", + }); + queryClient.invalidateQueries({ queryKey: [`/api/deliveries/${deliveryId}/reconciliation-comments`] }); + }, + onError: (error: any) => { + toast({ + title: "Erreur", + description: error?.message || "Impossible de supprimer le commentaire", + variant: "destructive", + }); + }, + }); + + const handleAddComment = (e: React.FormEvent) => { + e.preventDefault(); + if (!newComment.content.trim()) return; + + createCommentMutation.mutate(newComment); + }; + + const handleEditComment = (comment: ReconciliationComment) => { + setEditingId(comment.id); + setEditComment({ + content: comment.content, + type: comment.type, + }); + }; + + const handleSaveEdit = () => { + if (!editComment.content.trim() || !editingId) return; + + updateCommentMutation.mutate({ + id: editingId, + data: editComment, + }); + }; + + const handleCancelEdit = () => { + setEditingId(null); + setEditComment({ content: "", type: "info" }); + }; + + const handleDeleteComment = (id: number) => { + if (confirm("Êtes-vous sûr de vouloir supprimer ce commentaire ?")) { + deleteCommentMutation.mutate(id); + } + }; + + if (isLoading) { + return ( +
+
+
+
+ ); + } + + return ( +
+
+

+ + Commentaires de rapprochement + {comments.length > 0 && ( + + {comments.length} + + )} +

+ + {!isAdding && ( + + )} +
+ + {/* Formulaire d'ajout */} + {isAdding && ( + + + Nouveau commentaire + + +
+
+ + +
+ +
+ +