feat(bookings): enhance contract viewer and backoffice UI consistency

This commit is contained in:
ghost2023
2026-06-06 09:11:13 +03:00
parent 7a19a161e1
commit da0f1e4cc9
7 changed files with 185 additions and 85 deletions

View File

@@ -1,4 +1,10 @@
import { type MouseEvent, useCallback, useEffect, useMemo, useState } from "react";
import {
type MouseEvent,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { ChevronDown, ChevronRight } from "lucide-react";
import { cn } from "@/lib/utils";
@@ -20,21 +26,28 @@ const collectSidebarHrefs = (items: SidebarItem[]): string[] =>
items.flatMap((item) => {
const hrefs: string[] = [];
if (item.href) hrefs.push(item.href.toLowerCase());
if (item.children?.length) hrefs.push(...collectSidebarHrefs(item.children));
if (item.children?.length)
hrefs.push(...collectSidebarHrefs(item.children));
return hrefs;
});
const flattenSectionItems = (sections: SidebarSection[]) =>
sections.flatMap((section) => section.items);
const FreightSidebar = ({ sections, activeHref, onNavigate }: FreightSidebarProps) => {
const FreightSidebar = ({
sections,
activeHref,
onNavigate,
}: FreightSidebarProps) => {
const items = useMemo(() => flattenSectionItems(sections), [sections]);
const activePath = activeHref?.toLowerCase() ?? "";
const isHrefActive = useCallback(
(href: string) => {
const normalized = href.toLowerCase();
return activePath === normalized || activePath.startsWith(`${normalized}/`);
return (
activePath === normalized || activePath.startsWith(`${normalized}/`)
);
},
[activePath],
);
@@ -72,7 +85,8 @@ const FreightSidebar = ({ sections, activeHref, onNavigate }: FreightSidebarProp
return acc;
}, [activePath, branchContainsActive, isHrefActive, items]);
const [expanded, setExpanded] = useState<Record<string, boolean>>(defaultExpanded);
const [expanded, setExpanded] =
useState<Record<string, boolean>>(defaultExpanded);
useEffect(() => {
setExpanded((current) => ({ ...defaultExpanded, ...current }));
@@ -108,7 +122,11 @@ const FreightSidebar = ({ sections, activeHref, onNavigate }: FreightSidebarProp
: "text-gray-900",
);
const renderNavBranch = (children: SidebarItem[], depth: number, parentKey: string) =>
const renderNavBranch = (
children: SidebarItem[],
depth: number,
parentKey: string,
) =>
children.map((child) => {
const key = sidebarItemKey(child, parentKey);
const isGroup = Boolean(child.children?.length) && !child.href;
@@ -181,7 +199,9 @@ const FreightSidebar = ({ sections, activeHref, onNavigate }: FreightSidebarProp
const hasChildren = Boolean(item.children?.length);
const itemHref = item.href.toLowerCase();
const childActive = hasChildren ? branchContainsActive(item.children!) : false;
const childActive = hasChildren
? branchContainsActive(item.children!)
: false;
const isCurrentItem = hasChildren
? activePath === itemHref
: isHrefActive(itemHref);
@@ -206,10 +226,12 @@ const FreightSidebar = ({ sections, activeHref, onNavigate }: FreightSidebarProp
href={item.href}
onClick={(event) => navigateTo(event, item.href!)}
aria-current={isCurrentItem ? "page" : undefined}
className="flex min-w-0 flex-1 items-center gap-3 px-3 py-2.5 text-base font-medium leading-snug"
className="flex min-w-0 flex-1 items-center gap-3 px-3 py-2.5 text-sm leading-snug"
>
{item.icon ? (
<span className={iconClass(leafActive, isActive)}>{item.icon}</span>
<span className={iconClass(leafActive, isActive)}>
{item.icon}
</span>
) : null}
<span className="truncate">{item.label}</span>
</a>
@@ -257,10 +279,12 @@ const FreightSidebar = ({ sections, activeHref, onNavigate }: FreightSidebarProp
};
return (
<aside className="flex h-full max-h-full w-[340px] shrink-0 flex-col overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm">
<aside className="flex h-full max-h-full w-[280px] shrink-0 flex-col overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm">
<div className="flex shrink-0 items-center gap-2.5 border-b border-gray-100 px-5 py-5">
<img src={EDR_LOGO} alt="EDR Freight" className="h-9 w-auto" />
<span className="text-lg font-semibold tracking-tight text-gray-900">EDR Freight</span>
<span className="text-lg font-semibold tracking-tight text-gray-900">
EDR Freight
</span>
</div>
<nav className="flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto overscroll-contain px-3 py-4">
@@ -270,7 +294,7 @@ const FreightSidebar = ({ sections, activeHref, onNavigate }: FreightSidebarProp
className={cn(
"px-3 pb-1 text-xs font-semibold uppercase tracking-wide",
// Use a very light gray for ALL section titles, not just when mutedTitle is specified
"text-gray-400"
"text-sidebar-secondary-foreground",
)}
>
{section.title}