diff --git a/apps/web/app/(dashboard)/issues/page.tsx b/apps/web/app/(dashboard)/issues/page.tsx index 1d38961b..f4d20729 100644 --- a/apps/web/app/(dashboard)/issues/page.tsx +++ b/apps/web/app/(dashboard)/issues/page.tsx @@ -1,15 +1,263 @@ -export default function IssuesPage() { +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { + Columns3, + List, + Plus, + Bot, + Calendar, +} from "lucide-react"; +import type { IssueStatus, IssuePriority } from "@multica/types"; +import { + MOCK_ISSUES, + STATUS_ORDER, + STATUS_CONFIG, + PRIORITY_CONFIG, + type MockIssue, + type MockAssignee, +} from "./_data/mock"; + +// --------------------------------------------------------------------------- +// Shared sub-components +// --------------------------------------------------------------------------- + +function PriorityBadge({ priority }: { priority: IssuePriority }) { + const cfg = PRIORITY_CONFIG[priority]; return ( -
-
-

Issues

- -
-

- No issues yet. Create your first issue to get started. -

+ + {cfg.shortLabel} + + ); +} + +function AssigneeAvatar({ assignee }: { assignee: MockAssignee | null }) { + if (!assignee) return null; + return ( +
+ {assignee.type === "agent" ? ( + + ) : ( + assignee.avatar.charAt(0) + )} +
+ ); +} + +function StatusDot({ status }: { status: IssueStatus }) { + const cfg = STATUS_CONFIG[status]; + return ; +} + +function formatDueDate(date: string | null): string | null { + if (!date) return null; + const d = new Date(date); + return d.toLocaleDateString("en-US", { month: "short", day: "numeric" }); +} + +// --------------------------------------------------------------------------- +// Board View +// --------------------------------------------------------------------------- + +function BoardCard({ issue }: { issue: MockIssue }) { + const due = formatDueDate(issue.dueDate); + const isOverdue = + issue.dueDate && new Date(issue.dueDate) < new Date() && issue.status !== "done"; + + return ( + +
+ + {issue.key} +
+

{issue.title}

+
+ + {due && ( + + + {due} + + )} + {issue.comments.length > 0 && ( + + {issue.comments.length} 💬 + + )} +
+ + ); +} + +function BoardView() { + const visibleStatuses: IssueStatus[] = [ + "backlog", + "todo", + "in_progress", + "in_review", + "done", + ]; + + return ( +
+ {visibleStatuses.map((status) => { + const cfg = STATUS_CONFIG[status]; + const issues = MOCK_ISSUES.filter((i) => i.status === status); + return ( +
+ {/* Column header */} +
+ + {cfg.label} + {issues.length} +
+ {/* Cards */} +
+ {issues.map((issue) => ( + + ))} +
+
+ ); + })} +
+ ); +} + +// --------------------------------------------------------------------------- +// List View +// --------------------------------------------------------------------------- + +function ListRow({ issue }: { issue: MockIssue }) { + const due = formatDueDate(issue.dueDate); + const isOverdue = + issue.dueDate && new Date(issue.dueDate) < new Date() && issue.status !== "done"; + + return ( + + + {issue.key} + {issue.title} + {due && ( + + + {due} + + )} + + + ); +} + +function ListView() { + const visibleStatuses: IssueStatus[] = [ + "in_review", + "in_progress", + "todo", + "backlog", + "done", + ]; + + return ( +
+ {visibleStatuses.map((status) => { + const cfg = STATUS_CONFIG[status]; + const issues = MOCK_ISSUES.filter((i) => i.status === status); + if (issues.length === 0) return null; + return ( +
+ {/* Group header */} +
+ + {cfg.label} + {issues.length} +
+ {/* Rows */} +
+ {issues.map((issue) => ( + + ))} +
+
+ ); + })} +
+ ); +} + +// --------------------------------------------------------------------------- +// Page +// --------------------------------------------------------------------------- + +type ViewMode = "board" | "list"; + +export default function IssuesPage() { + const [view, setView] = useState("board"); + + return ( +
+ {/* Header */} +
+
+

All Issues

+ {/* View toggle */} +
+ + +
+
+ +
+ + {/* Content */} +
+ {view === "board" ? : } +
); }