small adaptions
This commit is contained in:
@@ -6,16 +6,38 @@ import {
|
|||||||
FolderOpen,
|
FolderOpen,
|
||||||
BarChart3,
|
BarChart3,
|
||||||
LogOut,
|
LogOut,
|
||||||
|
ChevronDown,
|
||||||
|
Settings,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useAuth } from "@/contexts/AuthContext";
|
import { useAuth } from "@/contexts/AuthContext";
|
||||||
|
import { useState, useRef, useEffect } from "react";
|
||||||
|
|
||||||
export function Navbar() {
|
export function Navbar() {
|
||||||
const { user, logout } = useAuth();
|
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: "/dashboard", label: "Dashboard", icon: Clock },
|
||||||
{ to: "/time-entries", label: "Time Entries", icon: List },
|
{ to: "/time-entries", label: "Time Entries", icon: List },
|
||||||
{ to: "/statistics", label: "Statistics", icon: BarChart3 },
|
{ to: "/statistics", label: "Statistics", icon: BarChart3 },
|
||||||
|
];
|
||||||
|
|
||||||
|
const managementItems = [
|
||||||
{ to: "/clients", label: "Clients", icon: Briefcase },
|
{ to: "/clients", label: "Clients", icon: Briefcase },
|
||||||
{ to: "/projects", label: "Projects", icon: FolderOpen },
|
{ to: "/projects", label: "Projects", icon: FolderOpen },
|
||||||
];
|
];
|
||||||
@@ -32,7 +54,8 @@ export function Navbar() {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="hidden sm:ml-8 sm:flex sm:space-x-4">
|
<div className="hidden sm:ml-8 sm:flex sm:space-x-4">
|
||||||
{navItems.map((item) => (
|
{/* Main Navigation Items */}
|
||||||
|
{mainNavItems.map((item) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={item.to}
|
key={item.to}
|
||||||
to={item.to}
|
to={item.to}
|
||||||
@@ -48,6 +71,46 @@ export function Navbar() {
|
|||||||
{item.label}
|
{item.label}
|
||||||
</NavLink>
|
</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>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
@@ -64,15 +127,17 @@ export function Navbar() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile navigation */}
|
{/* Mobile navigation */}
|
||||||
<div className="sm:hidden border-t border-gray-200">
|
<div className="sm:hidden border-t border-gray-200">
|
||||||
<div className="flex justify-around py-2">
|
<div className="flex flex-wrap">
|
||||||
{navItems.map((item) => (
|
{/* Mobile: Show all nav items directly (no dropdown) */}
|
||||||
|
{[...mainNavItems, ...managementItems].map((item) => (
|
||||||
<NavLink
|
<NavLink
|
||||||
key={item.to}
|
key={item.to}
|
||||||
to={item.to}
|
to={item.to}
|
||||||
className={({ isActive }) =>
|
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"
|
isActive ? "text-primary-600" : "text-gray-600"
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useTimeEntries } from "@/hooks/useTimeEntries";
|
|||||||
import {
|
import {
|
||||||
formatDate,
|
formatDate,
|
||||||
formatDurationFromDates,
|
formatDurationFromDates,
|
||||||
formatDuration,
|
formatDurationHoursMinutes,
|
||||||
} from "@/utils/dateUtils";
|
} from "@/utils/dateUtils";
|
||||||
import { startOfDay, endOfDay } from "date-fns";
|
import { startOfDay, endOfDay } from "date-fns";
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ export function DashboardPage() {
|
|||||||
<StatCard
|
<StatCard
|
||||||
icon={Clock}
|
icon={Clock}
|
||||||
label="Today"
|
label="Today"
|
||||||
value={formatDuration(totalTodaySeconds)}
|
value={formatDurationHoursMinutes(totalTodaySeconds)}
|
||||||
color="blue"
|
color="blue"
|
||||||
/>
|
/>
|
||||||
<StatCard
|
<StatCard
|
||||||
|
|||||||
@@ -30,6 +30,19 @@ export function formatDuration(totalSeconds: number): string {
|
|||||||
return parts.join(":");
|
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 {
|
export function calculateDuration(startTime: string, endTime: string): number {
|
||||||
const start = parseISO(startTime);
|
const start = parseISO(startTime);
|
||||||
const end = parseISO(endTime);
|
const end = parseISO(endTime);
|
||||||
|
|||||||
Reference in New Issue
Block a user