-
Notifications
You must be signed in to change notification settings - Fork 60
[#332] Blade/member pagination & search #358
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Ran ui-add for shadcn pagination component Fixed any issues from the generated pagination.tsx file Exported ButtonProps from button.tsx Updated members-table to include the optional inputs
Implemented Generic Pagination to members-table component
Removed logic rendering total amount of member
Updated getMembers and getMemberCount endpoints Moved filtering logic from frontend to backend Commented out a lot of logic for the moment
Added compatability to search via full name
(This endpoint is getting large :p)
Ran format, lint and typecheck
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds server-driven member listing with pagination, sorting, filtering, and debounced search; new client pagination and page-size selector components; a UI pagination primitives module; a generic Changes
Sequence DiagramsequenceDiagram
participant User as User
participant UI as "Members UI (Client)"
participant Router as "Next Router"
participant API as "Member API (Server)"
participant DB as "Database"
User->>UI: change page / pageSize / search / sort / filters
UI->>Router: update query params (page,pageSize,searchTerm,sort...,school,major)
Router->>API: query getMembers(currentPage,pageSize,searchTerm,sortField,sortOrder,sortByTime,schoolFilter,majorFilter)
API->>DB: SELECT ... WHERE (ILIKE / filters) ORDER BY ... LIMIT ... OFFSET ...
DB-->>API: rows
API->>DB: COUNT ... WHERE (filters)
DB-->>API: totalCount
API-->>UI: return members + totalCount
UI->>User: render rows, pagination, page-size selector
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 5 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🤖 Fix all issues with AI agents
In `@apps/blade/src/app/admin/_components/CustomPagination.tsx`:
- Around line 81-86: The PaginationPrevious/PaginationNext controls still fire
changePage when at bounds because aria-disabled only informs AT; update the
CustomPagination component to also prevent activation: set the control's
disabled prop (or remove/replace the onClick) when currentPage <= 1 for
PaginationPrevious and when currentPage >= totalPages for PaginationNext, and
keep aria-disabled in sync; additionally guard the changePage invocation inside
the onClick handlers (no-op if out of range) so clicking or programmatic
activation cannot change pages when disabled.
In `@apps/blade/src/app/admin/_components/CustomPaginationSelect.tsx`:
- Around line 1-8: Add the Next.js client directive to this component so event
handlers like onValueChange work on the client: insert the string "use client"
as the very first line of the CustomPaginationSelect component file (before any
imports) so the React component and handlers (e.g., Select, SelectTrigger,
onValueChange usage) run on the client rather than the server.
In `@apps/blade/src/app/admin/club/members/_components/members-table.tsx`:
- Line 49: The currentPage computed in members-table.tsx (const currentPage =
Number(searchParams.get("page") || 1)) isn't validated and can become NaN or
negative; update the logic that derives currentPage (used by the MembersTable
pagination) to parse the value (e.g., parseInt), treat non-numeric results as 1,
clamp the result to a minimum of 1, and if you have a known totalPages or
pageCount available in this component further clamp to that upper bound before
using it for rendering or navigation.
In `@packages/api/src/routers/member.ts`:
- Around line 374-393: The time-based sorting branch currently calls
query.orderBy(...) without reassigning the result and mixes
Member.dateCreated/timeCreated inconsistently; change the sort-by-time branch to
assign the result back (query = query.orderBy(...)) and use the correct columns
consistently (both clauses should reference Member.timeCreated, using asc when
input.sortOrder === "asc" and desc when "desc") so that sorting by time is
actually applied; keep the other branches (sortField and default) unchanged.
- Around line 400-407: The getMemberCount endpoint is currently exposed as
publicProcedure which allows unauthenticated enumeration via the searchTerm
filter; change its declaration from publicProcedure to permProcedure and apply
the same permission check used by getMembers (controlPerms.or(["READ_MEMBERS",
"READ_CLUB_DATA"], ctx)) so only authorized users can call getMemberCount, and
keep the existing searchTerm input handling; alternatively, if public access is
required, remove the searchTerm filtering entirely from getMemberCount so it
only returns an unfiltered total count.
🧹 Nitpick comments (6)
apps/blade/src/app/admin/_hooks/debounce.ts (1)
3-19: Clean debounce implementation!The hook correctly handles cleanup to prevent stale updates. One tiny typo on line 7: "timout" → "timeout".
- // Set a timout to update the debounced value after the short delay + // Set a timeout to update the debounced value after the short delaypackages/ui/src/pagination.tsx (1)
44-62: Consider keyboard accessibility forPaginationLink.
PaginationLinkrenders an<a>tag, but it's used withonClickhandlers (withouthref) inCustomPagination. Anchor tags withouthrefaren't keyboard-focusable by default.Two options:
- Add
role="button"andtabIndex={0}when nohrefis provided- Use a
<button>element whenonClickis the primary interaction♻️ Suggested enhancement
const PaginationLink = ({ className, isActive, size = "icon", + href, ...props }: PaginationLinkProps) => ( <a aria-current={isActive ? "page" : undefined} + role={href ? undefined : "button"} + tabIndex={href ? undefined : 0} className={cn( buttonVariants({ variant: isActive ? "outline" : "ghost", size, }), className, )} + href={href} {...props} /> );packages/api/src/routers/member.ts (1)
359-372: Consider extracting the shared filter logic.The ILIKE filter conditions are duplicated between
getMembers(lines 359-372) andgetMemberCount(lines 414-424). Extracting this to a helper would improve maintainability.♻️ Example refactor
// At the top of the file or in a shared utils const buildMemberSearchFilter = (searchPattern: string) => or( ilike(Member.firstName, searchPattern), ilike(Member.lastName, searchPattern), ilike(Member.email, searchPattern), ilike(Member.discordUser, searchPattern), ilike(Member.company, searchPattern), sql`CONCAT(${Member.firstName}, ' ', ${Member.lastName}) ILIKE ${searchPattern}`, );Also applies to: 409-424
apps/blade/src/app/admin/_components/CustomPagination.tsx (1)
16-21: Minor: Interface name doesn't match component purpose.
MemberPaginationPropssuggests member-specific usage, butCustomPaginationis generic. Consider renaming toCustomPaginationPropsfor consistency.-interface MemberPaginationProps { +interface CustomPaginationProps { itemCount: number; pageSize: number; currentPage: number; className?: string; }apps/blade/src/app/admin/club/members/_components/members-table.tsx (1)
52-63: Consider adding loading and error states.The component renders immediately with empty data (
members ?? [],totalCount ?? 0) without indicating loading or errors. Per repo patterns, gating rendering until all required data succeeds provides a more coherent UX.♻️ Suggested pattern
const { data: members, isLoading: membersLoading, error: membersError } = api.member.getMembers.useQuery({...}); const { data: totalCount, isLoading: countLoading } = api.member.getMemberCount.useQuery({...}); const { data: duesPayingStatus, isLoading: duesLoading } = api.member.getDuesPayingMembers.useQuery(); const isLoading = membersLoading || countLoading || duesLoading; if (isLoading) return <LoadingSpinner />; if (membersError) return <ErrorMessage error={membersError} />;Based on learnings: "gating rendering should occur only when all required data fetches succeed...implement a unified loading/state or error handling."
apps/blade/src/app/admin/_components/CustomPaginationSelect.tsx (1)
28-30: "Members" label is hardcoded — limits reusability.If you plan to reuse this component elsewhere (e.g., for events or hackers), consider adding a
labelprop.interface CustomPaginationSelectProps { pageSize: number; onPageSizeChange: (pageSize: number) => void; options?: number[]; className?: string; + label?: string; } // In the component: - <SelectValue>{pageSize} Members</SelectValue> + <SelectValue>{pageSize} {label ?? "Members"}</SelectValue>
apps/blade/src/app/admin/club/members/_components/members-table.tsx
Outdated
Show resolved
Hide resolved
Resolved coderabbit commented issues
Ran format, lint, and typecheck
b20359a to
9beb119
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@apps/blade/src/app/admin/club/members/_components/members-table.tsx`:
- Around line 126-155: The major filter needs an explicit "All Majors" option
and the school button label has a typo; update the ResponsiveComboBox for majors
to include "All Majors" as the first item (similar to schools), ensure
getItemValue/getItemLabel handle that option and onItemSelect maps "All Majors"
to clearing the filter (call setMajorFilter("") and reset page param), and fix
the school buttonPlaceholder from "All School's" to "All Schools" so both combo
boxes behave consistently and are clearable; reference ResponsiveComboBox,
setMajorFilter, setSchoolFilter, buttonPlaceholder, and onItemSelect.
- Around line 55-73: The component currently renders when members are available
but other dependent queries (duesPayingStatus, schools, majors, totalCount) may
still be loading or errored; update the render gating to wait for a unified
ready state by combining the queries' loading/error flags from
api.member.getMembers.useQuery, getMemberCount.useQuery,
getDuesPayingMembers.useQuery, getDistinctSchools.useQuery, and
getDistinctMajors.useQuery (e.g., aggregate isLoading/isError or status fields)
and show a single loading spinner or an error UI if any query is
loading/errored, only rendering the table and using
members/duesPayingStatus/schools/majors when all queries have succeeded.
| const { data: members } = api.member.getMembers.useQuery({ | ||
| currentPage, | ||
| pageSize, | ||
| searchTerm: debounceSearchTerm, | ||
| sortField: sortByTime ? undefined : (sortField ?? undefined), | ||
| sortOrder: sortByTime ? timeSortOrder : (sortOrder ?? undefined), | ||
| sortByTime, | ||
| schoolFilter, | ||
| majorFilter, | ||
| }); | ||
| const { data: totalCount } = api.member.getMemberCount.useQuery({ | ||
| searchTerm: debounceSearchTerm, | ||
| schoolFilter, | ||
| majorFilter, | ||
| }); | ||
| const { data: duesPayingStatus } = api.member.getDuesPayingMembers.useQuery(); | ||
| const { data: schools } = api.member.getDistinctSchools.useQuery(); | ||
| const { data: majors } = api.member.getDistinctMajors.useQuery(); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid partial rendering while dependent queries load or fail.
Right now, the table can render with members loaded but duesPayingStatus/filters still pending or failed, which can mislabel dues status and show incomplete filter options. Gate rendering on a unified loading/error state so the UI appears only when all required data is ready.
✅ Suggested unified gating
- const { data: members } = api.member.getMembers.useQuery({
+ const membersQuery = api.member.getMembers.useQuery({
currentPage,
pageSize,
searchTerm: debounceSearchTerm,
sortField: sortByTime ? undefined : (sortField ?? undefined),
sortOrder: sortByTime ? timeSortOrder : (sortOrder ?? undefined),
sortByTime,
schoolFilter,
majorFilter,
});
- const { data: totalCount } = api.member.getMemberCount.useQuery({
+ const totalCountQuery = api.member.getMemberCount.useQuery({
searchTerm: debounceSearchTerm,
schoolFilter,
majorFilter,
});
- const { data: duesPayingStatus } = api.member.getDuesPayingMembers.useQuery();
- const { data: schools } = api.member.getDistinctSchools.useQuery();
- const { data: majors } = api.member.getDistinctMajors.useQuery();
+ const duesQuery = api.member.getDuesPayingMembers.useQuery();
+ const schoolsQuery = api.member.getDistinctSchools.useQuery();
+ const majorsQuery = api.member.getDistinctMajors.useQuery();
+
+ const isLoading =
+ membersQuery.isLoading ||
+ totalCountQuery.isLoading ||
+ duesQuery.isLoading ||
+ schoolsQuery.isLoading ||
+ majorsQuery.isLoading;
+
+ const hasError =
+ membersQuery.error ||
+ totalCountQuery.error ||
+ duesQuery.error ||
+ schoolsQuery.error ||
+ majorsQuery.error;
+
+ if (isLoading) return <div>Loading members...</div>;
+ if (hasError) return <div>Failed to load members.</div>;
+
+ const members = membersQuery.data ?? [];
+ const totalCount = totalCountQuery.data ?? 0;
+ const duesPayingStatus = duesQuery.data ?? [];
+ const schools = schoolsQuery.data ?? [];
+ const majors = majorsQuery.data ?? [];Based on learnings: In the KnightHacks/forge repository, apply the pattern in TSX React components: gating rendering should occur only when all required data fetches succeed. Do not render partial success when some queries fail; instead, implement a unified loading/state or error handling that surfaces a single, coherent state once all data is ready or failed.
Also applies to: 232-274
🤖 Prompt for AI Agents
In `@apps/blade/src/app/admin/club/members/_components/members-table.tsx` around
lines 55 - 73, The component currently renders when members are available but
other dependent queries (duesPayingStatus, schools, majors, totalCount) may
still be loading or errored; update the render gating to wait for a unified
ready state by combining the queries' loading/error flags from
api.member.getMembers.useQuery, getMemberCount.useQuery,
getDuesPayingMembers.useQuery, getDistinctSchools.useQuery, and
getDistinctMajors.useQuery (e.g., aggregate isLoading/isError or status fields)
and show a single loading spinner or an error UI if any query is
loading/errored, only rendering the table and using
members/duesPayingStatus/schools/majors when all queries have succeeded.
| <div> | ||
| <ResponsiveComboBox | ||
| items={["All Schools", ...(schools ?? [])]} | ||
| renderItem={(school) => <span>{school}</span>} | ||
| getItemValue={(school) => school} | ||
| getItemLabel={(school) => school} | ||
| onItemSelect={(school) => { | ||
| setSchoolFilter(school === "All Schools" ? "" : school); | ||
| const params = new URLSearchParams(searchParams); | ||
| params.set("page", "1"); | ||
| router.push("?" + params.toString()); | ||
| }} | ||
| buttonPlaceholder="All School's" | ||
| inputPlaceholder="Search schools..." | ||
| /> | ||
| </div> | ||
| <div> | ||
| <ResponsiveComboBox | ||
| items={majors ?? []} | ||
| renderItem={(major) => <span>{major}</span>} | ||
| getItemValue={(major) => major} | ||
| getItemLabel={(major) => major} | ||
| onItemSelect={(major) => { | ||
| setMajorFilter(major); | ||
| const params = new URLSearchParams(searchParams); | ||
| params.set("page", "1"); | ||
| router.push("?" + params.toString()); | ||
| }} | ||
| buttonPlaceholder="All Majors" | ||
| inputPlaceholder="Search majors..." |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add a clearable “All Majors” option (and fix the school label).
Once a major is selected there’s no way to clear it back to “All.” Also, the school placeholder reads “All School’s.” Consider adding an explicit “All Majors” option and fixing the label.
✨ Suggested fix
- <ResponsiveComboBox
- items={["All Schools", ...(schools ?? [])]}
+ <ResponsiveComboBox
+ items={["All Schools", ...(schools ?? [])]}
renderItem={(school) => <span>{school}</span>}
getItemValue={(school) => school}
getItemLabel={(school) => school}
onItemSelect={(school) => {
setSchoolFilter(school === "All Schools" ? "" : school);
const params = new URLSearchParams(searchParams);
params.set("page", "1");
router.push("?" + params.toString());
}}
- buttonPlaceholder="All School's"
+ buttonPlaceholder="All Schools"
inputPlaceholder="Search schools..."
/>- <ResponsiveComboBox
- items={majors ?? []}
+ <ResponsiveComboBox
+ items={["All Majors", ...(majors ?? [])]}
renderItem={(major) => <span>{major}</span>}
getItemValue={(major) => major}
getItemLabel={(major) => major}
onItemSelect={(major) => {
- setMajorFilter(major);
+ setMajorFilter(major === "All Majors" ? "" : major);
const params = new URLSearchParams(searchParams);
params.set("page", "1");
router.push("?" + params.toString());
}}
buttonPlaceholder="All Majors"
inputPlaceholder="Search majors..."
/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| <div> | |
| <ResponsiveComboBox | |
| items={["All Schools", ...(schools ?? [])]} | |
| renderItem={(school) => <span>{school}</span>} | |
| getItemValue={(school) => school} | |
| getItemLabel={(school) => school} | |
| onItemSelect={(school) => { | |
| setSchoolFilter(school === "All Schools" ? "" : school); | |
| const params = new URLSearchParams(searchParams); | |
| params.set("page", "1"); | |
| router.push("?" + params.toString()); | |
| }} | |
| buttonPlaceholder="All School's" | |
| inputPlaceholder="Search schools..." | |
| /> | |
| </div> | |
| <div> | |
| <ResponsiveComboBox | |
| items={majors ?? []} | |
| renderItem={(major) => <span>{major}</span>} | |
| getItemValue={(major) => major} | |
| getItemLabel={(major) => major} | |
| onItemSelect={(major) => { | |
| setMajorFilter(major); | |
| const params = new URLSearchParams(searchParams); | |
| params.set("page", "1"); | |
| router.push("?" + params.toString()); | |
| }} | |
| buttonPlaceholder="All Majors" | |
| inputPlaceholder="Search majors..." | |
| <div> | |
| <ResponsiveComboBox | |
| items={["All Schools", ...(schools ?? [])]} | |
| renderItem={(school) => <span>{school}</span>} | |
| getItemValue={(school) => school} | |
| getItemLabel={(school) => school} | |
| onItemSelect={(school) => { | |
| setSchoolFilter(school === "All Schools" ? "" : school); | |
| const params = new URLSearchParams(searchParams); | |
| params.set("page", "1"); | |
| router.push("?" + params.toString()); | |
| }} | |
| buttonPlaceholder="All Schools" | |
| inputPlaceholder="Search schools..." | |
| /> | |
| </div> | |
| <div> | |
| <ResponsiveComboBox | |
| items={["All Majors", ...(majors ?? [])]} | |
| renderItem={(major) => <span>{major}</span>} | |
| getItemValue={(major) => major} | |
| getItemLabel={(major) => major} | |
| onItemSelect={(major) => { | |
| setMajorFilter(major === "All Majors" ? "" : major); | |
| const params = new URLSearchParams(searchParams); | |
| params.set("page", "1"); | |
| router.push("?" + params.toString()); | |
| }} | |
| buttonPlaceholder="All Majors" | |
| inputPlaceholder="Search majors..." | |
| /> | |
| </div> |
🤖 Prompt for AI Agents
In `@apps/blade/src/app/admin/club/members/_components/members-table.tsx` around
lines 126 - 155, The major filter needs an explicit "All Majors" option and the
school button label has a typo; update the ResponsiveComboBox for majors to
include "All Majors" as the first item (similar to schools), ensure
getItemValue/getItemLabel handle that option and onItemSelect maps "All Majors"
to clearing the filter (call setMajorFilter("") and reset page param), and fix
the school buttonPlaceholder from "All School's" to "All Schools" so both combo
boxes behave consistently and are clearable; reference ResponsiveComboBox,
setMajorFilter, setSchoolFilter, buttonPlaceholder, and onItemSelect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@packages/api/src/routers/member.ts`:
- Around line 327-438: getMembers permits CHECKIN_CLUB_EVENT via
controlPerms.or([...], ctx) while getMemberCount and related endpoints require
READ_MEMBERS or READ_CLUB_DATA, causing inconsistent access; make the permission
sets consistent by changing the controlPerms.or call in getMembers to require
the same permissions as getMemberCount (READ_MEMBERS or READ_CLUB_DATA) or
alternatively update getMemberCount to include CHECKIN_CLUB_EVENT—locate the
controlPerms.or invocation inside the getMembers handler and adjust the array of
permission strings (READ_MEMBERS, READ_CLUB_DATA, etc.) to match the other
endpoints (or vice versa) so all member list/count/filter endpoints enforce the
same permission set.
In `@packages/ui/src/pagination.tsx`:
- Around line 102-113: The wrapper in PaginationEllipsis currently has
aria-hidden which hides the entire subtree (including the sr-only text); remove
aria-hidden from the <span> wrapper in PaginationEllipsis and instead set
aria-hidden="true" on the decorative MoreHorizontal icon component so the icon
is ignored by AT while the "More pages" sr-only text remains accessible to
screen readers.
- Around line 44-64: PaginationLink lacks Space key activation when rendered as
a button (href undefined). Add an onKeyDown handler on the <a> in the
PaginationLink component that, when href is falsy, detects the Space key (e.key
=== " " || e.key === "Spacebar" || e.keyCode === 32), calls e.preventDefault(),
and then invokes the existing click behavior by calling props.onClick?.(e) (or
falling back to e.currentTarget.click() if no onClick provided); keep the
handler attached only when href is undefined and ensure other props are still
spread into the element so existing handlers are preserved.
🧹 Nitpick comments (2)
packages/api/src/routers/member.ts (1)
353-423: Avoid unbounded member fetch when pagination params are missingIf
currentPageorpageSizeis omitted, the handler returns all members, undermining server-side pagination and risking large responses. Prefer safe defaults and always apply offset/limit.🔧 Suggested fix (default pagination)
- if (input?.currentPage && input.pageSize) { - // Calculate the offset and set the search term to be sql friendly - const offset = (input.currentPage - 1) * input.pageSize; - const searchPattern = `%${input.searchTerm ?? ""}%`; + const currentPage = input?.currentPage ?? 1; + const pageSize = input?.pageSize ?? 10; + const offset = (currentPage - 1) * pageSize; + const searchPattern = `%${input?.searchTerm ?? ""}%`; - return await query.offset(offset).limit(input.pageSize); - } - - return await db.query.Member.findMany(); + return await query.offset(offset).limit(pageSize);apps/blade/src/app/admin/club/members/_components/members-table.tsx (1)
119-123: Reset pagination when the search term changesIf a user is on page N and searches, the result set may shrink and show an empty page. Reset to page 1 on search input changes to avoid that UX trap.
🔧 Suggested fix (reset page on search)
- <Input + <Input placeholder="Search members..." value={searchTerm} - onChange={(e) => setSearchTerm(e.target.value)} + onChange={(e) => { + setSearchTerm(e.target.value); + if (currentPage !== 1) { + const params = new URLSearchParams(searchParams); + params.set("page", "1"); + router.push("?" + params.toString()); + } + }} className="pl-8" />
9beb119 to
1f2c6c7
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/blade/src/app/admin/club/members/_components/members-table.tsx (1)
117-124:⚠️ Potential issue | 🟡 MinorReset pagination when search changes.
If a user is on a later page, tightening the search can yield empty results. Resetting to page 1 avoids that UX.🔧 Suggested change
- <Input + <Input placeholder="Search members..." value={searchTerm} - onChange={(e) => setSearchTerm(e.target.value)} + onChange={(e) => { + setSearchTerm(e.target.value); + if (currentPage !== 1) { + const params = new URLSearchParams(searchParams); + params.set("page", "1"); + router.push("?" + params.toString()); + } + }} className="pl-8" />
🤖 Fix all issues with AI agents
In `@packages/api/src/routers/member.ts`:
- Around line 353-424: The current logic only builds filters/sorting when both
input.currentPage and input.pageSize are present, causing no filters applied
otherwise; change the flow in the handler so you always construct the base query
(let query = db.select().from(Member)), build the conditions array and apply
query = query.where(and(...conditions)) when conditions.length>0, and always
apply the sorting block (the input.sortByTime / input.sortField branches) to
that query; only apply offset and limit when both input.currentPage and
input.pageSize exist (compute offset = (currentPage-1)*pageSize and call
query.offset(...).limit(...)); return the final query (with or without
pagination) instead of short-circuiting to db.query.Member.findMany(),
referencing the existing symbols Member, query, conditions, input.currentPage,
input.pageSize, sortByTime, sortField, and offset/limit logic.
|
out of scope |
|
Note to self: Skeleton page to handle loading state, because searching up a something looks ugly :( |
Why
The site had NO pagination at all, which made it difficult to navigate all 70 something members listed. Additionally, the search bar functionality was limited. This PR aimed at fixing these issues.
What
Issue(s): #332
Test Plan
Images
Checklist
db:pushbefore mergingSummary by CodeRabbit
New Features
Chores