Move working documents to its own area, rename dashboard
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
import { invalidateAll } from '$app/navigation';
|
||||
import { goto } from '$app/navigation';
|
||||
import { page } from '$app/state';
|
||||
import { clientSession, sessionHydrated } from '$lib/session';
|
||||
import { clientSession, hasModuleAccess, sessionHydrated } from '$lib/session';
|
||||
import { onMount, tick } from 'svelte';
|
||||
import packageInfo from '../../../package.json';
|
||||
|
||||
@@ -18,22 +18,23 @@
|
||||
label: string;
|
||||
shortLabel: string;
|
||||
icon?: 'home';
|
||||
moduleKey?: string;
|
||||
};
|
||||
|
||||
const dashboardItem: NavItem = { href: '/', label: 'Dashboard', shortLabel: 'DB', icon: 'home' };
|
||||
const dashboardItem: NavItem = { href: '/', label: 'Dashboard', shortLabel: 'DB', icon: 'home', moduleKey: 'dashboard' };
|
||||
const workingDocumentItems: NavItem[] = [
|
||||
{ href: '/raw-materials', label: 'Raw Materials', shortLabel: 'RM' },
|
||||
{ href: '/mixes', label: 'Mix Master', shortLabel: 'MM' },
|
||||
{ href: '/products', label: 'Products', shortLabel: 'PR' },
|
||||
{ href: '/scenarios', label: 'Scenarios', shortLabel: 'SC' }
|
||||
{ href: '/raw-materials', label: 'Raw Materials', shortLabel: 'RM', moduleKey: 'raw_materials' },
|
||||
{ href: '/mixes', label: 'Mix Master', shortLabel: 'MM', moduleKey: 'mix_master' },
|
||||
{ href: '/products', label: 'Products', shortLabel: 'PR', moduleKey: 'products' },
|
||||
{ href: '/scenarios', label: 'Scenarios', shortLabel: 'SC', moduleKey: 'scenarios' }
|
||||
];
|
||||
const navigation = [dashboardItem, ...workingDocumentItems];
|
||||
const accessControlItem: NavItem = { href: '/client-access', label: 'Client Access', shortLabel: 'AC', moduleKey: 'client_access' };
|
||||
const navigation = [dashboardItem, ...workingDocumentItems, accessControlItem];
|
||||
|
||||
const footerLinks = [
|
||||
{ href: '/products', label: 'Delivered Pricing', shortLabel: 'DP' },
|
||||
{ href: '/scenarios', label: 'Planning View', shortLabel: 'PV' }
|
||||
];
|
||||
const primaryBottomNavigation = [dashboardItem, ...workingDocumentItems.slice(0, 3)];
|
||||
|
||||
const searchItems: SearchItem[] = [
|
||||
{
|
||||
@@ -88,12 +89,30 @@
|
||||
let quickMenuOpen = $state(false);
|
||||
let userMenuOpen = $state(false);
|
||||
let navOpen = $state(false);
|
||||
let workingDocumentsExpanded = $state(true);
|
||||
let showBottomNav = $state(false);
|
||||
let isRestoringSession = $state(false);
|
||||
let restoredToken = $state<string | null>(null);
|
||||
let paletteInput: HTMLInputElement | null = $state(null);
|
||||
const appVersion = `v${packageInfo.version}`;
|
||||
const currentYear = new Date().getFullYear();
|
||||
const visibleDashboardItem = $derived(
|
||||
!$clientSession || !dashboardItem.moduleKey || hasModuleAccess($clientSession, dashboardItem.moduleKey) ? dashboardItem : null
|
||||
);
|
||||
const visibleWorkingDocumentItems = $derived(
|
||||
!$clientSession
|
||||
? workingDocumentItems
|
||||
: workingDocumentItems.filter((item) => !item.moduleKey || hasModuleAccess($clientSession, item.moduleKey))
|
||||
);
|
||||
const visibleFooterLinks = $derived([
|
||||
...footerLinks,
|
||||
...(!$clientSession || !hasModuleAccess($clientSession, 'client_access', 'manage')
|
||||
? []
|
||||
: [{ href: accessControlItem.href, label: accessControlItem.label, shortLabel: accessControlItem.shortLabel }])
|
||||
]);
|
||||
const primaryBottomNavigation = $derived(
|
||||
[...(visibleDashboardItem ? [visibleDashboardItem] : []), ...visibleWorkingDocumentItems.slice(0, 3)]
|
||||
);
|
||||
|
||||
function matchesRoute(href: string, pathname: string) {
|
||||
return href === '/' ? pathname === '/' : pathname.startsWith(href);
|
||||
@@ -111,7 +130,8 @@
|
||||
'/mixes/new': 'Create a new mix worksheet for Hunter Premium Produce',
|
||||
'/products': 'Track delivered product pricing and margin views',
|
||||
'/settings': 'Review your workspace profile and application settings',
|
||||
'/scenarios': 'Compare alternate pricing and production assumptions'
|
||||
'/scenarios': 'Compare alternate pricing and production assumptions',
|
||||
'/client-access': 'Manage user access, module permissions, and audit history'
|
||||
};
|
||||
|
||||
return descriptions[pathname] ?? 'Hunter Premium Produce client workspace';
|
||||
@@ -256,50 +276,63 @@
|
||||
</button>
|
||||
|
||||
<nav class="nav-list" aria-label="Client navigation">
|
||||
<a class:active={matchesRoute(dashboardItem.href, page.url.pathname)} href={dashboardItem.href}>
|
||||
<span class="nav-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M3.75 10.5 12 3.75l8.25 6.75"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 9.75v9h13.5v-9"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10.125 18.75v-5.25h3.75v5.25"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>{dashboardItem.label}</span>
|
||||
</a>
|
||||
{#if visibleDashboardItem}
|
||||
<a class:active={matchesRoute(visibleDashboardItem.href, page.url.pathname)} href={visibleDashboardItem.href}>
|
||||
<span class="nav-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M3.75 10.5 12 3.75l8.25 6.75"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 9.75v9h13.5v-9"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10.125 18.75v-5.25h3.75v5.25"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>{visibleDashboardItem.label}</span>
|
||||
</a>
|
||||
{/if}
|
||||
</nav>
|
||||
|
||||
<div class="nav-group" aria-label="Working documents">
|
||||
<p class="nav-group-label">Working Documents</p>
|
||||
<nav class="nav-sublist" aria-label="Working document pages">
|
||||
{#each workingDocumentItems as item}
|
||||
<a class:active={matchesRoute(item.href, page.url.pathname)} href={item.href}>
|
||||
<span class="nav-icon">{item.shortLabel}</span>
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
{/each}
|
||||
</nav>
|
||||
<div class="nav-group" aria-label="Working documents" hidden={!visibleWorkingDocumentItems.length}>
|
||||
<button
|
||||
aria-controls="working-documents-nav"
|
||||
aria-expanded={workingDocumentsExpanded}
|
||||
class="nav-group-toggle"
|
||||
type="button"
|
||||
onclick={() => (workingDocumentsExpanded = !workingDocumentsExpanded)}
|
||||
>
|
||||
<span class="nav-group-label">Working Documents</span>
|
||||
<span class:open={workingDocumentsExpanded} class="chevron"></span>
|
||||
</button>
|
||||
{#if workingDocumentsExpanded}
|
||||
<nav class="nav-sublist" id="working-documents-nav" aria-label="Working document pages">
|
||||
{#each visibleWorkingDocumentItems as item}
|
||||
<a class:active={matchesRoute(item.href, page.url.pathname)} href={item.href}>
|
||||
<span class="nav-icon">{item.shortLabel}</span>
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
{/each}
|
||||
</nav>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
{#each footerLinks as item}
|
||||
{#each visibleFooterLinks as item}
|
||||
<a href={item.href}>
|
||||
<span class="nav-icon muted">{item.shortLabel}</span>
|
||||
<span>{item.label}</span>
|
||||
@@ -498,43 +531,58 @@
|
||||
|
||||
<div class="drawer-grid">
|
||||
<nav class="drawer-section" aria-label="All workspace pages">
|
||||
<a class:active={matchesRoute(dashboardItem.href, page.url.pathname)} href={dashboardItem.href} onclick={() => (navOpen = false)}>
|
||||
<span class="nav-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M3.75 10.5 12 3.75l8.25 6.75"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 9.75v9h13.5v-9"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10.125 18.75v-5.25h3.75v5.25"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>{dashboardItem.label}</span>
|
||||
</a>
|
||||
{#if visibleDashboardItem}
|
||||
<a class:active={matchesRoute(visibleDashboardItem.href, page.url.pathname)} href={visibleDashboardItem.href} onclick={() => (navOpen = false)}>
|
||||
<span class="nav-icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path
|
||||
d="M3.75 10.5 12 3.75l8.25 6.75"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M5.25 9.75v9h13.5v-9"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<path
|
||||
d="M10.125 18.75v-5.25h3.75v5.25"
|
||||
stroke="currentColor"
|
||||
stroke-width="1.85"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</span>
|
||||
<span>{visibleDashboardItem.label}</span>
|
||||
</a>
|
||||
{/if}
|
||||
|
||||
<div class="drawer-group">
|
||||
<p class="drawer-group-label">Working Documents</p>
|
||||
{#each workingDocumentItems as item}
|
||||
<a class:active={matchesRoute(item.href, page.url.pathname)} href={item.href} onclick={() => (navOpen = false)}>
|
||||
<span class="nav-icon">{item.shortLabel}</span>
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
{/each}
|
||||
<div class="drawer-group" hidden={!visibleWorkingDocumentItems.length}>
|
||||
<button
|
||||
aria-controls="drawer-working-documents-nav"
|
||||
aria-expanded={workingDocumentsExpanded}
|
||||
class="nav-group-toggle drawer-group-toggle"
|
||||
type="button"
|
||||
onclick={() => (workingDocumentsExpanded = !workingDocumentsExpanded)}
|
||||
>
|
||||
<span class="drawer-group-label">Working Documents</span>
|
||||
<span class:open={workingDocumentsExpanded} class="chevron"></span>
|
||||
</button>
|
||||
{#if workingDocumentsExpanded}
|
||||
<div id="drawer-working-documents-nav" class="drawer-sublist">
|
||||
{#each visibleWorkingDocumentItems as item}
|
||||
<a class:active={matchesRoute(item.href, page.url.pathname)} href={item.href} onclick={() => (navOpen = false)}>
|
||||
<span class="nav-icon">{item.shortLabel}</span>
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -565,7 +613,7 @@
|
||||
</div>
|
||||
|
||||
<div class="drawer-footer">
|
||||
{#each footerLinks as item}
|
||||
{#each visibleFooterLinks as item}
|
||||
<a href={item.href} onclick={() => (navOpen = false)}>
|
||||
<span>{item.label}</span>
|
||||
<small>{item.shortLabel}</small>
|
||||
@@ -683,6 +731,10 @@
|
||||
padding: 0.9rem;
|
||||
background: var(--panel);
|
||||
border-right: 1px solid var(--line);
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar-body {
|
||||
@@ -865,6 +917,19 @@
|
||||
padding-top: 0.15rem;
|
||||
}
|
||||
|
||||
.nav-group-toggle {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 0.75rem;
|
||||
width: 100%;
|
||||
padding: 0 0.68rem;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.nav-group-label,
|
||||
.drawer-group-label {
|
||||
margin: 0;
|
||||
@@ -876,7 +941,7 @@
|
||||
}
|
||||
|
||||
.nav-group-label {
|
||||
padding: 0 0.68rem;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.nav-sublist {
|
||||
@@ -917,6 +982,7 @@
|
||||
.sidebar-footer {
|
||||
margin-top: auto;
|
||||
padding-top: 0.6rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-meta {
|
||||
@@ -925,6 +991,7 @@
|
||||
padding: 0.85rem 0.3rem 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.78rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.sidebar-meta small {
|
||||
@@ -1429,10 +1496,19 @@
|
||||
padding-top: 0.35rem;
|
||||
}
|
||||
|
||||
.drawer-group-label {
|
||||
.drawer-group-toggle {
|
||||
padding: 0 0.2rem;
|
||||
}
|
||||
|
||||
.drawer-group-label {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.drawer-sublist {
|
||||
display: grid;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.drawer-footer {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
|
||||
Reference in New Issue
Block a user