feat: grouped projects page (api + tower) - #94
Merged
Merged
Conversation
Adds an `ungrouped=true` query parameter to GET /api/projects that returns only projects whose GroupId is null. Mutually exclusive with `groupId` — combining both yields a 400 ProblemDetails. Powers the ungrouped bucket on the redesigned Projects page.
Adds GET /api/projects/grouped that returns the first page of projects for every group plus a separate ungrouped bucket, all sorted by name ascending. Sections empty after applying the optional `search` filter are omitted. Per-group page size defaults to 10, capped at 100. Built to power the redesigned Projects page in a single round trip; each section returns a cursor that can be passed back to GET /api/projects with groupId or ungrouped=true to fetch subsequent pages.
Replaces the flat Projects list with a grouped view powered by GET /api/projects/grouped: one section per owning group plus a separate "Other projects" bucket for ungrouped projects, all sorted by name. Sections empty after applying the search filter are omitted. The visible filter row is removed; search now lives behind a Filter button + popover that submits on Enter or Done. Active filters surface as dismissible chips just below the page header. Per-group "Show more" buttons lazily fetch the next 4 projects via the existing list endpoint using the cursor returned by each section. The old per-row group filter and sort dropdowns are gone. A new nested route /projects/group/$groupId hosts the focused per-group flat list behind each "View all" link. Adds tests for ProjectsFilterPopover, ActiveFilterChips, and the new relative-time formatter.
Emit a structured warning when the groups list for the composite endpoint returns a non-null next cursor so operations can detect when tenants exceed the 10-group soft cap before users notice missing sections.
- Move the show-more pagination merge from the render body to a useEffect in ProjectGroupSection and OtherProjectsSection, removing conditional setState during render. - Use a single-group GET via a new useGroup hook on the per-group page instead of fetching the full groups list and filtering client side. - Add staleTime to useGroupedProjects so navigating back from a per-group page does not immediately refetch the index. - Replace the redundant string|'ungrouped' alias with an UngroupedScope sentinel constant. - Restore alphabetical import order in useProjects.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
GET /api/projects/groupedendpoint that returns the first page of projects per group plus a separate ungrouped bucket, sorted by name; powers the redesigned Projects page in a single round tripGET /api/projectsgains anungrouped=truefilter (mutually exclusive withgroupId) so the per-section "Show more" can paginate the ungrouped bucket/projects/group/$groupIdfor the per-group focused flat list (target of the "View all →" links)Notable behaviour
ListGroupedProjectsHandlerfans out toIGroupStoreand per-groupIProjectStore.ListAsynccalls in parallel viaTask.WhenAll; sections empty after applying the search filter are omitted server-sideBuild / dep changes
Aspire.Hosting.MongoDB/Aspire.Hosting.Testing13.2.4 → 13.3.0,Azure.Extensions.AspNetCore.DataProtection.Blobs1.5.2 → 1.5.3,Azure.Extensions.AspNetCore.DataProtection.Keys1.6.2 → 1.6.3,Verify.XunitV331.16.2 → 31.16.3SharpCompressfor GHSA-6c8g-7p36-r338 inGroundControl.Persistence.MongoDbandGroundControl.AppHost