diff --git a/apps/mobile/src/home.test.ts b/apps/mobile/src/home.test.ts index 3ab4737..6c805aa 100644 --- a/apps/mobile/src/home.test.ts +++ b/apps/mobile/src/home.test.ts @@ -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 & Pick): Booking { return { @@ -14,6 +14,21 @@ function booking(partial: Partial & Pick): 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"] }), diff --git a/apps/mobile/src/home.ts b/apps/mobile/src/home.ts index 10e102e..1aad37c 100644 --- a/apps/mobile/src/home.ts +++ b/apps/mobile/src/home.ts @@ -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 { + 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)); diff --git a/apps/mobile/src/screens/HomeScreen.tsx b/apps/mobile/src/screens/HomeScreen.tsx index cfb8d5a..a0ee6bf 100644 --- a/apps/mobile/src/screens/HomeScreen.tsx +++ b/apps/mobile/src/screens/HomeScreen.tsx @@ -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; @@ -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([]); diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 07f5982..ef47892 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -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 diff --git a/docs/TASKS.md b/docs/TASKS.md index 5489a13..5b2f2f8 100644 --- a/docs/TASKS.md +++ b/docs/TASKS.md @@ -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. |