Skip to content

Ezra/321 request fulfillment and history dashboard - #342

Merged
ND68 merged 2 commits into
mainfrom
ezra/321-request-fulfillment-and-history-dashboard
Apr 14, 2026
Merged

ND68 merged 2 commits into
mainfrom
ezra/321-request-fulfillment-and-history-dashboard

Conversation

@ezraexe

@ezraexe ezraexe commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Description

Resolves ticket number: #321

Explain what your code changes:
Added an admin side Requests dashboard with Current and Past tabs
Backend:
services/adminRequestsService.ts - classifies request at Current or Past, groups by date + partner
app/api/adminRequests/route.ts - receives GET requests from the frontend, checks staff permissions, and asks the service for current or past data
types/api/adminRequests.types.ts - defines the shape of the data the API sends back to the frontend tables

Frontend:
app/adminRequests/page.tsx - auth gatekeeper page
screens/AdminRequestsScreen.tsx - main page with Current / Past tab switcher
components/CurrentRequestsTable.tsx - page for the tab with Current tab switchers
component/PastRequestsTable.tsx - page for tab with past tab switchers
components/NavBarLayout.tsx - new sidebar link added

List the steps you took to test your code:

Checklist

  • The ticket is mentioned above
  • The changes fulfill the success criteria of the ticket
  • The changes were self-reviewed
  • A review was requested

@ezraexe
ezraexe requested a review from ND68 April 14, 2026 02:45
@ezraexe ezraexe linked an issue Apr 14, 2026 that may be closed by this pull request
@netlify

netlify Bot commented Apr 14, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for hope-for-haiti ready!

Name Link
🔨 Latest commit 659c75f
🔍 Latest deploy log https://app.netlify.com/projects/hope-for-haiti/deploys/69ddaa4e4095c10008ee693c
😎 Deploy Preview https://deploy-preview-342--hope-for-haiti.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@greptile-apps

greptile-apps Bot commented Apr 14, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds an admin-side Requests dashboard with "Current" and "Past" tabs, backed by a new service that classifies GeneralItemRequest records in memory and a GET /api/adminRequests route that gates on the requestRead permission.

  • Unbounded DB fetch / in-memory pagination (adminRequestsService.ts lines 117–120): both getCurrentRequests and getPastRequests issue a findMany with no take/skip, loading the entire request history before slicing. getPastRequests is the worst case — no DB-level date filter is applied at all. This will cause timeouts as data grows.
  • Wrong dateFulfilledOrDropped date (adminRequestsService.ts lines 224–231): when finalQuantity === 0 and donorOffer.state === FINALIZED, archivedAt is null, so the fallback is request.createdAt — the "Past" table displays the request creation date as the fulfillment/drop date, which is incorrect.

Confidence Score: 4/5

  • Safe to merge after addressing the two P1 issues in the service layer: unbounded DB query and incorrect fallback date for FINALIZED offers.
  • Two P1 findings in adminRequestsService.ts — one that will cause reliability problems at scale (full-table fetch on every paginated request) and one that shows semantically wrong dates in the Past tab. Remaining findings are P2 (error handling in table components, unused ref, new-request misclassification edge case).
  • src/services/adminRequestsService.ts requires the most attention — both P1 issues live there.

Important Files Changed

