210 lines
8.2 KiB
TypeScript
210 lines
8.2 KiB
TypeScript
'use client';
|
|
|
|
import React, { useState } from 'react';
|
|
import {
|
|
MagnifyingGlassIcon,
|
|
BellIcon,
|
|
Bars3Icon,
|
|
ChevronDownIcon
|
|
} from '@heroicons/react/24/outline';
|
|
import { cn } from '@/utils/cn';
|
|
import { useAuth } from '@/contexts/AuthContext';
|
|
import Link from 'next/link';
|
|
|
|
interface TopBarProps {
|
|
onMenuClick: () => void;
|
|
sidebarCollapsed: boolean;
|
|
}
|
|
|
|
export function TopBar({ onMenuClick, sidebarCollapsed }: TopBarProps) {
|
|
const { user, logout } = useAuth();
|
|
const [searchQuery, setSearchQuery] = useState('');
|
|
const [showNotifications, setShowNotifications] = useState(false);
|
|
const [showUserMenu, setShowUserMenu] = useState(false);
|
|
|
|
const getUserInitials = (name: string) => {
|
|
return name.split(' ').map(n => n[0]).join('').toUpperCase();
|
|
};
|
|
|
|
const notifications = [
|
|
{
|
|
id: 1,
|
|
title: 'Tugas Baru Tersedia',
|
|
message: 'HACCP - Hazard Analysis Critical Control Point',
|
|
time: '5 menit yang lalu',
|
|
isRead: false
|
|
},
|
|
{
|
|
id: 2,
|
|
title: 'Ujian Akan Dimulai',
|
|
message: 'SLHS - Sanitasi Lingkungan Hidup Sehat dalam 30 menit',
|
|
time: '25 menit yang lalu',
|
|
isRead: false
|
|
},
|
|
{
|
|
id: 3,
|
|
title: 'Sertifikat Siap Diunduh',
|
|
message: 'Sertifikat Halal - Sistem Jaminan Halal telah tersedia',
|
|
time: '2 jam yang lalu',
|
|
isRead: true
|
|
}
|
|
];
|
|
|
|
const unreadCount = notifications.filter(n => !n.isRead).length;
|
|
|
|
return (
|
|
<header className="bg-white border-b border-gray-200 h-16">
|
|
<div className="flex items-center justify-between h-full px-4 sm:px-6 lg:px-8">
|
|
{/* Left side */}
|
|
<div className="flex items-center space-x-4">
|
|
{/* Mobile menu button */}
|
|
<button
|
|
onClick={onMenuClick}
|
|
className="lg:hidden rounded-md p-2 text-gray-400 hover:bg-gray-100 hover:text-gray-500"
|
|
>
|
|
<Bars3Icon className="h-6 w-6" />
|
|
</button>
|
|
|
|
{/* Search bar */}
|
|
<div className="relative flex-1 max-w-lg">
|
|
<div className="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
|
|
<MagnifyingGlassIcon className="h-5 w-5 text-gray-400" />
|
|
</div>
|
|
<input
|
|
type="text"
|
|
value={searchQuery}
|
|
onChange={(e) => setSearchQuery(e.target.value)}
|
|
className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-lg leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-primary-500 focus:border-primary-500 sm:text-sm"
|
|
placeholder="Cari kursus, materi, atau instruktur..."
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Right side */}
|
|
<div className="flex items-center space-x-4">
|
|
{/* Notifications */}
|
|
<div className="relative">
|
|
<button
|
|
onClick={() => setShowNotifications(!showNotifications)}
|
|
className="relative rounded-full p-2 text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
|
>
|
|
<BellIcon className="h-6 w-6" />
|
|
{unreadCount > 0 && (
|
|
<span className="absolute -top-1 -right-1 h-5 w-5 rounded-full bg-red-500 flex items-center justify-center">
|
|
<span className="text-xs font-medium text-white">{unreadCount}</span>
|
|
</span>
|
|
)}
|
|
</button>
|
|
|
|
{/* Notifications dropdown */}
|
|
{showNotifications && (
|
|
<div className="absolute right-0 mt-2 w-80 bg-white rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 z-50">
|
|
<div className="p-4">
|
|
<h3 className="text-sm font-medium text-gray-900 mb-3">Notifikasi</h3>
|
|
<div className="space-y-3 max-h-64 overflow-y-auto">
|
|
{notifications.map((notification) => (
|
|
<div
|
|
key={notification.id}
|
|
className={cn(
|
|
'p-3 rounded-lg border cursor-pointer hover:bg-gray-50',
|
|
notification.isRead ? 'bg-white border-gray-200' : 'bg-blue-50 border-blue-200'
|
|
)}
|
|
>
|
|
<div className="flex justify-between items-start">
|
|
<div className="flex-1">
|
|
<p className="text-sm font-medium text-gray-900">
|
|
{notification.title}
|
|
</p>
|
|
<p className="text-sm text-gray-600 mt-1">
|
|
{notification.message}
|
|
</p>
|
|
<p className="text-xs text-gray-400 mt-2">
|
|
{notification.time}
|
|
</p>
|
|
</div>
|
|
{!notification.isRead && (
|
|
<div className="w-2 h-2 bg-blue-500 rounded-full ml-2 mt-1"></div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
))}
|
|
</div>
|
|
<div className="mt-3 pt-3 border-t border-gray-200">
|
|
<button className="text-sm text-primary-600 hover:text-primary-700 font-medium">
|
|
Lihat Semua Notifikasi
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* User menu */}
|
|
<div className="relative">
|
|
<button
|
|
onClick={() => setShowUserMenu(!showUserMenu)}
|
|
className="flex items-center space-x-3 rounded-full p-2 text-sm hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-primary-500"
|
|
>
|
|
<div className="h-8 w-8 rounded-full bg-gradient-to-r from-primary-500 to-secondary-500 flex items-center justify-center">
|
|
<span className="text-sm font-medium text-white">
|
|
{user ? getUserInitials(user.name) : 'U'}
|
|
</span>
|
|
</div>
|
|
<div className="hidden md:block text-left">
|
|
<p className="text-sm font-medium text-gray-700">{user?.name || 'User'}</p>
|
|
<p className="text-xs text-gray-500 capitalize">{user?.role || 'User'}</p>
|
|
</div>
|
|
<ChevronDownIcon className="h-4 w-4 text-gray-400" />
|
|
</button>
|
|
|
|
{/* User dropdown */}
|
|
{showUserMenu && (
|
|
<div className="absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg ring-1 ring-black ring-opacity-5 z-50">
|
|
<div className="py-1">
|
|
<Link
|
|
href="/profile"
|
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
onClick={() => setShowUserMenu(false)}
|
|
>
|
|
Profil Saya
|
|
</Link>
|
|
<Link
|
|
href="/settings"
|
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
onClick={() => setShowUserMenu(false)}
|
|
>
|
|
Pengaturan
|
|
</Link>
|
|
<Link
|
|
href="/help"
|
|
className="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
onClick={() => setShowUserMenu(false)}
|
|
>
|
|
Bantuan
|
|
</Link>
|
|
<div className="border-t border-gray-100">
|
|
<button
|
|
className="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100"
|
|
onClick={async () => {
|
|
try {
|
|
setShowUserMenu(false);
|
|
logout();
|
|
} catch (error) {
|
|
console.error('Logout button error:', error);
|
|
}
|
|
}}
|
|
>
|
|
Keluar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
}
|
|
|
|
export default TopBar; |