From d1b64ddd78d8b8dc3eca76038a75071ab2a575d9 Mon Sep 17 00:00:00 2001 From: Arne Rief Date: Mon, 22 Dec 2025 12:56:20 +0100 Subject: Sidebar split into smaller components --- frontend/src/components/AddRobotForm.tsx | 121 +++++++++++++ frontend/src/components/RobotList.tsx | 84 +++++++++ frontend/src/components/Sidebar.tsx | 234 +++----------------------- frontend/src/components/SimulationActions.tsx | 103 ++++++++++++ frontend/src/pages/Dashboard.tsx | 74 +------- 5 files changed, 336 insertions(+), 280 deletions(-) create mode 100644 frontend/src/components/AddRobotForm.tsx create mode 100644 frontend/src/components/RobotList.tsx create mode 100644 frontend/src/components/SimulationActions.tsx diff --git a/frontend/src/components/AddRobotForm.tsx b/frontend/src/components/AddRobotForm.tsx new file mode 100644 index 0000000..c178657 --- /dev/null +++ b/frontend/src/components/AddRobotForm.tsx @@ -0,0 +1,121 @@ +import { + useState, + type ChangeEvent, + type Dispatch, + type SetStateAction, +} from "react"; +import type { ErrorResponse } from "../types/error"; +import type { CreateRobotResponse } from "../types/robot"; + +type Props = { + apiUrl: string; + errorMessage: string; + token: string | null; + setErrorMessage: Dispatch>; + setIsAddingRobot: Dispatch>; +}; + +function AddRobotForm({ + apiUrl, + errorMessage, + token, + setErrorMessage, + setIsAddingRobot, +}: Props) { + const [isSubmitting, setIsSubmitting] = useState(false); + const [newRobotName, setNewRobotName] = useState(""); + + // Cancel robot creation + function handleCancel() { + setIsAddingRobot(false); + setNewRobotName(""); + setErrorMessage(""); + } + + function handleInputChange(event: ChangeEvent) { + if (errorMessage) { + setErrorMessage(""); + } + + setNewRobotName(event.target.value); + } + + // Create new robot + async function handleSubmit() { + if (!newRobotName.trim()) { + setErrorMessage("Please enter a name for the robot."); + return; + } + + setIsSubmitting(true); + setErrorMessage(""); + + try { + const response = await fetch(`${apiUrl}/robots`, { + method: "POST", + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "application/json", + }, + body: JSON.stringify({ name: newRobotName.trim() }), + }); + + if (!response.ok) { + const errorData: ErrorResponse = await response.json(); + throw new Error( + errorData.message || + `Error creating the new robot: ${response.status}` + ); + } + + const data: CreateRobotResponse = await response.json(); + console.log("Robot created successfully: ", data); + + // Reset form and close input field + setNewRobotName(""); + setIsAddingRobot(false); + } catch (error) { + console.error("Error creating the new robot:", error); + + if (error instanceof Error) { + setErrorMessage(error.message); + } else { + setErrorMessage("Error creating the new robot."); + } + } finally { + setIsSubmitting(false); + } + } + + return ( +
+ +
+ + +
+
+ ); +} + +export default AddRobotForm; + diff --git a/frontend/src/components/RobotList.tsx b/frontend/src/components/RobotList.tsx new file mode 100644 index 0000000..d711372 --- /dev/null +++ b/frontend/src/components/RobotList.tsx @@ -0,0 +1,84 @@ +import { useState } from "react"; +import type { Robot } from "../types/robot"; + +type ExpandedRobotsState = Record; + +type Props = { robots: Robot[]; }; + +function RobotList({ robots }: Props) { + const [expandedRobots, setExpandedRobots] = useState( + {} + ); + + function toggleRobotHistory(robotId: number) { + setExpandedRobots((prev) => ({ + ...prev, + [robotId]: !prev[robotId], + })); + } + + return ( +
    + {robots.map((robot) => { + const isExpanded = expandedRobots[robot?.id]; + + return ( +
  • +

    {robot?.name}

    + +

    + Status:{" "} + + {robot?.status} + +

    + +

    Position:

    +
      +
    • Lat: {robot?.lat}
    • +
    • Lon: {robot?.lon}
    • +
    + + + +
    +
      + {robot?.robot_positions?.length ? ( + robot?.robot_positions?.map( + (pos, index) => ( +
    • + {`Lat: ${pos?.lat}, Lon: ${pos?.lon}`} +
    • + ) + ) + ) : ( +
    • + No previous positions. +
    • + )} +
    +
    +
  • + ); + })} +
+ ); +} + +export default RobotList; + diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 2e66865..8bd795b 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,116 +1,27 @@ -import { - useState, - type ChangeEvent, - type Dispatch, - type SetStateAction, -} from "react"; -import "../styles/Button.css"; -import "../styles/Sidebar.css"; -import type { ErrorResponse } from "../types/error"; -import type { CreateRobotResponse, Robot } from "../types/robot"; - -type ExpandedRobotsState = Record; +import { useState, type Dispatch, type SetStateAction } from "react"; +import API_URL from "../config"; +import "../styles/button.css"; +import "../styles/sidebar.css"; +import type { Robot } from "../types/robot"; +import AddRobotForm from "./AddRobotForm"; +import RobotList from "./RobotList"; +import SimulationActions from "./SimulationActions"; type Props = { - activeSimulation: boolean; errorMessage: string; - setErrorMessage: Dispatch>; - token: string | null; robots: Robot[]; - onStartAllRobots: () => Promise; - onStopAllRobots: () => Promise; + token: string | null; + setErrorMessage: Dispatch>; }; -const API_URL = import.meta.env.VITE_API_URL; - -function Sidebar({ - activeSimulation, - errorMessage, - setErrorMessage, - token, - robots, - onStartAllRobots, - onStopAllRobots, -}: Props) { - const [expandedRobots, setExpandedRobots] = useState( - {} - ); +function Sidebar({ errorMessage, robots, token, setErrorMessage }: Props) { const [isAddingRobot, setIsAddingRobot] = useState(false); - const [newRobotName, setNewRobotName] = useState(""); - const [isSubmitting, setIsSubmitting] = useState(false); - - function toggleRobotHistory(robotId: number) { - setExpandedRobots((prev) => ({ - ...prev, - [robotId]: !prev[robotId], - })); - } function handleAddClick() { setIsAddingRobot(true); - setNewRobotName(""); setErrorMessage(""); } - function handleCancel() { - setIsAddingRobot(false); - setNewRobotName(""); - setErrorMessage(""); - } - - async function handleSubmit() { - if (!newRobotName.trim()) { - setErrorMessage("Please enter a name for the robot."); - return; - } - - setIsSubmitting(true); - setErrorMessage(""); - - try { - const response = await fetch(`${API_URL}/robots`, { - method: "POST", - headers: { - Authorization: `Bearer ${token}`, - "Content-Type": "application/json", - }, - body: JSON.stringify({ name: newRobotName.trim() }), - }); - - if (!response.ok) { - const errorData: ErrorResponse = await response.json(); - throw new Error( - errorData.message || - `Error creating the new robot: ${response.status}` - ); - } - - const data: CreateRobotResponse = await response.json(); - console.log("Robot created successfully: ", data); - - // Reset form and close input field - setNewRobotName(""); - setIsAddingRobot(false); - } catch (error) { - console.error("Error creating the new robot:", error); - - if (error instanceof Error) { - setErrorMessage(error.message); - } else { - setErrorMessage("Error creating the new robot."); - } - } finally { - setIsSubmitting(false); - } - } - - function handleInputChange(event: ChangeEvent) { - setNewRobotName(event.target.value); - if (errorMessage) { - setErrorMessage(""); - } - } - return (
@@ -120,124 +31,31 @@ function Sidebar({ onClick={handleAddClick} disabled={isAddingRobot} > - + Neu + + Add
- {isAddingRobot && ( -
- -
- - -
-
+ {isAddingRobot && ( + )} {errorMessage && (
{errorMessage}
)} -
- - - -
- -
    - {robots.map((robot) => { - const isExpanded = expandedRobots[robot.id]; - - return ( -
  • -

    {robot.name}

    - -

    - Status:{" "} - - {robot.status} - -

    - -

    Position:

    -
      -
    • Lat: {robot.lat}
    • -
    • Lon: {robot.lon}
    • -
    - - + -
    -
      - {robot.robot_positions?.length ? ( - robot.robot_positions.map( - (pos, index) => ( -
    • - {`Lat: ${pos.lat}, Lon: ${pos.lon}`} -
    • - ) - ) - ) : ( -
    • - No previous positions. -
    • - )} -
    -
    -
  • - ); - })} -
+
); } diff --git a/frontend/src/components/SimulationActions.tsx b/frontend/src/components/SimulationActions.tsx new file mode 100644 index 0000000..c91b933 --- /dev/null +++ b/frontend/src/components/SimulationActions.tsx @@ -0,0 +1,103 @@ +import { useState, type Dispatch, type SetStateAction } from "react"; +import type { ErrorResponse } from "../types/error"; + +type Props = { + apiUrl: string; + token: string | null; + setErrorMessage: Dispatch>; +}; + +function SimulationActions({ apiUrl, token, setErrorMessage }: Props) { + const [isSimulationActive, setIsSimulationActive] = + useState(false); + + // TODO type responses + async function handleStartAllRobots() { + setIsSimulationActive(true); + + try { + const response = await fetch(`${apiUrl}/robots/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 set all robots moving." + ); + } + + console.log("All robots set moving."); + } catch (error) { + console.error("Error starting robots:", error); + + if (error instanceof Error) { + setErrorMessage(error.message); + } else { + setErrorMessage("An unexpected error occurred."); + } + + setIsSimulationActive(false); + } + } + + async function handleStopAllRobots() { + setIsSimulationActive(false); + + try { + const response = await fetch(`${apiUrl}/robots/stop`, { + 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 set all robots idle." + ); + } + + console.log("All robots set idle."); + } catch (error) { + console.error("Error stopping robots:", error); + + if (error instanceof Error) { + setErrorMessage(error.message); + } else { + setErrorMessage("An unexpected error occurred."); + } + + setIsSimulationActive(true); + } + } + + return ( +
+ + + +
+ ); +} + +export default SimulationActions; + diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index b8dd2c4..088fee2 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -14,7 +14,6 @@ import type { Robot, RobotsResponse } from "../types/robot"; function Dashboard() { const [errorMessage, setErrorMessage] = useState(""); const [isLoading, setIsLoading] = useState(true); - const [isSimulationActive, setIsSimulationActive] = useState(false); const [robots, setRobots] = useState([]); const navigate = useNavigate(); @@ -29,72 +28,6 @@ function Dashboard() { navigate("/login", { replace: true }); } - async function handleStartAllRobots() { - setIsSimulationActive(true); - - try { - const response = await fetch(`${API_URL}/robots/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 set all robots moving." - ); - } - - console.log("All robots set moving."); - } catch (error) { - console.error("Error starting robots:", error); - - if (error instanceof Error) { - setErrorMessage(error.message); - } else { - setErrorMessage("An unexpected error occurred."); - } - - setIsSimulationActive(false); - } - } - - async function handleStopAllRobots() { - setIsSimulationActive(false); - - try { - const response = await fetch(`${API_URL}/robots/stop`, { - 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 set all robots idle." - ); - } - - console.log("All robots set idle."); - } catch (error) { - console.error("Error stopping robots:", error); - - if (error instanceof Error) { - setErrorMessage(error.message); - } else { - setErrorMessage("An unexpected error occurred."); - } - - setIsSimulationActive(true); - } - } - // Request robot data from backend on component mount useEffect(() => { // Additional safety check to protect this page from unauthorized access @@ -162,13 +95,10 @@ function Dashboard() {
); -- cgit v1.2.3