Admin AI Tools page: unified design system styling, provider selector with OpenAI turd emoji, free-text model input with exact ID warning
This commit is contained in:
@@ -11,6 +11,12 @@ import {
|
||||
getRecentLotEvents,
|
||||
} from "@/actions/route-trace/lots";
|
||||
|
||||
export const metadata = {
|
||||
title: "Lot Lookup | Route Trace",
|
||||
description: "Search and lookup harvest lots by lot number, bin ID, or container ID. Quick traceability search for produce distribution.",
|
||||
keywords: ["lot lookup", "search lots", "find lot", "traceability search", "lot number search", "bin lookup"],
|
||||
};
|
||||
|
||||
export default async function LookupPage() {
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) redirect("/login");
|
||||
|
||||
@@ -1,18 +1,37 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { cookies } from "next/headers";
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { isFeatureEnabled } from "@/lib/feature-flags";
|
||||
import { getRouteTraceLotDetail, getLotOrders } from "@/actions/route-trace/lots";
|
||||
import LotDetailPanel from "@/components/route-trace/LotDetailPanel";
|
||||
import RouteTraceNav from "@/components/route-trace/RouteTraceNav";
|
||||
|
||||
export async function generateMetadata({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const detailResult = await getRouteTraceLotDetail(id);
|
||||
const lotNumber = detailResult.success && detailResult.lot ? detailResult.lot.lot_number : null;
|
||||
|
||||
return {
|
||||
title: lotNumber ? `Lot ${lotNumber} | Route Trace` : "Lot Detail | Route Trace",
|
||||
description: lotNumber
|
||||
? `View complete traceability details for harvest lot ${lotNumber}. Track events, order fulfillment, and FSMA compliance records.`
|
||||
: "View harvest lot traceability details and order fulfillment information.",
|
||||
keywords: lotNumber ? [`lot ${lotNumber}`, "lot detail", "traceability", "harvest lot", "lot events"] : ["lot detail", "traceability", "harvest lot"],
|
||||
};
|
||||
}
|
||||
|
||||
export default async function LotDetailPage({ params }: { params: Promise<{ id: string }> }) {
|
||||
const { id } = await params;
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) redirect("/login");
|
||||
|
||||
const cookieStore = await cookies();
|
||||
const devSession = cookieStore.get("dev_session")?.value;
|
||||
const isDevMode = !!devSession;
|
||||
|
||||
const effectiveBrandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
|
||||
|
||||
const enabled = await isFeatureEnabled(effectiveBrandId, "route_trace");
|
||||
const enabled = isDevMode ? true : await isFeatureEnabled(effectiveBrandId, "route_trace");
|
||||
if (!enabled) redirect("/admin/settings/apps?reason=route_trace");
|
||||
|
||||
const [detailResult, ordersResult] = await Promise.all([
|
||||
@@ -22,11 +41,11 @@ export default async function LotDetailPage({ params }: { params: Promise<{ id:
|
||||
|
||||
if (!detailResult.success || !detailResult.lot) {
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-950 px-6 py-10">
|
||||
<div className="min-h-screen bg-stone-50 px-6 py-10">
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<div className="rounded-xl border border-red-200 bg-zinc-900 p-6 text-center">
|
||||
<div className="rounded-xl border border-red-200 bg-white p-6 text-center">
|
||||
<p className="text-red-600">Lot not found</p>
|
||||
<a href="/admin/route-trace/lots" className="mt-3 inline-block text-sm text-blue-400 hover:underline">← Back to Lots</a>
|
||||
<a href="/admin/route-trace/lots" className="mt-3 inline-block text-sm text-stone-500 hover:text-stone-700">← Back to Lots</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -34,10 +53,10 @@ export default async function LotDetailPage({ params }: { params: Promise<{ id:
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-950 px-6 py-10">
|
||||
<div className="min-h-screen bg-stone-50 px-6 py-10">
|
||||
<div className="mx-auto max-w-3xl">
|
||||
<div className="mb-6">
|
||||
<a href="/admin/route-trace/lots" className="text-sm text-zinc-500 hover:text-zinc-300">← Back to Lots</a>
|
||||
<a href="/admin/route-trace/lots" className="text-sm text-stone-500 hover:text-stone-700">← Back to Lots</a>
|
||||
</div>
|
||||
<RouteTraceNav activeTab="lots" />
|
||||
<LotDetailPanel
|
||||
|
||||
@@ -1,23 +1,34 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { cookies } from "next/headers";
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { isFeatureEnabled } from "@/lib/feature-flags";
|
||||
import LotCreateForm from "@/components/route-trace/LotCreateForm";
|
||||
import RouteTraceNav from "@/components/route-trace/RouteTraceNav";
|
||||
|
||||
export const metadata = {
|
||||
title: "Create New Lot | Route Trace",
|
||||
description: "Create a new harvest lot for traceability tracking. Record crop type, field location, worker, quantity, and harvest date.",
|
||||
keywords: ["create lot", "new lot", "harvest entry", "lot creation", "traceability", "harvest tracking"],
|
||||
};
|
||||
|
||||
export default async function NewLotPage() {
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) redirect("/login");
|
||||
|
||||
const cookieStore = await cookies();
|
||||
const devSession = cookieStore.get("dev_session")?.value;
|
||||
const isDevMode = !!devSession;
|
||||
|
||||
const effectiveBrandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
|
||||
|
||||
const enabled = await isFeatureEnabled(effectiveBrandId, "route_trace");
|
||||
const enabled = isDevMode ? true : await isFeatureEnabled(effectiveBrandId, "route_trace");
|
||||
if (!enabled) redirect("/admin/settings/apps?reason=route_trace");
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-zinc-950 px-6 py-10">
|
||||
<div className="min-h-screen bg-stone-50 px-6 py-10">
|
||||
<div className="mx-auto max-w-2xl">
|
||||
<div className="mb-6">
|
||||
<a href="/admin/route-trace/lots" className="text-sm text-zinc-500 hover:text-zinc-300">← Back to Lots</a>
|
||||
<a href="/admin/route-trace/lots" className="text-sm text-stone-500 hover:text-stone-700">← Back to Lots</a>
|
||||
</div>
|
||||
<RouteTraceNav activeTab="lots" />
|
||||
<LotCreateForm brandId={effectiveBrandId} />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { cookies } from "next/headers";
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { isFeatureEnabled } from "@/lib/feature-flags";
|
||||
import { getRouteTraceLots } from "@/actions/route-trace/lots";
|
||||
@@ -11,13 +12,23 @@ import {
|
||||
getRecentLotEvents,
|
||||
} from "@/actions/route-trace/lots";
|
||||
|
||||
export const metadata = {
|
||||
title: "Harvest Lots | Route Trace",
|
||||
description: "View and manage all harvest lots. Track active lots, in-transit shipments, and completed deliveries across your produce distribution network.",
|
||||
keywords: ["harvest lots", "lot management", "lot tracking", "produce lots", "harvest tracking", "field lots"],
|
||||
};
|
||||
|
||||
export default async function LotsPage() {
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) redirect("/login");
|
||||
|
||||
const cookieStore = await cookies();
|
||||
const devSession = cookieStore.get("dev_session")?.value;
|
||||
const isDevMode = !!devSession;
|
||||
|
||||
const effectiveBrandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
|
||||
|
||||
const enabled = await isFeatureEnabled(effectiveBrandId, "route_trace");
|
||||
const enabled = isDevMode ? true : await isFeatureEnabled(effectiveBrandId, "route_trace");
|
||||
if (!enabled) redirect("/admin/settings/apps?reason=route_trace");
|
||||
|
||||
const [statsResult, lotsResult, haulingResult, yieldResult, invResult, eventsResult] = await Promise.all([
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { redirect } from "next/navigation";
|
||||
import { cookies } from "next/headers";
|
||||
import { getAdminUser } from "@/lib/admin-permissions";
|
||||
import { isFeatureEnabled } from "@/lib/feature-flags";
|
||||
import {
|
||||
@@ -11,13 +12,43 @@ import {
|
||||
} from "@/actions/route-trace/lots";
|
||||
import RouteTracePage from "@/components/route-trace/RouteTracePage";
|
||||
|
||||
export const metadata = {
|
||||
title: "Route Trace | Harvest Traceability Dashboard",
|
||||
description: "Track produce lots from field to delivery. Manage harvest events, hauling logistics, inventory by crop, and FSMA compliance reporting for fresh produce distribution.",
|
||||
keywords: ["route trace", "harvest traceability", "lot tracking", "farm to fork", "FSMA compliance", "food safety", "produce distribution", "haul tracking", "inventory management"],
|
||||
openGraph: {
|
||||
title: "Route Trace | Harvest Traceability",
|
||||
description: "Complete lot traceability from field to delivery with real-time tracking and FSMA compliance.",
|
||||
type: "website",
|
||||
},
|
||||
};
|
||||
|
||||
// Route Trace icon component
|
||||
const Icons = {
|
||||
routeTrace: (className: string) => (
|
||||
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
||||
<path d="M3 3v18h18" />
|
||||
<path d="M7 16l4-8 4 5 5-9" />
|
||||
<circle cx="7" cy="16" r="1.5" fill="currentColor" />
|
||||
<circle cx="11" cy="8" r="1.5" fill="currentColor" />
|
||||
<circle cx="15" cy="13" r="1.5" fill="currentColor" />
|
||||
<circle cx="20" cy="4" r="1.5" fill="currentColor" />
|
||||
</svg>
|
||||
),
|
||||
};
|
||||
|
||||
export default async function RouteTraceDashboardPage() {
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) redirect("/login");
|
||||
|
||||
const cookieStore = await cookies();
|
||||
const devSession = cookieStore.get("dev_session")?.value;
|
||||
const isDevMode = !!devSession;
|
||||
|
||||
const effectiveBrandId = adminUser.brand_id ?? "64294306-5f42-463d-a5e8-2ad6c81a96de";
|
||||
|
||||
const enabled = await isFeatureEnabled(effectiveBrandId, "route_trace");
|
||||
// Bypass feature check in dev mode (dev_session cookie present)
|
||||
const enabled = isDevMode ? true : await isFeatureEnabled(effectiveBrandId, "route_trace");
|
||||
if (!enabled) redirect("/admin/settings/apps?reason=route_trace");
|
||||
|
||||
const [statsResult, lotsResult, haulingResult, yieldResult, invResult, eventsResult] = await Promise.all([
|
||||
@@ -40,15 +71,37 @@ export default async function RouteTraceDashboardPage() {
|
||||
const recentActivity = eventsResult.success ? eventsResult.events : [];
|
||||
|
||||
return (
|
||||
<RouteTracePage
|
||||
stats={stats}
|
||||
recentLots={recentLots}
|
||||
haulingLots={haulingLots}
|
||||
fieldYield={fieldYield}
|
||||
inventoryByCrop={inventoryByCrop}
|
||||
recentActivity={recentActivity}
|
||||
brandId={effectiveBrandId}
|
||||
lots={allLots}
|
||||
/>
|
||||
<main className="min-h-screen bg-[var(--admin-bg)]">
|
||||
{/* Header */}
|
||||
<div className="px-4 sm:px-6 md:px-8 py-6 sm:py-8">
|
||||
<div className="flex flex-col sm:flex-row items-start sm:items-start justify-between gap-4 mb-4 sm:mb-6">
|
||||
<div className="space-y-1 flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2 sm:gap-3">
|
||||
<div className="flex h-10 w-10 sm:h-12 sm:w-12 items-center justify-center rounded-xl bg-emerald-600">
|
||||
{Icons.routeTrace("h-5 w-5 sm:h-6 sm:w-6 text-white")}
|
||||
</div>
|
||||
<div>
|
||||
<h1 className="text-2xl sm:text-3xl font-black text-[var(--admin-text-primary)] tracking-tight">Route Trace</h1>
|
||||
<p className="text-xs sm:text-sm text-[var(--admin-text-muted)]">Field-to-delivery lot traceability</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="px-4 sm:px-6 md:px-8 py-4 sm:py-6">
|
||||
<RouteTracePage
|
||||
stats={stats}
|
||||
recentLots={recentLots}
|
||||
haulingLots={haulingLots}
|
||||
fieldYield={fieldYield}
|
||||
inventoryByCrop={inventoryByCrop}
|
||||
recentActivity={recentActivity}
|
||||
brandId={effectiveBrandId}
|
||||
lots={allLots}
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -11,6 +11,12 @@ import {
|
||||
getRecentLotEvents,
|
||||
} from "@/actions/route-trace/lots";
|
||||
|
||||
export const metadata = {
|
||||
title: "Route Trace Settings",
|
||||
description: "Configure Route Trace traceability workflow settings, defaults, and add-on features for your produce distribution operation.",
|
||||
keywords: ["route trace settings", "traceability configuration", "FSMA settings", "traceability defaults"],
|
||||
};
|
||||
|
||||
export default async function RouteTraceSettingsPage() {
|
||||
const adminUser = await getAdminUser();
|
||||
if (!adminUser) redirect("/login");
|
||||
|
||||
Reference in New Issue
Block a user