Skip to content

Commit

Permalink
Order ID for Teleop Robot (#522)
Browse files Browse the repository at this point in the history
* Order ID for robot registration

* Register robot with order id from orders page
  • Loading branch information
Winston-Hsiao authored Nov 4, 2024
1 parent 73eb220 commit ca35b0d
Show file tree
Hide file tree
Showing 9 changed files with 283 additions and 14 deletions.
21 changes: 20 additions & 1 deletion frontend/src/components/modals/EditRobotModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,14 @@ interface EditRobotModalProps {
robotData: {
name: string;
description: string | null;
order_id?: string | null;
},
) => Promise<void>;
robot: {
id: string;
name: string;
description: string | null | undefined;
order_id?: string | null;
};
}

Expand All @@ -38,12 +40,14 @@ export function EditRobotModal({
}: EditRobotModalProps) {
const [name, setName] = useState(robot.name);
const [description, setDescription] = useState(robot.description || "");
const [orderId, setOrderId] = useState(robot.order_id || "");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
setName(robot.name);
setDescription(robot.description || "");
setOrderId(robot.order_id || "");
}, [robot]);

const handleEdit = useCallback(async () => {
Expand All @@ -63,6 +67,7 @@ export function EditRobotModal({
await onEdit(robot.id, {
name,
description: description || null,
order_id: orderId || null,
});
onClose();
} catch (error) {
Expand All @@ -81,7 +86,7 @@ export function EditRobotModal({
setIsLoading(false);
}
}
}, [name, description, robot.id, onEdit, onClose]);
}, [name, description, orderId, robot.id, onEdit, onClose]);

return (
<Dialog open={isOpen} onOpenChange={onClose}>
Expand Down Expand Up @@ -124,6 +129,20 @@ export function EditRobotModal({
{description.length}/2048 characters
</div>
</div>
<div className="grid gap-2">
<Label
htmlFor="orderId"
className="text-sm font-medium text-gray-12"
>
Order ID (Optional)
</Label>
<Input
id="orderId"
value={orderId}
onChange={(e) => setOrderId(e.target.value)}
className="bg-gray-2 border-gray-3 text-gray-12"
/>
</div>
</div>
{error && <div className="text-red-500 text-sm mt-2">{error}</div>}
<div className="flex justify-end">
Expand Down
36 changes: 32 additions & 4 deletions frontend/src/components/modals/RegisterRobotModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useCallback, useState } from "react";
import { useCallback, useEffect, useState } from "react";

import { Button } from "@/components/ui/button";
import {
Expand All @@ -22,23 +22,37 @@ interface RegisterRobotModalProps {
listing_id: string;
order_id?: string | null;
}) => Promise<void>;
initialValues?: {
order_id?: string;
listing_id?: string;
};
}

export function RegisterRobotModal({
isOpen,
onClose,
onAdd,
initialValues,
}: RegisterRobotModalProps) {
const [name, setName] = useState("");
const [description, setDescription] = useState("");
const [listingId, setListingId] = useState("");
const [listingId, setListingId] = useState(initialValues?.listing_id || "");
const [orderId, setOrderId] = useState(initialValues?.order_id || "");
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState<string | null>(null);

useEffect(() => {
if (initialValues) {
if (initialValues.listing_id) setListingId(initialValues.listing_id);
if (initialValues.order_id) setOrderId(initialValues.order_id);
}
}, [initialValues]);

const resetModalData = useCallback(() => {
setName("");
setDescription("");
setListingId("");
setOrderId("");
}, []);

const handleAdd = useCallback(async () => {
Expand All @@ -59,7 +73,7 @@ export function RegisterRobotModal({
listing_id: listingId,
name,
description: description || null,
order_id: null,
order_id: orderId || null,
});
resetModalData();
} catch (error) {
Expand All @@ -78,7 +92,7 @@ export function RegisterRobotModal({
setIsLoading(false);
}
}
}, [name, description, listingId, onAdd, resetModalData]);
}, [name, description, listingId, orderId, onAdd, resetModalData]);

