MotorSignal-App/website/components/ops/operations-sidebar.tsx

184 lines
5.8 KiB
TypeScript

'use client';
import React from 'react';
import { OperationsNavId, OperationsRole } from './types';
interface OperationsSidebarProps {
activeNav: OperationsNavId;
onSelectNav: (nav: OperationsNavId) => void;
isOpenOnMobile: boolean;
onCloseMobile: () => void;
role: OperationsRole;
}
export function OperationsSidebar({
activeNav,
onSelectNav,
isOpenOnMobile,
onCloseMobile,
role,
}: OperationsSidebarProps) {
const isOwner = role === 'owner';
const navItems: Array<{
id: OperationsNavId;
label: string;
icon: string;
badge?: string;
badgeType?: 'primary' | 'neutral' | 'disabled';
}> = [
{
id: 'overview',
label: 'Overview',
icon: 'dashboard',
},
{
id: 'users',
label: 'Users',
icon: 'person_search',
},
{
id: 'incidents',
label: 'Incidents',
icon: 'assignment',
},
{
id: 'support',
label: 'Support Center',
icon: 'contact_support',
},
{
id: 'cms',
label: 'Website Content',
icon: 'edit_note',
},
{
id: 'audit',
label: 'Audit',
icon: 'verified_user',
badge: isOwner ? 'Owner' : 'Restricted',
badgeType: isOwner ? 'primary' : 'neutral',
},
{
id: 'operators',
label: 'Operators',
icon: 'group',
badge: 'Disabled',
badgeType: 'disabled',
},
{
id: 'session',
label: 'My Session',
icon: 'badge',
},
];
const sidebarContent = (
<div className="flex flex-col h-full bg-surface-container-lowest border-r border-outline-variant/30 text-on-surface">
{/* Brand Masthead */}
<div className="h-16 px-6 flex items-center gap-3 border-b border-outline-variant/30 shrink-0">
<div className="w-8 h-8 rounded-lg bg-surface-container-high border border-primary-container/40 flex items-center justify-center text-primary-container shadow-[0_0_12px_rgba(0,242,254,0.2)]">
<span className="material-symbols-outlined text-[18px]">terminal</span>
</div>
<div className="flex flex-col">
<span className="font-headline-sm text-sm font-bold tracking-tight text-on-surface">
Operations
</span>
<span className="text-[10px] font-mono text-primary-fixed-dim">
Platform Console
</span>
</div>
</div>
{/* Navigation Links */}
<nav className="flex-1 px-3 py-4 space-y-1 overflow-y-auto" aria-label="Operations Navigation">
{navItems.map((item) => {
const isActive = activeNav === item.id;
return (
<button
key={item.id}
type="button"
onClick={() => {
onSelectNav(item.id);
onCloseMobile();
}}
aria-current={isActive ? 'page' : undefined}
className={`w-full flex items-center justify-between px-3 py-2.5 rounded-lg text-xs font-medium transition-all group focus-visible:ring-2 focus-visible:ring-primary-container focus-visible:outline-none ${
isActive
? 'bg-surface-container-high text-primary-container font-semibold border-l-2 border-primary-container'
: 'text-on-surface-variant hover:text-on-surface hover:bg-surface-container-low'
}`}
>
<div className="flex items-center gap-2.5">
<span
className={`material-symbols-outlined text-[18px] transition-colors ${
isActive
? 'text-primary-container'
: 'text-outline group-hover:text-on-surface'
}`}
>
{item.icon}
</span>
<span>{item.label}</span>
</div>
{item.badge && (
<span
className={`px-1.5 py-0.5 rounded text-[10px] font-mono ${
item.badgeType === 'primary'
? 'bg-primary-container/15 text-primary-container border border-primary-container/30'
: item.badgeType === 'disabled'
? 'bg-surface-container-highest/60 text-outline border border-outline-variant/20'
: 'bg-surface-container text-on-surface-variant border border-outline-variant/30'
}`}
>
{item.badge}
</span>
)}
</button>
);
})}
</nav>
{/* Footer Authority Boundary Notice */}
<div className="p-4 border-t border-outline-variant/20 bg-surface-container-lowest/50 text-[10px] text-outline space-y-1">
<div className="flex items-center gap-1 font-mono text-on-surface-variant">
<span className="material-symbols-outlined text-[12px] text-primary-fixed-dim">
shield_lock
</span>
<span>Isolated Session</span>
</div>
<p className="leading-tight">
Authority strictly bounded to <code className="text-on-surface font-mono">ops.operators</code>.
</p>
</div>
</div>
);
return (
<>
{/* Desktop Persistent Sidebar */}
<aside className="hidden lg:block fixed top-0 bottom-0 left-0 w-64 z-40">
{sidebarContent}
</aside>
{/* Mobile Backdrop & Drawer */}
{isOpenOnMobile && (
<div className="lg:hidden fixed inset-0 z-50 flex">
{/* Backdrop */}
<div
className="fixed inset-0 bg-black/60 backdrop-blur-sm transition-opacity"
onClick={onCloseMobile}
aria-hidden="true"
/>
{/* Drawer Panel */}
<div className="relative w-64 max-w-[80vw] h-full shadow-2xl z-10 animate-in slide-in-from-left duration-200">
{sidebarContent}
</div>
</div>
)}
</>
);
}