espace-paie-odentas/app/(app)/staff/tickets/[id]/page.tsx

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