166 lines
5.5 KiB
TypeScript
166 lines
5.5 KiB
TypeScript
"use client"
|
|
|
|
import * as React from "react"
|
|
import { useRouter } from "next/navigation"
|
|
import {
|
|
BellIcon,
|
|
LogOutIcon,
|
|
SettingsIcon,
|
|
UserIcon,
|
|
} from "lucide-react"
|
|
|
|
import { logout } from "@/lib/api/session"
|
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
|
import { Badge } from "@/components/ui/badge"
|
|
import { Button } from "@/components/ui/button"
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuGroup,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu"
|
|
import { SidebarTrigger } from "@/components/ui/sidebar"
|
|
import { GlobalCustomerSearch } from "@/components/hexa/global-customer-search"
|
|
import { RefreshButton } from "@/components/hexa/refresh-button"
|
|
import { ThemeToggle } from "@/components/hexa/theme-toggle"
|
|
import { useClientProfile } from "@/components/hexa/client-profile-context"
|
|
|
|
interface SiteHeaderProps {
|
|
date: Date | null
|
|
onDateChange: (date: Date) => void
|
|
notificationCount?: number
|
|
onRefresh?: () => void
|
|
demo?: boolean
|
|
onNavigate?: (key: string) => void
|
|
}
|
|
|
|
function SiteHeader({
|
|
date,
|
|
notificationCount = 0,
|
|
onRefresh,
|
|
demo = false,
|
|
onNavigate,
|
|
}: SiteHeaderProps) {
|
|
const fullDate = date?.toLocaleDateString("en-LK", { month: "long", day: "numeric", year: "numeric", timeZone: "Asia/Colombo" }) ?? "Today"
|
|
const compactDate = date?.toLocaleDateString("en-LK", { month: "short", day: "numeric", timeZone: "Asia/Colombo" }) ?? "Today"
|
|
const router = useRouter()
|
|
const profile = useClientProfile()
|
|
|
|
async function handleLogout() {
|
|
if (demo) {
|
|
router.push("/login")
|
|
return
|
|
}
|
|
await logout()
|
|
router.replace("/login")
|
|
}
|
|
|
|
async function navigateToProfile() {
|
|
if (onNavigate) {
|
|
onNavigate("settings")
|
|
return
|
|
}
|
|
router.push("/dashboard/settings")
|
|
}
|
|
|
|
const displayName = demo ? "Demo Gym" : profile.clientName ?? profile.gymName ?? "My Gym"
|
|
const initials =
|
|
displayName
|
|
.split(/\s+/)
|
|
.filter(Boolean)
|
|
.slice(0, 2)
|
|
.map((w) => w[0]?.toUpperCase())
|
|
.join("") || "G"
|
|
|
|
return (
|
|
<header className="sticky top-0 z-30 flex h-16 shrink-0 items-center gap-2 border-b bg-background/95 px-3 backdrop-blur supports-backdrop-filter:bg-background/80 sm:px-4">
|
|
<SidebarTrigger className="rounded-full" />
|
|
|
|
<div className="ml-1 flex h-9 shrink-0 items-center rounded-full border bg-muted/30 px-3 text-sm" aria-label="Today's date">
|
|
<span className="hidden sm:inline">{fullDate}</span>
|
|
<span className="sm:hidden">{compactDate}</span>
|
|
</div>
|
|
|
|
<div className="flex flex-1 justify-center px-2">
|
|
<GlobalCustomerSearch demo={demo} />
|
|
</div>
|
|
|
|
<div className="ml-auto flex items-center gap-2">
|
|
<RefreshButton onRefresh={onRefresh} />
|
|
<ThemeToggle />
|
|
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger
|
|
render={
|
|
<Button variant="outline" size="icon" className="relative rounded-full">
|
|
<BellIcon />
|
|
{notificationCount > 0 && (
|
|
<Badge className="absolute -top-1.5 -right-1.5 size-5 justify-center rounded-full p-0 text-[0.65rem]">
|
|
{notificationCount}
|
|
</Badge>
|
|
)}
|
|
</Button>
|
|
}
|
|
/>
|
|
<DropdownMenuContent align="end" className="w-72">
|
|
<DropdownMenuGroup>
|
|
<DropdownMenuLabel>Notifications</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem className="flex-col items-start gap-0.5">
|
|
<span className="font-medium">New membership payment received</span>
|
|
<span className="text-xs text-muted-foreground">2 minutes ago</span>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem className="flex-col items-start gap-0.5">
|
|
<span className="font-medium">Staff member added</span>
|
|
<span className="text-xs text-muted-foreground">1 hour ago</span>
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem className="flex-col items-start gap-0.5">
|
|
<span className="font-medium">Gym plan updated</span>
|
|
<span className="text-xs text-muted-foreground">Yesterday</span>
|
|
</DropdownMenuItem>
|
|
</DropdownMenuGroup>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger
|
|
render={
|
|
<button aria-label="Profile menu">
|
|
<Avatar>
|
|
<AvatarFallback className="bg-primary text-primary-foreground">
|
|
{initials}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
</button>
|
|
}
|
|
/>
|
|
<DropdownMenuContent align="end" className="w-56">
|
|
<DropdownMenuGroup>
|
|
<DropdownMenuLabel>{displayName}</DropdownMenuLabel>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem onClick={navigateToProfile}>
|
|
<UserIcon />
|
|
Profile
|
|
</DropdownMenuItem>
|
|
<DropdownMenuItem>
|
|
<SettingsIcon />
|
|
Settings
|
|
</DropdownMenuItem>
|
|
<DropdownMenuSeparator />
|
|
<DropdownMenuItem variant="destructive" onClick={handleLogout}>
|
|
<LogOutIcon />
|
|
{demo ? "Exit demo" : "Log out"}
|
|
</DropdownMenuItem>
|
|
</DropdownMenuGroup>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
</div>
|
|
</header>
|
|
)
|
|
}
|
|
|
|
export { SiteHeader }
|