From 5666970629f2059c4469feee825af54c393a74bf Mon Sep 17 00:00:00 2001 From: fullsizemalt <106900403+fullsizemalt@users.noreply.github.com> Date: Sat, 27 Dec 2025 12:04:21 -0800 Subject: [PATCH] style: Calm dashboard redesign - attention hierarchy - Replace uniform room cards with compact table view - Only show elevated cards for WARNING/CRITICAL items - Expandable rows for drill-down details - Healthy systems shown as simple rows with status dots - Trend indicators instead of all raw metrics upfront - Much calmer overall visual hierarchy --- frontend/src/pages/DashboardPage.tsx | 470 +++++++++++++++++---------- 1 file changed, 302 insertions(+), 168 deletions(-) diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx index bf17078..ddfbf90 100644 --- a/frontend/src/pages/DashboardPage.tsx +++ b/frontend/src/pages/DashboardPage.tsx @@ -1,202 +1,336 @@ -import { useState, useEffect } from 'react'; +import { useState } from 'react'; import { useAuth } from '../context/AuthContext'; -import { useToast } from '../context/ToastContext'; import { - Activity, AlertCircle, CheckCircle2, Clock, ArrowRight, - Plus, - Filter, - Layers, - LayoutGrid, - List + ChevronRight, + TrendingUp, + TrendingDown, + Minus, + Thermometer, + Droplets, + Leaf, + Activity, + MoreHorizontal } from 'lucide-react'; -import { RoomStatusCard } from '../components/dashboard/RoomStatusCard'; import { Card } from '../components/ui/card'; -import { motion } from 'framer-motion'; +import { motion, AnimatePresence } from 'framer-motion'; +import { cn } from '../lib/utils'; -// Mock Data for Facility Overview -const MOCK_ROOMS: Array<{ - id: string; - name: string; - phase: 'VEG' | 'FLOWER' | 'DRY' | 'CURE'; - status: 'OK' | 'WARNING' | 'CRITICAL'; - strains: string[]; - metrics: { temp: number; humidity: number; vpd: number; co2: number }; - tasks: { due: number; completed: number }; -}> = [ - { id: '1', name: 'Flower Room A', phase: 'FLOWER', status: 'OK', strains: ['OG Kush', 'Gelato #41'], metrics: { temp: 78.4, humidity: 52, vpd: 1.25, co2: 1200 }, tasks: { due: 4, completed: 12 } }, - { id: '2', name: 'Flower Room B', phase: 'FLOWER', status: 'WARNING', strains: ['Blue Dream'], metrics: { temp: 82.1, humidity: 68, vpd: 1.10, co2: 1150 }, tasks: { due: 8, completed: 5 } }, - { id: '3', name: 'Veg Room 1', phase: 'VEG', status: 'OK', strains: ['Clones - Batch 402'], metrics: { temp: 76.2, humidity: 65, vpd: 0.85, co2: 800 }, tasks: { due: 2, completed: 20 } }, - { id: '4', name: 'Veg Room 2', phase: 'VEG', status: 'OK', strains: ['Mothers'], metrics: { temp: 75.8, humidity: 62, vpd: 0.90, co2: 800 }, tasks: { due: 3, completed: 15 } }, - { id: '5', name: 'Drying Room', phase: 'DRY', status: 'OK', strains: ['Harvest 12/15'], metrics: { temp: 62.0, humidity: 60, vpd: 0.75, co2: 400 }, tasks: { due: 1, completed: 30 } }, - { id: '6', name: 'Cure Vault', phase: 'CURE', status: 'OK', strains: ['Bulk - Wedding Cake'], metrics: { temp: 65.4, humidity: 58, vpd: 0.80, co2: 400 }, tasks: { due: 0, completed: 45 } }, - { id: '7', name: 'Flower Room C', phase: 'FLOWER', status: 'CRITICAL', strains: ['Runtz'], metrics: { temp: 88.5, humidity: 72, vpd: 0.95, co2: 1250 }, tasks: { due: 12, completed: 2 } }, - { id: '8', name: 'Flower Room D', phase: 'FLOWER', status: 'OK', strains: ['Sour Diesel'], metrics: { temp: 77.9, humidity: 50, vpd: 1.30, co2: 1200 }, tasks: { due: 5, completed: 10 } }, - ]; - -const MOCK_ALERTS = [ - { id: '1', level: 'CRITICAL', source: 'Flower Room C', message: 'Temperature threshold exceeded (>85°F)', time: '5m ago' }, - { id: '2', level: 'WARNING', source: 'Flower Room B', message: 'Humidity climbing above target RH (68%)', time: '12m ago' }, - { id: '3', level: 'INFO', source: 'System', message: 'Irrigation cycle complete in Veg 1', time: '45m ago' }, +// Mock Data - separated by status for different display treatments +const MOCK_ROOMS = [ + { id: '1', name: 'Flower Room A', phase: 'FLOWER', status: 'OK', strains: ['OG Kush', 'Gelato #41'], metrics: { temp: 78.4, humidity: 52, vpd: 1.25, co2: 1200 }, trend: 'stable', tasks: { due: 4, completed: 12 } }, + { id: '2', name: 'Flower Room B', phase: 'FLOWER', status: 'WARNING', strains: ['Blue Dream'], metrics: { temp: 82.1, humidity: 68, vpd: 1.10, co2: 1150 }, trend: 'up', tasks: { due: 8, completed: 5 }, issue: 'Humidity climbing' }, + { id: '3', name: 'Veg Room 1', phase: 'VEG', status: 'OK', strains: ['Clones - Batch 402'], metrics: { temp: 76.2, humidity: 65, vpd: 0.85, co2: 800 }, trend: 'stable', tasks: { due: 2, completed: 20 } }, + { id: '4', name: 'Veg Room 2', phase: 'VEG', status: 'OK', strains: ['Mothers'], metrics: { temp: 75.8, humidity: 62, vpd: 0.90, co2: 800 }, trend: 'stable', tasks: { due: 3, completed: 15 } }, + { id: '5', name: 'Drying Room', phase: 'DRY', status: 'OK', strains: ['Harvest 12/15'], metrics: { temp: 62.0, humidity: 60, vpd: 0.75, co2: 400 }, trend: 'down', tasks: { due: 1, completed: 30 } }, + { id: '6', name: 'Cure Vault', phase: 'CURE', status: 'OK', strains: ['Bulk - Wedding Cake'], metrics: { temp: 65.4, humidity: 58, vpd: 0.80, co2: 400 }, trend: 'stable', tasks: { due: 0, completed: 45 } }, + { id: '7', name: 'Flower Room C', phase: 'FLOWER', status: 'CRITICAL', strains: ['Runtz'], metrics: { temp: 88.5, humidity: 72, vpd: 0.95, co2: 1250 }, trend: 'up', tasks: { due: 12, completed: 2 }, issue: 'Temperature exceeded threshold' }, + { id: '8', name: 'Flower Room D', phase: 'FLOWER', status: 'OK', strains: ['Sour Diesel'], metrics: { temp: 77.9, humidity: 50, vpd: 1.30, co2: 1200 }, trend: 'stable', tasks: { due: 5, completed: 10 } }, ]; export default function DashboardPage() { const { user } = useAuth(); - const { addToast } = useToast(); - const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid'); + const [expandedRoom, setExpandedRoom] = useState(null); + + // Separate rooms by urgency + const criticalRooms = MOCK_ROOMS.filter(r => r.status === 'CRITICAL'); + const warningRooms = MOCK_ROOMS.filter(r => r.status === 'WARNING'); + const healthyRooms = MOCK_ROOMS.filter(r => r.status === 'OK'); + + const healthyCount = healthyRooms.length; + const totalRooms = MOCK_ROOMS.length; return ( -
- {/* Page Header Section */} -
+
+ {/* Compact Header */} +
-

