import StopTableClient from "@/components/admin/StopTableClient"; import LocationsTab from "@/components/admin/LocationsTab"; import StopsLocationsTabs from "@/components/admin/StopsLocationsTabs"; import StatsStrip from "@/components/admin/StatsStrip"; import { supabase } from "@/lib/supabase"; import { getAdminUser } from "@/lib/admin-permissions"; import { adminListLocations } from "@/actions/locations"; import AdminAccessDenied from "@/components/admin/AdminAccessDenied"; import { PageHeader } from "@/components/admin/design-system"; import { redirect } from "next/navigation"; import { TabSwitcher } from "@/components/admin/TabSwitcher"; const StopIcon = () => ( ); type TabValue = "stops" | "locations"; function isTab(v: string | undefined): v is TabValue { return v === "stops" || v === "locations"; } type PageProps = { searchParams: Promise<{ tab?: string }>; }; export default async function AdminStopsPage({ searchParams }: PageProps) { const adminUser = await getAdminUser(); if (!adminUser) return ; if (!adminUser.can_manage_stops) redirect("/admin/pickup"); const params = await searchParams; const tab: TabValue = isTab(params.tab) ? params.tab : "stops"; // Always fetch stops + locations; the page is fast and a server component can // hand both to the client. The Locations tab only needs the array — it does // its own filtering in JS. Stops tab uses the existing client table. const stopsQuery = supabase .from("stops") .select(` id, city, state, date, time, location, active, deleted_at, brand_id, address, zip, cutoff_time, status, brands ( name ) `) .is("deleted_at", null) .order("date", { ascending: true }); if (adminUser?.brand_id) { stopsQuery.eq("brand_id", adminUser.brand_id); } const [{ data: stops, error: stopsError }, locations] = await Promise.all([ stopsQuery, adminListLocations(adminUser.brand_id ?? ""), ]); if (stopsError) { return ( Admin / Stops & Routes Error loading stops {stopsError.message} ); } // Derive stats for the strip. The stops/locations arrays are already on hand. const safeStops = stops ?? []; const cityCount = new Set( safeStops.map((s) => `${(s.city ?? "").toLowerCase()}|${(s.state ?? "").toLowerCase()}`) ).size; const stateCount = new Set(safeStops.map((s) => (s.state ?? "").toUpperCase())).size; const draftCount = safeStops.filter((s) => s.status === "draft").length; const activeCount = safeStops.filter((s) => s.active && s.status !== "draft").length; const inactiveCount = safeStops.filter((s) => !s.active && s.status !== "draft").length; const locationCityCount = new Set( locations.map((l) => (l.city ?? "").toLowerCase()) ).size; const locationWithStops = locations.filter((l) => l.stop_count > 0).length; const subtitle = tab === "locations" ? "Reusable venues. Each stop links to one venue, so editing here updates every stop using it." : adminUser?.brand_id ? "Manage stops, venues, and the routes that connect them." : "Manage stops, venues, and the routes that connect them."; const stopsStats = [ { value: safeStops.length, label: "stops" }, { value: cityCount, label: "cities" }, { value: stateCount, label: "states" }, { value: activeCount, label: "active" }, { value: draftCount, label: "draft", emphasis: draftCount > 0 }, { value: inactiveCount, label: "inactive" }, ]; const locationsStats = [ { value: locations.length, label: "venues" }, { value: locationCityCount, label: "cities" }, { value: locationWithStops, label: "with stops" }, ]; return ( {/* Header */} } title="Stops & Routes" subtitle={subtitle} /> {/* Card: tabs + content */} {tab === "stops" ? ( ) : ( )} ); }
{stopsError.message}