Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

61 linesCodeBlameRaw
1import { useLocation, useMatches } from "react-router";
2
3import { NavTab, NavTabs } from "./ui/nav-tabs";
4import { Hint } from "./ui/hint";
5import { workPath } from "../lib/work-items";
6
7/** Work's views, as its tabs list them: the path under `-/work` ("" is the Plan), and what each is. */
8export const WORK_TABS = [
9 { path: "", label: "Plan", about: "What the workspace is on now, what comes next, and what can wait" },
10 { path: "board", label: "Board", about: "Work items as they move" },
11 { path: "triage", label: "Triage", about: "New work items nobody has placed yet" },
12 { path: "all", label: "All items", about: "Every work item, with filters" },
13 { path: "milestones", label: "Milestones", about: "Goals with a due date" },
14 { path: "sessions", label: "Sessions", about: "What is cooking across the workspace" },
15] as const;
16
17/**
18 * Which of Work's tabs `pathname` is (its path under `-/work`, "" for the
19 * Plan), or null for Work's other pages: one item, one milestone, one
20 * session, a new item, labels.
21 */
22export function workTab(pathname: string, workspace: string): string | null {
23 const base = workPath(workspace);
24 const path = pathname.replace(/\.data$/, "").replace(/\/+$/, "");
25 if (path.toLowerCase() === base) return "";
26 if (!path.toLowerCase().startsWith(`${base}/`)) return null;
27 const rest = path.slice(base.length + 1);
28 return WORK_TABS.some((tab) => tab.path === rest) ? rest : null;
29}
30
31/** How many wait in Triage, from whichever page's loader read the plan (`triageTotal`): none shown when no page did. */
32function useTriageTotal(): number | undefined {
33 for (const match of useMatches()) {
34 const data = match.loaderData as { triageTotal?: unknown } | undefined;
35 if (data && typeof data.triageTotal === "number") return data.triageTotal;
36 }
37 return undefined;
38}
39
40/**
41 * Work's tabs, over each of its views: Plan, Board, Triage (with how many
42 * wait there), All items, Milestones and Sessions.
43 */
44export function WorkTabs({ workspace }: { workspace: string }) {
45 const { pathname } = useLocation();
46 const current = workTab(pathname, workspace);
47 const triage = useTriageTotal();
48 return (
49 <div className="mb-6 border-b border-line pb-3">
50 <NavTabs label="Work" variant="pill">
51 {WORK_TABS.map((tab) => (
52 <Hint key={tab.path} label={tab.about}>
53 <NavTab variant="pill" to={workPath(workspace, tab.path)} current={current === tab.path} count={tab.path === "triage" ? triage : undefined}>
54 {tab.label}
55 </NavTab>
56 </Hint>
57 ))}
58 </NavTabs>
59 </div>
60 );
61}