From 5fb619a5cf5e7587ddf06761ed98e893292ed50b Mon Sep 17 00:00:00 2001
From: Z8MB1E
Date: Mon, 21 Sep 2026 20:47:57 -0400
Subject: [PATCH] fix(operations): move page data helpers to surface modules
Satisfies the Next 16 rule that page files only export page fields:
getSupplyBoxDetail/getSupplyBoxList and getReservationList/getReservationDetail
move to reservations/surface.ts and pages import from it.
---
.../logistics/supply-boxes/[id]/page.tsx | 18 +--
.../logistics/supply-boxes/page.tsx | 8 +-
.../operations/reservations/[id]/page.tsx | 69 +++++------
.../operations/reservations/page.tsx | 86 ++++----------
.../operations/reservations/surface.ts | 109 ++++++++++++++++++
...operation-reservation-surfaces.int.spec.ts | 34 ++++--
6 files changed, 207 insertions(+), 117 deletions(-)
create mode 100644 src/app/(frontend)/operations/reservations/surface.ts
diff --git a/src/app/(frontend)/logistics/supply-boxes/[id]/page.tsx b/src/app/(frontend)/logistics/supply-boxes/[id]/page.tsx
index 02db3db..e800ce6 100644
--- a/src/app/(frontend)/logistics/supply-boxes/[id]/page.tsx
+++ b/src/app/(frontend)/logistics/supply-boxes/[id]/page.tsx
@@ -63,7 +63,7 @@ export interface SupplyBoxDetailData {
* user, never overrideAccess). Returns null for an unknown id so the page can
* render a not-found notice.
*/
-export async function getSupplyBoxDetail(
+async function getSupplyBoxDetail(
payload: PayloadType,
user: SurfaceUser,
id: string,
@@ -114,17 +114,15 @@ function Section({ title, children }: { title: string; children: React.ReactNode
function kvRow(label: string, value: React.ReactNode) {
return (
- {label}
+
+ {label}
+
{value}
);
}
-export default async function SupplyBoxDetailPage({
- params,
-}: {
- params: Promise<{ id: string }>;
-}) {
+export default async function SupplyBoxDetailPage({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const payload = await getPayload({ config });
const { user: authUser } = await payload.auth({
@@ -257,7 +255,9 @@ export default async function SupplyBoxDetailPage({
key={cmd.id}
className="flex items-center justify-between gap-2 border border-white/10 px-2 py-1.5"
>
- {cmd.type}
+
+ {cmd.type}
+
);
-}
\ No newline at end of file
+}
diff --git a/src/app/(frontend)/logistics/supply-boxes/page.tsx b/src/app/(frontend)/logistics/supply-boxes/page.tsx
index 4b5aa1c..652b701 100644
--- a/src/app/(frontend)/logistics/supply-boxes/page.tsx
+++ b/src/app/(frontend)/logistics/supply-boxes/page.tsx
@@ -92,7 +92,7 @@ export interface SupplyBoxListData {
* mints a fresh sessionId, so rows from older generations are stale snapshots;
* only the newest sessionId per server is kept.
*/
-export async function getSupplyBoxList(
+async function getSupplyBoxList(
payload: PayloadType,
user: SurfaceUser,
): Promise {
@@ -151,8 +151,8 @@ export default async function SupplyBoxesPage() {
- Crate inventory synced from the game server through the Arma bridge.
- Rows show the newest generation per server; older sessions are filtered out.
+ Crate inventory synced from the game server through the Arma bridge. Rows show the newest
+ generation per server; older sessions are filtered out.
@@ -209,4 +209,4 @@ export default async function SupplyBoxesPage() {
)}
);
-}
\ No newline at end of file
+}
diff --git a/src/app/(frontend)/operations/reservations/[id]/page.tsx b/src/app/(frontend)/operations/reservations/[id]/page.tsx
index 69cce7d..dea59a6 100644
--- a/src/app/(frontend)/operations/reservations/[id]/page.tsx
+++ b/src/app/(frontend)/operations/reservations/[id]/page.tsx
@@ -8,6 +8,7 @@ import { LedgerRow, NoticeState, PageShell } from "@/components/frontend/operati
import type { SurfaceUser } from "@/lib/operations/surface";
import { CancelButton } from "@/components/frontend/operations/reservations/ReservationCancelButton";
import type { OperationReservation } from "@/payload-types";
+import { getReservationDetail } from "../surface";
type PayloadType = Awaited>;
@@ -21,14 +22,18 @@ function relationId(value: unknown): number | null {
}
function relationLabel(value: unknown): string {
- if (typeof value === "object" && value !== null && "codeName" in (value as { codeName?: unknown })) {
+ if (
+ typeof value === "object" &&
+ value !== null &&
+ "codeName" in (value as { codeName?: unknown })
+ ) {
const codeName = (value as { codeName?: unknown }).codeName;
if (typeof codeName === "string" && codeName) return codeName;
}
return String(relationId(value) ?? "n/a");
}
-interface ReservationDetail {
+export interface ReservationDetail {
reservation: OperationReservation | null;
canCancel: boolean;
}
@@ -40,31 +45,6 @@ interface ReservationDetail {
* canCancel is true only when the row is still reserved AND the viewer holds
* operation-reservations:update.
*/
-export async function getReservationDetail(
- payload: PayloadType,
- user: SurfaceUser,
- id: string,
-): Promise {
- let reservation: OperationReservation | null = null;
- try {
- reservation = await payload.findByID({
- collection: "operation-reservations",
- id: Number(id),
- depth: 1,
- user: user ?? undefined,
- });
- } catch {
- reservation = null;
- }
-
- const canCancel =
- reservation !== null &&
- reservation.status === "reserved" &&
- user !== null &&
- (await hasPermission(payload, user, "operation-reservations:update"));
-
- return { reservation, canCancel };
-}
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
@@ -78,7 +58,9 @@ function Section({ title, children }: { title: string; children: React.ReactNode
function kvRow(label: string, value: React.ReactNode) {
return (
- {label}
+
+ {label}
+
{value}
);
@@ -156,7 +138,10 @@ export default async function ReservationDetailPage({
) : (
{personnel.map((member) => (
- -
+
-
{relationLabel(member.user)} · id {relationId(member.user)}
@@ -201,7 +186,9 @@ export default async function ReservationDetailPage({
{relationLabel(line.resource)} · id {relationId(line.resource)}
- {line.amount}
+
+ {line.amount}
+
))}
@@ -211,7 +198,12 @@ export default async function ReservationDetailPage({
{budget?.amount != null || budget?.account != null ? (
- {kvRow("Account", budget.account != null ? `${relationLabel(budget.account)} · id ${relationId(budget.account)}` : "n/a")}
+ {kvRow(
+ "Account",
+ budget.account != null
+ ? `${relationLabel(budget.account)} · id ${relationId(budget.account)}`
+ : "n/a",
+ )}
{kvRow("Amount", budget.amount ?? "n/a")}
) : (
@@ -221,14 +213,23 @@ export default async function ReservationDetailPage({
- {kvRow("Origin", reservation.origin != null ? relationLabel(reservation.origin) : "n/a")}
- {kvRow("Destination", reservation.destination != null ? relationLabel(reservation.destination) : "n/a")}
+ {kvRow(
+ "Origin",
+ reservation.origin != null ? relationLabel(reservation.origin) : "n/a",
+ )}
+ {kvRow(
+ "Destination",
+ reservation.destination != null ? relationLabel(reservation.destination) : "n/a",
+ )}
- {kvRow("Created by", reservation.createdBy != null ? relationLabel(reservation.createdBy) : "n/a")}
+ {kvRow(
+ "Created by",
+ reservation.createdBy != null ? relationLabel(reservation.createdBy) : "n/a",
+ )}
{kvRow("Created at", reservation.createdAt)}
{kvRow("Expires", reservation.expiresAt ?? "n/a")}
diff --git a/src/app/(frontend)/operations/reservations/page.tsx b/src/app/(frontend)/operations/reservations/page.tsx
index 0b80490..8f24fcd 100644
--- a/src/app/(frontend)/operations/reservations/page.tsx
+++ b/src/app/(frontend)/operations/reservations/page.tsx
@@ -18,8 +18,9 @@ import { listReservationOptions } from "@/lib/operations/reservationOptions";
import { hasLogisticsQualification } from "@/utils/access-control/hasLogisticsQualification";
import { CancelButton } from "@/components/frontend/operations/reservations/ReservationCancelButton";
import { CreateReservationDialog } from "@/components/frontend/operations/reservations/CreateReservationDialog";
+import { getReservationList, type ReservationListData } from "./surface";
-const RESERVATION_PAGE_SIZE = 20;
+export const RESERVATION_PAGE_SIZE = 20;
export interface ReservationRow {
id: number;
@@ -34,16 +35,7 @@ export interface ReservationFilters {
operationId?: string;
}
-export interface ReservationListData {
- rows: ReservationRow[];
- /** True when the viewer holds operation-reservations:read. False -> empty rows. */
- capability: boolean;
- page: number;
- totalDocs: number;
- totalPages: number;
-}
-
-type PayloadType = Awaited>;
+export type PayloadType = Awaited>;
/**
* List of allocation reservations with status/operationId filters and
@@ -53,51 +45,6 @@ type PayloadType = Awaited>;
* result (capability false) so the page can render the permission notice
* instead of a list that looks like "no data".
*/
-export async function getReservationList(
- payload: PayloadType,
- user: SurfaceUser,
- filters: ReservationFilters,
- requestedPage = 1,
-): Promise {
- const canSee = await hasPermission(payload, user, "operation-reservations:read");
- if (!canSee) {
- return { rows: [], capability: false, page: 1, totalDocs: 0, totalPages: 0 };
- }
-
- const conditions: Where[] = [];
- if (filters.status) conditions.push({ status: { equals: filters.status } });
- if (filters.operationId) conditions.push({ operationId: { equals: filters.operationId } });
- const where: Where | undefined = conditions.length > 0 ? { and: conditions } : undefined;
-
- const found = await payload.find({
- collection: "operation-reservations",
- where,
- sort: "-createdAt",
- page: Math.max(1, requestedPage),
- limit: RESERVATION_PAGE_SIZE,
- depth: 0,
- user: user ?? undefined,
- });
-
- const rows: ReservationRow[] = found.docs.map((doc) => {
- const r = doc as unknown as ReservationRow;
- return {
- id: r.id,
- reservationKey: r.reservationKey,
- operationId: r.operationId,
- status: r.status,
- expiresAt: r.expiresAt ?? null,
- };
- });
-
- return {
- rows,
- capability: true,
- page: found.page ?? Math.max(1, requestedPage),
- totalDocs: found.totalDocs ?? found.docs.length,
- totalPages: found.totalPages ?? 1,
- };
-}
function reservationHref(filters: ReservationFilters, page: number): string {
const params = new URLSearchParams();
@@ -140,7 +87,12 @@ export default async function ReservationsPage({
status: params.status || undefined,
operationId: params.operationId || undefined,
};
- const data = await getReservationList(payload, surfaceUser, filters, Number.isNaN(page) ? 1 : page);
+ const data = await getReservationList(
+ payload,
+ surfaceUser,
+ filters,
+ Number.isNaN(page) ? 1 : page,
+ );
// The cancel control is server-gated: only viewers holding
// operation-reservations:update may render it, and only on reserved rows.
@@ -154,9 +106,7 @@ export default async function ReservationsPage({
const canCreate = user
? await hasPermission(payload, user, "operation-reservations:create")
: false;
- const canManageLogistics = user
- ? await hasLogisticsQualification(payload, user)
- : false;
+ const canManageLogistics = user ? await hasLogisticsQualification(payload, user) : false;
const options = canCreate && user ? await listReservationOptions(payload, user) : null;
return (
@@ -168,9 +118,16 @@ export default async function ReservationsPage({
row captures what was consumed or returned.
-