Skip to content
Open
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -162,3 +162,4 @@ node_modules/

# analytics
hackathon_site/registration/analytics/data/
hackathon_site/Hardware Counting 2025 MakeUofT - Hardware Count Overview.csv
26 changes: 24 additions & 2 deletions hackathon_site/dashboard/frontend/src/api/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ export const teamOrderListSerialization = (
);

if (hardwareInTableRow.length > 0 && !allItemsReturnedOrRejected) {
(order.status === "Submitted" || order.status === "Ready for Pickup"
// include "In Progress" status with pending orders so admins can see who's packing
(order.status === "Submitted" ||
order.status === "In Progress" ||
order.status === "Ready for Pickup"
? pendingOrders
: checkedOutOrders
).push({
Expand All @@ -106,6 +109,8 @@ export const teamOrderListSerialization = (
hardwareInTableRow,
createdTime: order.created_at,
updatedTime: order.updated_at,
packing_admin_id: order.packing_admin_id, // track who is packing this order
packing_admin_name: order.packing_admin_name, // display admin name in ui
});
}
}
Expand Down Expand Up @@ -205,10 +210,13 @@ export const sortCheckedOutOrders = (

export const sortPendingOrders = (orders: OrderInTable[]): OrderInTable[] => {
let ready_orders = [];
let in_progress_orders = []; // track orders currently being packed
let submitted_orders = [];
for (let order of orders) {
if (order.status === "Ready for Pickup") {
ready_orders.push(order);
} else if (order.status === "In Progress") {
in_progress_orders.push(order);
} else {
submitted_orders.push(order);
}
Expand All @@ -220,13 +228,27 @@ export const sortPendingOrders = (orders: OrderInTable[]): OrderInTable[] => {
);
});

in_progress_orders.sort((order1, order2) => {
return (
new Date(order1.updatedTime).valueOf() -
new Date(order2.updatedTime).valueOf()
);
});

submitted_orders.sort((order1, order2) => {
return (
new Date(order1.updatedTime).valueOf() -
new Date(order2.updatedTime).valueOf()
);
});

orders.splice(0, orders.length, ...submitted_orders, ...ready_orders);
// sort order: submitted first, then in progress (being packed), then ready for pickup
orders.splice(
0,
orders.length,
...submitted_orders,
...in_progress_orders,
...ready_orders
);
return orders;
};
16 changes: 14 additions & 2 deletions hackathon_site/dashboard/frontend/src/api/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,12 +102,12 @@ export interface ProfileWithUser extends ProfileWithoutTeamNumber {
/** Orders API */
export type OrderStatus =
| "Submitted"
| "In Progress" // new status for tracking order packing
| "Ready for Pickup"
| "Picked Up"
| "Cancelled"
| "Returned"
| "Pending"
| "In Progress";
| "Pending";

export type PartReturnedHealth =
| "Healthy"
Expand All @@ -131,6 +131,8 @@ export interface Order {
total_credits: number;
created_at: string;
updated_at: string;
packing_admin_id?: number | null; // track which admin is packing this order
packing_admin_name?: string | null; // admin's full name for display
}

export type OrderOrdering = "" | "created_at" | "-created_at";
Expand All @@ -156,6 +158,8 @@ export interface OrderInTable {
status: OrderStatus;
createdTime: string;
updatedTime: string;
packing_admin_id?: number | null; // track which admin is packing this order
packing_admin_name?: string | null; // admin's full name for display
}

export type ReturnedItem = ItemsInOrder & { quantity: number; time: string };
Expand Down Expand Up @@ -198,3 +202,11 @@ export interface Incident {
created_at: string;
updated_at: string;
}

/** Order Lock API */
export interface OrderLockStatus {
orders_locked: boolean;
locked_by: string | null;
locked_at: string | null;
reason: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { teamSelector, teamSizeSelector } from "slices/event/teamSlice";
import { isTestUserSelector } from "slices/users/userSlice";
import { projectDescriptionSelector } from "slices/event/teamSlice";
import { getCreditsUsedSelector } from "slices/order/orderSlice";
import { ordersLockedSelector } from "slices/hardware/orderLockSlice";
import {
hardwareSignOutEndDate,
hardwareSignOutStartDate,
Expand All @@ -33,6 +34,7 @@ const CartSummary = () => {
const subtotalCredits = useSelector(subtotalCreditsSelector);
const creditsUsed = useSelector(getCreditsUsedSelector);
const creditsAvailable = useSelector(teamSelector)?.credits;
const ordersLocked = useSelector(ordersLockedSelector);
const projectedCredits = creditsAvailable
? creditsAvailable - creditsUsed - subtotalCredits
: 0;
Expand Down Expand Up @@ -84,6 +86,7 @@ const CartSummary = () => {
(projectDescription &&
projectDescription.length < minProjectDescriptionLength) ||
(!isTestUser && isOutsideSignOutPeriod) ||
(!isTestUser && ordersLocked) ||
projectedCredits < 0
}
onClick={onSubmit}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import { useSelector } from "react-redux";
import AlertBox from "components/general/AlertBox/AlertBox";
import { ordersLockedSelector } from "slices/hardware/orderLockSlice";

const OrderLockAlert = () => {
const ordersLocked = useSelector(ordersLockedSelector);

return ordersLocked ? (
<AlertBox
data-testid="order-lock-alert"
title="Order Submissions Currently Locked"
error="Hardware order submissions are currently locked by administrators. Please contact the hardware team at hardware@makeuoft.ca for assistance."
type="warning"
/>
) : null;
};

export default OrderLockAlert;
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,17 @@ interface OrderProps {
time: string;
id: number;
status: string;
packingAdminName?: string | null; // display who is packing this order
}

const OrderCard = ({ teamCode, orderQuantity, time, id, status }: OrderProps) => {
const OrderCard = ({
teamCode,
orderQuantity,
time,
id,
status,
packingAdminName,
}: OrderProps) => {
const date = new Date(time);
const month = date.toLocaleString("default", { month: "short" });
const day = date.getDate();
Expand All @@ -25,6 +33,10 @@ const OrderCard = ({ teamCode, orderQuantity, time, id, status }: OrderProps) =>
{ title: "Order Qty", value: orderQuantity },
{ title: "Time", value: `${month} ${day}, ${hoursAndMinutes}` },
{ title: "ID", value: id },
// show which admin is packing this order when status is "In Progress"
...(status === "In Progress" && packingAdminName
? [{ title: "Packing Admin", value: packingAdminName }]
: []),
];

return (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from "react";
import { useDispatch, useSelector } from "react-redux";
import Button from "@material-ui/core/Button";
import LockIcon from "@material-ui/icons/Lock";
import LockOpenIcon from "@material-ui/icons/LockOpen";
import CircularProgress from "@material-ui/core/CircularProgress";
import {
lockStatusSelector,
isLoadingSelector,
toggleLock,
} from "slices/hardware/orderLockSlice";
import { displaySnackbar } from "slices/ui/uiSlice";
import { AppDispatch } from "slices/store";

const OrderLockButton = () => {
const dispatch = useDispatch<AppDispatch>();
const lockStatus = useSelector(lockStatusSelector);
const isLoading = useSelector(isLoadingSelector);

const handleToggle = async () => {
const newLockState = !lockStatus.orders_locked;
try {
const result = await dispatch(
toggleLock({
orders_locked: newLockState,
reason: "",
})
);

if (toggleLock.fulfilled.match(result)) {
dispatch(
displaySnackbar({
message: newLockState
? "Order submissions have been locked"
: "Order submissions have been unlocked",
options: { variant: "success" },
})
);
} else {
dispatch(
displaySnackbar({
message: "Failed to toggle lock status",
options: { variant: "error" },
})
);
}
} catch (error) {
dispatch(
displaySnackbar({
message: "Failed to toggle lock status",
options: { variant: "error" },
})
);
}
};

return (
<Button
variant="contained"
color={lockStatus.orders_locked ? "secondary" : "primary"}
startIcon={
isLoading ? (
<CircularProgress size={20} color="inherit" />
) : lockStatus.orders_locked ? (
<LockIcon />
) : (
<LockOpenIcon />
)
}
onClick={handleToggle}
disabled={isLoading}
data-testid="order-lock-button"
>
{lockStatus.orders_locked ? "Unlock Orders" : "Lock Orders"}
</Button>
);
};

export default OrderLockButton;
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,11 @@ const OrderFilter = ({ handleReset, handleSubmit }: FormikValues) => {
status: "Submitted",
numOrders: numStatuses["Submitted"],
},
{
// new status to show orders currently being packed
status: "In Progress",
numOrders: numStatuses["In Progress"],
},
{
status: "Ready for Pickup",
numOrders: numStatuses["Ready for Pickup"],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,14 @@ const OrdersTable = ({ ordersData }: OrdersTableProps) => {
minWidth: 250,
renderCell: (params) => <OrderStateIcon status={params.value} />,
},
{
// show which admin is currently packing this order to prevent double-packing
field: "packing_admin_name",
headerName: "Packing Admin",
flex: 1,
minWidth: 150,
valueGetter: (params) => params.value || "-",
},
{ field: "updated_at", headerName: "Updated At" },
{ field: "request", headerName: "Request" },
];
Expand Down
Loading
Loading