From e836e7dd4ed5e9fa60e949d159100040b22a8f48 Mon Sep 17 00:00:00 2001
From: Arne Rief <riearn@proton.me>
Date: Mon, 22 Dec 2025 21:20:39 +0100
Subject: Movement simulator for all and single robot, project v1 ready

---
 frontend/src/components/RobotList.tsx | 98 +++++++++++++++++++++++++++++++++--
 1 file changed, 93 insertions(+), 5 deletions(-)

(limited to 'frontend/src/components/RobotList.tsx')

diff --git a/frontend/src/components/RobotList.tsx b/frontend/src/components/RobotList.tsx
index d711372..21d8e63 100644
--- a/frontend/src/components/RobotList.tsx
+++ b/frontend/src/components/RobotList.tsx
@@ -1,11 +1,22 @@
-import { useState } from "react";
-import type { Robot } from "../types/robot";
+import {
+    useState,
+    type Dispatch,
+    type MouseEvent,
+    type SetStateAction,
+} from "react";
+import type { ErrorResponse } from "../types/error";
+import type { Robot, SimulationResponse } from "../types/robot";
 
 type ExpandedRobotsState = Record<number, boolean>;
 
-type Props = { robots: Robot[]; };
+type Props = {
+    apiUrl: string;
+    robots: Robot[];
+    token: string | null;
+    setErrorMessage: Dispatch<SetStateAction<string>>;
+};
 
-function RobotList({ robots }: Props) {
+function RobotList({ apiUrl, robots, token, setErrorMessage }: Props) {
     const [expandedRobots, setExpandedRobots] = useState<ExpandedRobotsState>(
         {}
     );
@@ -17,15 +28,89 @@ function RobotList({ robots }: Props) {
         }));
     }
 
+    // Move or stop individual robot
+    async function controlSingleRobot(
+        event: MouseEvent<HTMLButtonElement>,
+        robotId: number,
+        robotStatus: Robot["status"]
+    ) {
+        const isRobotMoving = robotStatus === "moving";
+        const button = event.currentTarget;
+        button.disabled = true; // prevent spamming
+
+        try {
+            // Make button clickable again after 1 second
+            setTimeout(() => {
+                button.disabled = false;
+            }, 1000);
+
+            const response = await fetch(
+                `${apiUrl}/robots/${robotId}/${
+                    isRobotMoving ? "stop" : "move"
+                }`,
+                {
+                    method: "POST",
+                    headers: {
+                        Authorization: `Bearer ${token}`,
+                        "Content-Type": "application/json",
+                    },
+                }
+            );
+
+            if (!response.ok) {
+                const errorData: ErrorResponse = await response.json();
+                throw new Error(
+                    errorData.message ||
+                        `Failed to ${
+                            isRobotMoving ? "stop" : "start"
+                        } robot ID ${robotId}.`
+                );
+            }
+
+            const data: SimulationResponse = await response.json();
+            console.log(data.message);
+        } catch (error) {
+            console.error(
+                `Error ${
+                    isRobotMoving ? "stopping" : "starting"
+                } robot ID ${robotId}: `,
+                error
+            );
+
+            if (error instanceof Error) {
+                setErrorMessage(error.message);
+            } else {
+                setErrorMessage("An unexpected error occurred.");
+            }
+        }
+    }
+
     return (
         <ul className="sidebar-robot-list">
-            {robots.map((robot) => {
+            {robots?.map((robot) => {
                 const isExpanded = expandedRobots[robot?.id];
 
                 return (
                     <li key={robot?.id}>
                         <p className="robot-name">{robot?.name}</p>
 
+                        {/* Move/stop individual robot  */}
+                        <button
+                            className={`btn btn-single-robot btn-${
+                                robot?.status === "idle" ? "start" : "stop"
+                            }`}
+                            onClick={(event) =>
+                                controlSingleRobot(
+                                    event,
+                                    robot?.id,
+                                    robot?.status
+                                )
+                            }
+                        >
+                            {robot?.status === "idle" ? "MOVE" : "STOP"}
+                        </button>
+
+                        {/* Movement status  */}
                         <p>
                             Status:{" "}
                             <span className={`robot-status-${robot?.status}`}>
@@ -33,12 +118,14 @@ function RobotList({ robots }: Props) {
                             </span>
                         </p>
 
+                        {/* Current position */}
                         <p className="robot-coordinates-label">Position:</p>
                         <ul className="robot-coordinates">
                             <li>Lat: {robot?.lat}</li>
                             <li>Lon: {robot?.lon}</li>
                         </ul>
 
+                        {/* Expand position log */}
                         <button
                             className="btn btn-robot-history-toggle"
                             onClick={() => toggleRobotHistory(robot?.id)}
@@ -52,6 +139,7 @@ function RobotList({ robots }: Props) {
                             </span>
                         </button>
 
+                        {/* Position log/history */}
                         <div
                             className={`robot-history ${
                                 isExpanded ? "expanded" : ""
-- 
cgit v1.2.3

