Skip to content

Improve admin matching and pricing polish#154

Merged
Taleef7 merged 2 commits intomainfrom
codex/open-issues-batch-3
Apr 27, 2026
Merged

Improve admin matching and pricing polish#154
Taleef7 merged 2 commits intomainfrom
codex/open-issues-batch-3

Conversation

@Taleef7
Copy link
Copy Markdown
Owner

@Taleef7 Taleef7 commented Apr 27, 2026

Summary

  • Adds admin request text search across student/requester names and subjects while preserving the existing status/subject/level filters.
  • Adds tutor availability overlap hints on the request assignment screen, plus profile links and breadcrumbs on admin detail pages.
  • Standardizes PKR display through a shared formatter and updates MVP/backlog docs to reflect the completed issue batch.

Verification

  • npm test
  • npm run typecheck
  • npm run lint
  • npm run build with local placeholder Supabase/WhatsApp/bank env vars
  • npx playwright test e2e/landing.spec.ts --project=chromium with the same local placeholder env vars
  • git diff --check
  • rg -n p_package_id . --glob '!node_modules/**' returned no matches

Notes

  • The local Supabase wrapper build path could not run because Docker Desktop's Linux engine pipe was unavailable on this machine, so I used placeholder local env vars for the production build instead. No database migrations or schema changes are included in this PR.

Closes #116
Closes #123
Closes #124
Closes #135
Closes #147
Closes #148

Copilot AI review requested due to automatic review settings April 27, 2026 03:48
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Improves admin request matching workflows and UI polish by adding request text search, availability overlap hints, breadcrumbs/profile links, and consistent PKR currency formatting.

Changes:

  • Added shared formatPkr() currency formatter and replaced ad-hoc PKR rendering across landing/dashboard/admin pages.
  • Added admin request text search (student/requester/subject) and extracted request-search helpers with tests.
  • Added availability overlap helpers + UI hint badges, plus admin breadcrumbs and match-detail profile links; updated planning docs accordingly.

Reviewed changes

