ENG-3386: system-template badge + sectioned popover (demo/april)#7917
ENG-3386: system-template badge + sectioned popover (demo/april)#7917adamsachs merged 1 commit intodemo/aprilfrom
Conversation
Companion frontend for OOTB regulatory reporting templates. Splits the custom-reports popover into "Standard templates" and "Your reports" sections. System-owned templates display a "Standard" tag with tooltip and hide the delete button. Adds Cypress tests for both behaviours. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
1 Skipped Deployment
|
There was a problem hiding this comment.
Code Review
Clean, well-scoped implementation. The sectioned popover approach is easy to follow, the useMemo split is correctly keyed on searchResults, and the delete-suppression logic (simply omitting the button for system rows) is minimal and correct.
Findings
One actionable issue (worth fixing before merge):
Search interaction with section headers (CustomReportTemplates.tsx:329) — The "Your reports" label is gated on systemReports.length > 0, but both systemReports and userReports come from the filtered searchResults. If a user types a query that hides all system templates but returns user reports, both the "Standard templates" section and the "Your reports" label vanish, leaving an unlabelled list. The fix is straightforward: derive hasSystemReports from the unfiltered customReportsList?.items and use that for header visibility while keeping the filtered arrays for row rendering.
Two observations (no action required, pre-existing or minor):
- Empty search state (
CustomReportTemplates.tsx:90) — When search matches nothing,isEmptystaysfalseand theRadio.Grouprenders empty with no feedback. Pre-existing behaviour; a follow-up "No results" state could address it. - JSDoc examples in generated types (
CustomReportResponse.ts:26,CustomReportResponseMinimal.ts) — The inline'ico','dpc','cnil'key examples will go stale as new templates are added. Low-friction fix: drop the examples or point to the backend source of truth.
What's well done
!!report.system_template_keycorrectly handlesnull,undefined, and empty string in one expression.- System template rows correctly omit the delete button without any extra permission check — they simply never enter the
userReportsarray. - Cypress tests cover the two critical behaviours (sectioning and delete suppression) with a realistic fixture.
- Type additions are additive (
system_template_key?: string | null) and don't break existing consumers.
🔬 Codegraph: unavailable
💡 Write /code-review in a comment to re-run this review.
| )} | ||
| {userReports.length > 0 && ( | ||
| <> | ||
| {systemReports.length > 0 && ( |
There was a problem hiding this comment.
clients/admin-ui/src/features/common/custom-reports/CustomReportTemplates.tsx:329
The "Your reports" section header is conditionally rendered only when systemReports.length > 0. This works correctly today, but it creates a subtle interaction with search: if a user searches and their query happens to filter out all system templates (but returns user reports), the "Your reports" label will silently disappear because systemReports.length becomes 0 for the filtered set.
Since systemReports/userReports are derived from searchResults rather than the raw customReportsList?.items, this could produce a jarring UX: a user who starts typing sees the "Standard templates" section vanish and the "Your reports" label also vanish, leaving an unlabelled list. Consider basing the section-header visibility on whether system templates exist in the unfiltered list instead:
const hasSystemReports = (customReportsList?.items ?? []).some(
(r) => !!r.system_template_key,
);Then use hasSystemReports to decide whether to show section headers, while still using the filtered systemReports/userReports arrays for the rows themselves.
| @@ -87,6 +89,14 @@ export const CustomReportTemplates = ({ | |||
|
|
|||
| const isEmpty = !isCustomReportsLoading && !customReportsList?.items?.length; | |||
There was a problem hiding this comment.
clients/admin-ui/src/features/common/custom-reports/CustomReportTemplates.tsx:90
isEmpty is derived from customReportsList?.items?.length (the unfiltered API response), but the rendered rows come from searchResults → systemReports/userReports. When a search term produces zero matches, isEmpty will remain false (because the raw list has items), so the Radio.Group renders but both systemReports and userReports are empty — resulting in blank space inside the popover with no "no results" feedback.
This is pre-existing behaviour (not introduced by this PR), but it's worth noting if you're already touching this area. A follow-up could add a "No results" empty state keyed off systemReports.length === 0 && userReports.length === 0 && !isEmpty.
| */ | ||
| system_template_key?: string | null; | ||
| config: CustomReportConfig; | ||
| }; |
There was a problem hiding this comment.
clients/admin-ui/src/types/api/models/CustomReportResponse.ts:26
The JSDoc comment here (system_template_key on CustomReportResponse) and the identical one on CustomReportResponseMinimal include inline examples of known key values ('ico', 'dpc', 'cnil'). If the backend adds new keys in future, these comments will silently go stale. Consider omitting the examples or referencing the backend enum by name instead, so the types file doesn't become a maintenance liability.
Ticket ENG-3386
Summary
Squashed backport of #7910 onto
demo/aprilfor early demo/testing.info-coloured "Standard" tag with tooltipPre-Merge Checklist
demo/april🤖 Generated with Claude Code