Add Operations/Finance mode switcher to sidebar nav

Two-tab strip between Dashboard and the Operations section lets users
toggle between the shop-management view and the accounting view.
FOUC-prevention: server-side controller detection stamps data-nav-mode
on <html> before first paint; CSS hides the inactive side instantly.
JS (nav-mode.js) auto-switches to Finance when navigating to an
accounting controller, restores saved preference everywhere else.
Vendors appears in both modes; all 39 nav items carry data-nav tags.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-10 13:40:14 -04:00
parent 4fd9c52aaf
commit 0204430fa5
2 changed files with 165 additions and 39 deletions
@@ -0,0 +1,49 @@
(function () {
'use strict';
const STORAGE_KEY = 'pcl-nav-mode';
// Controllers that live exclusively in the Finance side.
// When navigating to one of these, the sidebar auto-switches to Finance.
const FINANCE_CONTROLLERS = new Set([
'bills', 'accounts', 'journalentries', 'vendorcredits',
'bankreconciliations', 'fixedassets', 'budgets', 'recurringtemplates',
'accountingexport', 'taxrates'
]);
/** Returns 'fin' if the current page belongs to Finance; null otherwise (use saved pref). */
function detectMode() {
const ctrl = (document.body.dataset.controller || '').toLowerCase();
return FINANCE_CONTROLLERS.has(ctrl) ? 'fin' : null;
}
function applyMode(mode) {
// CSS uses [data-nav-mode] on <html> to show/hide items — update it first.
document.documentElement.dataset.navMode = mode;
// Sync button states.
document.querySelectorAll('.nav-mode-btn').forEach(btn => {
const active = btn.dataset.mode === mode;
btn.classList.toggle('active', active);
btn.setAttribute('aria-selected', active ? 'true' : 'false');
});
localStorage.setItem(STORAGE_KEY, mode);
}
function init() {
const forced = detectMode();
const saved = localStorage.getItem(STORAGE_KEY) || 'ops';
applyMode(forced || saved);
document.querySelectorAll('.nav-mode-btn').forEach(btn => {
btn.addEventListener('click', () => applyMode(btn.dataset.mode));
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', init);
} else {
init();
}
})();