125 lines
4.0 KiB
TypeScript
125 lines
4.0 KiB
TypeScript
// app/admin/categories/page.tsx
|
|
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { useAuth } from "@/context/AuthContext";
|
|
import {
|
|
fetchAdminCategories,
|
|
type AdminCategory,
|
|
} from "@/lib/api/adminCategoryMappings";
|
|
|
|
export default function AdminCategoriesPage() {
|
|
const { token } = useAuth();
|
|
const [categories, setCategories] = useState<AdminCategory[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!token) return;
|
|
|
|
const load = async () => {
|
|
try {
|
|
setLoading(true);
|
|
const cats = await fetchAdminCategories(token);
|
|
setCategories(cats);
|
|
} catch (e: any) {
|
|
console.error(e);
|
|
setError(e?.message ?? "Failed to load categories");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
load();
|
|
}, [token]);
|
|
|
|
if (!token) {
|
|
return (
|
|
<div className="p-6 text-sm text-red-500">
|
|
You must be logged in to view this page.
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="p-6 space-y-4">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between">
|
|
<div>
|
|
<h1 className="text-lg font-semibold tracking-tight">
|
|
Canonical categories
|
|
</h1>
|
|
<p className="mt-1 text-xs text-zinc-400">
|
|
These are your standardized builder categories and their groupings.
|
|
</p>
|
|
</div>
|
|
{/* Placeholder for future actions (add/edit) */}
|
|
{/* <button className="rounded-md bg-emerald-600 px-3 py-1 text-xs font-medium text-white hover:bg-emerald-500">
|
|
+ Add category
|
|
</button> */}
|
|
</div>
|
|
|
|
{/* Error */}
|
|
{error && (
|
|
<div className="rounded border border-red-500/60 bg-red-950/30 px-3 py-2 text-xs text-red-200">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{/* Table */}
|
|
{loading ? (
|
|
<div className="text-sm text-zinc-400">Loading…</div>
|
|
) : (
|
|
<div className="overflow-x-auto rounded-lg border border-zinc-800 bg-zinc-950/60">
|
|
<table className="min-w-full text-left text-xs">
|
|
<thead className="bg-zinc-900/80 text-zinc-400">
|
|
<tr>
|
|
<th className="px-3 py-2 font-medium">Group</th>
|
|
<th className="px-3 py-2 font-medium">Name</th>
|
|
<th className="px-3 py-2 font-medium">Slug</th>
|
|
<th className="px-3 py-2 font-medium">Sort order</th>
|
|
<th className="px-3 py-2 font-medium">Description</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{categories.map((cat) => (
|
|
<tr
|
|
key={cat.id}
|
|
className="border-t border-zinc-800/70 hover:bg-zinc-900/60"
|
|
>
|
|
<td className="px-3 py-2 text-[11px] text-zinc-400">
|
|
{cat.groupName ?? "—"}
|
|
</td>
|
|
<td className="px-3 py-2 text-zinc-100">{cat.name}</td>
|
|
<td className="px-3 py-2 text-[11px] text-zinc-500">
|
|
<code>{cat.slug}</code>
|
|
</td>
|
|
<td className="px-3 py-2 text-[11px] text-zinc-400">
|
|
{cat.sortOrder ?? "—"}
|
|
</td>
|
|
<td className="px-3 py-2 text-zinc-300">
|
|
{cat.description || (
|
|
<span className="text-zinc-500">No description</span>
|
|
)}
|
|
</td>
|
|
</tr>
|
|
))}
|
|
|
|
{categories.length === 0 && (
|
|
<tr>
|
|
<td
|
|
colSpan={5}
|
|
className="px-3 py-4 text-center text-xs text-zinc-500"
|
|
>
|
|
No categories found. Seed the <code>part_categories</code>{" "}
|
|
table to get started.
|
|
</td>
|
|
</tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
} |