diff --git a/apps/web/app/(dashboard)/issues/[id]/page.tsx b/apps/web/app/(dashboard)/issues/[id]/page.tsx index 06441140..38af2c1a 100644 --- a/apps/web/app/(dashboard)/issues/[id]/page.tsx +++ b/apps/web/app/(dashboard)/issues/[id]/page.tsx @@ -3,11 +3,11 @@ import { use } from "react"; import Link from "next/link"; import { - ArrowLeft, Bot, Calendar, ChevronRight, User, + MessageSquare, } from "lucide-react"; import { MOCK_ISSUES, @@ -24,6 +24,7 @@ import { StatusIcon, PriorityIcon } from "../page"; function timeAgo(dateStr: string): string { const diff = Date.now() - new Date(dateStr).getTime(); const minutes = Math.floor(diff / 60000); + if (minutes < 1) return "just now"; if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; @@ -32,7 +33,7 @@ function timeAgo(dateStr: string): string { } function formatDate(date: string | null): string { - if (!date) return "None"; + if (!date) return "—"; return new Date(date).toLocaleDateString("en-US", { month: "short", day: "numeric", @@ -40,30 +41,45 @@ function formatDate(date: string | null): string { }); } -function ActorAvatar({ actor, size = "sm" }: { actor: MockAssignee; size?: "sm" | "md" }) { - const sizeClass = size === "sm" ? "h-5 w-5 text-[10px]" : "h-6 w-6 text-xs"; +function shortDate(date: string): string { + return new Date(date).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }); +} + +// --------------------------------------------------------------------------- +// Avatar +// --------------------------------------------------------------------------- + +function Avatar({ + person, + size = 20, +}: { + person: MockAssignee; + size?: number; +}) { + const isAgent = person.type === "agent"; return (
- {actor.type === "agent" ? ( - - ) : ( - actor.avatar.charAt(0) - )} + {isAgent ? : person.avatar.charAt(0)}
); } // --------------------------------------------------------------------------- -// Properties Sidebar +// Property row (Linear-style: label left, clickable value right) // --------------------------------------------------------------------------- -function PropertyRow({ +function PropRow({ label, children, }: { @@ -71,9 +87,11 @@ function PropertyRow({ children: React.ReactNode; }) { return ( -
- {label} -
{children}
+
+ {label} +
+ {children} +
); } @@ -103,6 +121,7 @@ export default function IssueDetailPage({ const isOverdue = issue.dueDate && new Date(issue.dueDate) < new Date() && issue.status !== "done"; + // Merge activity + comments into timeline const timeline = [ ...issue.activity.map((a) => ({ id: a.id, @@ -118,148 +137,160 @@ export default function IssueDetailPage({ content: c.body, createdAt: c.createdAt, })), - ].sort( - (a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime() - ); + ].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime()); return (
- {/* ---- Left: Content ---- */} + {/* ================================================================ + LEFT: Content area + ================================================================ */}
- {/* Breadcrumb bar */} -
+ {/* Header bar */} +
Issues - - {issue.key} + + {issue.key}
-
+ {/* Content */} +
+ {/* Issue key */} +
{issue.key}
+ {/* Title */} -

{issue.title}

+

+ {issue.title} +

{/* Description */} {issue.description && ( -
+
{issue.description}
)} - {/* Activity */} -
-
-

- Activity -

-
+ {/* Separator */} +
-
+ {/* Activity */} +
+

Activity

+ +
{timeline.map((entry) => entry.kind === "comment" ? ( -
- + /* ---- Comment ---- */ +
+
{entry.actor.name} - + {timeAgo(entry.createdAt)}
-
+

{entry.content} -

+

) : ( + /* ---- Activity entry ---- */
-
- -
- - {entry.actor.name} - - {entry.content} - - {timeAgo(entry.createdAt)} + + + + {entry.actor.name} + {" "} + {entry.content} + {timeAgo(entry.createdAt)}
) )} +
- {/* Comment placeholder */} -
-
- -
-
- Leave a comment... -
+ {/* Comment input */} +
+
+ +
+
+ Leave a comment...
- {/* ---- Right: Properties ---- */} -
-
- Properties -
+ {/* ================================================================ + RIGHT: Properties sidebar + ================================================================ */} +
+
+
+ Properties +
-
- - - - {statusCfg.label} - - +
+ + + {statusCfg.label} + - - - {priorityCfg.label} - + + + {priorityCfg.label} + - - {issue.assignee ? ( - <> - - {issue.assignee.name} - - ) : ( - Unassigned - )} - + + {issue.assignee ? ( + <> + + {issue.assignee.name} + + ) : ( + Unassigned + )} + - - - - {formatDate(issue.dueDate)} - - + + {issue.dueDate ? ( + + {shortDate(issue.dueDate)} + + ) : ( + None + )} + - - - {issue.creator.name} - + + + {issue.creator.name} + +
- - {formatDate(issue.createdAt)} - - - - {formatDate(issue.updatedAt)} - +
+ + {shortDate(issue.createdAt)} + + + {shortDate(issue.updatedAt)} + +