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}
+
))
)}