+

Facility Overview

-
-
- NORCAL-01 Live -
- - {new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })} +

+ {new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })} +

+
+ + {/* Quick Status Summary */} +
+
+
+ + {healthyCount}/{totalRooms} systems nominal
+
-
-
- - + {/* Attention Required Section - Only shows if issues exist */} + {(criticalRooms.length > 0 || warningRooms.length > 0) && ( +
+

+ + Attention Required + + {criticalRooms.length + warningRooms.length} + +

+ +
+ {/* Critical items get largest cards */} + {criticalRooms.map(room => ( + + ))} + {/* Warnings are slightly smaller */} + {warningRooms.map(room => ( + + ))}
-
+ )} + + {/* All Systems Status - Compact Table */} +
+
+

+ + All Zones +

+
-
-
- {/* Main Content: Grow Rooms Grid */} -
-
- {MOCK_ROOMS.map((room, idx) => ( - + {/* Table Header */} +
+
Zone
+
Phase
+
Status
+
Temp
+
RH
+
+
+ + {/* Table Rows */} +
+ {MOCK_ROOMS.map(room => ( + - - + room={room} + isExpanded={expandedRoom === room.id} + onToggle={() => setExpandedRoom(expandedRoom === room.id ? null : room.id)} + /> ))} - - {/* New Room Placeholder */} -
-
+ + - {/* Sidebar: Activity & Alerts */} -
- {/* Alerts Panel */} - -
-

- - Active Alerts -

- 3 -
-
- {MOCK_ALERTS.map(alert => ( -
-
-
-
-
- {alert.source} - {alert.time} -
-

- {alert.message} -

-
-
-
- ))} -
- - - - {/* Critical Tasks Panel */} - -
-

- - Today's Critical -

-
-
- {[ - { room: 'Flower C', task: 'Adjust Reservoir pH', priority: 'HIGH' }, - { room: 'Flower B', task: 'IPM Foliar Application', priority: 'HIGH' }, - { room: 'Veg 1', task: 'Batch Transfer (402)', priority: 'MED' }, - ].map((task, i) => ( -
-
- {task.room.split(' ')[0][0]}{task.room.split(' ')[1] || ''} -
-
-

{task.task}

-

{task.room}

-
- -
- ))} - -
-
- - {/* KPI High-Level Summary */} -
-
-

Total Batches

-
24
-
-
-

Active Alerts

-
3
-
-
-
-
+ {/* Bottom Stats - Very Minimal */} +
+ + + + +
); } +// Attention Card - Only for issues that need action +function AttentionCard({ room, severity }: { room: any, severity: 'critical' | 'warning' }) { + const isCritical = severity === 'critical'; + + return ( + + +
+
+
+ + {room.name} + + {severity} + +
+

+ {room.issue} +

+
+ + + {room.metrics.temp}°F + + + + {room.metrics.humidity}% + +
+
+ +
+
+
+ ); +} + +// Compact Row for All Systems table +function RoomRow({ room, isExpanded, onToggle }: { room: any, isExpanded: boolean, onToggle: () => void }) { + const statusDot = { + OK: 'bg-[var(--color-primary)]', + WARNING: 'bg-[var(--color-warning)]', + CRITICAL: 'bg-[var(--color-error)]' + }; + + const TrendIcon = room.trend === 'up' ? TrendingUp : room.trend === 'down' ? TrendingDown : Minus; + + return ( + <> +
+ {/* Zone Name */} +
+
+ {room.name} +
+ + {/* Phase */} +
+ + {room.phase} + +
+ + {/* Status - Very subtle for OK */} +
+ {room.status === 'OK' ? ( + Normal + ) : ( + + {room.status} + + )} +
+ + {/* Temp with trend */} +
+ 85 ? "text-[var(--color-error)] font-bold" : "text-[var(--color-text-secondary)]" + )}> + {room.metrics.temp}°F + + +
+ + {/* Humidity */} +
+ 70 ? "text-[var(--color-warning)] font-bold" : "text-[var(--color-text-secondary)]" + )}> + {room.metrics.humidity}% + +
+ + {/* Expand */} +
+ +
+
+ + {/* Expanded Details */} + + {isExpanded && ( + +
+ + + + +
+
+
+ Strains: {room.strains.join(', ')} + Tasks: {room.tasks.due} pending, {room.tasks.completed} done +
+ +
+
+ )} +
+ + ); +} + +// Minimal Stat Tile +function MiniStat({ label, value, trend }: { label: string, value: string, trend: 'up' | 'down' | 'stable' }) { + const TrendIcon = trend === 'up' ? TrendingUp : trend === 'down' ? TrendingDown : Minus; + + return ( +
+
+

{label}

+

{value}

+
+ +
+ ); +} + +// Detail tile for expanded view +function MetricTile({ label, value, target }: { label: string, value: string, target: string }) { + return ( +
+

{label}

+

{value}

+

Target: {target}

+
+ ); +}