Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion frontend/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
<link rel="icon" type="image/svg+xml" href="/vite.svg">
</head>
<body>
<div id="root"></div>
<div id="root"></div>
<script type="module" src="./src/index.tsx"></script>
</body>
</html>
2 changes: 2 additions & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@
"cmdk": "^1.1.1",
"date-fns": "^4.1.0",
"embla-carousel-react": "^8.6.0",
"fuse.js": "^7.1.0",
"lucide-react": "^0.563.0",
"motion": "^12.23.24",
"react": "^19.2.4",
"react-day-picker": "^9.13.1",
"react-dom": "^19.2.4",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
type HTMLAttributes,
type ImgHTMLAttributes,
type ReactElement,
type SVGAttributes,
useCallback,
type ReactNode
} from "react";
Expand Down Expand Up @@ -87,7 +88,7 @@ const Logo = (props: ImgHTMLAttributes<HTMLImageElement>) => {
);
};

const HamburgerIcon = ({ className, ...props }: React.SVGAttributes<SVGElement>) => (
const HamburgerIcon = ({ className, ...props }: SVGAttributes<SVGElement>) => (
<svg
className={cn("pointer-events-none", className)}
width={16}
Expand Down Expand Up @@ -145,7 +146,7 @@ const NotificationMenu = ({
</div>
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={() => onItemClick?.("dashboard")}>
<DropdownMenuItem onClick={() => onItemClick?.("/dashboard")}>
View all notifications
</DropdownMenuItem>
</DropdownMenuContent>
Expand Down Expand Up @@ -286,7 +287,7 @@ const NavbarComponent = forwardRef<HTMLElement, NavbarProps>(
)}
{...props}
>
<div className="flex h-16 items-center justify-between gap-4">
<nav className="flex h-16 items-center justify-between gap-4">
<div className="flex items-center gap-2">
{isMobile && (
<Popover>
Expand Down Expand Up @@ -365,7 +366,7 @@ const NavbarComponent = forwardRef<HTMLElement, NavbarProps>(
onItemClick={onUserItemClick}
/>
</div>
</div>
</nav>
</header>
);
}
Expand Down
27 changes: 27 additions & 0 deletions frontend/src/components/pages/orders/OrderBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { ReactElement } from "react";

import { Badge } from "@/components/ui/badge";

import { OrderStatus } from "./orders";

export default function OrderBadge (props: { status: OrderStatus }): ReactElement<{ status: OrderStatus }> | null {
const color = props.status === OrderStatus.Denied
? "bg-red-500"
: props.status === OrderStatus.Pending
? "bg-gray-500"
: props.status === OrderStatus.Delivered
? "bg-green-500"
: "bg-amber-500";

return (
<Badge className={`uppercase ms-2 ${color}`}>
{
props.status === OrderStatus.Denied
? "Denied"
: props.status === OrderStatus.Approved
? "Approved"
: "Pending"
}
</Badge>
);
}
77 changes: 77 additions & 0 deletions frontend/src/components/pages/orders/OrderCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { useRef, type ReactElement } from "react";
import {
Check,
CircleSmall,
Ellipsis,
Pencil,
X
} from "lucide-react";

import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle
} from "@/components/ui/card";
import { Separator } from "@/components/ui/separator";
import { Tooltip, TooltipTrigger, TooltipContent } from "@/components/ui/tooltip";

import OrderBadge from "./OrderBadge";

import {
OrderColors,
OrderStatus,
type Order,
OrderAction
} from "./orders";

export default function OrderEntry (props: { order: Order, admin: boolean, orderTask: (order: Order, action: OrderAction) => void }): ReactElement<{ order: Order, isAdmin: boolean, orderTask: (order: Order, action: OrderAction) => void }> | null {
const viewDetails = useRef<HTMLButtonElement>(null);
return (
<Card className="gap-0 cursor-pointer transition-shadow hover:shadow-2xl" onClick={e => (e.stopPropagation(), props.orderTask(props.order, OrderAction.View))}>
<CardHeader>
<div className="flex">
<CardTitle className="leading-[1.5rem]">{props.order.id}</CardTitle>
<OrderBadge status={props.order.status} />
<div className="flex flex-row ms-auto">
{props.order.reviews.map((x, i) => <CircleSmall key={i} fill={x ? OrderColors.GREEN : x === false ? OrderColors.RED : OrderColors.GRAY} className={`${x ? "text-green-500" : x === false ? "text-red-500" : "text-gray-500"}`} />)}
</div>
</div>
</CardHeader>
<CardContent className="relative">
<Separator />
<p className="text-sm mt-2"><strong>Subsystem</strong> - {props.order.requester.subsystem}</p>
<p className="text-sm mb-2"><strong>Subtotal</strong> - ${props.order.price} ({props.order.length} {props.order.length === 1 ? "item" : "items"})</p>
<Separator />
<p className="text-xs mt-2"><strong>Requester</strong> - {props.order.requester.displayName}</p>
<p className="text-xs mt-1"><strong>Deadline</strong> - {new Date(props.order.deadline).toLocaleDateString()}</p>
</CardContent>
<CardFooter className="mt-2 gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Button ref={viewDetails} variant="default" size="icon-sm" className="hover:bg-black" onClick={e => (e.stopPropagation(), props.orderTask(props.order, OrderAction.View))}><Ellipsis /></Button>
</TooltipTrigger>
<TooltipContent side="bottom">View Details</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild><Button variant="default" size="icon-sm" className="bg-gray-500 hover:bg-black hover:text-white" onClick={e => (e.stopPropagation(), props.orderTask(props.order, OrderAction.Edit))}><Pencil /></Button></TooltipTrigger>
<TooltipContent side="bottom">Edit</TooltipContent>
</Tooltip>
{props.admin && (
<>
<Tooltip>
<TooltipTrigger asChild><Button variant="default" size="icon-sm" className="bg-green-500 hover:bg-black hover:text-white" onClick={e => (e.stopPropagation(), props.orderTask(props.order, OrderAction.Approve))} disabled={props.order.status !== OrderStatus.Pending}><Check /></Button></TooltipTrigger>
<TooltipContent side="bottom">Approve</TooltipContent>
</Tooltip>
<Tooltip>
<TooltipTrigger asChild><Button variant="default" size="icon-sm" className="bg-red-500 hover:bg-black hover:text-white" onClick={e => (e.stopPropagation(), props.orderTask(props.order, OrderAction.Deny))} disabled={props.order.status !== OrderStatus.Pending}><X /></Button></TooltipTrigger>
<TooltipContent side="bottom">Deny</TooltipContent>
</Tooltip>
</>
)}
</CardFooter>
</Card>
);
}
11 changes: 11 additions & 0 deletions frontend/src/components/pages/orders/OrderDetails.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { ReactElement } from "react";

import type { DetailedOrder } from "./orders";

export default function OrderDetails (props: { order: DetailedOrder | undefined }): ReactElement<{ order: DetailedOrder | undefined }> | null {
return props.order
? (
<div></div>
)
: null;
};
Loading
Loading