Copilot reviewed 20 out of 20 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/utils/currency.ts Introduces shared PKR formatter used across UI.
lib/utils/availability.ts Adds overlap computation utilities for structured availability windows.
lib/utils/tests/currency.test.ts Unit tests for PKR formatter output.
lib/utils/tests/availability.test.ts Unit tests for overlap and summary helpers.
lib/admin/request-search.ts Adds normalization + tokenized in-memory filtering for admin requests.
lib/admin/tests/request-search.test.ts Unit tests for request search normalization/filtering behavior.
components/admin/AdminBreadcrumbs.tsx Adds reusable breadcrumbs component for admin detail pages.
components/admin/tests/AdminBreadcrumbs.test.ts Verifies breadcrumbs render deterministic return links.
app/page.tsx Switches landing package price display to formatPkr().
app/dashboard/packages/new/page.tsx Uses formatPkr() for package selection pricing display.
app/dashboard/packages/[id]/page.tsx Uses formatPkr() for package payment amount display.
app/admin/payments/page.tsx Uses formatPkr() for admin payment amount display.
app/admin/requests/page.tsx Adds q search param support + composes search with existing filters/pagination.
app/admin/requests/RequestFilters.tsx Adds search form UI and preserves filter params when submitting/clearing.
app/admin/requests/[id]/page.tsx Adds breadcrumbs and passes request availability windows into matching UI.
app/admin/requests/[id]/AssignTutorForm.tsx Adds overlap hint badges per tutor and wires overlap helper into tutor cards.
app/admin/tutors/[id]/page.tsx Adds admin breadcrumbs on tutor detail page.
app/admin/matches/[id]/page.tsx Adds breadcrumbs and links to admin user search / tutor detail from match detail.
docs/plan-CorvEd.md Marks related MVP/backlog items as completed.
docs/GAP_ANALYSIS.md Updates resolved doc gap to reflect p_request_id guidance cleanup.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/admin/requests/page.tsx Outdated
Comment on lines +90 to +94
if (activeSearch) {
const [{ data: requestsData }, { data: subjectsData }] = await Promise.all([
dataQuery,
subjectsQuery,
])
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

In the search branch (if (activeSearch)), dataQuery is executed without .range(...) / any limit, which means this path can fetch the full filtered requests dataset into memory before doing JS filtering + pagination. This is likely to become slow/expensive as the requests table grows (and can also be impacted by any PostgREST max-rows cap, making totalCount incomplete). Consider moving the text search into the DB (e.g., PostgREST or/ilike across for_student_name, user_profiles.display_name, and subjects.name, or a view/RPC for full-text search) so pagination + counts stay server-side.

Copilot uses AI. Check for mistakes.
Comment thread app/admin/requests/page.tsx Outdated
const { status, subject, level, page } = await searchParams
const { status, subject, level, q, page } = await searchParams
const activeStatus: FilterStatus = ALL_STATUSES.includes(status ?? '') ? status! : 'all'
const activeSearch = normalizeAdminRequestSearch(q)
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

activeSearch is normalized (trim/collapse whitespace + lowercased) and then reused for building URLs and as the search input's defaultValue. This means a user searching for e.g. "Amina Khan" will see the UI and subsequent links rewrite it to "amina khan", which is surprising and doesn't strictly preserve the query param as-entered. Consider keeping the raw q for display/URL generation and only normalizing internally for token matching.

Suggested change
const activeSearch = normalizeAdminRequestSearch(q)
const activeSearch = typeof q === 'string' ? q : ''
const normalizedActiveSearch = normalizeAdminRequestSearch(activeSearch)

Copilot uses AI. Check for mistakes.
Comment thread app/admin/matches/[id]/page.tsx Outdated
href={`/admin/users?search=${encodeURIComponent(studentProfile.display_name)}`}
className="text-[#1040C0] underline-offset-4 hover:underline"
>
{studentName}
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

The student profile link is gated on studentProfile.display_name and the search param uses studentProfile.display_name, but the link text uses studentName (which is the child name for parent-created requests). For parent requests this can look like the child's name links to the child's profile, but it actually navigates to the parent user search. Consider adjusting the link label to match the requester profile name (or rendering separate "Student" vs "Requester" rows) so the navigation target is unambiguous.

Suggested change
{studentName}
{studentProfile.display_name}

Copilot uses AI. Check for mistakes.
Comment thread lib/utils/currency.ts Outdated
Comment on lines +1 to +6
export function formatPkr(amount: number | null | undefined): string {
const value = Number.isFinite(amount) ? Math.max(0, Math.round(amount ?? 0)) : 0
const formatted = new Intl.NumberFormat('en-PK', {
maximumFractionDigits: 0,
minimumFractionDigits: 0,
}).format(value)
Copy link

Copilot AI Apr 27, 2026

Choose a reason for hiding this comment

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

formatPkr constructs a new Intl.NumberFormat instance on every call. This can add avoidable overhead when rendering lists (landing packages, admin payments, etc.). Consider hoisting a single formatter instance to module scope (or caching lazily) and reusing it inside formatPkr.

Suggested change
export function formatPkr(amount: number | null | undefined): string {
const value = Number.isFinite(amount) ? Math.max(0, Math.round(amount ?? 0)) : 0
const formatted = new Intl.NumberFormat('en-PK', {
maximumFractionDigits: 0,
minimumFractionDigits: 0,
}).format(value)
const pkrNumberFormatter = new Intl.NumberFormat('en-PK', {
maximumFractionDigits: 0,
minimumFractionDigits: 0,
})
export function formatPkr(amount: number | null | undefined): string {
const value = Number.isFinite(amount) ? Math.max(0, Math.round(amount ?? 0)) : 0
const formatted = pkrNumberFormatter.format(value)

Copilot uses AI. Check for mistakes.
@Taleef7
Copy link
Copy Markdown
Owner Author

Taleef7 commented Apr 27, 2026

@copilot apply changes based on the comments in this thread

…DB-level text search, link label fix

Agent-Logs-Url: https://github.com/Taleef7/CorvEd/sessions/1814cfea-3d7d-4359-83cc-92d639d10484

Co-authored-by: Taleef7 <89072337+Taleef7@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 27, 2026

@copilot apply changes based on the comments in this thread

All four review comments addressed in commit 3844202:

  1. lib/utils/currency.tsIntl.NumberFormat instance hoisted to module scope so it's created once and reused on every formatPkr() call.

  2. app/admin/requests/page.tsx line 51 — Raw q is now kept as activeSearch (trim only) for URLs and the search input's defaultValue. A separate normalizedSearch variable is used only for internal token matching.

  3. app/admin/requests/page.tsx lines 90–94 — Text search is now fully DB-level. For each search token we look up matching user_ids (by display_name ilike) and subject_ids (by name ilike) in parallel, then build a PostgREST or() filter across for_student_name, created_by_user_id, and subject_id. All token lookups across multiple terms are batched into a single Promise.all. User-supplied %/_ are escaped so they're treated as literals, not SQL wildcards. Pagination and counts are now always server-side.

  4. app/admin/matches/[id]/page.tsx line 228 — The profile link text now shows studentProfile.display_name (the requester account being searched) instead of studentName. For parent requests the child's name still appears as a (Student: …) annotation alongside the link.

@Taleef7 Taleef7 merged commit a0cc8c2 into main Apr 27, 2026
1 check passed
@Taleef7 Taleef7 deleted the codex/open-issues-batch-3 branch April 27, 2026 04:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment