| 1 | | − | import { ArrowRight, Hand, Plus } from "lucide-react"; |
| 1 | + | import { ArrowRight, Hand, Plus, Settings, Users } from "lucide-react"; |
| 2 | 2 | | import { type ReactNode, useEffect } from "react"; |
| 3 | 3 | | import { Link, data, useRevalidator, useRouteLoaderData } from "react-router"; |
| 4 | 4 | | |
| ⋯ |
| 141 | 141 | | type Step = { done: boolean; title: string; about: string; to: string | null; action: string }; |
| 142 | 142 | | |
| 143 | 143 | | /** |
| 144 | + | * The workspace being looked at, from mission control: what it holds and |
| 145 | + | * what is moving in it, and the way to its people and settings. The rest of |
| 146 | + | * the page stays the viewer's own, across every workspace. |
| 147 | + | */ |
| 148 | + | function WorkspaceCard({ slug, role, repos, active }: { slug: string; role: string; repos: Repo[]; active: Active[] }) { |
| 149 | + | const mine = active.filter((item) => item.repo.namespace.toLowerCase() === slug); |
| 150 | + | const stats = [ |
| 151 | + | { value: repos.filter((repo) => repo.namespace.toLowerCase() === slug).length, label: "repositories" }, |
| 152 | + | { value: mine.length, label: "pull requests open" }, |
| 153 | + | { value: mine.filter((item) => item.lifecycle?.stage === "needs_you").length, label: "need you" }, |
| 154 | + | ]; |
| 155 | + | return ( |
| 156 | + | <div className="rounded-xl border border-line bg-surface p-5"> |
| 157 | + | <div className="flex items-center gap-3"> |
| 158 | + | <Avatar name={slug} size={32} square /> |
| 159 | + | <div className="min-w-0"> |
| 160 | + | <Link to={`/${slug}`} className="block truncate font-medium hover:underline"> |
| 161 | + | {slug} |
| 162 | + | </Link> |
| 163 | + | <p className="text-xs text-muted">Workspace · {role}</p> |
| 164 | + | </div> |
| 165 | + | </div> |
| 166 | + | <dl className="mt-4 grid grid-cols-3 gap-2"> |
| 167 | + | {stats.map((stat) => ( |
| 168 | + | <div key={stat.label} className="rounded-lg bg-bg/60 px-2.5 py-2 ring-1 ring-line"> |
| 169 | + | <dt className="sr-only">{stat.label}</dt> |
| 170 | + | <dd className="text-lg font-semibold tabular-nums tracking-tight">{stat.value}</dd> |
| 171 | + | <dd className="text-[0.6875rem] leading-tight text-muted">{stat.label}</dd> |
| 172 | + | </div> |
| 173 | + | ))} |
| 174 | + | </dl> |
| 175 | + | <div className="mt-4 flex flex-wrap gap-x-4 gap-y-1 text-sm"> |
| 176 | + | <Link to={`/${slug}/-/people`} className="inline-flex items-center gap-1.5 text-muted hover:text-fg"> |
| 177 | + | <Users size={13} /> |
| 178 | + | Members |
| 179 | + | </Link> |
| 180 | + | <Link |
| 181 | + | to={role === "owner" ? `/${slug}/-/settings` : `/${slug}/-/people`} |
| 182 | + | className="inline-flex items-center gap-1.5 text-muted hover:text-fg" |
| 183 | + | > |
| 184 | + | <Settings size={13} /> |
| 185 | + | Settings |
| 186 | + | </Link> |
| 187 | + | <Link to={`/${slug}`} className="inline-flex items-center gap-1 text-accent hover:underline"> |
| 188 | + | Its page <ArrowRight size={13} /> |
| 189 | + | </Link> |
| 190 | + | </div> |
| 191 | + | </div> |
| 192 | + | ); |
| 193 | + | } |
| 194 | + | |
| 195 | + | /** |
| 144 | 196 | | * The first things to do, ticked off as they are done, until an outcome has |
| 145 | 197 | | * been handed to agents. Shown above everything else on mission control. |
| 146 | 198 | | */ |
| ⋯ |
| 343 | 395 | | </div> |
| 344 | 396 | | |
| 345 | 397 | | <aside className="space-y-4"> |
| 398 | + | {shell?.workspace && ( |
| 399 | + | <WorkspaceCard |
| 400 | + | slug={shell.workspace.slug.toLowerCase()} |
| 401 | + | role={shell.workspace.role} |
| 402 | + | repos={repos} |
| 403 | + | active={active} |
| 404 | + | /> |
| 405 | + | )} |
| 346 | 406 | | <div className="rounded-xl border border-line bg-surface p-5"> |
| 347 | 407 | | <h2 className="font-medium">Connect an agent</h2> |
| 348 | 408 | | <p className="mt-1.5 text-sm text-muted"> |