small adaptions
This commit is contained in:
@@ -6,16 +6,38 @@ import {
|
||||
FolderOpen,
|
||||
BarChart3,
|
||||
LogOut,
|
||||
ChevronDown,
|
||||
Settings,
|
||||
} from "lucide-react";
|
||||
import { useAuth } from "@/contexts/AuthContext";
|
||||
import { useState, useRef, useEffect } from "react";
|
||||
|
||||
export function Navbar() {
|
||||
const { user, logout } = useAuth();
|
||||
const [managementOpen, setManagementOpen] = useState(false);
|
||||
const dropdownRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const navItems = [
|
||||
// Close dropdown when clicking outside
|
||||
useEffect(() => {
|
||||
function handleClickOutside(event: MouseEvent) {
|
||||
if (
|
||||
dropdownRef.current &&
|
||||
!dropdownRef.current.contains(event.target as Node)
|
||||
) {
|
||||
setManagementOpen(false);
|
||||
}
|
||||
}
|
||||
document.addEventListener("mousedown", handleClickOutside);
|
||||
return () => document.removeEventListener("mousedown", handleClickOutside);
|
||||
}, []);
|
||||
|
||||
const mainNavItems = [
|
||||
{ to: "/dashboard", label: "Dashboard", icon: Clock },
|
||||
{ to: "/time-entries", label: "Time Entries", icon: List },
|
||||
{ to: "/statistics", label: "Statistics", icon: BarChart3 },
|
||||
];
|
||||
|
||||
const managementItems = [
|
||||
{ to: "/clients", label: "Clients", icon: Briefcase },
|
||||
{ to: "/projects", label: "Projects", icon: FolderOpen },
|
||||
];
|
||||
@@ -32,7 +54,8 @@ export function Navbar() {
|
||||
</span>
|
||||
</div>
|
||||
<div className="hidden sm:ml-8 sm:flex sm:space-x-4">
|
||||
{navItems.map((item) => (
|
||||
{/* Main Navigation Items */}
|
||||
{mainNavItems.map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
@@ -48,6 +71,46 @@ export function Navbar() {
|
||||
{item.label}
|
||||
</NavLink>
|
||||
))}
|
||||
|
||||
{/* Management Dropdown */}
|
||||
<div className="relative" ref={dropdownRef}>
|
||||
<button
|
||||
onClick={() => setManagementOpen(!managementOpen)}
|
||||
className={`inline-flex h-full items-center px-3 py-2 text-sm font-medium rounded-md transition-colors ${
|
||||
managementOpen
|
||||
? "text-primary-600 bg-primary-50"
|
||||
: "text-gray-600 hover:text-gray-900 hover:bg-gray-50"
|
||||
}`}
|
||||
>
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
<span>Management</span>
|
||||
<ChevronDown
|
||||
className={`h-4 w-4 ml-1 transition-transform ${managementOpen ? "rotate-180" : ""}`}
|
||||
/>
|
||||
</button>
|
||||
|
||||
{managementOpen && (
|
||||
<div className="absolute top-full left-0 mt-1 w-48 bg-white rounded-lg shadow-lg border border-gray-200 py-1 z-50">
|
||||
{managementItems.map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
onClick={() => setManagementOpen(false)}
|
||||
className={({ isActive }) =>
|
||||
`flex items-center px-4 py-2 text-sm transition-colors ${
|
||||
isActive
|
||||
? "bg-primary-50 text-primary-600"
|
||||
: "text-gray-700 hover:bg-gray-50"
|
||||
}`
|
||||
}
|
||||
>
|
||||
<item.icon className="h-4 w-4 mr-2" />
|
||||
{item.label}
|
||||
</NavLink>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center space-x-4">
|
||||
@@ -64,15 +127,17 @@ export function Navbar() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mobile navigation */}
|
||||
<div className="sm:hidden border-t border-gray-200">
|
||||
<div className="flex justify-around py-2">
|
||||
{navItems.map((item) => (
|
||||
<div className="flex flex-wrap">
|
||||
{/* Mobile: Show all nav items directly (no dropdown) */}
|
||||
{[...mainNavItems, ...managementItems].map((item) => (
|
||||
<NavLink
|
||||
key={item.to}
|
||||
to={item.to}
|
||||
className={({ isActive }) =>
|
||||
`flex flex-col items-center p-2 text-xs font-medium rounded-md ${
|
||||
`flex-1 min-w-[80px] flex flex-col items-center p-2 text-xs font-medium ${
|
||||
isActive ? "text-primary-600" : "text-gray-600"
|
||||
}`
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import { useTimeEntries } from "@/hooks/useTimeEntries";
|
||||
import {
|
||||
formatDate,
|
||||
formatDurationFromDates,
|
||||
formatDuration,
|
||||
formatDurationHoursMinutes,
|
||||
} from "@/utils/dateUtils";
|
||||
import { startOfDay, endOfDay } from "date-fns";
|
||||
|
||||
@@ -40,7 +40,7 @@ export function DashboardPage() {
|
||||
<StatCard
|
||||
icon={Clock}
|
||||
label="Today"
|
||||
value={formatDuration(totalTodaySeconds)}
|
||||
value={formatDurationHoursMinutes(totalTodaySeconds)}
|
||||
color="blue"
|
||||
/>
|
||||
<StatCard
|
||||
|
||||
@@ -30,6 +30,19 @@ export function formatDuration(totalSeconds: number): string {
|
||||
return parts.join(":");
|
||||
}
|
||||
|
||||
export function formatDurationHoursMinutes(totalSeconds: number): string {
|
||||
const hours = Math.floor(totalSeconds / 3600);
|
||||
const minutes = Math.floor((totalSeconds % 3600) / 60);
|
||||
|
||||
if (hours === 0) {
|
||||
return `${minutes}m`;
|
||||
}
|
||||
if (minutes === 0) {
|
||||
return `${hours}h`;
|
||||
}
|
||||
return `${hours}h ${minutes}m`;
|
||||
}
|
||||
|
||||
export function calculateDuration(startTime: string, endTime: string): number {
|
||||
const start = parseISO(startTime);
|
||||
const end = parseISO(endTime);
|
||||
|
||||
Reference in New Issue
Block a user