diff --git a/src/app/admin/route-trace/lots/new/page.tsx b/src/app/admin/route-trace/lots/new/page.tsx index 103599f..2e9e3d0 100644 --- a/src/app/admin/route-trace/lots/new/page.tsx +++ b/src/app/admin/route-trace/lots/new/page.tsx @@ -1,38 +1,6 @@ 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 = isDevMode ? true : await isFeatureEnabled(effectiveBrandId, "route_trace"); - if (!enabled) redirect("/admin/settings/apps?reason=route_trace"); - - return ( -
-
-
- ← Back to Lots -
- - -
-
- ); +export default function NewLotPage() { + // New lot creation is now handled via modal in the Lots tab + redirect("/admin/route-trace/lots"); } \ No newline at end of file diff --git a/src/components/route-trace/LotCreateModal.tsx b/src/components/route-trace/LotCreateModal.tsx new file mode 100644 index 0000000..1c96644 --- /dev/null +++ b/src/components/route-trace/LotCreateModal.tsx @@ -0,0 +1,455 @@ +"use client"; + +import { useState, useTransition, useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { createHarvestLot } from "@/actions/route-trace/lots"; + +type Props = { + isOpen: boolean; + onClose: () => void; + brandId: string; +}; + +export default function LotCreateModal({ isOpen, onClose, brandId }: Props) { + const router = useRouter(); + const [isPending, startTransition] = useTransition(); + const [error, setError] = useState(null); + const [notesOpen, setNotesOpen] = useState(false); + + const [crop_type, setCropType] = useState(""); + const [variety, setVariety] = useState(""); + const [harvest_date, setHarvestDate] = useState(new Date().toISOString().split("T")[0]); + const [field_location, setFieldLocation] = useState(""); + const [field_block, setFieldBlock] = useState(""); + const [worker_name, setWorkerName] = useState(""); + const [packer_name, setPackerName] = useState(""); + const [quantity_lbs, setQuantityLbs] = useState(""); + const [yield_estimate_lbs, setYieldEstimateLbs] = useState(""); + const [yield_unit, setYieldUnit] = useState("lbs"); + const [customYieldUnit, setCustomYieldUnit] = useState(""); + const [bin_id, setBinId] = useState(""); + const [container_id, setContainerId] = useState(""); + const [pallets, setPallets] = useState(""); + const [notes, setNotes] = useState(""); + + // Reset form when modal opens + useEffect(() => { + if (isOpen) { + setCropType(""); + setVariety(""); + setHarvestDate(new Date().toISOString().split("T")[0]); + setFieldLocation(""); + setFieldBlock(""); + setWorkerName(""); + setPackerName(""); + setQuantityLbs(""); + setYieldEstimateLbs(""); + setYieldUnit("lbs"); + setCustomYieldUnit(""); + setBinId(""); + setContainerId(""); + setPallets(""); + setNotes(""); + setError(null); + setNotesOpen(false); + } + }, [isOpen]); + + if (!isOpen) return null; + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(null); + startTransition(async () => { + const result = await createHarvestLot(brandId, { + crop_type, + variety: variety || undefined, + harvest_date, + field_location: field_location || undefined, + worker_name: worker_name || undefined, + packer_name: packer_name || undefined, + quantity_lbs: quantity_lbs ? Number(quantity_lbs) : undefined, + notes: notes || undefined, + bin_id: bin_id || undefined, + container_id: container_id || undefined, + field_block: field_block || undefined, + yield_estimate_lbs: yield_estimate_lbs ? Number(yield_estimate_lbs) : undefined, + yield_unit: yield_unit === "custom" ? customYieldUnit.trim() || undefined : yield_unit, + pallets: pallets ? Number(pallets) : undefined, + }); + if (result.success && result.lot) { + onClose(); + router.push(`/admin/route-trace/lots/${result.lot.id}`); + } else { + setError(result.error ?? "Failed to create lot"); + } + }); + } + + return ( +
+ {/* Backdrop */} +
+ + {/* Modal */} +
+
+
+
+ + + +
+
+

New Harvest Lot

+

Quick entry — scan or fill in the fields below

+
+
+ +
+ +
+ {error && ( +
+ {error} +
+ )} + + {/* Required fields */} +
+
+ + setCropType(e.target.value)} + placeholder="e.g. Sweet Corn" + required + className="w-full rounded-xl border px-4 py-4 text-lg font-medium outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+
+ + setVariety(e.target.value)} + placeholder="e.g. Golden Bantam" + className="w-full rounded-xl border px-4 py-4 text-lg font-medium outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+
+ +
+ + setHarvestDate(e.target.value)} + required + className="w-full rounded-xl border px-4 py-4 text-lg font-medium outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+ + {/* Field & Location */} +
+

Field & Location

+
+
+ + setFieldLocation(e.target.value)} + placeholder="e.g. North Field" + required + className="w-full rounded-xl border px-4 py-3.5 text-base outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+
+
+ + setFieldBlock(e.target.value)} + placeholder="e.g. Block A" + className="w-full rounded-xl border px-3 py-3 text-sm outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+
+ + setWorkerName(e.target.value)} + placeholder="e.g. Maria S." + className="w-full rounded-xl border px-3 py-3 text-sm outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+
+
+
+ + {/* Quantity + Yield */} +
+

Weight & Yield

+
+
+ + setQuantityLbs(e.target.value)} + placeholder="e.g. 4800" + min="0" + step="0.01" + className="w-full rounded-xl border px-3 py-3.5 text-base outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+
+ +
+ setYieldEstimateLbs(e.target.value)} + placeholder="e.g. 5000" + min="0" + step="0.01" + className="flex-1 px-3 py-3.5 text-base outline-none bg-transparent" + style={{ color: "var(--admin-text-primary)" }} + /> + +
+ {yield_unit === "custom" && ( + setCustomYieldUnit(e.target.value)} + className="mt-1.5 w-full rounded-xl border px-3 py-2 text-sm outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> + )} +
+
+
+ + {/* Bin + Container */} +
+

Bin, Container & Pallets

+
+
+ + setBinId(e.target.value)} + placeholder="e.g. BIN-001" + className="w-full rounded-xl border px-3 py-3 text-sm outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+
+ + setContainerId(e.target.value)} + placeholder="e.g. CONT-A42" + className="w-full rounded-xl border px-3 py-3 text-sm outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+
+
+
+ + setPallets(e.target.value)} + placeholder="e.g. 4" + min="0" + className="w-full rounded-xl border px-3 py-3 text-sm outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+
+ + setPackerName(e.target.value)} + placeholder="e.g. Jose R." + className="w-full rounded-xl border px-3 py-3 text-sm outline-none transition-colors" + style={{ + borderColor: "var(--admin-border)", + backgroundColor: "var(--admin-bg-subtle)", + color: "var(--admin-text-primary)" + }} + /> +
+
+
+ + {/* Notes — collapsible */} +
+ + {notesOpen && ( +