Files
sp80/sample_interface/src/app/components/Sidebar.tsx
admin 30397732f9 fix(ui): Header branding, sidebar collapse, and layout fitting 1024x600
- Header: Change 'Data Station' to 'RTU', use TCK logo, fix sizing
- Sidebar: MENU text now clickable to collapse/expand
- RainfallView: Fit 1024x600 without scrolling
- GraphView: Fit 1024x600 screen
2026-03-13 11:26:26 +08:00

194 lines
5.8 KiB
TypeScript

import { useState } from "react";
import { Link, useLocation } from "react-router";
import {
Home,
BarChart3,
Settings,
Wrench,
Gauge,
HardDrive,
LogIn,
ChevronRight,
ChevronDown
} from "lucide-react";
interface SidebarProps {
collapsed: boolean;
onToggle: () => void;
}
interface MenuItem {
id: string;
label: string;
icon: React.ReactNode;
path?: string;
children?: SubMenuItem[];
}
interface SubMenuItem {
id: string;
label: string;
path: string;
}
const menuItems: MenuItem[] = [
{
id: "home",
label: "HOME",
icon: <Home className="w-5 h-5" />,
path: "/rainfall",
},
{
id: "graph",
label: "GRAPH",
icon: <BarChart3 className="w-5 h-5" />,
path: "/graph",
},
{
id: "utility",
label: "UTILITY",
icon: <Wrench className="w-5 h-5" />,
children: [
{ id: "station-info", label: "Station Info", path: "/utility/station-info" },
{ id: "datetime", label: "Date / Time setting", path: "/utility/datetime" },
{ id: "mobile", label: "Mobile Setting", path: "/utility/mobile" },
{ id: "adc", label: "ADC Setting", path: "/utility/adc" },
{ id: "rainfall", label: "Rainfall Setting", path: "/utility/rainfall" },
{ id: "evap", label: "EVAP Setting", path: "/utility/evap" },
{ id: "gprs", label: "GPRS Setting", path: "/utility/gprs" },
{ id: "level", label: "Level Setting", path: "/utility/level" },
{ id: "siren", label: "SIREN Setting", path: "/utility/siren" },
{ id: "network", label: "Network Setup", path: "/utility/network" },
],
},
{
id: "calibration",
label: "CALIBRATION",
icon: <Gauge className="w-5 h-5" />,
path: "/calibration",
},
{
id: "flash-memory",
label: "FLASH MEMORY",
icon: <HardDrive className="w-5 h-5" />,
path: "/flash-memory",
},
{
id: "setting",
label: "SETTING",
icon: <Settings className="w-5 h-5" />,
path: "/setting",
},
{
id: "login",
label: "LOGIN",
icon: <LogIn className="w-5 h-5" />,
path: "/login",
},
];
export function Sidebar({ collapsed, onToggle }: SidebarProps) {
const location = useLocation();
const [expandedItems, setExpandedItems] = useState<Set<string>>(new Set(["utility"]));
const toggleExpanded = (id: string) => {
const newExpanded = new Set(expandedItems);
if (newExpanded.has(id)) {
newExpanded.delete(id);
} else {
newExpanded.add(id);
}
setExpandedItems(newExpanded);
};
const isActive = (path?: string) => {
if (!path) return false;
return location.pathname === path;
};
const isParentActive = (children?: SubMenuItem[]) => {
if (!children) return false;
return children.some(child => location.pathname === child.path);
};
return (
<div
className={`bg-gray-950 border-r border-gray-700 transition-all duration-300 flex flex-col ${
collapsed ? "w-16" : "w-64"
}`}
>
<div className="p-2 border-b border-gray-700 flex items-center justify-between">
<button
onClick={onToggle}
className="flex items-center gap-2 text-white font-bold text-sm hover:bg-gray-800 px-2 py-1 rounded transition-colors"
>
<ChevronRight className={`w-4 h-4 transition-transform ${collapsed ? "" : "rotate-180"}`} />
{!collapsed && <span>MENU</span>}
</button>
</div>
<nav className="flex-1 overflow-y-auto py-2">
{menuItems.map((item) => (
<div key={item.id}>
{item.children ? (
<>
<button
onClick={() => toggleExpanded(item.id)}
className={`w-full flex items-center gap-3 px-3 py-2.5 text-sm transition-colors ${
isParentActive(item.children)
? "bg-blue-600 text-white"
: "text-gray-300 hover:bg-gray-800 hover:text-white"
}`}
title={collapsed ? item.label : undefined}
>
<span className="flex-shrink-0">{item.icon}</span>
{!collapsed && (
<>
<span className="flex-1 text-left">{item.label}</span>
<ChevronDown
className={`w-4 h-4 transition-transform ${
expandedItems.has(item.id) ? "" : "-rotate-90"
}`}
/>
</>
)}
</button>
{!collapsed && expandedItems.has(item.id) && (
<div className="bg-gray-900">
{item.children.map((child) => (
<Link
key={child.id}
to={child.path}
className={`block px-3 py-2 pl-11 text-xs transition-colors ${
isActive(child.path)
? "bg-blue-600 text-white"
: "text-gray-400 hover:bg-gray-800 hover:text-white"
}`}
>
{child.label}
</Link>
))}
</div>
)}
</>
) : (
<Link
to={item.path!}
className={`flex items-center gap-3 px-3 py-2.5 text-sm transition-colors ${
isActive(item.path)
? "bg-blue-600 text-white"
: "text-gray-300 hover:bg-gray-800 hover:text-white"
}`}
title={collapsed ? item.label : undefined}
>
<span className="flex-shrink-0">{item.icon}</span>
{!collapsed && <span>{item.label}</span>}
</Link>
)}
</div>
))}
</nav>
</div>
);
}