157 lines
6.2 KiB
TypeScript
157 lines
6.2 KiB
TypeScript
import React, { useEffect, useRef, useState } from 'react';
|
|
import { useAuth } from '../context/AuthContext.tsx';
|
|
import { useFarm } from '../context/FarmContext.tsx';
|
|
import { formatDateId } from '../utils/format.ts';
|
|
|
|
type HeaderProps = {
|
|
onToggleSidebar: () => void;
|
|
isTimelineVisible: boolean;
|
|
onToggleTimeline: () => void;
|
|
};
|
|
|
|
const Header: React.FC<HeaderProps> = ({
|
|
onToggleSidebar,
|
|
isTimelineVisible,
|
|
onToggleTimeline,
|
|
}) => {
|
|
const { user, logout } = useAuth();
|
|
const {
|
|
sites,
|
|
kandangs,
|
|
cycles,
|
|
selectedSiteId,
|
|
selectedKandangId,
|
|
selectedCycleId,
|
|
setSelectedSiteId,
|
|
setSelectedKandangId,
|
|
setSelectedCycleId,
|
|
} = useFarm();
|
|
const [open, setOpen] = useState(false);
|
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
|
|
useEffect(() => {
|
|
const onClick = (event: MouseEvent) => {
|
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {
|
|
setOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', onClick);
|
|
return () => document.removeEventListener('mousedown', onClick);
|
|
}, []);
|
|
|
|
if (!user) return null;
|
|
|
|
return (
|
|
<header className="bg-white shadow-sm h-16 flex-shrink-0 z-10 border-b border-gray-200">
|
|
<div className="flex items-center justify-between h-16 px-4 sm:px-6 lg:px-8">
|
|
<div className="flex items-center gap-3 flex-wrap">
|
|
<button
|
|
onClick={onToggleSidebar}
|
|
className="text-gray-500 hover:text-gray-800 focus:outline-none -ml-2"
|
|
aria-label="Toggle sidebar"
|
|
>
|
|
<i className="fa-solid fa-bars text-xl w-10 h-10 p-2 rounded-full hover:bg-gray-100" />
|
|
</button>
|
|
<div className="flex items-center gap-2">
|
|
<i className="fa-solid fa-map-marker-alt text-gray-500" />
|
|
<select
|
|
value={selectedSiteId ?? ''}
|
|
onChange={(e) => setSelectedSiteId(e.target.value ? Number(e.target.value) : null)}
|
|
className="px-3 py-1.5 border border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 bg-white text-sm text-gray-900"
|
|
>
|
|
{sites.length === 0 && <option value="">Tidak ada site</option>}
|
|
{sites.map((site) => (
|
|
<option key={site.id} value={site.id}>
|
|
{site.site_name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<i className="fa-solid fa-warehouse text-gray-500" />
|
|
<select
|
|
value={selectedKandangId ?? ''}
|
|
onChange={(e) => setSelectedKandangId(e.target.value ? Number(e.target.value) : null)}
|
|
className="px-3 py-1.5 border border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 bg-white text-sm text-gray-900"
|
|
>
|
|
{kandangs.length === 0 && <option value="">Tidak ada kandang</option>}
|
|
{kandangs.map((kandang) => (
|
|
<option key={kandang.id} value={kandang.id}>
|
|
{kandang.kandang_name}
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<i className="fa-solid fa-rotate text-gray-500" />
|
|
<select
|
|
value={selectedCycleId ?? ''}
|
|
onChange={(e) => setSelectedCycleId(e.target.value ? Number(e.target.value) : null)}
|
|
className="px-3 py-1.5 border border-gray-300 rounded-md shadow-sm focus:ring-red-500 focus:border-red-500 bg-white text-sm text-gray-900"
|
|
>
|
|
{cycles.length === 0 && <option value="">Tidak ada siklus</option>}
|
|
{cycles.map((cycle) => (
|
|
<option key={cycle.id} value={cycle.id}>
|
|
{formatDateId(cycle.start_date)} (
|
|
{cycle.status === 'active' ? 'Aktif' : 'Selesai'})
|
|
</option>
|
|
))}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div className="flex items-center ml-auto">
|
|
<button
|
|
onClick={onToggleTimeline}
|
|
title="Toggle Linimasa"
|
|
className="hidden sm:flex items-center text-gray-500 hover:text-gray-800 focus:outline-none p-2 rounded-full hover:bg-gray-100"
|
|
>
|
|
<i
|
|
className={`fa-solid fa-calendar-week text-xl ${isTimelineVisible ? 'text-red-600' : 'text-gray-400'}`}
|
|
/>
|
|
</button>
|
|
|
|
<div className="relative ml-4" ref={dropdownRef}>
|
|
<button
|
|
onClick={() => setOpen(!open)}
|
|
className="flex items-center space-x-2 focus:outline-none"
|
|
>
|
|
<div className="w-10 h-10 rounded-full bg-red-100 text-red-700 flex items-center justify-center font-bold">
|
|
{user.user_name.slice(0, 1).toUpperCase()}
|
|
</div>
|
|
<div className="hidden lg:block text-left">
|
|
<p className="text-sm font-semibold text-gray-800">{user.user_name}</p>
|
|
<p className="text-xs text-gray-500">
|
|
{user.status === 'superadmin' ? 'Admin' : 'Pengguna'}
|
|
</p>
|
|
</div>
|
|
<i className="fa-solid fa-chevron-down text-xs text-gray-500" />
|
|
</button>
|
|
{open && (
|
|
<div className="origin-top-right absolute right-0 mt-2 w-56 rounded-md shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5 z-50">
|
|
<div className="px-4 py-3 border-b">
|
|
<p className="text-sm font-semibold text-gray-800">{user.user_name}</p>
|
|
<p className="text-xs text-gray-500">
|
|
{user.status === 'superadmin' ? 'Admin' : 'Pengguna'}
|
|
</p>
|
|
</div>
|
|
<button
|
|
onClick={() => {
|
|
void logout();
|
|
setOpen(false);
|
|
}}
|
|
className="w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100 flex items-center"
|
|
>
|
|
<i className="fa-solid fa-sign-out-alt w-4 mr-2 text-gray-500" />
|
|
Keluar
|
|
</button>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</header>
|
|
);
|
|
};
|
|
|
|
export default Header;
|