From ad7bb76a6550ef55fa24c0c925dc61821d24e536 Mon Sep 17 00:00:00 2001 From: maximus Date: Wed, 24 Jun 2026 22:07:49 +0200 Subject: [PATCH] upgrade ticket system --- src/layouts/AppLayout.jsx | 69 ++++++++++++++++++++++++++++++++++++--- src/pages/app/Support.jsx | 38 ++++++++++++++++++--- 2 files changed, 97 insertions(+), 10 deletions(-) diff --git a/src/layouts/AppLayout.jsx b/src/layouts/AppLayout.jsx index 4cb22fe..e51ac3e 100644 --- a/src/layouts/AppLayout.jsx +++ b/src/layouts/AppLayout.jsx @@ -1,6 +1,7 @@ // src/layouts/AppLayout.jsx -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import { Outlet, NavLink, useNavigate } from 'react-router-dom'; +import { getClientTickets } from '../services/api'; // ============================================================================ // COMPOSANT : MODAL DE CONFIRMATION DE DÉCONNEXION @@ -60,6 +61,25 @@ const ConfirmLogoutModal = ({ isOpen, onClose, onConfirm }) => { export default function AppLayout() { const navigate = useNavigate(); const [showLogoutModal, setShowLogoutModal] = useState(false); + const [unreadCount, setUnreadCount] = useState(0); + + const checkUnreadTickets = async () => { + try { + const data = await getClientTickets(); + if (data && data.list) { + // On cherche les tickets qui ont le statut 'on_hold' + const unread = data.list.filter(t => t.status === 'on_hold' || t.unread).length; + setUnreadCount(unread); + } + } catch (err) { + // Erreur silencieuse : on ne pollue pas l'écran si le réseau saute + } + }; + useEffect(() => { + checkUnreadTickets(); // Premier check immédiat + const intervalId = setInterval(checkUnreadTickets, 120000); // Check toutes les 2 minutes + return () => clearInterval(intervalId); + }, []); // Ouvre simplement le modal const handleLogoutClick = () => { @@ -154,9 +174,48 @@ export default function AppLayout() {
// GESTION
- Facturation - Tickets Support - Profil & Sécurité + {/*Facturation*/} + +
+ Tickets Support + + {/* LE BADGE DE NOTIFICATION */} + {unreadCount > 0 && ( + + {unreadCount} NEW + + )} +
+
+ {/*Profil & Sécurité*/} + {/* === MODULES VERROUILLÉS (WORK IN PROGRESS) === */} +
+ Facturation + PROCHAINEMENT +
+ +
+ Profil & Sécurité + PROCHAINEMENT +
{/* Bas de la Sidebar (Déconnexion) */} @@ -179,7 +238,7 @@ export default function AppLayout() { {/* ========================================== */}
{/* Le composant injecte le contenu de la page demandée sans recharger la sidebar */} - +
{/* MODAL DE DÉCONNEXION */} diff --git a/src/pages/app/Support.jsx b/src/pages/app/Support.jsx index 0942a91..2870014 100644 --- a/src/pages/app/Support.jsx +++ b/src/pages/app/Support.jsx @@ -1,4 +1,5 @@ -import { useState, useEffect, useCallback } from 'react'; +import { useState, useEffect, useCallback, useRef } from 'react'; +import { useOutletContext } from 'react-router-dom'; import { getClientTickets, createTicket, getHelpdesks, getTicketDetails, replyTicket } from '../../services/api'; // Ajout des routes de détails et réponses import { LifeBuoy, Plus, MessageSquare, Clock, CheckCircle2, Send, AlertCircle, Loader, Lock } from 'lucide-react'; @@ -27,6 +28,8 @@ const NotificationModal = ({ notification, onClose }) => { // COMPOSANT PRINCIPAL : SUPPORT TICKETS // ============================================================================ export default function Support() { + const { refreshNavbarTickets } = useOutletContext(); + const [tickets, setTickets] = useState([]); const [helpdesks, setHelpdesks] = useState([]); const [isLoading, setIsLoading] = useState(true); @@ -45,6 +48,16 @@ export default function Support() { const [isCreatingTicket, setIsCreatingTicket] = useState(false); const [activeTicket, setActiveTicket] = useState(null); + const messagesEndRef = useRef(null); + const scrollToBottom = () => { + messagesEndRef.current?.scrollIntoView({ behavior: "smooth" }); + }; + useEffect(() => { + if (activeTicket && activeTicket.messages && !isLoadingConversation) { + scrollToBottom(); + } + }, [activeTicket?.messages, isLoadingConversation]); + const triggerAlert = (title, message, type = "info") => { setCustomAlert({ title, message, type }); }; @@ -60,7 +73,7 @@ export default function Support() { db_id: tkt.id, subject: tkt.subject, department: tkt.helpdesk?.name || 'Support Technique', - status: (tkt.status === 'open' || tkt.status === 'closed') ? tkt.status : 'pending', + status: ['open', 'closed', 'on_hold'].includes(tkt.status) ? tkt.status : 'pending', lastUpdate: tkt.updated_at || tkt.created_at || 'Récemment', messages: [] }))); @@ -122,6 +135,7 @@ export default function Support() { try { await replyTicket(activeTicket.db_id, replyMessage); + loadSupportData(); setReplyMessage(''); // Nettoyer l'input // Rechargement instantané du fil de discussion pour afficher le message soumis @@ -134,6 +148,7 @@ export default function Support() { })); setActiveTicket(prev => prev ? { ...prev, messages: formattedMessages } : null); } + if (refreshNavbarTickets) refreshNavbarTickets(); } catch (err) { triggerAlert("Échec d'envoi", "Votre réponse n'a pas pu être transmise : " + err.message, "error"); } @@ -151,6 +166,7 @@ export default function Support() { setSubject(''); setMessage(''); loadSupportData(); + if (refreshNavbarTickets) refreshNavbarTickets(); triggerAlert("Ticket Ouvert", "Votre demande a bien été enregistrée sur le Service Desk.", "success"); } catch (err) { triggerAlert("Échec", "Erreur lors de la création du ticket : " + err.message, "error"); @@ -162,6 +178,7 @@ export default function Support() { const getStatusConfig = (status) => { switch (status) { case 'open': return { color: 'text-cyan-400', bg: 'bg-cyan-500/10', border: 'border-cyan-500/20', label: 'SUPPORT', icon: }; + case 'on_hold': return { color: 'text-yellow-400', bg: 'bg-yellow-500/10', border: 'border-yellow-500/20', label: 'RÉPONSE REÇUE', icon: }; case 'pending': return { color: 'text-orange-400', bg: 'bg-orange-500/10', border: 'border-orange-500/20', label: 'EN ATTENTE', icon: }; case 'closed': return { color: 'text-gray-400', bg: 'bg-gray-500/10', border: 'border-gray-500/20', label: 'RÉSOLU', icon: }; default: return { color: 'text-gray-400', bg: 'bg-gray-800', border: 'border-gray-700', label: status.toUpperCase() }; @@ -224,7 +241,17 @@ export default function Support() { >
- {ticket.id} +
+ {ticket.id} + + {/* LE RADAR : Pastille clignotante en cas de réponse */} + {ticket.status === 'on_hold' && ( + + + + + )} +
{conf.icon} {conf.label} @@ -327,11 +354,12 @@ export default function Support() { {msg.date}
{msg.text}
+
)) )}