diff --git a/src/app/dashboard/sites/page.tsx b/src/app/dashboard/sites/page.tsx index 76a847c..43eac6d 100644 --- a/src/app/dashboard/sites/page.tsx +++ b/src/app/dashboard/sites/page.tsx @@ -12,6 +12,8 @@ import { } from "@/lib/metrics"; import { Topbar } from "@/components/layout/sidebar"; import { SitesView, type SiteSummary } from "./sites-view"; +import { SetScopeButton } from "@/components/layout/set-scope-button"; +import { getSiteScope, type SearchParams } from "@/lib/site-scope"; import { AddSiteButton, type SiteTemplateOption, @@ -19,9 +21,20 @@ import { export const dynamic = "force-dynamic"; -export default async function SitesPage() { +export default async function SitesPage({ + searchParams, +}: { + searchParams: Promise; +}) { const pb = await pbAdmin(); + // Sites list is intentionally exempt from scope (see plan ยง12): + // scoping it to one site would leave a one-row comparison table. + // We still read the allowed list so we can hide sites the user may + // not see, when PocketBase users land. + const scope = await getSiteScope(await searchParams); + const allowedIds = new Set(scope.allowed.map((s) => s.id)); + const [ sites, fixedCosts, @@ -117,7 +130,9 @@ export default async function SitesPage() { incomeBySite.set(c.site, list); } - const summaries: SiteSummary[] = sites.map((s) => { + const summaries: SiteSummary[] = sites + .filter((s) => allowedIds.has(s.id)) + .map((s) => { const siteFuel = fuelBySite.get(s.id) ?? []; const spend = computeSiteSpend({ diff --git a/src/app/dashboard/sites/sites-view.tsx b/src/app/dashboard/sites/sites-view.tsx index 34833e9..3b7196c 100644 --- a/src/app/dashboard/sites/sites-view.tsx +++ b/src/app/dashboard/sites/sites-view.tsx @@ -11,6 +11,7 @@ import { } from "lucide-react"; import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; +import { SetScopeButton } from "@/components/layout/set-scope-button"; import { cn, formatCurrency, formatNumber } from "@/lib/utils"; export type SiteStatus = "just-started" | "under" | "on-track" | "over"; @@ -239,6 +240,13 @@ function SiteCard({ site }: { site: SiteSummary }) { {formatCurrency(site.projectedProfit)} + + {/* Per-row scope action. Sits at the bottom of the card, on its + own row, so it never shares a hit target with the row's own + `Link` to the detail page. */} +
+ +
); diff --git a/src/components/layout/set-scope-button.tsx b/src/components/layout/set-scope-button.tsx new file mode 100644 index 0000000..5d02224 --- /dev/null +++ b/src/components/layout/set-scope-button.tsx @@ -0,0 +1,36 @@ +"use client"; + +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { Crosshair } from "lucide-react"; +import { SITE_SCOPE_COOKIE, SITE_SCOPE_PARAM } from "@/lib/site-scope"; + +/** + * Sets the global scope to one site from the Sites list. Deliberately + * NOT wired to the row's link: navigating must never silently mutate + * scope, so choosing a scope is its own explicit action. + */ +export function SetScopeButton({ siteId, siteName }: { siteId: string; siteName: string }) { + const router = useRouter(); + const pathname = usePathname(); + const params = useSearchParams(); + + return ( + + ); +}