refactor: update App and StatusPage components for improved styling and functionality
This commit is contained in:
parent
9eeffde903
commit
5e329a7f39
|
|
@ -33,28 +33,30 @@ export default function App() {
|
|||
return (
|
||||
<div className="size-full flex flex-col bg-neutral-50">
|
||||
<Toaster position="top-center" />
|
||||
<header className="bg-white border-b border-neutral-200 px-4 py-3 flex-shrink-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<img src={logoSvg} alt="Meatloaf" className="h-8" />
|
||||
<header className="bg-[#4d4d4d] px-0 py-0 flex-shrink-0">
|
||||
<div className="flex items-stretch justify-between min-h-[56px]">
|
||||
<div className="flex items-center h-full">
|
||||
<img src={logoSvg} alt="Meatloaf" className="h-full max-h-[56px] w-auto object-contain" />
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => setShowSearch(true)}
|
||||
className="p-2 hover:bg-neutral-100 rounded-lg"
|
||||
className="p-2 hover:bg-[#5e5e5e] rounded-lg"
|
||||
>
|
||||
<Search className="w-5 h-5" />
|
||||
<Search className="w-5 h-5 text-white" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('tools')}
|
||||
className="p-2 hover:bg-neutral-100 rounded-lg"
|
||||
className="p-2 hover:bg-[#5e5e5e] rounded-lg"
|
||||
>
|
||||
<Wrench className="w-5 h-5" />
|
||||
<Wrench className="w-5 h-5 text-white" />
|
||||
</button>
|
||||
<div className="relative">
|
||||
<button
|
||||
onClick={() => setShowProfileMenu(!showProfileMenu)}
|
||||
className="p-2 hover:bg-neutral-100 rounded-lg"
|
||||
className="p-2 hover:bg-[#5e5e5e] rounded-lg"
|
||||
>
|
||||
<User className="w-5 h-5" />
|
||||
<User className="w-5 h-5 text-white" />
|
||||
</button>
|
||||
{showProfileMenu && (
|
||||
<div className="absolute right-0 top-12 bg-white rounded-lg shadow-lg border border-neutral-200 py-2 min-w-[200px] z-20">
|
||||
|
|
@ -65,21 +67,21 @@ export default function App() {
|
|||
}}
|
||||
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
|
||||
>
|
||||
<Settings className="w-4 h-4" />
|
||||
<Settings className="w-4 h-4 text-[#4d4d4d]" />
|
||||
Settings
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowProfileMenu(false)}
|
||||
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
|
||||
>
|
||||
<Bell className="w-4 h-4" />
|
||||
<Bell className="w-4 h-4 text-[#4d4d4d]" />
|
||||
Notifications
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setShowProfileMenu(false)}
|
||||
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2"
|
||||
>
|
||||
<FileText className="w-4 h-4" />
|
||||
<FileText className="w-4 h-4 text-[#4d4d4d]" />
|
||||
Documentation
|
||||
</button>
|
||||
<div className="border-t border-neutral-200 my-2" />
|
||||
|
|
@ -87,7 +89,7 @@ export default function App() {
|
|||
onClick={() => setShowProfileMenu(false)}
|
||||
className="w-full px-4 py-2 text-left hover:bg-neutral-50 flex items-center gap-2 text-red-600"
|
||||
>
|
||||
<LogOut className="w-4 h-4" />
|
||||
<LogOut className="w-4 h-4 text-[#4d4d4d]" />
|
||||
Logout
|
||||
</button>
|
||||
</div>
|
||||
|
|
@ -101,61 +103,49 @@ export default function App() {
|
|||
{pages[currentPage]}
|
||||
</main>
|
||||
|
||||
<nav className="bg-white border-t border-neutral-200 flex-shrink-0">
|
||||
<nav className="bg-[#4d4d4d] flex-shrink-0">
|
||||
<div className="flex">
|
||||
<button
|
||||
onClick={() => setCurrentPage('status')}
|
||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
||||
currentPage === 'status' ? 'text-blue-600' : 'text-neutral-600'
|
||||
}`}
|
||||
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||
>
|
||||
<Activity className="w-5 h-5" />
|
||||
<span className="text-xs">Status</span>
|
||||
<Activity className="w-5 h-5 text-white" />
|
||||
<span className="text-xs text-white">Status</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('devices')}
|
||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
||||
currentPage === 'devices' ? 'text-blue-600' : 'text-neutral-600'
|
||||
}`}
|
||||
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||
>
|
||||
<HardDrive className="w-5 h-5" />
|
||||
<span className="text-xs">Devices</span>
|
||||
<HardDrive className="w-5 h-5 text-white" />
|
||||
<span className="text-xs text-white">Devices</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('iec')}
|
||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
||||
currentPage === 'iec' ? 'text-blue-600' : 'text-neutral-600'
|
||||
}`}
|
||||
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||
>
|
||||
<Cpu className="w-5 h-5" />
|
||||
<span className="text-xs">IEC</span>
|
||||
<Cpu className="w-5 h-5 text-white" />
|
||||
<span className="text-xs text-white">IEC</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('network')}
|
||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
||||
currentPage === 'network' ? 'text-blue-600' : 'text-neutral-600'
|
||||
}`}
|
||||
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||
>
|
||||
<Network className="w-5 h-5" />
|
||||
<span className="text-xs">Network</span>
|
||||
<Network className="w-5 h-5 text-white" />
|
||||
<span className="text-xs text-white">Network</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('other')}
|
||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
||||
currentPage === 'other' ? 'text-blue-600' : 'text-neutral-600'
|
||||
}`}
|
||||
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||
>
|
||||
<MoreHorizontal className="w-5 h-5" />
|
||||
<span className="text-xs">More</span>
|
||||
<MoreHorizontal className="w-5 h-5 text-white" />
|
||||
<span className="text-xs text-white">More</span>
|
||||
</button>
|
||||
<button
|
||||
onClick={() => setCurrentPage('general')}
|
||||
className={`flex-1 flex flex-col items-center gap-1 py-2 ${
|
||||
currentPage === 'general' ? 'text-blue-600' : 'text-neutral-600'
|
||||
}`}
|
||||
className="flex-1 flex flex-col items-center gap-1 py-2"
|
||||
>
|
||||
<Settings className="w-5 h-5" />
|
||||
<span className="text-xs">General</span>
|
||||
<Settings className="w-5 h-5 text-white" />
|
||||
<span className="text-xs text-white">General</span>
|
||||
</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
|
|
|||
|
|
@ -1,11 +1,21 @@
|
|||
import { useState } from 'react';
|
||||
import { HardDrive, Activity, Wifi, Signal, Clock } from 'lucide-react';
|
||||
import DeviceDetailOverlay from './DeviceDetailOverlay';
|
||||
|
||||
interface StatusPageProps {
|
||||
config: any;
|
||||
setConfig: (config: any) => void;
|
||||
}
|
||||
|
||||
export default function StatusPage({ config }: StatusPageProps) {
|
||||
export default function StatusPage({ config, setConfig }: StatusPageProps) {
|
||||
// Mock memory stats
|
||||
const memory = {
|
||||
heap: { total: 4096, free: 1024 }, // in KB
|
||||
psram: { total: 8192, free: 4096 },
|
||||
};
|
||||
|
||||
// Overlay state for active device
|
||||
const [showDeviceOverlay, setShowDeviceOverlay] = useState(false);
|
||||
// Find the first enabled device as the active device
|
||||
const findActiveDevice = () => {
|
||||
if (config.iec?.devices?.drive) {
|
||||
|
|
@ -42,16 +52,39 @@ export default function StatusPage({ config }: StatusPageProps) {
|
|||
<h2 className="text-sm text-neutral-500">System Status</h2>
|
||||
|
||||
<div className="bg-white border border-neutral-200 rounded-lg p-4">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<div className="w-10 h-10 bg-green-100 rounded-full flex items-center justify-center">
|
||||
<Signal className="w-5 h-5 text-green-600" />
|
||||
</div>
|
||||
<div>
|
||||
<div className="font-medium">Online</div>
|
||||
<div className="text-sm text-neutral-500">All systems operational</div>
|
||||
<div className="mb-4">
|
||||
<div className="text-xs text-neutral-500 mb-1">Memory Utilization</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Heap Graph */}
|
||||
<div>
|
||||
<div className="flex justify-between text-xs mb-0.5">
|
||||
<span>Heap</span>
|
||||
<span>{memory.heap.free} KB free / {memory.heap.total} KB</span>
|
||||
</div>
|
||||
<div className="w-full h-3 bg-neutral-200 rounded overflow-hidden">
|
||||
<div
|
||||
className="h-3 bg-blue-500"
|
||||
style={{ width: `${((memory.heap.total - memory.heap.free) / memory.heap.total) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/* PSRAM Graph */}
|
||||
<div>
|
||||
<div className="flex justify-between text-xs mb-0.5">
|
||||
<span>PSRAM</span>
|
||||
<span>{memory.psram.free} KB free / {memory.psram.total} KB</span>
|
||||
</div>
|
||||
<div className="w-full h-3 bg-neutral-200 rounded overflow-hidden">
|
||||
<div
|
||||
className="h-3 bg-green-500"
|
||||
style={{ width: `${((memory.psram.total - memory.psram.free) / memory.psram.total) * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div className="grid grid-cols-2 gap-4">
|
||||
<div>
|
||||
<div className="text-xs text-neutral-500">WiFi</div>
|
||||
|
|
@ -59,6 +92,10 @@ export default function StatusPage({ config }: StatusPageProps) {
|
|||
<Wifi className="w-3 h-3 text-green-600" />
|
||||
<span>Connected</span>
|
||||
</div>
|
||||
<div className="text-xs text-neutral-500 mt-1">IP Address</div>
|
||||
<div className="text-sm text-neutral-700">192.168.1.100</div>
|
||||
<div className="text-xs text-neutral-500 mt-1">MAC Address</div>
|
||||
<div className="text-sm text-neutral-700">AA:BB:CC:DD:EE:FF</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-xs text-neutral-500">Uptime</div>
|
||||
|
|
@ -74,7 +111,10 @@ export default function StatusPage({ config }: StatusPageProps) {
|
|||
<>
|
||||
<h2 className="text-sm text-neutral-500 pt-2">Active Device</h2>
|
||||
|
||||
<div className="bg-white border border-neutral-200 rounded-lg p-4">
|
||||
<div
|
||||
className="bg-white border border-neutral-200 rounded-lg p-4 cursor-pointer hover:bg-neutral-50 transition"
|
||||
onClick={() => setShowDeviceOverlay(true)}
|
||||
>
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="w-10 h-10 bg-blue-100 rounded-full flex items-center justify-center">
|
||||
|
|
@ -106,8 +146,66 @@ export default function StatusPage({ config }: StatusPageProps) {
|
|||
<div className="text-lg font-medium text-green-600">{stats.errors}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Media switch buttons if media set is detected */}
|
||||
{(() => {
|
||||
// Media set detection logic (copied from DeviceDetailOverlay)
|
||||
const url = activeDevice.url;
|
||||
if (!url) return null;
|
||||
const match = url.match(/^(.+?)(\d+)(\.[^.]+)$/);
|
||||
if (!match) return null;
|
||||
const [, prefix, num, ext] = match;
|
||||
const currentNum = parseInt(num);
|
||||
const mediaSet = [];
|
||||
for (let i = 1; i <= 10; i++) {
|
||||
mediaSet.push(`${prefix}${i}${ext}`);
|
||||
}
|
||||
return (
|
||||
<div className="flex flex-wrap gap-2 mt-2">
|
||||
{mediaSet.map((file, idx) => (
|
||||
<button
|
||||
key={file}
|
||||
className={`px-2 py-1 rounded text-xs border ${url === file ? 'bg-blue-600 text-white border-blue-600' : 'bg-neutral-100 text-neutral-700 border-neutral-300'}`}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
if (setConfig) {
|
||||
const newConfig = JSON.parse(JSON.stringify(config));
|
||||
let current = newConfig;
|
||||
if (current.iec && current.iec.devices && current.iec.devices.drive && current.iec.devices.drive[num]) {
|
||||
current.iec.devices.drive[num].url = file;
|
||||
setConfig(newConfig);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{idx + 1}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
})()}
|
||||
</div>
|
||||
|
||||
{showDeviceOverlay && (
|
||||
<DeviceDetailOverlay
|
||||
device={{
|
||||
id: `drive-${activeDevice.number}`,
|
||||
number: activeDevice.number,
|
||||
type: 'drive',
|
||||
name: activeDevice.name,
|
||||
enabled: activeDevice.enabled,
|
||||
url: activeDevice.url,
|
||||
mode: activeDevice.mode
|
||||
}}
|
||||
config={config}
|
||||
setConfig={setConfig}
|
||||
onClose={() => setShowDeviceOverlay(false)}
|
||||
onNavigate={() => {}}
|
||||
hasPrev={false}
|
||||
hasNext={false}
|
||||
/>
|
||||
)}
|
||||
|
||||
<h2 className="text-sm text-neutral-500 pt-2 flex items-center gap-2">
|
||||
<Activity className="w-4 h-4" />
|
||||
Activity Log
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user