redesign: ticket detail — cleaner transaction cards, system events as timeline

- System events now render as subtle timeline entries (icon dot + text)
  instead of heavy bordered boxes
- Message cards are cleaner: rounded avatars, no card borders,
  just typography and spacing. Internal badge is subtler.
- Removed border/shadow from message cards — cleaner, more modern

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Gjermund Høsøien Wiggen
2026-06-09 22:47:00 +02:00
parent 5308ee8653
commit 8b371ae3c2

View File

@@ -125,9 +125,8 @@ function TransactionCard({
if (isSystem) { if (isSystem) {
let message = tx.transaction_type; let message = tx.transaction_type;
if (tx.transaction_type === "Create") { if (tx.transaction_type === "Create") message = "Ticket created";
message = "Ticket created"; else if (tx.transaction_type === "StatusChange") {
} else if (tx.transaction_type === "StatusChange") {
const oldLabel = tx.old_value ? statusLabel(tx.old_value) : "?"; const oldLabel = tx.old_value ? statusLabel(tx.old_value) : "?";
const newLabel = tx.new_value ? statusLabel(tx.new_value) : "?"; const newLabel = tx.new_value ? statusLabel(tx.new_value) : "?";
message = `Status changed from ${oldLabel} to ${newLabel}`; message = `Status changed from ${oldLabel} to ${newLabel}`;
@@ -135,60 +134,45 @@ function TransactionCard({
message = tx.new_value ? `Assigned to ${userLabel(users, tx.new_value)}` : "Unassigned"; message = tx.new_value ? `Assigned to ${userLabel(users, tx.new_value)}` : "Unassigned";
} else if (tx.transaction_type === "CustomFieldChange") { } else if (tx.transaction_type === "CustomFieldChange") {
const fieldName = tx.field ? customFieldLabels[tx.field] ?? "Custom field" : "Custom field"; const fieldName = tx.field ? customFieldLabels[tx.field] ?? "Custom field" : "Custom field";
message = tx.new_value message = tx.new_value ? `${fieldName} set to ${tx.new_value}` : `${fieldName} cleared`;
? `${fieldName} set to ${tx.new_value}`
: `${fieldName} cleared`;
} }
return ( return (
<div className="grid grid-cols-[28px_minmax(0,1fr)] gap-3 px-6 py-3"> <div className="flex items-center gap-3 px-8 py-2.5">
<div className="flex justify-center"> <div className="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-muted/40">
<span className="mt-1 flex h-5 w-5 items-center justify-center rounded bg-muted text-muted-foreground"> <CircleIcon className="h-2.5 w-2.5 text-muted-foreground/60" />
<BotIcon className="h-3.5 w-3.5" />
</span>
</div>
<div className="rounded-md border border-border bg-muted/55 px-3 py-2 text-sm">
<div className="flex flex-wrap items-center gap-x-2 gap-y-1">
<span className="font-medium text-foreground">{message}</span>
<span className="text-xs text-muted-foreground">{timeAgo}</span>
</div>
</div> </div>
<span className="text-xs text-muted-foreground">{message}</span>
<span className="text-[10px] text-muted-foreground/50">{timeAgo}</span>
</div> </div>
); );
} }
return ( return (
<article className="grid grid-cols-[28px_minmax(0,1fr)] gap-3 px-6 py-4"> <div className="px-8 py-3">
<div <div className="flex items-start gap-3">
className="flex h-7 w-7 items-center justify-center rounded-md text-[11px] font-semibold text-white" <div
style={{ backgroundColor: getInitialColor(userLabel(users, tx.creator_id)) }} className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-[11px] font-semibold text-white"
> style={{ backgroundColor: getInitialColor(userLabel(users, tx.creator_id)) }}
{getInitial(userLabel(users, tx.creator_id))} >
</div> {getInitial(userLabel(users, tx.creator_id))}
<div className="overflow-hidden rounded-md border border-border bg-card shadow-sm"> </div>
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border bg-muted/35 px-3 py-2"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2"> <div className="flex items-baseline gap-2">
{isMessage ? ( <span className="text-sm font-semibold text-foreground">{userLabel(users, tx.creator_id)}</span>
<MessageSquareIcon className="h-3.5 w-3.5 text-muted-foreground" />
) : (
<FileTextIcon className="h-3.5 w-3.5 text-muted-foreground" />
)}
<span className="text-sm font-semibold text-foreground">
{userLabel(users, tx.creator_id)}
</span>
{isInternal && ( {isInternal && (
<span className="rounded bg-amber-500/12 px-1.5 py-0.5 text-[10px] font-semibold uppercase text-amber-700 dark:text-amber-300"> <span className="rounded bg-amber-500/10 px-1 py-0 text-[10px] font-semibold text-amber-600 dark:text-amber-400">
Internal Internal
</span> </span>
)} )}
<span className="text-[11px] text-muted-foreground/50">{timeAgo}</span>
</div> </div>
<span className="text-xs text-muted-foreground">{timeAgo}</span> <p className="mt-1.5 whitespace-pre-wrap text-sm leading-relaxed text-foreground/90">
{body}
</p>
</div> </div>
<p className="whitespace-pre-wrap px-3 py-3 text-sm leading-6 text-foreground">
{body}
</p>
</div> </div>
</article> </div>
); );
} }