Skip to content

Commit 6416e4f

Browse files
refactor(ui): homogenize Account, Organization detail, and Admin pages (#4187)
* chore(git): ignore top-level .worktrees/ directory * feat(core): add coreConfirmDialog shared destructive-confirm component Options API dialog with simple yes/no, typed-confirmation gate, loading mode, custom confirmColor/titleClass, and default slot override. * feat(core): add coreAvatarUploader (v-badge camera overlay, no inline styles) * test(core): use flushPromises in coreAvatarUploader test (cleaner than setTimeout) * feat(admin): currentBreadcrumb state so sub-views push titles to the layout * test(admin): split currentBreadcrumb test for clearer failure messages * refactor(admin): banners on top, tabs in PageHeader, breadcrumb support for sub-views * refactor(admin): resolveActiveTab uses allTabs (single source of truth) * refactor(admin): drop v-container on list sub-views; users.view uses coreConfirmDialog * refactor(admin): drop gratuitous div wrapper in admin.users.view (Vue 3 multi-root) * refactor(admin): activity view — drop chrome + inline styles, use v-row/v-col + v-table hover * refactor(admin): user detail — drop nested PageHeader, push breadcrumb, shared dialog * refactor(users): profile component — coreAvatarUploader + native Vuetify labels, no inline styles * refactor(users): drop dead isActiveOrg method from profile component * refactor(admin): migrate layout to CoreSurfaceTabBar — chrome convergence with Account+Org Drop inline v-tabs, activeTab data, $route watcher, isValidTab, tabTo, resolveActiveTab, extraTabs computed. Replace with CoreSurfaceTabBar receiving allTabs (built-in + config extras) and adminCan predicate. Matches user.view.vue + organization.detail.component.vue pattern. * refactor(users): account dialogs — Delete Account + Leave Org use coreConfirmDialog * refactor(organizations): detail — Delete Organization uses coreConfirmDialog with org-name gate * fix(admin): remove unused beforeEach import in admin.user.view tests * fix(admin): clear stale breadcrumb on blank user; fix org delete JSDoc - publishBreadcrumb() now calls clearBreadcrumb() when the user record is null/blank, preventing stale title display during same-component navigation between /admin/users/:id routes (Copilot finding). - Add test: "clears breadcrumb when user resets to blank record". - Fix misleading JSDoc on deleteConfirmTarget() in organization.detail — the confirm button is guarded by v-if on the trigger, not by the empty confirmText fallback (Copilot finding). * fix(review): address CodeRabbit + Copilot findings - admin.users.view: replace inline :style binding on membership chips with :class="cursor-pointer" (no-inline-style convention, CR finding) - admin.activity.view: add keyboard semantics to expandable rows (tabindex=0, role=button, aria-expanded, @keydown.enter+.space) + test (CR finding) - organizations.detail layout test: fix tautological assertion for deleteConfirmTarget null fallback (CR finding) - admin.store: add full JSDoc (@param/@returns) to setBreadcrumb + clearBreadcrumb - admin.layout: add @returns {void} to clearError - admin.user.view: add JSDoc to user watcher handler + beforeUnmount - admin.user.view test: add JSDoc to mountView helper - admin.layout test: add JSDoc to mountLayout helper - core.avatarUploader: add JSDoc to triggerUpload + onFile - core.confirmDialog: add JSDoc to data, canConfirm, modelValue watcher, onCancel, onConfirm; document the intentional non-self-close design - user.profile.component: add @returns {void} to syncForm + syncOrganizations - user.profile.component test: add JSDoc to mountProfile helper
1 parent 9701f85 commit 6416e4f

27 files changed

Lines changed: 1387 additions & 774 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,3 +81,6 @@ mongod
8181
.github/*local.*
8282
.claude/worktrees/
8383
playwright.local.config.js
84+
85+
# Top-level git worktrees (per superpowers convention)
86+
.worktrees/

src/modules/admin/stores/admin.store.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export const useAdminStore = defineStore('admin', {
5555
users: [],
5656
organizations: [],
5757
error: null,
58+
currentBreadcrumb: null,
5859
readiness: [],
5960
auditLogs: [],
6061
auditTotal: 0,
@@ -124,6 +125,25 @@ export const useAdminStore = defineStore('admin', {
124125
this.user = defaultUser();
125126
},
126127

128+
/**
129+
* @desc Set (or clear) the current breadcrumb published by an admin sub-view.
130+
* A shallow copy is stored so callers cannot mutate the store state directly.
131+
* Pass `null` to clear (identical effect to `clearBreadcrumb()`).
132+
* @param {{ title: string, titleClass?: string } | null} payload - Breadcrumb data, or null to clear.
133+
* @returns {void}
134+
*/
135+
setBreadcrumb(payload) {
136+
this.currentBreadcrumb = payload ? { ...payload } : null;
137+
},
138+
139+
/**
140+
* @desc Clear the current breadcrumb (reset to null). Called by admin sub-views on unmount.
141+
* @returns {void}
142+
*/
143+
clearBreadcrumb() {
144+
this.currentBreadcrumb = null;
145+
},
146+
127147
/**
128148
* @desc Fetch SaaS readiness checklist from the admin API.
129149
* @returns {Promise<void>}

src/modules/admin/tests/admin.activity.view.unit.tests.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,44 @@ describe('admin.activity.view', () => {
181181
expect(getAuditLogs).not.toHaveBeenCalled();
182182
});
183183
});
184+
185+
import { readFileSync } from 'fs';
186+
import { fileURLToPath } from 'url';
187+
import { dirname, resolve } from 'path';
188+
189+
describe('admin.activity.view — template chrome', () => {
190+
it('does NOT wrap its template in <v-container> (admin layout owns chrome)', () => {
191+
const here = dirname(fileURLToPath(import.meta.url));
192+
const sfc = readFileSync(resolve(here, '../views/admin.activity.view.vue'), 'utf8');
193+
const tmpl = sfc.split('<script>')[0];
194+
expect(tmpl).not.toMatch(/<v-container/);
195+
});
196+
197+
it('has zero inline style="…" attributes in its template', () => {
198+
const here = dirname(fileURLToPath(import.meta.url));
199+
const sfc = readFileSync(resolve(here, '../views/admin.activity.view.vue'), 'utf8');
200+
const tmpl = sfc.split('<script>')[0];
201+
// Allow :style="…" (dynamic bindings, none expected here) and reject plain style="…".
202+
// But neither should appear in this view after the refactor.
203+
expect(tmpl).not.toMatch(/\bstyle\s*=\s*"/);
204+
expect(tmpl).not.toMatch(/:style\s*=\s*"/);
205+
});
206+
207+
it('uses v-table with the hover prop for row affordance', () => {
208+
const here = dirname(fileURLToPath(import.meta.url));
209+
const sfc = readFileSync(resolve(here, '../views/admin.activity.view.vue'), 'utf8');
210+
const tmpl = sfc.split('<script>')[0];
211+
expect(tmpl).toMatch(/<v-table[^>]*\bhover\b/);
212+
});
213+
214+
it('expandable rows have keyboard semantics (tabindex, role, aria-expanded, keydown handlers)', () => {
215+
const here = dirname(fileURLToPath(import.meta.url));
216+
const sfc = readFileSync(resolve(here, '../views/admin.activity.view.vue'), 'utf8');
217+
const tmpl = sfc.split('<script>')[0];
218+
expect(tmpl).toMatch(/tabindex="0"/);
219+
expect(tmpl).toMatch(/role="button"/);
220+
expect(tmpl).toMatch(/aria-expanded/);
221+
expect(tmpl).toMatch(/@keydown\.enter/);
222+
expect(tmpl).toMatch(/@keydown\.space/);
223+
});
224+
});

0 commit comments

Comments
 (0)