Skip to content

feat: grouped projects page (api + tower) - #94

Merged
mrtaikandi merged 6 commits into
mainfrom
projects-group
May 9, 2026
Merged

feat: grouped projects page (api + tower)#94
mrtaikandi merged 6 commits into
mainfrom
projects-group

Conversation

@mrtaikandi

@mrtaikandi mrtaikandi commented May 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • API: new GET /api/projects/grouped endpoint 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 trip
  • API: GET /api/projects gains an ungrouped=true filter (mutually exclusive with groupId) so the per-section "Show more" can paginate the ungrouped bucket
  • Tower: full Projects page redesign — grouped sections, "Other projects" bucket for ungrouped, filter popover with submit-on-Enter, dismissible filter chips, per-section "Show more"
  • Tower: new nested route /projects/group/$groupId for the per-group focused flat list (target of the "View all →" links)

Notable behaviour

  • ListGroupedProjectsHandler fans out to IGroupStore and per-group IProjectStore.ListAsync calls in parallel via Task.WhenAll; sections empty after applying the search filter are omitted server-side
  • Per-group page size defaults to 10, capped at 100; first 4 are returned by default in the UI per the design spec
  • Composite endpoint loads up to 10 groups (soft assumption for the current scale; logs a warning if more groups exist so we can spot when the cap is hit before users notice missing sections)
  • Old per-row group filter and sort dropdowns are removed; sort is fixed to name asc throughout

Build / dep changes

  • Bumps Aspire.Hosting.MongoDB / Aspire.Hosting.Testing 13.2.4 → 13.3.0, Azure.Extensions.AspNetCore.DataProtection.Blobs 1.5.2 → 1.5.3, Azure.Extensions.AspNetCore.DataProtection.Keys 1.6.2 → 1.6.3, Verify.XunitV3 31.16.2 → 31.16.3
  • Pins transitive SharpCompress for GHSA-6c8g-7p36-r338 in GroundControl.Persistence.MongoDb and GroundControl.AppHost

mrtaikandi added 6 commits May 9, 2026 09:32
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.
@mrtaikandi
mrtaikandi merged commit bd9db8f into main May 9, 2026
2 checks passed
@mrtaikandi
mrtaikandi deleted the projects-group branch May 9, 2026 10:42
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.

1 participant