182 lines
7.4 KiB
TypeScript
182 lines
7.4 KiB
TypeScript
export const dynamic = "force-dynamic";
|
|
import { createSbServer, createSbServiceRole } from "@/lib/supabaseServer";
|
|
import { Mail } from "lucide-react";
|
|
|
|
function formatDate(d?: string | null) {
|
|
if (!d) return "—";
|
|
try { return new Intl.DateTimeFormat("fr-FR", { dateStyle: "medium", timeStyle: "short" }).format(new Date(d)); } catch { return d as string; }
|
|
}
|
|
|
|
function formatRole(role: string): string {
|
|
const roleMap: Record<string, string> = {
|
|
'SUPER_ADMIN': 'Super Admin',
|
|
'ADMIN': 'Administrateur',
|
|
'AGENT': 'Agent',
|
|
'USER': 'Utilisateur',
|
|
};
|
|
return roleMap[role.toUpperCase()] || role;
|
|
}
|
|
|
|
function getRoleBadgeColor(role: string): string {
|
|
const colorMap: Record<string, string> = {
|
|
'SUPER_ADMIN': 'bg-purple-100 text-purple-800',
|
|
'ADMIN': 'bg-blue-100 text-blue-800',
|
|
'AGENT': 'bg-green-100 text-green-800',
|
|
'USER': 'bg-gray-100 text-gray-800',
|
|
};
|
|
return colorMap[role.toUpperCase()] || 'bg-gray-100 text-gray-800';
|
|
}
|
|
|
|
import StaffTicketActions from "@/components/staff/StaffTicketActions";
|
|
import MarkTicketRead from "@/components/staff/MarkTicketRead";
|
|
|
|
export default async function StaffTicketDetail({ params }: { params: { id: string } }) {
|
|
const { id } = params;
|
|
const sb = createSbServer();
|
|
const { data: { user } } = await sb.auth.getUser();
|
|
if (!user) return (<main className="p-6"><h1 className="text-lg font-semibold">Accès refusé</h1></main>);
|
|
|
|
const { data: me } = await sb.from("staff_users").select("is_staff").eq("user_id", user.id).maybeSingle();
|
|
if (!me?.is_staff) return (<main className="p-6"><h1 className="text-lg font-semibold">Accès refusé</h1></main>);
|
|
|
|
const { data: ticket, error: tErr } = await sb
|
|
.from("tickets")
|
|
.select("id, org_id, subject, status, priority, created_by, assigned_to, source, last_message_at, message_count, unread_by_client, unread_by_staff, created_at, updated_at")
|
|
.eq("id", id)
|
|
.maybeSingle();
|
|
if (tErr || !ticket) return (<main className="p-6"><h1 className="text-lg font-semibold">Erreur</h1><p className="text-sm text-rose-600">{tErr?.message || 'Ticket introuvable'}</p></main>);
|
|
|
|
console.log('📋 [ticket page] Ticket:', { id: ticket.id, created_by: ticket.created_by, org_id: ticket.org_id });
|
|
|
|
// Récupérer le nom de l'organisation
|
|
const { data: organization } = await sb
|
|
.from("organizations")
|
|
.select("name")
|
|
.eq("id", ticket.org_id)
|
|
.maybeSingle();
|
|
|
|
// Récupérer le nom de l'utilisateur créateur
|
|
let creatorName = "Utilisateur inconnu";
|
|
let creatorEmail = "";
|
|
let creatorRole = "";
|
|
|
|
if (ticket.created_by) {
|
|
console.log('📋 [ticket page] Tentative getUserById pour:', ticket.created_by);
|
|
|
|
// Utiliser le service role pour accéder aux données auth
|
|
const sbAdmin = createSbServiceRole();
|
|
const { data: creatorUser, error: userError } = await sbAdmin.auth.admin.getUserById(ticket.created_by);
|
|
console.log('📋 [ticket page] getUserById result:', {
|
|
hasData: !!creatorUser,
|
|
hasUser: !!creatorUser?.user,
|
|
hasEmail: !!creatorUser?.user?.email,
|
|
error: userError
|
|
});
|
|
|
|
if (creatorUser?.user) {
|
|
creatorEmail = creatorUser.user.email || "";
|
|
|
|
// Récupérer le nom depuis les métadonnées utilisateur
|
|
const metadata = creatorUser.user.user_metadata;
|
|
|
|
console.log('📋 [ticket page] User ID:', ticket.created_by);
|
|
console.log('📋 [ticket page] Email:', creatorUser.user.email);
|
|
console.log('📋 [ticket page] User metadata:', JSON.stringify(metadata, null, 2));
|
|
|
|
// Priorité 1: display_name (c'est là que Supabase Auth stocke le prénom)
|
|
if (metadata?.display_name) {
|
|
creatorName = metadata.display_name;
|
|
console.log('✅ [ticket page] Nom trouvé dans display_name:', creatorName);
|
|
}
|
|
// Priorité 2: first_name + last_name
|
|
else if (metadata?.first_name) {
|
|
creatorName = metadata.last_name
|
|
? `${metadata.first_name} ${metadata.last_name}`
|
|
: metadata.first_name;
|
|
console.log('✅ [ticket page] Nom trouvé dans first_name:', creatorName);
|
|
}
|
|
// Priorité 3: email
|
|
else {
|
|
creatorName = creatorUser.user.email || "Utilisateur inconnu";
|
|
console.log('⚠️ [ticket page] Aucun nom trouvé, utilisation de l\'email');
|
|
}
|
|
|
|
// Récupérer le rôle depuis organization_members
|
|
const { data: orgMember } = await sb
|
|
.from("organization_members")
|
|
.select("role")
|
|
.eq("user_id", ticket.created_by)
|
|
.eq("org_id", ticket.org_id)
|
|
.maybeSingle();
|
|
|
|
if (orgMember?.role) {
|
|
creatorRole = orgMember.role;
|
|
console.log('✅ [ticket page] Rôle trouvé:', creatorRole);
|
|
}
|
|
} else {
|
|
console.error('❌ [ticket page] Pas de données utilisateur retournées');
|
|
}
|
|
} else {
|
|
console.log('⚠️ [ticket page] Pas de created_by sur le ticket');
|
|
}
|
|
|
|
const { data: messages } = await sb
|
|
.from("ticket_messages")
|
|
.select("id, ticket_id, author_id, body, internal, via, created_at")
|
|
.eq("ticket_id", id)
|
|
.order("created_at", { ascending: true });
|
|
|
|
return (
|
|
<main className="p-6 space-y-4">
|
|
<MarkTicketRead ticketId={ticket.id} />
|
|
<div className="flex items-center justify-between">
|
|
<h1 className="text-lg font-semibold">{ticket.subject}</h1>
|
|
<StaffTicketActions ticketId={ticket.id} status={ticket.status} mode="status" />
|
|
</div>
|
|
|
|
{/* Afficher les informations de l'organisation et du créateur */}
|
|
<div className="rounded-lg border bg-slate-50 p-4 space-y-3">
|
|
<div className="text-sm">
|
|
<span className="text-slate-500">Organisation:</span>{' '}
|
|
<strong className="text-slate-900">{organization?.name || 'Non définie'}</strong>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<div className="text-sm">
|
|
<span className="text-slate-500">Ouvert par:</span>{' '}
|
|
<strong className="text-slate-900">{creatorName}</strong>
|
|
{creatorRole && (
|
|
<span className={`ml-2 inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${getRoleBadgeColor(creatorRole)}`}>
|
|
{formatRole(creatorRole)}
|
|
</span>
|
|
)}
|
|
</div>
|
|
{creatorEmail && (
|
|
<div className="text-sm text-slate-600 ml-0 flex items-center gap-1">
|
|
<Mail className="w-3.5 h-3.5" />
|
|
{creatorEmail}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="text-sm text-slate-600">Priorité: <strong>{ticket.priority}</strong> • Dernier message: {formatDate(ticket.last_message_at)} • Non lus (client/staff): {ticket.unread_by_client || 0} / {ticket.unread_by_staff || 0}</div>
|
|
|
|
<div className="rounded-2xl border bg-white p-4 space-y-4">
|
|
<div className="space-y-3">
|
|
{(messages || []).map((m) => (
|
|
<div key={m.id} className={`p-3 rounded-lg border ${m.internal ? 'bg-slate-50' : 'bg-white'}`}>
|
|
<div className="text-[11px] text-slate-500 flex items-center gap-2">
|
|
<span>{formatDate(m.created_at)}</span>
|
|
<span>•</span>
|
|
<span>{m.internal ? 'Interne (staff)' : (m.via === 'web' ? 'Client' : 'Staff')}</span>
|
|
</div>
|
|
<div className="text-sm whitespace-pre-wrap mt-1">{m.body}</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
|
|
<StaffTicketActions ticketId={ticket.id} status={ticket.status} mode="message" />
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|