Skip to content

Commit

Permalink
Edit robot (#58)
Browse files Browse the repository at this point in the history
* added edit robot page

* buttons for edit/delete robot and minor fixes

---------

Co-authored-by: Isaac Light <[email protected]>
  • Loading branch information
codekansas and is2ac2 authored Jun 10, 2024
1 parent 507e47a commit c2591b8
Show file tree
Hide file tree
Showing 9 changed files with 442 additions and 12 deletions.
5 changes: 5 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,10 @@
"react-router-dom": "^6.23.1",
"ts-jest": "^29.1.4",
"typescript-eslint": "*"
},
"jest": {
"moduleNameMapper": {
"^axios$": "axios/dist/node/axios.cjs"
}
}
}
2 changes: 2 additions & 0 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AlertQueue, AlertQueueProvider } from "hooks/alerts";
import { AuthenticationProvider, OneTimePasswordWrapper } from "hooks/auth";
import { ThemeProvider } from "hooks/theme";
import About from "pages/About";
import EditRobotForm from "pages/EditRobotForm";
import Home from "pages/Home";
import NotFound from "pages/NotFound";
import PartDetails from "pages/PartDetails";
Expand Down Expand Up @@ -38,6 +39,7 @@ const App = () => {
<Route path="/robots/add" element={<RobotForm />} />
<Route path="/parts/add" element={<PartForm />} />
<Route path="/robot/:id" element={<RobotDetails />} />
<Route path="/edit-robot/:id" element={<EditRobotForm />} />
<Route path="/parts/" element={<Parts />} />
<Route path="/part/:id" element={<PartDetails />} />
<Route path="robots/your" element={<YourRobots />} />
Expand Down
48 changes: 48 additions & 0 deletions frontend/src/hooks/api.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,22 @@ export class api {
}
}
}
public async currentUser(): Promise<string> {
try {
const response = await this.api.get("/robots/user/");
return response.data;
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error fetching current user:", error.response?.data);
throw new Error(
error.response?.data?.detail || "Error fetching current user",
);
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
public async addRobot(robot: Robot): Promise<void> {
const s = robot.name;
try {
Expand All @@ -113,6 +129,38 @@ export class api {
}
}
}
public async deleteRobot(id: string | undefined): Promise<void> {
const s = id;
try {
await this.api.delete(`robots/delete/${id}/`);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error deleting robot:", error.response?.data);
throw new Error(
error.response?.data?.detail || "Error deleting robot " + s,
);
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
public async editRobot(robot: Robot): Promise<void> {
const s = robot.name;
try {
await this.api.post(`robots/edit-robot/${robot.robot_id}/`, robot);
} catch (error) {
if (axios.isAxiosError(error)) {
console.error("Error editing robot:", error.response?.data);
throw new Error(
error.response?.data?.detail || "Error editing robot " + s,
);
} else {
console.error("Unexpected error:", error);
throw new Error("Unexpected error");
}
}
}
public async getParts(): Promise<Part[]> {
try {
const response = await this.api.get("/parts/");
Expand Down
248 changes: 248 additions & 0 deletions frontend/src/pages/EditRobotForm.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,248 @@
import { humanReadableError } from "constants/backend";
import { useAlertQueue } from "hooks/alerts";
import { api, Bom, Image, Part, Robot } from "hooks/api";
import { useAuthentication } from "hooks/auth";
import React, { ChangeEvent, FormEvent, useEffect, useState } from "react";
import { Button, Col, Form, Row } from "react-bootstrap";
import { useNavigate, useParams } from "react-router-dom";

const EditRobotForm: React.FC = () => {
const auth = useAuthentication();
const auth_api = new api(auth.api);

// Parse the ID from the URL.
const { id } = useParams();

// States.
const [message, setMessage] = useState<string | null>(null);
const [robot_name, setName] = useState<string>("");
const [robot_description, setDescription] = useState<string>("");
const [robot_bom, setBom] = useState<Bom[]>([]);
const [robot_images, setImages] = useState<Image[]>([]);
const [parts, setParts] = useState<Part[]>([]);
const [robot_id, setRobotId] = useState<string>("");

const { addAlert } = useAlertQueue();

useEffect(() => {
const fetchRobot = async () => {
try {
const robotData = await auth_api.getRobotById(id);
setName(robotData.name);
setDescription(robotData.description);
setBom(robotData.bom);
setImages(robotData.images);
setRobotId(robotData.robot_id);
} catch (err) {
addAlert(humanReadableError(err), "error");
}
};
fetchRobot();
}, [id]);

const handleImageChange = (
index: number,
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { name, value } = e.target;
const newImages = [...robot_images];
newImages[index][name as keyof Image] = value;
setImages(newImages);
};
const navigate = useNavigate();
const handleAddImage = () => {
setImages([...robot_images, { url: "", caption: "" }]);
};

const handleRemoveImage = (index: number) => {
const newImages = robot_images.filter((_, i) => i !== index);
setImages(newImages);
};

const handleBomChange = (
index: number,
e: ChangeEvent<HTMLInputElement | HTMLTextAreaElement>,
) => {
const { name, value } = e.target;
const newBom = [...robot_bom];
if (name === "quantity") {
newBom[index][name as "quantity"] = Number(value);
} else {
newBom[index][name as "part_id"] = value;
}

setBom(newBom);
};

const handleAddBom = () => {
setBom([...robot_bom, { part_id: "", quantity: 0 }]);
};

const handleRemoveBom = (index: number) => {
const newBom = robot_bom.filter((_, i) => i !== index);
setBom(newBom);
};

const handleSubmit = async (event: FormEvent<HTMLFormElement>) => {
event.preventDefault();
if (robot_images.length === 0) {
setMessage("Please upload at least one image.");
return;
}
const newFormData: Robot = {
robot_id: robot_id,
name: robot_name,
description: robot_description,
owner: "",
bom: robot_bom,
images: robot_images,
};
try {
await auth_api.editRobot(newFormData);
setMessage(`Robot edited successfully.`);
navigate(`/robots/your/`);
} catch (error) {
setMessage("Error adding robot ");
}
};

useEffect(() => {
const fetchParts = async () => {
try {
const partsQuery = await auth_api.getParts();
setParts(partsQuery);
} catch (err) {
console.error(err);
}
};
fetchParts();
}, []);

return (
<Row>
<h2>Edit Robot</h2>
{message && <p>{message}</p>}
<Form onSubmit={handleSubmit} className="mb-3">
Name:
<Form.Control
className="mb-3"
type="text"
placeholder="Robot Name:"
onChange={(e) => {
setName(e.target.value);
}}
value={robot_name}
required
/>
Description:
<Form.Control
className="mb-3"
type="text"
placeholder="Robot Description:"
onChange={(e) => {
setDescription(e.target.value);
}}
value={robot_description}
required
/>
Images:
{robot_images.map((image, index) => (
<Row key={index} className="mb-3">
<Col md={12}>
<Form.Control
className="mb-1"
type="text"
placeholder="Image URL"
name="url"
value={image.url}
onChange={(e) => handleImageChange(index, e)}
required
/>
<Form.Control
className="mb-1"
type="text"
placeholder="Image Caption"
name="caption"
value={image.caption}
onChange={(e) => handleImageChange(index, e)}
required
/>
</Col>
<Col md={12}>
<Button
className="mb-3"
size="sm"
variant="danger"
onClick={() => handleRemoveImage(index)}
>
Remove
</Button>
</Col>
</Row>
))}
<Col md={6}>
<Button className="mb-3" variant="primary" onClick={handleAddImage}>
Add Image
</Button>
</Col>
Bill of Materials:
{robot_bom.map((bom, index) => (
<Row key={index} className="mb-3">
<Col md={12}>
Part:
<Form.Control
className="mb-1"
as="select"
placeholder="Part Id: "
name="part_id"
value={bom.part_id}
onChange={(e) => handleBomChange(index, e)}
required
>
<option value="" disabled>
Select a Part
</option>
{parts.map((part, index) => (
<option key={index} value={part.part_id}>
{part.part_name}
</option>
))}
</Form.Control>
Quantity:
<Form.Control
className="mb-1"
type="number"
placeholder="Quantity:"
name="quantity"
value={bom.quantity}
onChange={(e) => handleBomChange(index, e)}
required
/>
</Col>
<Col md={12}>
<Button
className="mb-3"
size="sm"
variant="danger"
onClick={() => handleRemoveBom(index)}
>
Remove
</Button>
</Col>
</Row>
))}
<Col md={6}>
<Button className="mb-3" variant="primary" onClick={handleAddBom}>
Add Part
</Button>
</Col>
Submit:
<Col md={6}>
<Button type="submit">Confirm Changes!</Button>
</Col>
</Form>
</Row>
);
};

export default EditRobotForm;
Loading

0 comments on commit c2591b8

Please sign in to comment.