first commit
This commit is contained in:
commit
367533c02b
171 files changed
+101175
No files matched your search
@@ -0,0 +1,156 @@
|
||||
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;
|
||||
Reference in new issue
Block a user