Filename Overview
src/services/adminRequestsService.ts Core service with two P1 issues: all-records unbounded DB fetch with in-memory pagination (scalability/reliability), and incorrect dateFulfilledOrDropped date when a FINALIZED offer has finalQuantity === 0 (wrong data shown to users). Also has a potential misclassification of new unprocessed requests as "past".
src/app/api/adminRequests/route.ts API route is clean: validates session, checks partner exclusion, verifies permission, parses and validates query params with Zod, and delegates to the service. Error handling via errorResponse is consistent with the project pattern.
src/app/adminRequests/page.tsx Auth gate follows the existing project convention for client-side session checking. Redundant permission guard duplicates logic already enforced in the API route and AdminRequestsScreen, but is consistent with how other pages in the codebase are structured.
src/screens/AdminRequestsScreen.tsx Tab-switcher screen is straightforward. The availableTabs memo is overly complex for a permission that is always binary (both tabs are shown or neither is), but is otherwise correct. Uses space-x-4 (margins) in the tab row where gaps would be preferred per project checklist.
src/components/CurrentRequestsTable.tsx Fetches and renders current request groups correctly. Missing error handling in fetchTableData and tableRef is created but never used by the component itself.
src/components/PastRequestsTable.tsx Same structure as CurrentRequestsTable with an added "Quantity Fulfilled" column. Same missing error handling and unused tableRef concerns apply.
src/components/NavBarLayout.tsx Adds a "Requests" nav link gated behind !isPartnerUser && hasPermission(user, "requestRead"), consistent with other permission-gated links in the file.
src/types/api/adminRequests.types.ts Type definitions are well-structured, matching the service output shape. Missing newline at end of file.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant AdminRequestsPage as page.tsx (auth gate)
    participant AdminRequestsScreen as AdminRequestsScreen
    participant CurrentRequestsTable as CurrentRequestsTable / PastRequestsTable
    participant API as GET /api/adminRequests
    participant AdminRequestsService as adminRequestsService
    participant DB as Prisma / DB

    Browser->>AdminRequestsPage: Navigate to /adminRequests
    AdminRequestsPage->>AdminRequestsPage: "useSession() → check role & permission"
    AdminRequestsPage->>AdminRequestsScreen: render (if permitted)
    AdminRequestsScreen->>AdminRequestsScreen: Tab switch (Current / Past)
    AdminRequestsScreen->>CurrentRequestsTable: render active tab component
    CurrentRequestsTable->>API: "GET ?status=current&page=N&pageSize=M&filters=…"
    API->>API: auth() + checkPermission()
    API->>AdminRequestsService: getCurrentRequests(filters, page, pageSize)
    AdminRequestsService->>DB: findMany (ALL rows, no LIMIT)
    DB-->>AdminRequestsService: all GeneralItemRequests + relations
    AdminRequestsService->>AdminRequestsService: classifyRequest() per row (in memory)
    AdminRequestsService->>AdminRequestsService: "group by date|partnerId (in memory)"
    AdminRequestsService->>AdminRequestsService: paginate(sortedGroups) (in memory)
    AdminRequestsService-->>API: "{ data: CurrentRequestGroup[], total }"
    API-->>CurrentRequestsTable: JSON response
    CurrentRequestsTable-->>Browser: Render table rows
Loading

Reviews (1): Last reviewed commit: "added admin requests dashboard and api r..." | Re-trigger Greptile

Comment on lines +117 to +120
const requests = await db.generalItemRequest.findMany({
where,
include: requestInclude,
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 All records fetched for in-memory pagination

Both getCurrentRequests and getPastRequests issue an unbounded findMany — no take/skip — load every GeneralItemRequest row into memory, classify them, group them, and then slice. getPastRequests is the worst case because it also lacks a DB-level date filter; the entire request history is loaded just to compute which page of groups to return. As the dataset grows this will cause timeouts and OOM pressure.

The root issue is that classification requires joined data (line items, allocations, sign-offs) that can't be pushed into a single Prisma where. A pragmatic middle ground: apply a coarse DB-level filter (e.g. donorOffer.state for the past-requests path) to reduce the result set before the in-memory classification step.

Comment thread src/services/adminRequestsService.ts
Comment thread src/services/adminRequestsService.ts
Comment thread src/components/CurrentRequestsTable.tsx
} from "@/types/api/adminRequests.types";

export default function CurrentRequestsTable() {
const { apiClient } = useApiClient();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 tableRef is created but never used within the component

tableRef is passed to AdvancedBaseTable via ref={tableRef}, but CurrentRequestsTable is not a forwardRef component and never calls tableRef.current itself. The ref handle is therefore inaccessible to any parent. The same applies to PastRequestsTable. Either expose the ref with forwardRef/useImperativeHandle if a parent needs programmatic control, or remove the useRef and ref prop entirely to avoid dead code.

@ND68

ND68 commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator

Ignore greptile, implementation is sufficient. UI and table layout is clean, nice work.

@ND68
ND68 merged commit 6a0bfd9 into main Apr 14, 2026
5 checks passed
@ND68
ND68 deleted the ezra/321-request-fulfillment-and-history-dashboard branch April 14, 2026 16:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Request Fulfillment and History Dashboard

2 participants