From 6c416d7ce4a6dd566b042bdefa3f345e9a823865 Mon Sep 17 00:00:00 2001 From: navidgh67 Date: Mon, 2 Mar 2026 12:30:47 -0500 Subject: [PATCH] feat: separate equipment registration and experiment submission wizards - Split old 6-step /equipment wizard into two distinct workflows: - Equipment Registration (/equipment/register) - 3-step Stage 0 wizard for one-time physical machine setup (identity, parameters, data source) - Experiment Submission (/experiment/new) - 3-step Stages 1-2 wizard for experiment type definition and experiment planning - Update sidebar with 'Register Equip.' and 'New Experiment' admin items - Add admin nav section and new-experiment permission to all roles - Redirect /equipment to /equipment/list (equipment list page) - Fix '+ Register New' button on equipment list to point to /equipment/register --- web/app/equipment/list/page.tsx | 2 +- web/app/equipment/page.tsx | 544 +---------------------- web/app/equipment/register/page.tsx | 469 ++++++++++++++++++++ web/app/experiment/new/page.tsx | 639 ++++++++++++++++++++++++++++ web/components/sidebar.tsx | 5 +- web/lib/auth-context.tsx | 14 +- 6 files changed, 1127 insertions(+), 546 deletions(-) create mode 100644 web/app/equipment/register/page.tsx create mode 100644 web/app/experiment/new/page.tsx diff --git a/web/app/equipment/list/page.tsx b/web/app/equipment/list/page.tsx index 704773e..54c4f2c 100644 --- a/web/app/equipment/list/page.tsx +++ b/web/app/equipment/list/page.tsx @@ -92,7 +92,7 @@ export default function EquipmentListPage() {

diff --git a/web/app/equipment/page.tsx b/web/app/equipment/page.tsx index a54492e..5a38845 100644 --- a/web/app/equipment/page.tsx +++ b/web/app/equipment/page.tsx @@ -1,539 +1,11 @@ "use client"; -import { useState } from "react"; -import { cn } from "@/lib/utils"; -import { registerEquipment, type UserIdentity } from "@/lib/api-client"; -import { useAuth } from "@/lib/auth-context"; -import { - ChevronRight, - ChevronLeft, - Check, - Cpu, - Database, - Columns3, - Target, - AlertTriangle, - Gauge, - Loader2, -} from "lucide-react"; - -const STEPS = [ - { id: 1, label: "Equipment Metadata", icon: Cpu }, - { id: 2, label: "Data Source", icon: Database }, - { id: 3, label: "Schema Definition", icon: Columns3 }, - { id: 4, label: "Features & Targets", icon: Target }, - { id: 5, label: "Outlier Rules & DT Mode", icon: AlertTriangle }, - { id: 6, label: "Review & Submit", icon: Check }, -]; - -interface FormData { - // Step 1 - name: string; - domain_id: string; - equipment_model: string; - location: string; - description: string; - // Step 2 - source_type: string; - connection_host: string; - connection_port: string; - connection_database: string; - // Step 3 - columns: { name: string; type: string; unit: string }[]; - // Step 4 - features: string[]; - primary_target: string; - primary_objective: string; - secondary_target: string; - secondary_objective: string; - // Step 5 - outlier_method: string; - outlier_threshold: string; - dt_mode: string; -} - -const INITIAL_DATA: FormData = { - name: "", - domain_id: "", - equipment_model: "", - location: "", - description: "", - source_type: "postgresql", - connection_host: "", - connection_port: "5432", - connection_database: "", - columns: [ - { name: "", type: "float", unit: "" }, - { name: "", type: "float", unit: "" }, - ], - features: [], - primary_target: "", - primary_objective: "maximize", - secondary_target: "", - secondary_objective: "minimize", - outlier_method: "zscore", - outlier_threshold: "3.0", - dt_mode: "full", -}; - -export default function EquipmentPage() { - const { user } = useAuth(); - const [currentStep, setCurrentStep] = useState(1); - const [formData, setFormData] = useState(INITIAL_DATA); - const [submitting, setSubmitting] = useState(false); - const [submitResult, setSubmitResult] = useState<{ status: string; message: string } | null>(null); - - const updateField = (field: keyof FormData, value: unknown) => { - setFormData((prev) => ({ ...prev, [field]: value })); - }; - - const addColumn = () => { - setFormData((prev) => ({ - ...prev, - columns: [...prev.columns, { name: "", type: "float", unit: "" }], - })); - }; - - const updateColumn = (index: number, field: string, value: string) => { - setFormData((prev) => { - const cols = [...prev.columns]; - cols[index] = { ...cols[index], [field]: value }; - return { ...prev, columns: cols }; - }); - }; - - const removeColumn = (index: number) => { - setFormData((prev) => ({ - ...prev, - columns: prev.columns.filter((_, i) => i !== index), - })); - }; - - const handleSubmit = async () => { - setSubmitting(true); - setSubmitResult(null); - - // Auto-populate ownership from the authenticated user - const identity: UserIdentity = { id: user.id, organization: user.organization, role: user.role }; - const payload = { ...formData, owner_id: user.id, owner_org: user.organization }; - const result = await registerEquipment(payload, identity); - - if (result) { - setSubmitResult({ status: "success", message: result.message }); - } else { - setSubmitResult({ - status: "error", - message: "Registration failed. Is the FastAPI server running?", - }); - } - setSubmitting(false); - }; - - const renderInputField = ( - label: string, - field: keyof FormData, - placeholder: string, - type: string = "text" - ) => ( -
- - updateField(field, e.target.value)} - placeholder={placeholder} - className="w-full px-3 py-2 rounded-lg border border-[hsl(var(--border))] bg-[hsl(var(--background))] text-sm focus:outline-none focus:ring-2 focus:ring-[hsl(var(--ring))]" - /> -
- ); - - const renderStep = () => { - switch (currentStep) { - case 1: - return ( -
-

Equipment Metadata

-

- Provide basic information about the equipment being registered. -

-
- {renderInputField("Equipment Name", "name", "e.g., ICP Etcher")} - {renderInputField("Domain ID", "domain_id", "e.g., etcher (lowercase, no spaces)")} - {renderInputField("Equipment Model", "equipment_model", "e.g., PlasmaTherm Apex SLR")} - {renderInputField("Location", "location", "e.g., Birck Cleanroom Room 1234")} -
-
- -