Skip to content

refactor(admin): flatten to single routed tab bar (#3974)#3975

Merged
PierreBrisorgueil merged 2 commits intomasterfrom
refactor/3974-admin-tabs-flatten
Apr 15, 2026
Merged

refactor(admin): flatten to single routed tab bar (#3974)#3975
PierreBrisorgueil merged 2 commits intomasterfrom
refactor/3974-admin-tabs-flatten

Conversation

@PierreBrisorgueil
Copy link
Copy Markdown
Collaborator

@PierreBrisorgueil PierreBrisorgueil commented Apr 14, 2026

Summary

Resolves #3974. Flattens the admin section to a single routed tab bar so the built-in Users / Organizations / Readiness / Activity tabs become siblings of the downstream extras from config.admin.tabs, instead of being stacked under a separate "General" row.

  • Split admin.content.vue (488 lines) into four dedicated routed views:
    admin.users.view.vue, admin.organizations.view.vue,
    admin.readiness.view.vue, admin.activity.view.vue.
  • admin.router.js now mounts each section as a child of the /admin
    parent route (users, organizations, readiness, activity). The
    empty child '' redirects to { name: 'Admin Users' } so existing
    /admin links keep working. CASL meta propagates as before.
  • admin.layout.vue renders a single <v-tabs> row combining built-in
    tabs with the validated config.admin.tabs extras. Longest-prefix
    active-tab resolution keeps detail routes (/admin/users/:id)
    highlighting the parent tab.
  • Global concerns (error banner + mailer warning) moved from the old
    admin.content.vue into the layout so they stay visible across every
    admin tab, including downstream extras (bonus UX).
  • Readiness and Activity now fetch on mounted() instead of via a
    nested tab-model watch.
  • Redispatched the old admin.content.unit.tests.js across four
    per-view spec files and updated admin.router.unit.tests.js and
    admin.layout.unit.tests.js for the new structure.

Breaking change (URL-level)

Documented in MIGRATIONS.md. The old URL /admin used to land on the
nested General tab and now 301-redirects to /admin/users. New stable
URLs: /admin/users, /admin/organizations, /admin/readiness,
/admin/activity.

No config change required for downstream projects contributing
extras via config.admin.tabs + injectAdminChildren. Projects that
override admin.content.vue must delete that override (file removed);
replace with per-view overrides or a custom tab instead.

Test plan

  • Unit tests: npm run test:unit — 1023 passed
  • Coverage: npm run test:coverage — 98.99% statements / 93.68% branches, no thresholds lowered
  • Lint: npm run lint — clean
  • Build: npm run build — success
  • Visual smoke on trawl.me/admin after downstream deploys
  • /update-stack on downstream Vue projects that use
    config.admin.tabs (trawl_vue)

Summary by CodeRabbit

  • New Features

    • Reorganized admin navigation into a flat, routed structure with direct links to Users, Organizations, Readiness, and Activity sections.
    • Admin sections are now accessible via unique URLs (e.g., /admin/users), enabling deep-linking and bookmarking.
  • Improvements

    • Global error notifications and mail configuration warnings now persist across all admin pages.

Split `admin.content.vue` into four routed views (Users, Organizations,
Readiness, Activity) and mount them as children of the `/admin` parent
route. The admin layout now renders a single flat tab bar combining the
built-ins with the `config.admin.tabs` extras, so downstream modules
contribute siblings instead of being stacked under a separate "General"
bar. Global concerns (error banner + mailer warning) moved to the layout
to stay visible across every admin tab.

- Split `admin.content.vue` → `admin.users.view.vue`,
  `admin.organizations.view.vue`, `admin.readiness.view.vue`,
  `admin.activity.view.vue`
- Update `admin.router.js`: `''` redirects to `Admin Users`; each section
  gets a dedicated child route (`users`, `organizations`, `readiness`,
  `activity`) keeping CASL meta propagation
- Update `admin.layout.vue`: merge built-in + extra tabs in a single
  `<v-tabs>` row, longest-prefix active-tab resolution keeps detail
  routes (`/admin/users/:id`) highlighting the parent tab
- Readiness and Activity fetch on `mounted()` instead of via a nested
  tab-model watcher
- Redispatch unit tests across the four new views + refresh router and
  layout specs for the new structure
- Document the change in `MIGRATIONS.md` (URL-level breaking change;
  downstream config unchanged)
Copilot AI review requested due to automatic review settings April 14, 2026 21:09
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 14, 2026

Warning

Rate limit exceeded

@PierreBrisorgueil has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 51 minutes and 50 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 51 minutes and 50 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: b6323927-653c-45fc-9866-0a7b9d213e5f

📥 Commits

Reviewing files that changed from the base of the PR and between 3c25db5 and 1884b09.

📒 Files selected for processing (7)
  • MIGRATIONS.md
  • src/modules/admin/tests/admin.organizations.view.unit.tests.js
  • src/modules/admin/tests/admin.users.view.unit.tests.js
  • src/modules/admin/views/admin.activity.view.vue
  • src/modules/admin/views/admin.organizations.view.vue
  • src/modules/admin/views/admin.readiness.view.vue
  • src/modules/admin/views/admin.users.view.vue

Walkthrough

Restructures admin navigation from a nested tab UI (General tab containing Users/Organizations/Readiness/Activity in a v-window) to a flat routed architecture with dedicated child routes under /admin. Removes admin.content.vue, introduces four new routed view components, updates the router to define built-in child routes, and enhances admin.layout.vue with error banner and mailer warning rendering.

Changes

Cohort / File(s) Summary
Documentation
MIGRATIONS.md
Added migration entry (2026-04-14) documenting the URL-level admin navigation overhaul, including removal of nested tab UI and introduction of flat routed views with data-fetch timing changes from watcher to mounted().
Router & Layout Configuration
src/modules/admin/router/admin.router.js, src/modules/admin/views/admin.layout.vue
Replaced single General child route with four built-in routed children (users, organizations, readiness, activity) and redirect for empty path. Updated layout to render built-in tabs in flat row alongside config extras, with error banner and mailer warning moved from old content view.
Built-in View Components
src/modules/admin/views/admin.users.view.vue, src/modules/admin/views/admin.organizations.view.vue, src/modules/admin/views/admin.readiness.view.vue, src/modules/admin/views/admin.activity.view.vue
Added four new dedicated routed view components extracted from deleted admin.content.vue, each managing its own data fetching on mounted() instead of via watcher, with tab-specific state, computed properties, and methods.
Deleted Component
src/modules/admin/views/admin.content.vue
Removed entire tabbed content component (~488 lines) that previously nested Users, Organizations, Readiness, and Activity tabs in a v-window structure within a single General tab.
Router Tests
src/lib/helpers/tests/router.unit.tests.js, src/modules/admin/tests/admin.router.unit.tests.js
Updated test fixtures to reflect new child-route structure with four built-in routes instead of single General, added redirect assertion, and updated integration expectations for /admin/admin/users navigation.
Layout & Content Tests
src/modules/admin/tests/admin.layout.unit.tests.js, src/modules/admin/tests/admin.content.unit.tests.js
Enhanced layout tests with Pinia initialization, dynamic route path testing, and expectations for four built-in tabs; deleted entire content unit test file.
View Component Tests
src/modules/admin/tests/admin.users.view.unit.tests.js, src/modules/admin/tests/admin.organizations.view.unit.tests.js, src/modules/admin/tests/admin.readiness.view.unit.tests.js, src/modules/admin/tests/admin.activity.view.unit.tests.js
Added three new test suites for organizations and users views; refactored activity and readiness tests to target dedicated view files with mounted() data-fetch assertions instead of watcher-based trigger patterns.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Router
    participant AdminLayout
    participant ViewComponent
    participant AdminStore

    User->>Router: Navigate to /admin/users
    Router->>AdminLayout: Render with matched child route
    AdminLayout->>AdminLayout: Resolve active tab, render flat tab bar
    AdminLayout->>ViewComponent: Mount AdminUsers component via router-view
    ViewComponent->>ViewComponent: On mounted() hook
    ViewComponent->>AdminStore: Call getUsers()
    AdminStore-->>ViewComponent: Return users data
    ViewComponent->>ViewComponent: Update computed users from store
    ViewComponent-->>User: Render users table

    User->>Router: Click Organizations tab
    Router->>AdminLayout: Navigate to /admin/organizations
    AdminLayout->>ViewComponent: Unmount AdminUsers, mount AdminOrganizations
    ViewComponent->>ViewComponent: On mounted() hook
    ViewComponent->>AdminStore: Call getOrganizations()
    AdminStore-->>ViewComponent: Return organizations data
    ViewComponent-->>User: Render organizations table
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • #3814: Adds/modifies the Admin Readiness feature with dedicated view component and store action getReadiness, closely related to the readiness view extraction in this PR.
  • #3702: Modifies admin router and view component structure, directly related to the routing architecture and view component changes in this PR.
  • #3959: Implements parent-layout with routed child views and injected admin child routes, related to the conversion of admin to a flat routed structure.

Suggested labels

Feat, Tests

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'refactor(admin): flatten to single routed tab bar (#3974)' clearly summarizes the main change: converting the nested two-tab-bar admin UI into a single flat routed tab bar.
Description check ✅ Passed The PR description comprehensively covers the template sections: Summary (what/why), Scope (modules impacted), Validation (test results), Guardrails checks, and notes including migration breaking change and test plan.
Linked Issues check ✅ Passed All code changes directly implement the #3974 requirements: splitting admin.content.vue into four routed views, flattening the tab bar, moving global alerts to admin.layout.vue, updating routing and tests, and documenting the breaking change in MIGRATIONS.md.
Out of Scope Changes check ✅ Passed All code changes are within scope: router updates, component refactoring, layout consolidation, test redistribution, and migration documentation directly support the single-tab-bar restructuring goal without unrelated modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/3974-admin-tabs-flatten

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov
Copy link
Copy Markdown

codecov bot commented Apr 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 99.48%. Comparing base (c6f59d9) to head (1884b09).
⚠️ Report is 4 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #3975   +/-   ##
=======================================
  Coverage   99.48%   99.48%           
=======================================
  Files          30       30           
  Lines         968      968           
  Branches      267      267           
=======================================
  Hits          963      963           
  Misses          5        5           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

coderabbitai[bot]
coderabbitai bot previously requested changes Apr 14, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a 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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/modules/admin/views/admin.activity.view.vue`:
- Around line 193-202: The fetchActivityLogs method sets activityLoading true
then awaits useAdminStore().getAuditLogs but does not guarantee activityLoading
is reset if getAuditLogs throws; wrap the await call in a try/finally (inside
fetchActivityLogs) so activityLoading is set to true before the try and set to
false in the finally block, leaving the rest of the parameter passing (action,
userId, page, perPage) unchanged; update the fetchActivityLogs function to use
try/finally to ensure the progress state is always cleared.

In `@src/modules/admin/views/admin.readiness.view.vue`:
- Around line 82-86: The fetchReadiness method sets readinessLoading true then
awaits useAdminStore().getReadiness(), but if getReadiness() rejects
readinessLoading stays true; wrap the await in a try/finally so readinessLoading
is always set to false in the finally block. Update the fetchReadiness function
to use try { await useAdminStore().getReadiness(); } finally {
this.readinessLoading = false; } while keeping this.readinessLoading = true at
the start and preserving any existing error propagation or handling.

In `@src/modules/admin/views/admin.users.view.vue`:
- Around line 20-22: The click handler on the v-chip uses m.organizationId?._id
|| m.organizationId?.id and can push "undefined" when m.organizationId is
null/undefined; update the handler in admin.users.view.vue to guard first: check
for a valid id (e.g., const orgId = m.organizationId?._id ||
m.organizationId?.id) and only call $router.push('/admin/organizations/' +
orgId) when orgId is truthy, otherwise either no-op or navigate to a safe
fallback (e.g., organizations list); ensure the guard references the same
m.organizationId lookup used in the template display.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f59a23e7-8ef6-4e93-9c16-3bec0fac0d6a

📥 Commits

Reviewing files that changed from the base of the PR and between c6f59d9 and 3c25db5.

📒 Files selected for processing (16)
  • MIGRATIONS.md
  • src/lib/helpers/tests/router.unit.tests.js
  • src/modules/admin/router/admin.router.js
  • src/modules/admin/tests/admin.activity.view.unit.tests.js
  • src/modules/admin/tests/admin.content.unit.tests.js
  • src/modules/admin/tests/admin.layout.unit.tests.js
  • src/modules/admin/tests/admin.organizations.view.unit.tests.js
  • src/modules/admin/tests/admin.readiness.view.unit.tests.js
  • src/modules/admin/tests/admin.router.unit.tests.js
  • src/modules/admin/tests/admin.users.view.unit.tests.js
  • src/modules/admin/views/admin.activity.view.vue
  • src/modules/admin/views/admin.content.vue
  • src/modules/admin/views/admin.layout.vue
  • src/modules/admin/views/admin.organizations.view.vue
  • src/modules/admin/views/admin.readiness.view.vue
  • src/modules/admin/views/admin.users.view.vue
💤 Files with no reviewable changes (2)
  • src/modules/admin/tests/admin.content.unit.tests.js
  • src/modules/admin/views/admin.content.vue

Comment thread src/modules/admin/views/admin.activity.view.vue
Comment thread src/modules/admin/views/admin.readiness.view.vue
Comment thread src/modules/admin/views/admin.users.view.vue Outdated
Copy link
Copy Markdown

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

This PR refactors the Admin module navigation to use a single routed tab bar where built-in admin sections (Users / Organizations / Readiness / Activity) are routed children and render as siblings alongside downstream config.admin.tabs extras.

Changes:

  • Split the previous nested admin.content.vue into four dedicated routed views and removed the old nested tab content file.
  • Updated /admin routing to redirect to /admin/users and added explicit child routes for each built-in section.
  • Updated admin.layout.vue to render one flat <v-tabs> row (built-ins + validated extras) and moved global alerts (error + mailer warning) into the layout; redistributed unit tests accordingly.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
src/modules/admin/views/admin.users.view.vue New routed Users view extracted from old nested admin content.
src/modules/admin/views/admin.organizations.view.vue New routed Organizations view extracted from old nested admin content.
src/modules/admin/views/admin.readiness.view.vue New routed Readiness view; now fetches on mounted().
src/modules/admin/views/admin.activity.view.vue New routed Activity view; now fetches on mounted() and supports filters/pagination.
src/modules/admin/views/admin.layout.vue Renders a single flat tab row (built-ins + extras) and shows global alerts across all admin tabs.
src/modules/admin/views/admin.content.vue Removed; old nested tab/window implementation deleted.
src/modules/admin/router/admin.router.js Adds built-in child routes + redirect from empty child path to Users.
src/modules/admin/tests/admin.users.view.unit.tests.js New unit tests for Users routed view.
src/modules/admin/tests/admin.organizations.view.unit.tests.js New unit tests for Organizations routed view.
src/modules/admin/tests/admin.readiness.view.unit.tests.js Updated to test the new Readiness routed view.
src/modules/admin/tests/admin.activity.view.unit.tests.js Updated to test the new Activity routed view.
src/modules/admin/tests/admin.layout.unit.tests.js Updated to validate the flattened single-row tab bar behavior and active-tab resolution.
src/modules/admin/tests/admin.router.unit.tests.js Updated route structure + redirect expectations for the new child routes.
src/modules/admin/tests/admin.content.unit.tests.js Removed; tests redistributed to per-view specs.
src/lib/helpers/tests/router.unit.tests.js Updates the admin route fixture used by injectAdminChildren tests to match the new structure.
MIGRATIONS.md Documents the URL-level breaking change and downstream actions.

Comment on lines +145 to +146
await adminStore.updateUser({ id: item.id || item._id }, { roles: newRoles });
await this.fetchUsers();
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

useAdminStore().updateUser() can throw (the store rethrows on error). As written, an exception here will bubble up from the click handler and can result in an unhandled promise rejection + skipped refresh. Wrap the update/refresh in a try/catch (or handle the rejected Promise) so failures are surfaced via the existing adminStore.error without breaking the UI flow.

Suggested change
await adminStore.updateUser({ id: item.id || item._id }, { roles: newRoles });
await this.fetchUsers();
try {
await adminStore.updateUser({ id: item.id || item._id }, { roles: newRoles });
await this.fetchUsers();
} catch (error) {
// The admin store already records the failure state; prevent an unhandled rejection in the UI.
}

Copilot uses AI. Check for mistakes.
Comment on lines +164 to +166
await useAdminStore().deleteUser({ id: this.deleteDialog.userId });
this.deleteDialog.show = false;
await this.fetchUsers();
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

useAdminStore().deleteUser() can throw (the store rethrows on error). If deletion fails, this method will throw before closing the dialog and may trigger an unhandled promise rejection. Handle errors here (try/catch) so the dialog/UI state remains consistent and the store error banner can be shown.

Suggested change
await useAdminStore().deleteUser({ id: this.deleteDialog.userId });
this.deleteDialog.show = false;
await this.fetchUsers();
try {
await useAdminStore().deleteUser({ id: this.deleteDialog.userId });
this.deleteDialog.show = false;
await this.fetchUsers();
} catch (error) {
// The store handles error state/banner display; keep the dialog open on failure.
}

Copilot uses AI. Check for mistakes.
Comment on lines +129 to +133
* @param {object} [params] - Optional query params forwarded to the store.
* @returns {Promise<void>}
*/
async fetchUsers(params) {
await useAdminStore().getUsers(params);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The params argument here is documented as an object, but coreDataTableComponent calls fetchAction with a pagination query-string (see tools.pageRequest), and adminStore.getUsers() interpolates params into the URL. Update the JSDoc (and ideally the parameter name/type) to reflect the actual string format to avoid accidental /page/[object Object] calls.

Suggested change
* @param {object} [params] - Optional query params forwarded to the store.
* @returns {Promise<void>}
*/
async fetchUsers(params) {
await useAdminStore().getUsers(params);
* @param {string} [pageRequest] - Optional pagination/query-string segment forwarded to the store URL builder.
* @returns {Promise<void>}
*/
async fetchUsers(pageRequest) {
await useAdminStore().getUsers(pageRequest);

Copilot uses AI. Check for mistakes.
Comment on lines +54 to +58
* @param {object} [params] - Optional query params forwarded to the store.
* @returns {Promise<void>}
*/
async fetchOrganizations(params) {
await useAdminStore().getOrganizations(params);
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Same as Users: coreDataTableComponent calls fetchAction with a pagination query-string, and adminStore.getOrganizations() interpolates params into the URL. The JSDoc currently says {object}; update it to the correct string type/format (and consider renaming the arg) to prevent misuse.

Suggested change
* @param {object} [params] - Optional query params forwarded to the store.
* @returns {Promise<void>}
*/
async fetchOrganizations(params) {
await useAdminStore().getOrganizations(params);
* @param {string} [queryString] - Optional pagination query-string fragment forwarded to the store URL.
* @returns {Promise<void>}
*/
async fetchOrganizations(queryString) {
await useAdminStore().getOrganizations(queryString);

Copilot uses AI. Check for mistakes.
Comment on lines +81 to +82
await wrapper.vm.fetchUsers({ page: 1 });
expect(adminStore.getUsers).toHaveBeenCalledWith({ page: 1 });
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This test exercises fetchUsers with an object { page: 1 }, but in production coreDataTableComponent passes a pagination query-string (e.g. "0&5&search"), and the store action interpolates that string into the URL. Update the test to pass a representative string so it matches the real contract and doesn’t normalize incorrect usage.

Suggested change
await wrapper.vm.fetchUsers({ page: 1 });
expect(adminStore.getUsers).toHaveBeenCalledWith({ page: 1 });
await wrapper.vm.fetchUsers('0&5&search');
expect(adminStore.getUsers).toHaveBeenCalledWith('0&5&search');

Copilot uses AI. Check for mistakes.
Comment on lines +57 to +58
await wrapper.vm.fetchOrganizations({ page: 2 });
expect(adminStore.getOrganizations).toHaveBeenCalledWith({ page: 2 });
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

Same issue as the Users view test: fetchOrganizations is called with an object in this test, but coreDataTableComponent passes a pagination query-string that is interpolated into the request URL. Update the test to use a representative string value to reflect the real integration contract.

Suggested change
await wrapper.vm.fetchOrganizations({ page: 2 });
expect(adminStore.getOrganizations).toHaveBeenCalledWith({ page: 2 });
await wrapper.vm.fetchOrganizations('?page=2');
expect(adminStore.getOrganizations).toHaveBeenCalledWith('?page=2');

Copilot uses AI. Check for mistakes.
Comment thread MIGRATIONS.md Outdated

1. Run `/update-stack` to pull the changes.
2. Verify hard-coded links in your project. The old URL `/admin` used to
land on the nested "General" tab; it now 301-redirects to
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

This describes the /admin/admin/users change as a "301" redirect, but the implementation shown in this repo is a client-side vue-router redirect (not an HTTP 301). Unless there is also a server-level 301 configured elsewhere, consider rewording to "redirects" / "client-side redirects" to avoid misleading downstream maintainers.

Suggested change
land on the nested "General" tab; it now 301-redirects to
land on the nested "General" tab; it now redirects to

Copilot uses AI. Check for mistakes.
Comment on lines +13 to +22
><v-chip
v-for="m in item.memberships || []"
:key="m._id || m.id"
size="small"
:variant="isUserActiveOrg(item, m) ? 'flat' : 'tonal'"
:color="orgColor(m.organizationId)"
class="mr-1 text-capitalize"
style="cursor: pointer"
@click="$router.push('/admin/organizations/' + (m.organizationId?._id || m.organizationId?.id))"
>{{ (m.organizationId && m.organizationId.name) || '—' }} ({{ m.role || '—' }})</v-chip
Copy link

Copilot AI Apr 14, 2026

Choose a reason for hiding this comment

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

The organization membership chip is always clickable, but when m.organizationId is missing/null the click handler will navigate to /admin/organizations/undefined. Consider deriving an orgId first and only enabling navigation (and the pointer cursor) when it exists; otherwise render a non-clickable chip/state.

Suggested change
><v-chip
v-for="m in item.memberships || []"
:key="m._id || m.id"
size="small"
:variant="isUserActiveOrg(item, m) ? 'flat' : 'tonal'"
:color="orgColor(m.organizationId)"
class="mr-1 text-capitalize"
style="cursor: pointer"
@click="$router.push('/admin/organizations/' + (m.organizationId?._id || m.organizationId?.id))"
>{{ (m.organizationId && m.organizationId.name) || '—' }} ({{ m.role || '—' }})</v-chip
><template v-for="m in item.memberships || []" :key="m._id || m.id"
><v-chip
v-if="m.organizationId?._id || m.organizationId?.id"
size="small"
:variant="isUserActiveOrg(item, m) ? 'flat' : 'tonal'"
:color="orgColor(m.organizationId)"
class="mr-1 text-capitalize"
style="cursor: pointer"
@click="$router.push('/admin/organizations/' + (m.organizationId?._id || m.organizationId?.id))"
>{{ (m.organizationId && m.organizationId.name) || '—' }} ({{ m.role || '—' }})</v-chip
><v-chip
v-else
size="small"
:variant="isUserActiveOrg(item, m) ? 'flat' : 'tonal'"
:color="orgColor(m.organizationId)"
class="mr-1 text-capitalize"
>{{ (m.organizationId && m.organizationId.name) || '—' }} ({{ m.role || '—' }})</v-chip
></template

Copilot uses AI. Check for mistakes.
- Wrap readiness/activity fetch loading flags in try/finally so the
  progress bars never stay pinned on store rejection.
- Wrap admin.users store mutations in try/catch and guard membership
  chip navigation against undefined organization ids.
- Clarify JSDoc for fetchUsers / fetchOrganizations to reflect the
  pagination query-string contract used by coreDataTableComponent.
- Update unit tests to pass the documented string param to the fetch
  wrappers instead of an ad-hoc object.
- Reword MIGRATIONS.md to describe the /admin redirect as client-side
  via vue-router (it is not an HTTP 301).
@PierreBrisorgueil PierreBrisorgueil dismissed coderabbitai[bot]’s stale review April 14, 2026 21:46

All 3 inline findings already addressed in commit 1884b09 (loading-flag try/finally + undefined-orgId guard). CodeRabbit acknowledged the fixes via subsequent COMMENTED reviews — dismissing stale CHANGES_REQUESTED to unblock merge.

@PierreBrisorgueil PierreBrisorgueil merged commit a75d0ca into master Apr 15, 2026
6 checks passed
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.

admin: flatten to single tab bar, expose built-in tabs as routed children

2 participants