Skip to content

Commit a75d0ca

Browse files
refactor(admin): flatten to single routed tab bar (#3974) (#3975)
* refactor(admin): flatten to single routed tab bar (#3974) 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) * fix(admin): address CodeRabbit review feedback - 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).
1 parent b96d9a6 commit a75d0ca

16 files changed

Lines changed: 1188 additions & 833 deletions

MIGRATIONS.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,77 @@ Breaking changes and upgrade notes for downstream projects.
44

55
---
66

7+
## Admin tabs flattened to a single routed row (2026-04-14)
8+
9+
**Breaking change (URL-level).** The admin section now exposes its four
10+
built-in sections (Users, Organizations, Readiness, Activity) as routed
11+
siblings of the downstream `config.admin.tabs` extras. There is no more
12+
nested "General" tab with an internal `v-window` — every tab is a real
13+
URL and renders through the same `<router-view>`.
14+
15+
### What changed in the stack
16+
17+
- **REMOVED:** `src/modules/admin/views/admin.content.vue` — the inner
18+
nested tab bar is gone. Its four window items are now dedicated views.
19+
- **NEW:**
20+
- `src/modules/admin/views/admin.users.view.vue`
21+
- `src/modules/admin/views/admin.organizations.view.vue`
22+
- `src/modules/admin/views/admin.readiness.view.vue`
23+
- `src/modules/admin/views/admin.activity.view.vue`
24+
- **CHANGED:** `src/modules/admin/router/admin.router.js` — the parent
25+
`/admin` route now has dedicated children:
26+
- `''` → redirect to `{ name: 'Admin Users' }`
27+
- `users``Admin Users`
28+
- `users/:id``Admin User`
29+
- `organizations``Admin Organizations`
30+
- `organizations/:organizationId``Admin Organization`
31+
- `readiness``Admin Readiness`
32+
- `activity``Admin Activity`
33+
- **CHANGED:** `src/modules/admin/views/admin.layout.vue` — the tab bar
34+
now renders built-in tabs + `config.admin.tabs` extras in one flat row.
35+
The global error banner and the mailer warning were moved from the old
36+
`admin.content.vue` into the layout so they stay visible across every
37+
admin tab (including downstream extras).
38+
- Readiness and Activity now fetch their data on `mounted()` (previously
39+
via a `watch: { tab }` inside the old nested window).
40+
41+
### Action for downstream projects
42+
43+
**No config change is required.** The mechanism for contributing extra
44+
admin tabs via `config.admin.tabs` + `injectAdminChildren` is unchanged,
45+
and extras continue to render inline inside the admin layout.
46+
47+
1. Run `/update-stack` to pull the changes.
48+
2. Verify hard-coded links in your project. The old URL `/admin` used to
49+
land on the nested "General" tab; it now redirects to `/admin/users`
50+
(client-side via vue-router), so existing links keep working. If you
51+
want to point somewhere else, use one of the new stable URLs:
52+
- `/admin/users`
53+
- `/admin/organizations`
54+
- `/admin/readiness`
55+
- `/admin/activity`
56+
Optional check: `grep -r "/admin" src/`.
57+
3. Downstream projects that override `admin.content.vue` must delete the
58+
override — the file no longer exists. Replace any such override with
59+
a dedicated override of `admin.users.view.vue` /
60+
`admin.organizations.view.vue` / `admin.readiness.view.vue` /
61+
`admin.activity.view.vue`, or attach a custom tab via
62+
`config.admin.tabs` + `injectAdminChildren`.
63+
4. No route-name breakage for downstream extras — `injectAdminChildren`
64+
still mounts your routes as children of the same `/admin` parent.
65+
66+
### Why
67+
68+
Two stacked tab bars on `*/admin` (top-level General + extras, nested
69+
Users / Organizations / Readiness / Activity) was visually noisy and
70+
duplicated navigation. The original intent of `config.admin.tabs` was
71+
for downstream extras to live **alongside** the built-ins — not above
72+
them. Flattening gives every admin section a real URL, preserves
73+
deep-linking and browser back/forward, and keeps downstream extras
74+
config-driven with zero migration work.
75+
76+
---
77+
778
## Admin extra tabs are now nested routes (2026-04-12)
879

980
**Breaking change.** Downstream projects that added admin tabs via

src/lib/helpers/tests/router.unit.tests.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const makeAdminRoutes = () => [
1010
path: '/admin',
1111
component: { name: 'AdminLayout' },
1212
children: [
13-
{ path: '', name: 'Admin', component: { name: 'AdminContent' } },
13+
{ path: 'users', name: 'Admin Users', component: { name: 'AdminUsers' } },
1414
{ path: 'users/:id', name: 'Admin User', component: { name: 'AdminUser' } },
1515
],
1616
},

src/modules/admin/router/admin.router.js

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,28 @@
22
* Module dependencies.
33
*/
44
import adminLayout from '../views/admin.layout.vue';
5-
import adminContent from '../views/admin.content.vue';
5+
import adminUsers from '../views/admin.users.view.vue';
6+
import adminOrganizations from '../views/admin.organizations.view.vue';
7+
import adminReadiness from '../views/admin.readiness.view.vue';
8+
import adminActivity from '../views/admin.activity.view.vue';
69
import adminUser from '../views/admin.user.view.vue';
710
import adminOrganization from '../views/admin.organization.view.vue';
811

912
/**
1013
* Router configuration.
1114
*
1215
* The admin module exports a **parent route** (`/admin`) whose component
13-
* is the admin layout (page header + tab bar + `<router-view>`). Built-in
14-
* views (general content, user/organization detail) and any downstream
15-
* injected child routes are rendered inside that `<router-view>`.
16+
* is the admin layout (page header + tab bar + `<router-view>`). Each
17+
* built-in section (Users, Organizations, Readiness, Activity) is a
18+
* routed child so that the tab bar and the downstream-contributed extras
19+
* (`config.admin.tabs`) live in one flat navigation row.
1620
*
17-
* Downstream modules should **not** add sibling routes like
18-
* `/admin/knowledge` anymore. Instead, register them via the
19-
* `injectAdminChildren` helper in `@/lib/helpers/router` so they become
20-
* children of this parent route.
21+
* Detail views (`users/:id`, `organizations/:organizationId`) are also
22+
* children of the same parent — they deep-link inside the layout.
23+
*
24+
* Downstream modules that contribute an "admin tab" must register their
25+
* routes via the `injectAdminChildren` helper in `@/lib/helpers/router`
26+
* with **relative** paths (e.g. `'knowledge'`, not `'/admin/knowledge'`).
2127
*/
2228
export default [
2329
{
@@ -33,8 +39,12 @@ export default [
3339
children: [
3440
{
3541
path: '',
36-
name: 'Admin General',
37-
component: adminContent,
42+
redirect: { name: 'Admin Users' },
43+
},
44+
{
45+
path: 'users',
46+
name: 'Admin Users',
47+
component: adminUsers,
3848
meta: {
3949
action: 'manage', subject: 'UserAdmin',
4050
},
@@ -48,6 +58,14 @@ export default [
4858
action: 'manage', subject: 'UserAdmin',
4959
},
5060
},
61+
{
62+
path: 'organizations',
63+
name: 'Admin Organizations',
64+
component: adminOrganizations,
65+
meta: {
66+
action: 'manage', subject: 'UserAdmin',
67+
},
68+
},
5169
{
5270
path: 'organizations/:organizationId',
5371
name: 'Admin Organization',
@@ -57,6 +75,22 @@ export default [
5775
action: 'manage', subject: 'UserAdmin',
5876
},
5977
},
78+
{
79+
path: 'readiness',
80+
name: 'Admin Readiness',
81+
component: adminReadiness,
82+
meta: {
83+
action: 'manage', subject: 'UserAdmin',
84+
},
85+
},
86+
{
87+
path: 'activity',
88+
name: 'Admin Activity',
89+
component: adminActivity,
90+
meta: {
91+
action: 'manage', subject: 'UserAdmin',
92+
},
93+
},
6094
],
6195
},
6296
];

0 commit comments

Comments
 (0)