diff --git a/app/(protected)/layout.tsx b/app/(protected)/layout.tsx
index e5f4289..fd98d25 100644
--- a/app/(protected)/layout.tsx
+++ b/app/(protected)/layout.tsx
@@ -1,9 +1,8 @@
-// app/(protected)/layout.tsx
export const dynamic = 'force-dynamic'
import { createClient } from '@/lib/supabase/server'
import { redirect } from 'next/navigation'
-import { NotificationBell } from '@/components/notifications/bell'
+import { Sidebar } from '@/components/layout/sidebar'
export default async function ProtectedLayout({
children,
@@ -17,10 +16,22 @@ export default async function ProtectedLayout({
if (!user) redirect('/login')
+ const { data: profile } = await supabase
+ .from('users')
+ .select('role, name, email')
+ .eq('id', user.id)
+ .single()
+
return (
- <>
-
- {children}
- >
+
)
}
diff --git a/components/layout/sidebar.tsx b/components/layout/sidebar.tsx
new file mode 100644
index 0000000..4ccfef3
--- /dev/null
+++ b/components/layout/sidebar.tsx
@@ -0,0 +1,216 @@
+'use client'
+
+import Link from 'next/link'
+import { usePathname, useRouter } from 'next/navigation'
+import { createBrowserClient } from '@supabase/ssr'
+import { NotificationBell } from '@/components/notifications/bell'
+
+interface NavItem {
+ label: string
+ href: string
+ icon: React.ReactNode
+}
+
+interface SidebarProps {
+ role: string
+ userName: string
+ userEmail: string
+}
+
+const IconGrid = () => (
+
+)
+const IconList = () => (
+
+)
+const IconCheck = () => (
+
+)
+const IconChart = () => (
+
+)
+const IconSettings = () => (
+
+)
+const IconUsers = () => (
+
+)
+const IconPlus = () => (
+
+)
+const IconLogout = () => (
+
+)
+
+const NAV_ITEMS: Record = {
+ reporter: [
+ { label: 'My Reports', href: '/reporter', icon: },
+ { label: 'New Incident', href: '/report', icon: },
+ ],
+ supervisor: [
+ { label: 'Overview', href: '/supervisor', icon: },
+ { label: 'Incidents', href: '/supervisor/incidents', icon: },
+ ],
+ hse: [
+ { label: 'Dashboard', href: '/hse/dashboard', icon: },
+ { label: 'Incidents', href: '/hse/incidents', icon: },
+ { label: 'CAPA', href: '/hse/capa', icon: },
+ { label: 'Settings', href: '/hse/settings', icon: },
+ ],
+ management: [
+ { label: 'Dashboard', href: '/management', icon: },
+ ],
+ capa_owner: [
+ { label: 'My CAPAs', href: '/capa-owner', icon: },
+ ],
+ admin: [
+ { label: 'Admin', href: '/admin', icon: },
+ { label: 'Dashboard', href: '/hse/dashboard', icon: },
+ { label: 'Incidents', href: '/hse/incidents', icon: },
+ { label: 'CAPA', href: '/hse/capa', icon: },
+ { label: 'Management', href: '/management', icon: },
+ ],
+}
+
+function initials(name: string): string {
+ return name
+ .split(' ')
+ .map(w => w[0] ?? '')
+ .join('')
+ .toUpperCase()
+ .slice(0, 2)
+}
+
+function useNavItems(role: string): NavItem[] {
+ return NAV_ITEMS[role] ?? NAV_ITEMS.reporter
+}
+
+function NavLink({ item, pathname }: { item: NavItem; pathname: string }) {
+ // Exact match for root-level single-page roles, prefix match otherwise
+ const exactRoots = ['/reporter', '/supervisor', '/management', '/capa-owner', '/admin', '/report']
+ const isActive = exactRoots.includes(item.href)
+ ? pathname === item.href
+ : pathname.startsWith(item.href)
+
+ return (
+
+ {item.icon}
+ {item.label}
+
+ )
+}
+
+export function Sidebar({ role, userName, userEmail }: SidebarProps) {
+ const pathname = usePathname()
+ const router = useRouter()
+ const items = useNavItems(role)
+ const mobileItems = items.slice(0, 4)
+
+ const logout = async () => {
+ const supabase = createBrowserClient(
+ process.env.NEXT_PUBLIC_SUPABASE_URL!,
+ process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
+ )
+ await supabase.auth.signOut()
+ router.push('/login')
+ }
+
+ const displayName = userName || userEmail || 'User'
+ const roleLabel = role.replace(/_/g, ' ').replace(/\b\w/g, c => c.toUpperCase())
+
+ return (
+ <>
+ {/* Desktop sidebar */}
+
+
+ {/* Mobile bottom tab bar */}
+
+ >
+ )
+}