| 1 | import { useLocation, useMatches } from "react-router"; |
| 2 | |
| 3 | import { NavTab, NavTabs } from "./ui/nav-tabs"; |
| 4 | import { Hint } from "./ui/hint"; |
| 5 | import { 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. */ |
| 8 | export 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 | */ |
| 22 | export 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. */ |
| 32 | function 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 | */ |
| 44 | export 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 | } |