Skip to content
Merged
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
22 changes: 22 additions & 0 deletions app/api/admin/orders-by-timeslot/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,28 @@ export async function GET(request: NextRequest) {
});
}

// Sort chronologically by date, then start/end time. The Payload query
// only sorts by date, so slots on the same day would otherwise appear in
// an arbitrary order. `date` is "YYYY-MM-DD" and the times are zero-padded
// "HH:MM", so lexicographic comparison is chronological.
const normaliseDate = (value: unknown): string =>
typeof value === "string"
? value.includes("T")
? value.split("T")[0]
: value
: "";
timeslots = [...timeslots].sort((a, b) => {
const dateCmp = normaliseDate(a.date).localeCompare(
normaliseDate(b.date),
);
if (dateCmp !== 0) return dateCmp;
const startCmp = String(a.startTime ?? "").localeCompare(
String(b.startTime ?? ""),
);
if (startCmp !== 0) return startCmp;
return String(a.endTime ?? "").localeCompare(String(b.endTime ?? ""));
});

const timeslotIds = timeslots.map((t) => String(t.id));

// ── 2. Fetch orders in two parallel queries ──────────────────────
Expand Down
61 changes: 59 additions & 2 deletions components/admin/OrdersByTimeslotView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -195,9 +195,15 @@ function OrdersByTimeslotViewInner() {
[getFileKey],
);

/** Download all files for all orders in a timeslot group sequentially */
/**
* Open every downloadable file across the given orders in new tabs.
*
* Shared by the per-timeslot "Download All Files" button and the per-order
* download button, so both behave identically (parallel presign, sequential
* open with a small delay to dodge popup blockers).
*/
const downloadingRef = useRef(false);
const handleDownloadAllFiles = useCallback(
const downloadFilesForOrders = useCallback(
async (orders: OrderData[]) => {
if (downloadingRef.current) return;
downloadingRef.current = true;
Expand Down Expand Up @@ -265,6 +271,18 @@ function OrdersByTimeslotViewInner() {
[getFileKey],
);

/** Download all files across every order in a timeslot group. */
const handleDownloadAllFiles = useCallback(
(orders: OrderData[]) => downloadFilesForOrders(orders),
[downloadFilesForOrders],
);

/** Download all files for a single order. */
const handleDownloadOrderFiles = useCallback(
(order: OrderData) => downloadFilesForOrders([order]),
[downloadFilesForOrders],
);

return (
<div style={{ padding: "24px", maxWidth: "1200px", margin: "0 auto" }}>
<BackToDashboard />
Expand Down Expand Up @@ -457,6 +475,23 @@ function OrdersByTimeslotViewInner() {
>
⬇ Download All Files
</button>
<a
href={`/admin/collections/timeslots/${timeslot.id}`}
title="Open this timeslot to edit or delete it"
style={{
padding: "3px 10px",
fontSize: "12px",
fontWeight: 600,
background: "rgba(255,255,255,0.15)",
color: "#fff",
border: "1px solid rgba(255,255,255,0.3)",
borderRadius: "4px",
cursor: "pointer",
textDecoration: "none",
}}
>
⚙ Manage Timeslot
</a>
</div>
</div>

Expand Down Expand Up @@ -691,6 +726,28 @@ function OrdersByTimeslotViewInner() {
)}
</td>
<td style={{ padding: "10px 12px", textAlign: "right" }}>
{order.files?.some(
(f) => f.stagingKey || f.permanentKey,
) && (
<button
type="button"
onClick={() => handleDownloadOrderFiles(order)}
title="Download all files for this order"
style={{
padding: "4px 12px",
background: "#00695c",
color: "#fff",
border: "none",
borderRadius: "4px",
fontSize: "12px",
fontWeight: 600,
cursor: "pointer",
marginRight: "4px",
}}
>
⬇ Download All
</button>
)}
{order.status === OrderStatus.AWAITING_PICKUP && (
<button
type="button"
Expand Down
Loading