From 3818739c5901cc3f1d4596b24cfe1b827a2eca23 Mon Sep 17 00:00:00 2001
From: Arne Rief <riearn@proton.me>
Date: Mon, 22 Dec 2025 12:28:33 +0100
Subject: FE Sidebar, create & move requests, BE create controller

---
 frontend/src/components/Sidebar.tsx | 246 ++++++++++++++++++++++++++++++++++++
 1 file changed, 246 insertions(+)
 create mode 100644 frontend/src/components/Sidebar.tsx

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

diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx
new file mode 100644
index 0000000..2e66865
--- /dev/null
+++ b/frontend/src/components/Sidebar.tsx
@@ -0,0 +1,246 @@
+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<number, boolean>;
+
+type Props = {
+    activeSimulation: boolean;
+    errorMessage: string;
+    setErrorMessage: Dispatch<SetStateAction<string>>;
+    token: string | null;
+    robots: Robot[];
+    onStartAllRobots: () => Promise<void>;
+    onStopAllRobots: () => Promise<void>;
+};
+
+const API_URL = import.meta.env.VITE_API_URL;
+
+function Sidebar({
+    activeSimulation,
+    errorMessage,
+    setErrorMessage,
+    token,
+    robots,
+    onStartAllRobots,
+    onStopAllRobots,
+}: Props) {
+    const [expandedRobots, setExpandedRobots] = useState<ExpandedRobotsState>(
+        {}
+    );
+    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<HTMLInputElement>) {
+        setNewRobotName(event.target.value);
+        if (errorMessage) {
+            setErrorMessage("");
+        }
+    }
+
+    return (
+        <div className="sidebar">
+            <div className="sidebar-robots-header">
+                <h2>Your Robots</h2>
+                <button
+                    className="btn btn-add-robot"
+                    onClick={handleAddClick}
+                    disabled={isAddingRobot}
+                >
+                    + Neu
+                </button>
+            </div>
+
+            {isAddingRobot && (
+                <div className="add-robot-form">
+                    <input
+                        type="text"
+                        placeholder="Robot name..."
+                        value={newRobotName}
+                        onChange={handleInputChange}
+                        disabled={isSubmitting}
+                        autoFocus
+                    />
+                    <div className="add-robot-actions">
+                        <button
+                            className="btn btn-start"
+                            onClick={handleSubmit}
+                            disabled={isSubmitting}
+                        >
+                            {isSubmitting ? "Creating..." : "Create"}
+                        </button>
+                        <button
+                            className="btn btn-stop"
+                            onClick={handleCancel}
+                            disabled={isSubmitting}
+                        >
+                            Cancel
+                        </button>
+                    </div>
+                </div>
+            )}
+
+            {errorMessage && (
+                <div className="error-message">{errorMessage}</div>
+            )}
+
+            <div className="simulation-actions">
+                <button
+                    className="btn btn-start"
+                    onClick={onStartAllRobots}
+                    disabled={activeSimulation}
+                >
+                    Start simulation
+                </button>
+
+                <button
+                    className="btn btn-stop"
+                    onClick={onStopAllRobots}
+                    disabled={!activeSimulation}
+                >
+                    Stop simulation
+                </button>
+            </div>
+
+            <ul className="sidebar-robot-list">
+                {robots.map((robot) => {
+                    const isExpanded = expandedRobots[robot.id];
+
+                    return (
+                        <li key={robot.id}>
+                            <p className="robot-name">{robot.name}</p>
+
+                            <p>
+                                Status:{" "}
+                                <span
+                                    className={`robot-status-${robot.status}`}
+                                >
+                                    {robot.status}
+                                </span>
+                            </p>
+
+                            <p className="robot-coordinates-label">Position:</p>
+                            <ul className="robot-coordinates">
+                                <li>Lat: {robot.lat}</li>
+                                <li>Lon: {robot.lon}</li>
+                            </ul>
+
+                            <button
+                                className="btn btn-robot-history-toggle"
+                                onClick={() => toggleRobotHistory(robot.id)}
+                                aria-expanded={isExpanded}
+                            >
+                                Position history
+                                <span
+                                    className={`arrow ${
+                                        isExpanded ? "open" : ""
+                                    }`}
+                                >
+                                    ▾
+                                </span>
+                            </button>
+
+                            <div
+                                className={`robot-history ${
+                                    isExpanded ? "expanded" : ""
+                                }`}
+                            >
+                                <ul>
+                                    {robot.robot_positions?.length ? (
+                                        robot.robot_positions.map(
+                                            (pos, index) => (
+                                                <li key={index}>
+                                                    {`Lat: ${pos.lat}, Lon: ${pos.lon}`}
+                                                </li>
+                                            )
+                                        )
+                                    ) : (
+                                        <li className="robot-history-empty">
+                                            No previous positions.
+                                        </li>
+                                    )}
+                                </ul>
+                            </div>
+                        </li>
+                    );
+                })}
+            </ul>
+        </div>
+    );
+}
+
+export default Sidebar;
+
-- 
cgit v1.2.3

