diff options
| author | Arne Rief <riearn@proton.me> | 2025-12-22 21:20:39 +0100 |
|---|---|---|
| committer | Arne Rief <riearn@proton.me> | 2025-12-22 21:20:39 +0100 |
| commit | e836e7dd4ed5e9fa60e949d159100040b22a8f48 (patch) | |
| tree | a11954c06e55e8ef53fcb634fa5954dfcb42ffc3 /frontend/src/components/RobotList.tsx | |
| parent | d1b64ddd78d8b8dc3eca76038a75071ab2a575d9 (diff) | |
Movement simulator for all and single robot, project v1 ready
Diffstat (limited to 'frontend/src/components/RobotList.tsx')
| -rw-r--r-- | frontend/src/components/RobotList.tsx | 98 |
1 files changed, 93 insertions, 5 deletions
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" : "" |
