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.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" ? : }
+
);
}