init aegis
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
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('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('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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user