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
17 changes: 16 additions & 1 deletion apps/mobile/src/home.test.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import { test } from "node:test";
import type { Booking } from "./api.ts";
import { bookingsNeedingAttention, nextUpcomingReservation } from "./home.ts";
import { bookingsNeedingAttention, loadHomeBookings, nextUpcomingReservation } from "./home.ts";

function booking(partial: Partial<Booking> & Pick<Booking, "id" | "status">): Booking {
return {
Expand All @@ -14,6 +14,21 @@ function booking(partial: Partial<Booking> & Pick<Booking, "id" | "status">): Bo
};
}

test("loadHomeBookings asks for checked-out and reserved loans", async () => {
const requested: string[] = [];
const items = await loadHomeBookings({
async listMine(params) {
requested.push(`${params.status}:${params.pageSize}`);
return { items: [booking({ id: params.status ?? "x", status: params.status === "CHECKED_OUT" ? "CHECKED_OUT" : "RESERVED" })] };
},
});
assert.deepEqual(requested, ["CHECKED_OUT:100", "RESERVED:100"]);
assert.deepEqual(
items.map((item) => item.id),
["CHECKED_OUT", "RESERVED"],
);
});

test("bookingsNeedingAttention ranks overdue before collect", () => {
const items = [
booking({ id: "c", status: "RESERVED", reminders: ["COLLECT_NOW"] }),
Expand Down
13 changes: 13 additions & 0 deletions apps/mobile/src/home.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,19 @@ function rank(item: Booking): number {
return 9;
}

type HomeBookingSource = {
listMine(params: { status?: string; page?: number; pageSize?: number }): Promise<{ items: Booking[] }>;
};

/** Checked-out and reserved loans, so an older due item is not hidden behind newer history. */
export async function loadHomeBookings(source: HomeBookingSource): Promise<Booking[]> {
const [checkedOut, reserved] = await Promise.all([
source.listMine({ status: "CHECKED_OUT", page: 1, pageSize: 100 }),
source.listMine({ status: "RESERVED", page: 1, pageSize: 100 }),
]);
return [...checkedOut.items, ...reserved.items];
}

/** Next reserved window that is not already in the due-now list. */
export function nextUpcomingReservation(items: Booking[], now: number = Date.now()): Booking | null {
const attentionIds = new Set(bookingsNeedingAttention(items).map((item) => item.id));
Expand Down
6 changes: 3 additions & 3 deletions apps/mobile/src/screens/HomeScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
type CatalogueApi,
type Me,
} from "../api";
import { bookingsNeedingAttention, nextUpcomingReservation } from "../home";
import { bookingsNeedingAttention, loadHomeBookings, nextUpcomingReservation } from "../home";

type Props = {
api: CatalogueApi;
Expand All @@ -32,9 +32,9 @@ export function HomeScreen({ api, onOpenCatalogue, onOpenBookings, onOpenBooking
setLoading(true);
setError(null);
try {
const [profile, page] = await Promise.all([api.me(), api.listMine({ page: 1, pageSize: 20 })]);
const [profile, bookings] = await Promise.all([api.me(), loadHomeBookings(api)]);
setMe(profile);
setItems(page.items);
setItems(bookings);
} catch (caught) {
setMe(null);
setItems([]);
Expand Down
12 changes: 6 additions & 6 deletions docs/HANDOFF.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,18 @@ A new session should continue from here without reconstructing chat history.
## Current

- Phase: Enhancements
- Branch: `feature/enh-31-my-bookings-pages`
- Task: `ENH-31` My bookings pages — DONE
- Branch: `feature/enh-32-home-open-loans`
- Task: `ENH-32` Home open loans — DONE

## What changed

- My bookings loads 20 at a time and can load the next page
- Changing the status filter starts again at page 1
- Home loads the employee's checked-out and reserved bookings, up to 100 of each
- Returned and cancelled bookings stay off home
- An older due loan is no longer hidden behind the newest 20 rows

## Verification

- `./gradlew test --tests com.borrowhub.backend.booking.BookingMineCancelTest` BUILD SUCCESSFUL
- `pnpm --filter @borrowhub/mobile test` — 13 passed; typecheck
- `pnpm --filter @borrowhub/mobile test` — 14 passed; typecheck
- Expo UI not run

## Next
Expand Down
1 change: 1 addition & 0 deletions docs/TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,3 +112,4 @@ Local demo identity until Entra exists (`P0-01`). Live PKCE is blocked.
| ENH-29 | Booking equipment name | DONE | Evidence 2026-09-23: `BookingMineCancelTest` BUILD SUCCESSFUL (`equipmentName` is Pixel test phone). Mobile typecheck + 13 tests. A replayed idempotency body from before this field is an empty name. Expo UI not run. |
| ENH-30 | Open asset from booking | DONE | Evidence 2026-09-23: mobile typecheck + 13 tests. The asset tag on a booking opens that catalogue item, and back returns to the booking. Expo UI not run. |
| ENH-31 | My bookings pages | DONE | Evidence 2026-09-23: `BookingMineCancelTest` BUILD SUCCESSFUL (`page=2&pageSize=1` returns one of two). Mobile typecheck + 13 tests. Load more appends; a status change starts again at page 1. Expo UI not run. |
| ENH-32 | Home open loans | DONE | Evidence 2026-09-23: mobile typecheck + 14 tests. Home loads checked-out and reserved bookings (up to 100 each) instead of the newest 20 of every status. Returned and cancelled stay off home. Expo UI not run. |
Loading