diagnostic: simplify stops page to bare query + debug info
Deploy to route.crispygoat.com / deploy (push) Successful in 4m3s

This commit is contained in:
Tyler
2026-06-10 14:22:44 -06:00
parent 7d1e6f784b
commit f0a703794a
+91 -80
View File
@@ -1,13 +1,8 @@
import StopsDashboardClient from "@/components/admin/stops/StopsDashboardClient";
import StopsHeaderActions from "@/components/admin/StopsHeaderActions";
import { pool } from "@/lib/db";
import { getAdminUser } from "@/lib/admin-permissions";
import AdminAccessDenied from "@/components/admin/AdminAccessDenied";
import { PageHeader } from "@/components/admin/design-system";
import { redirect } from "next/navigation";
import { pool } from "@/lib/db";
import { type Stop } from "@/components/admin/stops/types";
const PAGE_SIZE = 50;
const StopIcon = () => (
<svg className="h-5 w-5 sm:h-6 sm:w-6 text-current" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
@@ -22,77 +17,43 @@ interface PageProps {
export default async function AdminStopsPage({ searchParams }: PageProps) {
const params = await searchParams;
const page = Math.max(1, parseInt(params.page ?? "1", 10) || 1);
const offset = (page - 1) * PAGE_SIZE;
const adminUser = await getAdminUser();
if (!adminUser) return <AdminAccessDenied />;
if (!adminUser.can_manage_stops) redirect("/admin/pickup");
if (!adminUser.can_manage_stops) {
redirect("/admin/pickup");
}
let stops: Stop[] = [];
let stops: unknown[] = [];
let error: string | null = null;
let totalCount = 0;
let error: { message: string } | null = null;
try {
const brandFilter = adminUser.brand_id
? `AND brand_id = '${adminUser.brand_id}'`
: "";
// Platform admin sees all stops, brand admin sees their brand's stops
const brandCondition = adminUser.brand_id
? `brand_id = '${adminUser.brand_id}'`
: "1=1";
// Get total count for pagination
const countResult = await pool.query<{ count: string }>(`
SELECT count(*) as count FROM stops WHERE status = 'active' ${brandFilter}
`);
const countResult = await pool.query<{ count: string }>(
`SELECT count(*) as count FROM stops WHERE ${brandCondition}`
);
totalCount = parseInt(countResult.rows[0]?.count ?? "0", 10);
// Get paginated stops
const { rows } = await pool.query<Stop & { brand_name: string } & { cutoff_date: string | null }>(`
SELECT
s.id,
s.city,
s.state,
s.date,
s.time,
s.location,
s.status,
s.brand_id,
s.address,
s.zip,
s.cutoff_date,
b.name as brand_name
FROM stops s
LEFT JOIN brands b ON b.id = s.brand_id
WHERE s.status = 'active'
${brandFilter}
ORDER BY s.date ASC
LIMIT ${PAGE_SIZE} OFFSET ${offset}
`);
stops = rows.map((r) => ({
id: r.id,
city: r.city ?? "",
state: r.state ?? "",
date: r.date ? String(r.date) : "",
time: r.time ?? "",
location: r.location ?? "",
active: r.status === "active",
brand_id: r.brand_id,
address: r.address,
zip: r.zip,
cutoff_time: r.cutoff_date,
status: r.status ?? "active",
brands: r.brand_name ? { name: r.brand_name } : { name: "" },
}));
const { rows } = await pool.query(
`SELECT s.id, s.city, s.state, s.date, s."time", s.location, s.status,
s.brand_id, s.address, s.zip, s.cutoff_date,
b.name as brand_name
FROM stops s
LEFT JOIN brands b ON b.id = s.brand_id
WHERE ${brandCondition}
ORDER BY s.date ASC
LIMIT 200`
);
stops = rows;
console.log("[admin/stops] Fetched", rows.length, "stops, total:", totalCount);
} catch (e) {
console.error("[admin/stops] Failed to load stops:", e);
error = { message: e instanceof Error ? e.message : String(e) };
error = e instanceof Error ? e.message : String(e);
console.error("[admin/stops] Query error:", error);
}
const totalPages = Math.ceil(totalCount / PAGE_SIZE);
if (error) {
return (
<main className="min-h-screen bg-[var(--admin-bg)]">
@@ -103,12 +64,16 @@ export default async function AdminStopsPage({ searchParams }: PageProps) {
<span className="text-[var(--admin-text-muted)]">/</span>
<span className="text-[var(--admin-text-primary)] font-medium">Stops & Routes</span>
</nav>
<h1 className="text-2xl sm:text-3xl font-black text-red-600 tracking-tight">
Error loading stops
</h1>
<pre className="mt-4 rounded-xl bg-white border border-[var(--admin-border)] p-4 text-sm text-[var(--admin-text-secondary)]">
{error.message}
<h1 className="text-2xl sm:text-3xl font-black text-red-600 tracking-tight">Error loading stops</h1>
<pre className="mt-4 rounded-xl bg-white border border-[var(--admin-border)] p-4 text-sm text-[var(--admin-text-secondary)] overflow-auto">
{error}
</pre>
<div className="mt-4 p-4 rounded-xl bg-stone-100 text-sm">
<p className="font-semibold">Debug info:</p>
<p>adminUser.brand_id: {adminUser.brand_id ?? "null"}</p>
<p>adminUser.role: {adminUser.role}</p>
<p>adminUser.email: {adminUser.email}</p>
</div>
</div>
</div>
</main>
@@ -125,19 +90,65 @@ export default async function AdminStopsPage({ searchParams }: PageProps) {
]}
icon={<StopIcon />}
title="Stops & Routes"
subtitle={adminUser?.brand_id ? "Managing stops for your brand." : "Manage routes, pickup locations, dates, and cutoff times."}
actions={<StopsHeaderActions brandId={adminUser?.brand_id ?? ""} />}
subtitle={`${totalCount} stops total`}
/>
</div>
{/* Content */}
<div className="px-4 sm:px-6 md:px-8 py-4 sm:py-6">
<StopsDashboardClient
stops={stops}
page={page}
totalPages={totalPages}
totalCount={totalCount}
/>
<div className="px-4 sm:px-6 md:px-8">
{stops.length === 0 ? (
<div className="rounded-2xl border border-dashed border-stone-300 bg-white p-16 text-center">
<div className="mx-auto mb-4 h-14 w-14 rounded-full bg-stone-100 flex items-center justify-center">
<svg className="h-7 w-7 text-stone-400" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={1.5}>
<path strokeLinecap="round" strokeLinejoin="round" d="M15 10.5a3 3 0 11-6 0 3 3 0 016 0z" />
<path strokeLinecap="round" strokeLinejoin="round" d="M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1115 0z" />
</svg>
</div>
<h3 className="font-display text-2xl font-medium text-stone-950">No stops found</h3>
<p className="mt-2 text-stone-500">
{adminUser.brand_id
? "No stops for your brand yet. Upload a schedule or add stops manually."
: "No stops in the database. Import the Tuxedo Tour schedule."}
</p>
<div className="mt-6 flex justify-center gap-3">
<a
href="/admin/stops/new"
className="rounded-xl bg-emerald-600 px-5 py-2.5 text-sm font-semibold text-white hover:bg-emerald-500"
>
Add Stop
</a>
</div>
</div>
) : (
<div className="rounded-2xl border border-stone-200 bg-white overflow-hidden">
<table className="w-full text-left text-sm">
<thead className="bg-stone-50 border-b border-stone-200">
<tr>
<th className="px-4 py-3 font-semibold text-stone-600">City</th>
<th className="px-4 py-3 font-semibold text-stone-600">Location</th>
<th className="px-4 py-3 font-semibold text-stone-600">Date</th>
<th className="px-4 py-3 font-semibold text-stone-600">Status</th>
<th className="px-4 py-3 font-semibold text-stone-600">Brand</th>
</tr>
</thead>
<tbody className="divide-y divide-stone-100">
{stops.map((s: any) => (
<tr key={s.id} className="hover:bg-stone-50">
<td className="px-4 py-3 font-medium text-stone-900">{s.city}, {s.state}</td>
<td className="px-4 py-3 text-stone-600">{s.location}</td>
<td className="px-4 py-3 font-mono text-stone-500">{String(s.date)}</td>
<td className="px-4 py-3">
<span className={`rounded-full px-2 py-0.5 text-xs font-medium ${
s.status === "active" ? "bg-emerald-100 text-emerald-700" :
s.status === "draft" ? "bg-amber-100 text-amber-700" :
"bg-stone-100 text-stone-500"
}`}>{s.status}</span>
</td>
<td className="px-4 py-3 text-stone-500 text-xs">{s.brand_name ?? "—"}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</main>
);