return (
<Dialog
Expand Down Expand Up @@ -143,6 +157,20 @@ export function RegisterRobotModal({
className="bg-gray-2 border-gray-3 text-gray-12"
/>
</div>
<div className="grid gap-2">
<Label
htmlFor="orderId"
className="text-sm font-medium text-gray-12"
>
Order ID (Optional)
</Label>
<Input
id="orderId"
value={orderId}
onChange={(e) => setOrderId(e.target.value)}
className="bg-gray-2 border-gray-3 text-gray-12"
/>
</div>
</div>
{error && <div className="text-red-500 text-sm mt-2">{error}</div>}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
Expand Down
114 changes: 108 additions & 6 deletions frontend/src/components/orders/OrderCard.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,33 @@
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";

import CancelOrderModal from "@/components/modals/CancelOrderModal";
import EditAddressModal from "@/components/modals/EditAddressModal";
import { RegisterRobotModal } from "@/components/modals/RegisterRobotModal";
import { Button } from "@/components/ui/button";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import type { paths } from "@/gen/api";
import { useAlertQueue } from "@/hooks/useAlertQueue";
import { useAuthentication } from "@/hooks/useAuth";
import { ApiError } from "@/lib/types/api";
import { formatPrice } from "@/lib/utils/formatNumber";
import { normalizeStatus } from "@/lib/utils/formatString";
import { Bot, Plus } from "lucide-react";

type OrderWithProduct =
paths["/orders/user-orders-with-products"]["get"]["responses"][200]["content"]["application/json"][0];

type Order =
paths["/orders/user-orders"]["get"]["responses"][200]["content"]["application/json"][0];

type Robot =
paths["/robots/check-order/{order_id}"]["get"]["responses"][200]["content"]["application/json"];

const orderStatuses = [
"processing",
"in_development",
Expand Down Expand Up @@ -47,6 +57,12 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
const { order, product } = orderWithProduct;
const [isEditAddressModalOpen, setIsEditAddressModalOpen] = useState(false);
const [isCancelOrderModalOpen, setIsCancelOrderModalOpen] = useState(false);
const [isRegisterRobotModalOpen, setIsRegisterRobotModalOpen] =
useState(false);
const { api } = useAuthentication();
const { addAlert, addErrorAlert } = useAlertQueue();
const [associatedRobot, setAssociatedRobot] = useState<Robot | null>(null);
const navigate = useNavigate();

const currentStatusIndex = orderStatuses.indexOf(order.status);
const isRedStatus = redStatuses.includes(order.status);
Expand Down Expand Up @@ -76,6 +92,66 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
return canModifyStatuses.includes(order.status);
};

const handleCreateRobot = async (robotData: {
name: string;
description: string | null;
listing_id: string;
order_id?: string | null;
}) => {
try {
const { data, error } = await api.client.POST("/robots/create", {
body: robotData,
});

if (error) {
const errorMessage =
typeof error.detail === "string"
? error.detail
: error.detail?.[0]?.msg || "Unknown error";
addErrorAlert(`Failed to create robot: ${errorMessage}`);
throw error;
}
setAssociatedRobot(data);
setIsRegisterRobotModalOpen(false);
addAlert("Robot registered successfully!", "success");
} catch (error) {
console.error("Error creating robot:", error);
if (error && typeof error === "object" && "detail" in error) {
const apiError = error as ApiError;
const errorMessage =
typeof apiError.detail === "string"
? apiError.detail
: apiError.detail?.[0]?.msg || "Unknown error";
throw new Error(errorMessage);
}
throw new Error("Failed to create robot. Please try again.");
}
};

useEffect(() => {
const checkForRobot = async () => {
try {
const { data: robot } = await api.client.GET(
`/robots/check-order/{order_id}`,
{ params: { path: { order_id: order.id } } },
);
setAssociatedRobot(robot || null);
} catch (error) {
console.error("Error checking for robot:", error);
}
};

checkForRobot();
}, [api.client, order.id]);

const handleRobotAction = () => {
if (associatedRobot) {
navigate("/terminal");
} else {
setIsRegisterRobotModalOpen(true);
}
};

return (
<div className="bg-white shadow-md rounded-lg p-4 md:p-6 w-full">
<h2 className="text-gray-12 font-bold text-2xl mb-1">{product.name}</h2>
Expand All @@ -85,27 +161,44 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
{normalizeStatus(order.status)}
</span>
</p>
<div className="text-sm sm:text-base text-gray-10 flex flex-col mb-4">

<div className="mb-4">
<Button
onClick={handleRobotAction}
variant="primary"
className="w-full sm:w-auto"
>
{associatedRobot ? (
<Bot className="mr-2 h-4 w-4" />
) : (
<Plus className="mr-2 h-4 w-4" />
)}
{associatedRobot ? "View Robot in Terminal" : "Register Robot"}
</Button>
</div>

<div className="text-sm sm:text-base text-gray-11 flex flex-col mb-4">
<p>Order ID: {order.id}</p>
<div className="mb-2">
<DropdownMenu>
<DropdownMenuTrigger className="text-primary-9 underline cursor-pointer">
<DropdownMenuTrigger className="text-gray-12 underline cursor-pointer">
Manage order
</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem
disabled={!canModifyOrder()}
onSelect={() => setIsEditAddressModalOpen(true)}
className="cursor-pointer hover:bg-gray-100"
className="cursor-pointer"
>
Change delivery address
</DropdownMenuItem>
<div className="border-t border-gray-11 mx-1"></div>
<DropdownMenuItem
disabled={!canModifyOrder()}
onSelect={() => setIsCancelOrderModalOpen(true)}
className="cursor-pointer hover:bg-gray-100"
className="cursor-pointer"
>
Cancel Order
Cancel order
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
Expand Down Expand Up @@ -225,6 +318,15 @@ const OrderCard: React.FC<{ orderWithProduct: OrderWithProduct }> = ({
order={order}
onOrderUpdate={handleOrderUpdate}
/>
<RegisterRobotModal
isOpen={isRegisterRobotModalOpen}
onClose={() => setIsRegisterRobotModalOpen(false)}
onAdd={handleCreateRobot}
initialValues={{
order_id: order.id,
// listing_id: product.id, // change later once listing for stompy pro/mini is created
}}
/>
</div>
);
};
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/components/pages/StompyPro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ const StompyPro: React.FC = () => {
"Regular software updates",
],
price: 1600000,
productId: "prod_Qyzd8f0gFMis7c", // developer
// productId: "prod_R0n3nkCO4aQdlg", // production
// productId: "prod_Qyzd8f0gFMis7c", // developer
productId: "prod_R0n3nkCO4aQdlg", // production
};

return (
Expand Down
21 changes: 21 additions & 0 deletions frontend/src/components/robots/RobotCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,27 @@ export default function RobotCard({
<p className="text-gray-12">{robot.listing_id}</p>
)}
</div>
<div>
<p className="text-gray-11">Order ID</p>
{robot.order_id ? (
<Tooltip
content="View order associated with robot"
position="bottom"
>
<Link
to={`/orders`}
className="text-gray-12 underline hover:text-primary-9 flex items-center gap-1 group"
>
<span className="group-hover:underline">
{robot.order_id}
</span>
<ExternalLink className="h-3 w-3" />
</Link>
</Tooltip>
) : (
<p className="text-gray-12">No associated order</p>
)}
</div>
<div>
<p className="text-gray-11">Registered</p>
<p className="text-gray-12">
Expand Down
Loading

0 comments on commit ca35b0d

Please sign in to comment.