update: Add Global Search on Header, Add Global Audit log for all actions.
This commit is contained in:
@@ -1,20 +1,40 @@
|
||||
// frontend/src/components/layout/Sidebar.jsx
|
||||
// Primary navigation sidebar — 224px wide, fixed, full height.
|
||||
//
|
||||
// Visual style (matches Stitch reference):
|
||||
// - Brand header with logo at top
|
||||
// - Section labels: plain uppercase text, generous padding, no rule lines
|
||||
// - Nav items: px-6 py-3, full width, 3px left bar + primary-subtle bg when active
|
||||
// - Collapsible groups: same row height as nav items
|
||||
// - Children: darker inset bg, deep left-indent, text-color hover
|
||||
// - Console Settings: pinned at bottom
|
||||
|
||||
import { useState } from 'react'
|
||||
import { NavLink, useLocation } from 'react-router-dom'
|
||||
import { useAuth } from '@/hooks/useAuth'
|
||||
import logoDark from '@/assets/logos/bell_systems_horizontal_darkMode.png'
|
||||
|
||||
// ─── Icon set ─────────────────────────────────────────────────────────────────
|
||||
// ─── SVG file icons (vite-plugin-svgr v4 — ?react query) ─────────────────────
|
||||
|
||||
import IcoDevices from '@/assets/side-menu-icons/devices.svg?react'
|
||||
import IcoDeviceOverview from '@/assets/side-menu-icons/device-overview.svg?react'
|
||||
import IcoFleet from '@/assets/side-menu-icons/fleet.svg?react'
|
||||
import IcoBlackbox from '@/assets/side-menu-icons/blackbox.svg?react'
|
||||
import IcoMelodies from '@/assets/side-menu-icons/melodies.svg?react'
|
||||
import IcoMelodiesEditor from '@/assets/side-menu-icons/melodies-editor.svg?react'
|
||||
import IcoComposer from '@/assets/side-menu-icons/composer.svg?react'
|
||||
import IcoArchetypes from '@/assets/side-menu-icons/archetypes.svg?react'
|
||||
import IcoMelodySettings from '@/assets/side-menu-icons/melody-settings.svg?react'
|
||||
import IcoCommunications from '@/assets/side-menu-icons/communications.svg?react'
|
||||
import IcoWhatsapp from '@/assets/side-menu-icons/whatsapp.svg?react'
|
||||
import IcoSms from '@/assets/side-menu-icons/sms.svg?react'
|
||||
import IcoHelpdesk from '@/assets/side-menu-icons/helpdesk.svg?react'
|
||||
import IcoCommsLog from '@/assets/side-menu-icons/communications-log.svg?react'
|
||||
import IcoCustomers from '@/assets/side-menu-icons/customers.svg?react'
|
||||
import IcoCustomerOverview from '@/assets/side-menu-icons/customer-overview.svg?react'
|
||||
import IcoOrders from '@/assets/side-menu-icons/orders.svg?react'
|
||||
import IcoProducts from '@/assets/side-menu-icons/products.svg?react'
|
||||
import IcoCatalog from '@/assets/side-menu-icons/product-catalog.svg?react'
|
||||
import IcoSnManager from '@/assets/side-menu-icons/sn-manager.svg?react'
|
||||
import IcoManufacturing from '@/assets/side-menu-icons/manufacturing.svg?react'
|
||||
import IcoInventory from '@/assets/side-menu-icons/inventory.svg?react'
|
||||
import IcoProvisioning from '@/assets/side-menu-icons/provision.svg?react'
|
||||
import IcoFirmware from '@/assets/side-menu-icons/firmware.svg?react'
|
||||
import IcoApi from '@/assets/side-menu-icons/api.svg?react'
|
||||
|
||||
// ─── Inline-only icons (no SVG file equivalent) ───────────────────────────────
|
||||
|
||||
const S = ({ children, ...p }) => (
|
||||
<svg
|
||||
@@ -29,38 +49,50 @@ const S = ({ children, ...p }) => (
|
||||
</svg>
|
||||
)
|
||||
|
||||
// Wrapper to normalise imported SVG file components to 16×16
|
||||
function SvgIcon({ Component }) {
|
||||
return (
|
||||
<Component
|
||||
width="16" height="16"
|
||||
aria-hidden="true" focusable="false"
|
||||
style={{ flexShrink: 0 }}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
const Icons = {
|
||||
dashboard: () => <S><rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/></S>,
|
||||
devices: () => <S><rect x="2" y="3" width="12" height="8" rx="1.5"/><path d="M5 14h6M8 11v3"/></S>,
|
||||
deviceOverview: () => <S><circle cx="8" cy="6" r="3"/><path d="M3 13c0-2.76 2.24-5 5-5s5 2.24 5 5"/></S>,
|
||||
fleet: () => <S><path d="M2 5h12M2 8h9M2 11h6"/></S>,
|
||||
devices: () => <SvgIcon Component={IcoDevices} />,
|
||||
deviceOverview: () => <SvgIcon Component={IcoDeviceOverview} />,
|
||||
fleet: () => <SvgIcon Component={IcoFleet} />,
|
||||
commandCenter: () => <S><rect x="2" y="2" width="12" height="12" rx="1.5"/><path d="M5 6l2 2-2 2M9 10h2"/></S>,
|
||||
blackBox: () => <S><rect x="2" y="4" width="12" height="8" rx="1"/><path d="M5 8h6"/></S>,
|
||||
blackBox: () => <SvgIcon Component={IcoBlackbox} />,
|
||||
deviceSettings: () => <SvgIcon Component={IcoMelodySettings} />,
|
||||
appUsers: () => <S><circle cx="6" cy="5" r="2.5"/><path d="M2 13c0-2.2 1.8-4 4-4"/><circle cx="12" cy="7" r="2"/><path d="M9.5 13c0-1.65 1.12-3 2.5-3s2.5 1.35 2.5 3"/></S>,
|
||||
melodies: () => <S><path d="M9 3v7"/><path d="M9 3l4-1v7"/><circle cx="7" cy="10" r="2"/><circle cx="11" cy="9" r="2"/></S>,
|
||||
library: () => <S><rect x="2" y="2" width="4" height="12" rx="1"/><rect x="7" y="4" width="4" height="10" rx="1"/><rect x="12" y="2" width="2" height="12" rx="1"/></S>,
|
||||
composer: () => <S><path d="M2 12L10 4l2 2-8 8H2v-2z"/><path d="M8 6l2 2"/></S>,
|
||||
archetypes: () => <S><path d="M8 2l2 4h4l-3 3 1 4-4-2.5L4 13l1-4-3-3h4z"/></S>,
|
||||
melodySettings: () => <S><path d="M2 4h2"/><path d="M6 4h8"/><circle cx="5" cy="4" r="1.5" fill="currentColor" stroke="none"/><path d="M2 8h6"/><path d="M10 8h4"/><circle cx="9" cy="8" r="1.5" fill="currentColor" stroke="none"/><path d="M2 12h9"/><path d="M13 12h1"/><circle cx="12" cy="12" r="1.5" fill="currentColor" stroke="none"/></S>,
|
||||
communications: () => <S><path d="M2 3h12v8H2z"/><path d="M5 14l3-3 3 3"/></S>,
|
||||
melodies: () => <SvgIcon Component={IcoMelodies} />,
|
||||
library: () => <SvgIcon Component={IcoMelodiesEditor} />,
|
||||
composer: () => <SvgIcon Component={IcoComposer} />,
|
||||
archetypes: () => <SvgIcon Component={IcoArchetypes} />,
|
||||
melodySettings: () => <SvgIcon Component={IcoMelodySettings} />,
|
||||
communications: () => <SvgIcon Component={IcoCommunications} />,
|
||||
mail: () => <S><rect x="2" y="4" width="12" height="9" rx="1"/><path d="M2 5l6 4 6-4"/></S>,
|
||||
whatsapp: () => <S><path d="M8 2a6 6 0 0 1 6 6c0 3.31-2.69 6-6 6a5.97 5.97 0 0 1-3.1-.86L2 14l.86-2.9A5.97 5.97 0 0 1 2 8a6 6 0 0 1 6-6z"/></S>,
|
||||
sms: () => <S><path d="M2 3h12v8H8l-3 2.5V11H2z"/></S>,
|
||||
helpdesk: () => <S><path d="M8 2a4 4 0 0 0-4 4c0 1.5.82 2.8 2 3.46V11h4V9.46A4 4 0 0 0 8 2z"/><path d="M6 13h4"/><path d="M8 11v2"/></S>,
|
||||
commsLog: () => <S><circle cx="8" cy="8" r="6"/><path d="M8 5v3l2 2"/></S>,
|
||||
customers: () => <S><rect x="3" y="2" width="10" height="7" rx="1"/><path d="M1 14c0-2.76 3.13-5 7-5s7 2.24 7 5"/></S>,
|
||||
customerOverview: () => <S><path d="M2 12l3-5 3 3 2-4 4 6"/></S>,
|
||||
orders: () => <S><rect x="3" y="2" width="10" height="12" rx="1"/><path d="M6 6h4M6 9h4M6 12h2"/></S>,
|
||||
whatsapp: () => <SvgIcon Component={IcoWhatsapp} />,
|
||||
sms: () => <SvgIcon Component={IcoSms} />,
|
||||
helpdesk: () => <SvgIcon Component={IcoHelpdesk} />,
|
||||
commsLog: () => <SvgIcon Component={IcoCommsLog} />,
|
||||
customers: () => <SvgIcon Component={IcoCustomers} />,
|
||||
customerOverview: () => <SvgIcon Component={IcoCustomerOverview} />,
|
||||
orders: () => <SvgIcon Component={IcoOrders} />,
|
||||
quotations: () => <S><rect x="3" y="2" width="10" height="12" rx="1"/><path d="M6 5h4M6 8h3M9 11l1-1 1 1 1-3"/></S>,
|
||||
products: () => <S><path d="M8 2L2 5v6l6 3 6-3V5z"/><path d="M8 2v9M2 5l6 3 6-3"/></S>,
|
||||
catalog: () => <S><rect x="2" y="2" width="5" height="5" rx="1"/><rect x="9" y="2" width="5" height="5" rx="1"/><rect x="2" y="9" width="5" height="5" rx="1"/><rect x="9" y="9" width="5" height="5" rx="1"/></S>,
|
||||
snManager: () => <S><rect x="2" y="5" width="12" height="6" rx="1"/><path d="M4 8h1M6 8h1M8 8h1M10 8h1"/></S>,
|
||||
products: () => <SvgIcon Component={IcoProducts} />,
|
||||
catalog: () => <SvgIcon Component={IcoCatalog} />,
|
||||
snManager: () => <SvgIcon Component={IcoSnManager} />,
|
||||
staffLog: () => <S><rect x="4" y="2" width="8" height="2" rx="1"/><rect x="2" y="3" width="12" height="11" rx="1"/><path d="M5 8h6M5 11h4"/></S>,
|
||||
manufacturing: () => <S><path d="M2 12l3-6 3 3 2-5 4 8H2z"/><circle cx="5" cy="5" r="1.5"/></S>,
|
||||
inventory: () => <S><rect x="2" y="7" width="5" height="7" rx="1"/><rect x="5.5" y="4" width="5" height="10" rx="1"/><rect x="9" y="2" width="5" height="12" rx="1"/></S>,
|
||||
provisioning: () => <S><path d="M8 2v8"/><path d="M5 8l3 3 3-3"/><path d="M3 13h10"/></S>,
|
||||
firmware: () => <S><rect x="3" y="4" width="10" height="8" rx="1"/><path d="M6 7h4M7 10h2"/><path d="M6 2h4M6 14h4"/></S>,
|
||||
api: () => <S><path d="M4 6l-2 2 2 2M12 6l2 2-2 2M9 4l-2 8"/></S>,
|
||||
manufacturing: () => <SvgIcon Component={IcoManufacturing} />,
|
||||
inventory: () => <SvgIcon Component={IcoInventory} />,
|
||||
provisioning: () => <SvgIcon Component={IcoProvisioning} />,
|
||||
firmware: () => <SvgIcon Component={IcoFirmware} />,
|
||||
api: () => <SvgIcon Component={IcoApi} />,
|
||||
settings: () => <S><circle cx="8" cy="8" r="2.5"/><path d="M8 2v1.5M8 12.5V14M2 8h1.5M12.5 8H14M3.5 3.5l1 1M11.5 11.5l1 1M3.5 12.5l1-1M11.5 4.5l1-1"/></S>,
|
||||
staff: () => <S><circle cx="8" cy="5" r="3"/><path d="M2 14c0-3.31 2.69-6 6-6s6 2.69 6 6"/></S>,
|
||||
publicFeatures: () => <S><path d="M2 8a6 6 0 1 0 12 0A6 6 0 0 0 2 8z"/><path d="M8 2a9 9 0 0 0 0 12M8 2a9 9 0 0 1 0 12M2 8h12"/></S>,
|
||||
@@ -99,7 +131,7 @@ const navSections = [
|
||||
{ to: '/devices', label: 'Fleet', icon: 'fleet', exact: true },
|
||||
{ to: '/mqtt/commands', label: 'Command Center', icon: 'commandCenter' },
|
||||
{ to: '/equipment/notes', label: 'BlackBox', icon: 'blackBox' },
|
||||
{ to: '/devices/settings', label: 'Device Settings', icon: 'settings', placeholder: true },
|
||||
{ to: '/devices/settings', label: 'Device Settings', icon: 'deviceSettings', placeholder: true },
|
||||
],
|
||||
},
|
||||
{ to: '/users', label: 'App Users', icon: 'appUsers', permission: 'app_users' },
|
||||
@@ -117,11 +149,11 @@ const navSections = [
|
||||
{
|
||||
label: 'Communications', icon: 'communications', permission: 'crm',
|
||||
children: [
|
||||
{ to: '/mail', label: 'Mailbox', icon: 'mail' },
|
||||
{ to: '/comms/whatsapp', label: 'WhatsApp', icon: 'whatsapp', placeholder: true },
|
||||
{ to: '/comms/sms', label: 'SMS', icon: 'sms', placeholder: true },
|
||||
{ to: '/crm/comms/helpdesk', label: 'Helpdesk', icon: 'helpdesk', exact: true },
|
||||
{ to: '/crm/comms', label: 'Comms Log', icon: 'commsLog', exact: true },
|
||||
{ to: '/mail', label: 'Mailbox', icon: 'mail' },
|
||||
{ to: '/comms/whatsapp', label: 'WhatsApp', icon: 'whatsapp', placeholder: true },
|
||||
{ to: '/comms/sms', label: 'SMS', icon: 'sms', placeholder: true },
|
||||
{ to: '/crm/comms/helpdesk', label: 'Helpdesk', icon: 'helpdesk', exact: true },
|
||||
{ to: '/crm/comms', label: 'Comms Log', icon: 'commsLog', exact: true },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -225,7 +257,7 @@ function CollapsibleGroup({ label, icon, children, currentPath, locked, open, on
|
||||
end={child.exact === true}
|
||||
className={({ isActive }) => `nav-child-link${isActive ? ' active' : ''}`}
|
||||
>
|
||||
{({ isActive }) => {
|
||||
{() => {
|
||||
const ChildIcon = Icons[child.icon] ?? Icons.placeholder
|
||||
return (
|
||||
<>
|
||||
@@ -274,7 +306,7 @@ export default function Sidebar() {
|
||||
<img
|
||||
src={logoDark}
|
||||
alt="BellSystems"
|
||||
style={{ height: '18px', width: 'auto', objectFit: 'contain' }}
|
||||
className="sidebar-brand-logo"
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user