diff --git a/apps/web/app/(dashboard)/_components/app-sidebar.tsx b/apps/web/app/(dashboard)/_components/app-sidebar.tsx index 7447b2d5..42a66298 100644 --- a/apps/web/app/(dashboard)/_components/app-sidebar.tsx +++ b/apps/web/app/(dashboard)/_components/app-sidebar.tsx @@ -14,6 +14,7 @@ import { Check, BookOpenText, SquarePen, + CircleUser, } from "lucide-react"; import { WorkspaceAvatar } from "@/features/workspace"; import { useIssueDraftStore } from "@/features/issues/stores/draft-store"; @@ -46,6 +47,7 @@ import { useModalStore } from "@/features/modals"; const primaryNav = [ { href: "/inbox", label: "Inbox", icon: Inbox }, + { href: "/my-issues", label: "My Issues", icon: CircleUser }, { href: "/issues", label: "Issues", icon: ListTodo }, ]; diff --git a/apps/web/app/(dashboard)/my-issues/page.tsx b/apps/web/app/(dashboard)/my-issues/page.tsx new file mode 100644 index 00000000..d8870b00 --- /dev/null +++ b/apps/web/app/(dashboard)/my-issues/page.tsx @@ -0,0 +1,7 @@ +"use client"; + +import { MyIssuesPage } from "@/features/my-issues"; + +export default function Page() { + return ; +} diff --git a/apps/web/features/my-issues/components/my-issues-page.tsx b/apps/web/features/my-issues/components/my-issues-page.tsx new file mode 100644 index 00000000..9a8c98a3 --- /dev/null +++ b/apps/web/features/my-issues/components/my-issues-page.tsx @@ -0,0 +1,171 @@ +"use client"; + +import { useMemo } from "react"; +import Link from "next/link"; +import { ChevronRight, User, Bot, SquarePen, ListTodo } from "lucide-react"; +import { Accordion } from "@base-ui/react/accordion"; +import type { Issue } from "@/shared/types"; +import { useAuthStore } from "@/features/auth"; +import { useWorkspaceStore } from "@/features/workspace"; +import { useIssueStore } from "@/features/issues/store"; +import { WorkspaceAvatar } from "@/features/workspace"; +import { StatusIcon } from "@/features/issues/components/status-icon"; +import { PriorityIcon } from "@/features/issues/components/priority-icon"; +import { ActorAvatar } from "@/components/common/actor-avatar"; +import { Skeleton } from "@/components/ui/skeleton"; + +interface GroupConfig { + key: string; + label: string; + icon: React.ComponentType<{ className?: string }>; +} + +const GROUPS: GroupConfig[] = [ + { key: "assigned_to_me", label: "Assigned to me", icon: User }, + { key: "assigned_to_my_agents", label: "Assigned to my agents", icon: Bot }, + { key: "created_by_me", label: "Created by me", icon: SquarePen }, +]; + +function formatDate(date: string): string { + return new Date(date).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + }); +} + +function IssueRow({ issue }: { issue: Issue }) { + return ( + + + + + {issue.identifier} + + {issue.title} + {issue.due_date && ( + + {formatDate(issue.due_date)} + + )} + {issue.assignee_type && issue.assignee_id && ( + + )} + + ); +} + +export function MyIssuesPage() { + const user = useAuthStore((s) => s.user); + const workspace = useWorkspaceStore((s) => s.workspace); + const agents = useWorkspaceStore((s) => s.agents); + const allIssues = useIssueStore((s) => s.issues); + const loading = useIssueStore((s) => s.loading); + + const myAgentIds = useMemo(() => { + if (!user) return new Set(); + return new Set(agents.filter((a) => a.owner_id === user.id).map((a) => a.id)); + }, [agents, user]); + + const grouped = useMemo(() => { + if (!user) return new Map(); + + const assignedToMe = allIssues.filter( + (i) => i.assignee_type === "member" && i.assignee_id === user.id, + ); + const assignedToMyAgents = allIssues.filter( + (i) => i.assignee_type === "agent" && i.assignee_id && myAgentIds.has(i.assignee_id), + ); + const createdByMe = allIssues.filter( + (i) => i.creator_type === "member" && i.creator_id === user.id, + ); + + const map = new Map(); + map.set("assigned_to_me", assignedToMe); + map.set("assigned_to_my_agents", assignedToMyAgents); + map.set("created_by_me", createdByMe); + return map; + }, [allIssues, user, myAgentIds]); + + if (loading) { + return ( +
+
+ + +
+
+ {Array.from({ length: 3 }).map((_, i) => ( +
+ + + +
+ ))} +
+
+ ); + } + + return ( +
+ {/* Header: Workspace breadcrumb */} +
+ + + {workspace?.name ?? "Workspace"} + + + My Issues +
+ + {/* Content */} +
+ g.key)} + > + {GROUPS.map((group) => { + const issues = grouped.get(group.key) ?? []; + const Icon = group.icon; + + return ( + + + + + + + {group.label} + + + {issues.length} + + + + + {issues.length > 0 ? ( + issues.map((issue) => ( + + )) + ) : ( +

+ No issues +

+ )} +
+
+ ); + })} +
+
+
+ ); +} diff --git a/apps/web/features/my-issues/index.ts b/apps/web/features/my-issues/index.ts new file mode 100644 index 00000000..f4d252aa --- /dev/null +++ b/apps/web/features/my-issues/index.ts @@ -0,0 +1 @@ +export { MyIssuesPage } from "./components/my-issues-page";