Files
aegis/src/pages/Support/TicketDetail.tsx
T
2026-07-23 09:51:34 +02:00

200 lines
7.3 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { pb } from '../../config/pocketbase';
interface Ticket {
id: string;
subject: string;
description: string;
category: string;
status: 'Ouvert' | 'En analyse' | 'Résolu';
created: string;
}
interface Message {
id: string;
content: string;
created: string;
expand?: {
author?: {
name?: string;
email?: string;
avatar?: string;
};
};
}
interface TicketDetailProps {
ticketId: string;
onBack: () => void;
}
export default function TicketDetail({ ticketId, onBack }: TicketDetailProps) {
const [ticket, setTicket] = useState<Ticket | null>(null);
const [messages, setMessages] = useState<Message[]>([]);
const [newMessage, setNewMessage] = useState('');
const [isLoading, setIsLoading] = useState(true);
const [isSending, setIsSending] = useState(false);
const currentUser = pb.authStore.record;
// Récupération du ticket et de ses messages associés
const fetchTicketData = async () => {
try {
const ticketData = await pb.collection('aegis_tickets').getOne<Ticket>(ticketId);
setTicket(ticketData);
const messageRecords = await pb.collection('aegis_ticket_messages').getFullList<Message>({
filter: `ticket = "${ticketId}"`,
expand: 'author',
sort: 'created',
});
setMessages(messageRecords);
} catch (error) {
console.error("Erreur lors du chargement de la discussion :", error);
} finally {
setIsLoading(false);
}
};
useEffect(() => {
fetchTicketData();
}, [ticketId]);
// Envoi d'un nouveau message dans la discussion
const handleSendMessage = async (e: React.FormEvent) => {
e.preventDefault();
if (!newMessage.trim() || !currentUser) return;
setIsSending(true);
try {
await pb.collection('aegis_ticket_messages').create({
ticket: ticketId,
author: currentUser.id,
content: newMessage.trim(),
});
setNewMessage('');
await fetchTicketData(); // Recharger les messages
} catch (error) {
console.error("Erreur lors de l'envoi du message :", error);
alert("Impossible d'envoyer le message.");
} finally {
setIsSending(false);
}
};
if (isLoading) {
return (
<div className="flex justify-center items-center py-20">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-900"></div>
</div>
);
}
if (!ticket) {
return (
<div className="bg-white rounded-xl p-6 text-center border border-slate-200">
<p className="text-sm text-slate-500 mb-4">Ticket introuvable ou accès non autorisé.</p>
<button onClick={onBack} className="px-4 py-2 bg-slate-900 text-white rounded-lg text-sm font-medium">
Retour aux tickets
</button>
</div>
);
}
return (
<div className="space-y-6 max-w-4xl mx-auto">
{/* Barre de navigation / Retour */}
<div className="flex items-center justify-between bg-white p-4 rounded-xl border border-slate-200 shadow-sm">
<button
onClick={onBack}
className="flex items-center space-x-2 text-sm font-medium text-slate-600 hover:text-slate-900 transition-colors"
>
<svg className="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M15 19l-7-7 7-7" />
</svg>
<span>Retour au Centre de Support</span>
</button>
<div className="flex items-center space-x-3">
<span className="text-xs font-semibold px-2.5 py-1 bg-slate-100 text-slate-700 rounded border border-slate-200 uppercase">
{ticket.category}
</span>
<span className={`px-3 py-1 rounded-full text-xs font-medium border ${
ticket.status === 'Résolu' ? 'bg-emerald-50 text-emerald-700 border-emerald-100' : 'bg-blue-50 text-blue-700 border-blue-100'
}`}>
{ticket.status}
</span>
</div>
</div>
{/* En-tête du ticket & Description initiale */}
<div className="bg-white p-6 rounded-xl border border-slate-200 shadow-sm space-y-4">
<h1 className="text-xl font-bold text-slate-900">{ticket.subject}</h1>
<div className="bg-slate-50 p-4 rounded-lg border border-slate-100 text-sm text-slate-700 whitespace-pre-wrap">
<p className="text-xs font-semibold text-slate-400 mb-1">Description initiale :</p>
{ticket.description}
</div>
<p className="text-xs text-slate-400">
Créé le {new Date(ticket.created).toLocaleString('fr-FR')}
</p>
</div>
{/* Fil de discussion / Messages */}
<div className="bg-white rounded-xl border border-slate-200 shadow-sm overflow-hidden flex flex-col h-[500px]">
<div className="p-4 border-b border-slate-100 bg-slate-50/50">
<h2 className="text-sm font-semibold text-slate-800">Échanges avec l'équipe GISE</h2>
</div>
{/* Liste des messages */}
<div className="flex-1 overflow-y-auto p-6 space-y-4">
{messages.length === 0 ? (
<div className="text-center py-12 text-sm text-slate-400 italic">
Aucun message pour l'instant. Démarrez la conversation ci-dessous.
</div>
) : (
messages.map((msg) => {
const isMyMessage = msg.author === currentUser?.id;
const authorName = msg.expand?.author?.name || msg.expand?.author?.email || 'Expert GISE';
return (
<div key={msg.id} className={`flex flex-col ${isMyMessage ? 'items-end' : 'items-start'}`}>
<div className="flex items-center space-x-2 mb-1">
<span className="text-xs font-medium text-slate-600">{authorName}</span>
<span className="text-[10px] text-slate-400">
{new Date(msg.created).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })}
</span>
</div>
<div className={`max-w-[80%] rounded-2xl px-4 py-3 text-sm shadow-sm ${
isMyMessage
? 'bg-blue-900 text-white rounded-br-none'
: 'bg-slate-100 text-slate-800 rounded-bl-none border border-slate-200/60'
}`}>
{msg.content}
</div>
</div>
);
})
)}
</div>
{/* Formulaire de réponse */}
<form onSubmit={handleSendMessage} className="p-4 border-t border-slate-100 bg-white flex items-center space-x-3">
<input
type="text"
value={newMessage}
onChange={(e) => setNewMessage(e.target.value)}
placeholder="Écrivez votre message ou complément d'information..."
className="flex-1 rounded-lg border border-slate-300 px-4 py-2.5 text-sm focus:border-blue-900 focus:outline-none focus:ring-1 focus:ring-blue-900"
/>
<button
type="submit"
disabled={isSending || !newMessage.trim()}
className="bg-blue-900 hover:bg-blue-800 text-white px-5 py-2.5 rounded-lg text-sm font-medium transition-colors disabled:opacity-50 shadow-sm"
>
{isSending ? 'Envoi...' : 'Envoyer'}
</button>
</form>
</div>
</div>
);
}