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
+116 -39
View File
@@ -32,6 +32,15 @@
_ => "#374151" // gray-700 for anything else
};
// Nav mode: used by FOUC-prevention inline script + nav-mode.js
var _finCtrlSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase) {
"bills", "accounts", "journalentries", "vendorcredits",
"bankreconciliations", "fixedassets", "budgets", "recurringtemplates",
"accountingexport", "taxrates"
};
var _navController = ViewContext.RouteData.Values["controller"]?.ToString() ?? "";
var _serverNavMode = _finCtrlSet.Contains(_navController) ? "fin" : "ops";
if (User.Identity?.IsAuthenticated == true)
{
if (isImpersonating)
@@ -319,6 +328,54 @@
width: 1.5rem;
}
/* ── Nav mode: FOUC prevention — CSS wins before JS runs ─────── */
html[data-nav-mode="ops"] [data-nav="fin"] { display: none !important; }
html[data-nav-mode="fin"] [data-nav="ops"] { display: none !important; }
/* ── Nav mode strip (Operations | Finance tab switcher) ──────── */
.nav-mode-strip {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 3px;
padding: 8px 12px 10px;
border-bottom: 1px solid rgba(255,255,255,0.08);
}
.nav-mode-btn {
display: flex;
align-items: center;
justify-content: center;
gap: 5px;
padding: 7px 6px;
border: none;
border-radius: 5px;
font-family: var(--font-mono, 'IBM Plex Mono', ui-monospace, monospace);
font-size: 0.64rem;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.08em;
cursor: pointer;
transition: background 0.15s, color 0.15s;
background: transparent;
color: rgba(255,255,255,0.38);
white-space: nowrap;
}
.nav-mode-btn i {
font-size: 0.85rem;
flex-shrink: 0;
}
.nav-mode-btn:hover {
background: rgba(255,255,255,0.08);
color: rgba(255,255,255,0.8);
}
.nav-mode-btn.active {
background: var(--pcl-ember, #d97706);
color: #fff;
}
/* Main Content */
.main-content {
margin-left: var(--sidebar-width);
@@ -878,7 +935,10 @@
@* Page-specific styles *@
@await RenderSectionAsync("Styles", required: false)
</head>
<body style="padding-top: var(--env-banner-height);">
<body style="padding-top: var(--env-banner-height);" data-controller="@_navController.ToLower()">
<script>
(function(){var s='@_serverNavMode',p=localStorage.getItem('pcl-nav-mode')||'ops';document.documentElement.dataset.navMode=s==='fin'?'fin':p;})();
</script>
@if (_isNonProd)
{
<div style="position:fixed;top:0;left:0;width:100%;height:var(--env-banner-height);z-index:2000;
@@ -1010,56 +1070,66 @@
var showInventorySection = hasInventory || hasVendors;
var showEquipmentSection = hasEquipment || hasMaintenance;
<div class="nav-section-title">Main Menu</div>
<a asp-controller="Dashboard" asp-action="Index" class="nav-link">
<div class="nav-section-title" data-nav="both">Main Menu</div>
<a asp-controller="Dashboard" asp-action="Index" class="nav-link" data-nav="both">
<i class="bi bi-house-door"></i>
<span>Dashboard</span>
</a>
@if (!isPlatformAdmin)
{
<div class="nav-mode-strip" role="tablist" aria-label="Navigation mode">
<button class="nav-mode-btn" data-mode="ops" role="tab" aria-selected="true" title="Operations">
<i class="bi bi-tools"></i>
<span>Operations</span>
</button>
<button class="nav-mode-btn" data-mode="fin" role="tab" aria-selected="false" title="Finance">
<i class="bi bi-journal-bookmark"></i>
<span>Finance</span>
</button>
</div>
@* ── Operations ───────────────────────────────────────────── *@
@if (showOperations)
{
<div class="nav-section-title">Operations</div>
<div class="nav-section-title" data-nav="ops">Operations</div>
}
@if (hasCustomers)
{
<a asp-controller="Customers" asp-action="Index" class="nav-link">
<a asp-controller="Customers" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-people"></i>
<span>Customers</span>
</a>
}
@if (hasQuotes)
{
<a asp-controller="Quotes" asp-action="Index" class="nav-link">
<a asp-controller="Quotes" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-file-text"></i>
<span>Quotes</span>
</a>
}
@if (hasJobs)
{
<a asp-controller="Jobs" asp-action="Index" class="nav-link">
<a asp-controller="Jobs" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-briefcase"></i>
<span>Jobs</span>
</a>
}
@if (hasInvoices)
{
<a asp-controller="Invoices" asp-action="Index" class="nav-link">
<a asp-controller="Invoices" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-receipt"></i>
<span>Invoices</span>
</a>
}
@if (hasCalendar)
{
<a asp-controller="Appointments" asp-action="Calendar" asp-route-view="month" class="nav-link">
<a asp-controller="Appointments" asp-action="Calendar" asp-route-view="month" class="nav-link" data-nav="ops">
<i class="bi bi-calendar-event"></i>
<span>Appointments</span>
</a>
}
@if (hasJobs)
{
<a asp-controller="JobsPriority" asp-action="Index" class="nav-link">
<a asp-controller="JobsPriority" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-clipboard2-check"></i>
<span>Daily Board</span>
</a>
@@ -1069,19 +1139,19 @@
@if (hasInvoices)
{
var _allowOnlinePayments = Context.Items["AllowOnlinePayments"] as bool? ?? false;
<div class="nav-section-title">Billing &amp; Payments</div>
<div class="nav-section-title" data-nav="ops">Billing &amp; Payments</div>
@if (_allowOnlinePayments)
{
<a asp-controller="Invoices" asp-action="OnlinePayments" class="nav-link">
<a asp-controller="Invoices" asp-action="OnlinePayments" class="nav-link" data-nav="ops">
<i class="bi bi-credit-card"></i>
<span>Online Payments</span>
</a>
}
<a asp-controller="CreditMemos" asp-action="Index" class="nav-link">
<a asp-controller="CreditMemos" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-journal-minus"></i>
<span>Credit Memos</span>
</a>
<a asp-controller="GiftCertificates" asp-action="Index" class="nav-link">
<a asp-controller="GiftCertificates" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-gift"></i>
<span>Gift Certificates</span>
</a>
@@ -1090,18 +1160,18 @@
@* ── Inventory & Purchasing ───────────────────────────────── *@
@if (hasProducts || showInventorySection)
{
<div class="nav-section-title">Inventory &amp; Purchasing</div>
<div class="nav-section-title" data-nav="ops">Inventory &amp; Purchasing</div>
}
@if (hasProducts)
{
<a asp-controller="CatalogItems" asp-action="Index" class="nav-link">
<a asp-controller="CatalogItems" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-book"></i>
<span>Product Catalog</span>
</a>
}
@if (hasInventory)
{
<a asp-controller="Inventory" asp-action="Index" class="nav-link">
<a asp-controller="Inventory" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-box-seam"></i>
<span>Inventory</span>
</a>
@@ -1109,11 +1179,11 @@
}
@if (hasVendors)
{
<a asp-controller="Vendors" asp-action="Index" class="nav-link">
<a asp-controller="Vendors" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-truck"></i>
<span>Vendors</span>
</a>
<a asp-controller="PurchaseOrders" asp-action="Index" class="nav-link">
<a asp-controller="PurchaseOrders" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-cart-check"></i>
<span>Purchase Orders</span>
</a>
@@ -1125,46 +1195,50 @@
var _allowAccounting = Context.Items["AllowAccounting"] as bool? ?? false;
if (_allowAccounting)
{
<div class="nav-section-title">Finance</div>
<a asp-controller="Bills" asp-action="Index" class="nav-link">
<div class="nav-section-title" data-nav="fin">Finance</div>
<a asp-controller="Bills" asp-action="Index" class="nav-link" data-nav="fin">
<i class="bi bi-receipt-cutoff"></i>
<span>Bills / Expenses</span>
</a>
<a asp-controller="Accounts" asp-action="Index" class="nav-link">
<a asp-controller="Vendors" asp-action="Index" class="nav-link" data-nav="fin">
<i class="bi bi-truck"></i>
<span>Vendors</span>
</a>
<a asp-controller="Accounts" asp-action="Index" class="nav-link" data-nav="fin">
<i class="bi bi-journal-bookmark"></i>
<span>Chart of Accounts</span>
</a>
<a asp-controller="JournalEntries" asp-action="Index" class="nav-link">
<a asp-controller="JournalEntries" asp-action="Index" class="nav-link" data-nav="fin">
<i class="bi bi-journal-text"></i>
<span>Journal Entries</span>
</a>
<a asp-controller="VendorCredits" asp-action="Index" class="nav-link">
<a asp-controller="VendorCredits" asp-action="Index" class="nav-link" data-nav="fin">
<i class="bi bi-arrow-return-left"></i>
<span>Vendor Credits</span>
</a>
<a asp-controller="BankReconciliations" asp-action="Index" class="nav-link">
<a asp-controller="BankReconciliations" asp-action="Index" class="nav-link" data-nav="fin">
<i class="bi bi-bank2"></i>
<span>Bank Reconciliation</span>
</a>
<a asp-controller="FixedAssets" asp-action="Index" class="nav-link">
<a asp-controller="FixedAssets" asp-action="Index" class="nav-link" data-nav="fin">
<i class="bi bi-building-gear"></i>
<span>Fixed Assets</span>
</a>
<a asp-controller="Budgets" asp-action="Index" class="nav-link">
<a asp-controller="Budgets" asp-action="Index" class="nav-link" data-nav="fin">
<i class="bi bi-pie-chart"></i>
<span>Budgets</span>
</a>
<a asp-controller="Accounts" asp-action="YearEndClose" class="nav-link">
<a asp-controller="Accounts" asp-action="YearEndClose" class="nav-link" data-nav="fin">
<i class="bi bi-calendar-check"></i>
<span>Year-End Close</span>
</a>
<a asp-controller="RecurringTemplates" asp-action="Index" class="nav-link">
<a asp-controller="RecurringTemplates" asp-action="Index" class="nav-link" data-nav="fin">
<i class="bi bi-arrow-repeat"></i>
<span>Recurring Transactions</span>
</a>
if (hasReports)
{
<a asp-controller="AccountingExport" asp-action="Index" class="nav-link">
<a asp-controller="AccountingExport" asp-action="Index" class="nav-link" data-nav="fin">
<i class="bi bi-box-arrow-up"></i>
<span>Accounting Export</span>
</a>
@@ -1173,33 +1247,33 @@
}
@* ── Shop Floor ───────────────────────────────────────────── *@
<div class="nav-section-title">Shop Floor</div>
<div class="nav-section-title" data-nav="ops">Shop Floor</div>
@if (hasEquipment)
{
<a asp-controller="Equipment" asp-action="Index" class="nav-link">
<a asp-controller="Equipment" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-gear"></i>
<span>Equipment</span>
</a>
}
@if (hasMaintenance)
{
<a asp-controller="Maintenance" asp-action="Index" class="nav-link">
<a asp-controller="Maintenance" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-tools"></i>
<span>Maintenance</span>
</a>
}
@if (hasInventory || _isAdminOrManager)
{
<a asp-controller="PowderInsights" asp-action="Index" class="nav-link">
<a asp-controller="PowderInsights" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-graph-up"></i>
<span>Powder Insights</span>
</a>
}
<a asp-controller="Jobs" asp-action="ShopDisplay" class="nav-link" target="_blank">
<a asp-controller="Jobs" asp-action="ShopDisplay" class="nav-link" data-nav="ops" target="_blank">
<i class="bi bi-display"></i>
<span>Shop Display</span>
</a>
<a asp-controller="Jobs" asp-action="ShopMobile" class="nav-link">
<a asp-controller="Jobs" asp-action="ShopMobile" class="nav-link" data-nav="ops">
<i class="bi bi-phone"></i>
<span>Shop Mobile</span>
</a>
@@ -1207,17 +1281,17 @@
@* ── Reports & Templates ──────────────────────────────────── *@
@if (hasReports || hasJobs)
{
<div class="nav-section-title">Reports &amp; Templates</div>
<div class="nav-section-title" data-nav="both">Reports &amp; Templates</div>
@if (hasReports)
{
<a asp-controller="Reports" asp-action="Landing" class="nav-link">
<a asp-controller="Reports" asp-action="Landing" class="nav-link" data-nav="both">
<i class="bi bi-bar-chart-line"></i>
<span>Reports</span>
</a>
}
@if (hasJobs)
{
<a asp-controller="JobTemplates" asp-action="Index" class="nav-link">
<a asp-controller="JobTemplates" asp-action="Index" class="nav-link" data-nav="ops">
<i class="bi bi-layout-text-window-reverse"></i>
<span>Job Templates</span>
</a>
@@ -1680,6 +1754,9 @@
<!-- Bootstrap 5 JS Bundle -->
<script src="~/lib/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- Nav mode switcher (Operations / Finance) -->
<script src="~/js/nav-mode.js" asp-append-version="true"></script>
<!-- Custom Scripts -->
<script>
// Add active class to current nav item