189 lines
5.9 KiB
TypeScript
189 lines
5.9 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: 'infrastructure',
|
|
label: 'Infrastructure',
|
|
icon: 'dns',
|
|
},
|
|
{
|
|
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>
|
|
)}
|
|
</>
|
|
);
|
|
}
|