Ticket IDs, audit log, markdown comments, tabbed detail page
- Tickets get a random display ID (V + 9 digits, e.g. V325813929) - Ticket detail page has Overview / Comments / Audit Log tabs - Audit log records every action (create, status, assignee, severity, reroute, title/overview edit, comment add/delete) with who and when - Comments redesigned: avatar (initials + color), markdown rendering via react-markdown + remark-gfm, Write/Preview toggle - Dashboard shows displayId and assignee avatar - URLs now use displayId (/tickets/V325813929) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import api from '../api/client'
|
||||
import Layout from '../components/Layout'
|
||||
import SeverityBadge from '../components/SeverityBadge'
|
||||
import StatusBadge from '../components/StatusBadge'
|
||||
import Avatar from '../components/Avatar'
|
||||
import { Ticket, TicketStatus } from '../types'
|
||||
|
||||
const STATUSES: { value: TicketStatus | ''; label: string }[] = [
|
||||
@@ -102,7 +103,7 @@ export default function Dashboard() {
|
||||
{tickets.map((ticket) => (
|
||||
<Link
|
||||
key={ticket.id}
|
||||
to={`/tickets/${ticket.id}`}
|
||||
to={`/tickets/${ticket.displayId}`}
|
||||
className="flex items-center gap-4 bg-white border border-gray-200 rounded-lg px-4 py-3 hover:border-blue-400 hover:shadow-sm transition-all group"
|
||||
>
|
||||
{/* Severity stripe */}
|
||||
@@ -122,6 +123,9 @@ export default function Dashboard() {
|
||||
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 mb-0.5">
|
||||
<span className="text-xs font-mono font-medium text-gray-400">
|
||||
{ticket.displayId}
|
||||
</span>
|
||||
<SeverityBadge severity={ticket.severity} />
|
||||
<StatusBadge status={ticket.status} />
|
||||
<span className="text-xs text-gray-400">
|
||||
@@ -131,15 +135,24 @@ export default function Dashboard() {
|
||||
<p className="text-sm font-medium text-gray-900 truncate group-hover:text-blue-700">
|
||||
{ticket.title}
|
||||
</p>
|
||||
<p className="text-xs text-gray-400 truncate mt-0.5">{ticket.overview}</p>
|
||||
</div>
|
||||
|
||||
<div className="text-right text-xs text-gray-400 flex-shrink-0 space-y-0.5">
|
||||
<div className="font-medium text-gray-600">
|
||||
{ticket.assignee?.displayName ?? 'Unassigned'}
|
||||
</div>
|
||||
<div>{ticket._count?.comments ?? 0} comments</div>
|
||||
<div>{formatDistanceToNow(new Date(ticket.createdAt), { addSuffix: true })}</div>
|
||||
<div className="flex items-center gap-3 flex-shrink-0">
|
||||
{ticket.assignee && (
|
||||
<div className="flex items-center gap-1.5 text-xs text-gray-500">
|
||||
<Avatar name={ticket.assignee.displayName} size="sm" />
|
||||
<span>{ticket.assignee.displayName}</span>
|
||||
</div>
|
||||
)}
|
||||
{!ticket.assignee && (
|
||||
<span className="text-xs text-gray-400">Unassigned</span>
|
||||
)}
|
||||
<span className="text-xs text-gray-400">
|
||||
{ticket._count?.comments ?? 0} comments
|
||||
</span>
|
||||
<span className="text-xs text-gray-400">
|
||||
{formatDistanceToNow(new Date(ticket.createdAt), { addSuffix: true })}
|
||||
</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
|
||||
Reference in New Issue
Block a user