style: Calm dashboard redesign - attention hierarchy
Some checks are pending
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run

- 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
This commit is contained in:
fullsizemalt 2025-12-27 12:04:21 -08:00
parent 38f9ef5f0b
commit 5666970629

View file

@ -1,202 +1,336 @@
import { useState, useEffect } from 'react'; import { useState } from 'react';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import { useToast } from '../context/ToastContext';
import { import {
Activity,
AlertCircle, AlertCircle,
CheckCircle2, CheckCircle2,
Clock, Clock,
ArrowRight, ArrowRight,
Plus, ChevronRight,
Filter, TrendingUp,
Layers, TrendingDown,
LayoutGrid, Minus,
List Thermometer,
Droplets,
Leaf,
Activity,
MoreHorizontal
} from 'lucide-react'; } from 'lucide-react';
import { RoomStatusCard } from '../components/dashboard/RoomStatusCard';
import { Card } from '../components/ui/card'; 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 // Mock Data - separated by status for different display treatments
const MOCK_ROOMS: Array<{ const MOCK_ROOMS = [
id: string; { 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 } },
name: string; { 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' },
phase: 'VEG' | 'FLOWER' | 'DRY' | 'CURE'; { 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 } },
status: 'OK' | 'WARNING' | 'CRITICAL'; { 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 } },
strains: string[]; { 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 } },
metrics: { temp: number; humidity: number; vpd: number; co2: number }; { 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 } },
tasks: { due: number; completed: number }; { 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 } },
{ 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' },
]; ];
export default function DashboardPage() { export default function DashboardPage() {
const { user } = useAuth(); const { user } = useAuth();
const { addToast } = useToast(); const [expandedRoom, setExpandedRoom] = useState<string | null>(null);
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
// 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 ( return (
<div className="space-y-8 pb-12"> <div className="space-y-6 pb-12 max-w-7xl mx-auto">
{/* Page Header Section */} {/* Compact Header */}
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-4"> <div className="flex items-center justify-between">
<div> <div>
<h1 className="text-3xl font-extrabold tracking-tight text-[var(--color-text-primary)] uppercase"> <h1 className="text-2xl font-bold tracking-tight text-[var(--color-text-primary)]">
Facility Overview Facility Overview
</h1> </h1>
<div className="flex items-center gap-3 mt-2"> <p className="text-sm text-[var(--color-text-tertiary)] mt-1">
<div className="flex items-center gap-1.5 px-2.5 py-1 rounded-full bg-[var(--color-primary-soft)] text-[var(--color-primary)] text-[10px] font-bold uppercase tracking-wider border border-[var(--color-primary)]/20"> {new Date().toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' })}
NORCAL-01 Live </p>
</div> </div>
<span className="text-[var(--color-text-tertiary)] text-sm">
{new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' })} {/* Quick Status Summary */}
<div className="flex items-center gap-6">
<div className="flex items-center gap-2">
<div className="w-3 h-3 rounded-full bg-[var(--color-primary)]" />
<span className="text-sm font-medium text-[var(--color-text-secondary)]">
{healthyCount}/{totalRooms} systems nominal
</span> </span>
</div> </div>
</div> </div>
<div className="flex items-center gap-2">
<div className="flex items-center bg-[var(--color-bg-tertiary)] border border-[var(--color-border-subtle)] rounded-full p-1">
<button
onClick={() => setViewMode('grid')}
className={`p-1.5 rounded-full transition-all ${viewMode === 'grid' ? "bg-[var(--color-primary)] text-[var(--color-text-inverse)] shadow-sm" : "text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]"}`}
>
<LayoutGrid size={18} />
</button>
<button
onClick={() => setViewMode('list')}
className={`p-1.5 rounded-full transition-all ${viewMode === 'list' ? "bg-[var(--color-primary)] text-[var(--color-text-inverse)] shadow-sm" : "text-[var(--color-text-tertiary)] hover:text-[var(--color-text-primary)]"}`}
>
<List size={18} />
</button>
</div>
<button className="flex items-center gap-2 bg-[var(--color-primary)] hover:bg-[var(--color-primary-hover)] text-[var(--color-text-inverse)] px-4 py-2.5 rounded-xl font-bold text-sm shadow-lg shadow-emerald-500/20 transition-all uppercase tracking-wider">
<Plus size={18} />
New Action
</button>
</div>
</div> </div>
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8"> {/* Attention Required Section - Only shows if issues exist */}
{/* Main Content: Grow Rooms Grid */} {(criticalRooms.length > 0 || warningRooms.length > 0) && (
<div className="lg:col-span-3"> <section className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-6"> <h2 className="text-xs font-bold uppercase tracking-wider text-[var(--color-text-tertiary)] flex items-center gap-2">
{MOCK_ROOMS.map((room, idx) => (
<motion.div
key={room.id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: idx * 0.05 }}
>
<RoomStatusCard {...room} />
</motion.div>
))}
{/* New Room Placeholder */}
<button className="h-full min-h-[220px] rounded-2xl border-2 border-dashed border-[var(--color-border-default)] flex flex-col items-center justify-center gap-2 text-[var(--color-text-tertiary)] hover:border-[var(--color-primary)] hover:text-[var(--color-primary)] transition-all group p-8">
<div className="p-3 rounded-full bg-[var(--color-bg-tertiary)] group-hover:bg-[var(--color-primary-soft)] transition-colors">
<Plus size={24} />
</div>
<span className="text-xs font-bold uppercase tracking-wider">Provision Zone</span>
</button>
</div>
</div>
{/* Sidebar: Activity & Alerts */}
<div className="space-y-8">
{/* Alerts Panel */}
<Card className="bg-[var(--color-bg-elevated)] border-[var(--color-border-subtle)] overflow-hidden">
<div className="p-4 border-b border-[var(--color-border-subtle)] bg-[var(--color-bg-tertiary)]/50 flex items-center justify-between">
<h4 className="text-xs font-bold uppercase tracking-wider text-[var(--color-text-tertiary)] flex items-center gap-2">
<AlertCircle size={14} className="text-[var(--color-error)]" /> <AlertCircle size={14} className="text-[var(--color-error)]" />
Active Alerts Attention Required
</h4> <span className="px-1.5 py-0.5 rounded-full bg-[var(--color-error)] text-[var(--color-text-inverse)] text-[10px] font-bold">
<span className="px-2 py-0.5 rounded-full bg-[var(--color-error)] text-[var(--color-text-inverse)] text-[10px] font-bold">3</span> {criticalRooms.length + warningRooms.length}
</div> </span>
<div className="divide-y divide-[var(--color-border-subtle)]"> </h2>
{MOCK_ALERTS.map(alert => (
<div key={alert.id} className="p-4 hover:bg-[var(--color-bg-tertiary)]/50 transition-colors group cursor-pointer"> <div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
<div className="flex items-start gap-3"> {/* Critical items get largest cards */}
<div className={`mt-1 w-1.5 h-1.5 rounded-full shrink-0 ${alert.level === 'CRITICAL' ? 'bg-[var(--color-error)]' : {criticalRooms.map(room => (
alert.level === 'WARNING' ? 'bg-[var(--color-warning)]' : 'bg-[var(--color-accent)]' <AttentionCard key={room.id} room={room} severity="critical" />
}`} /> ))}
<div className="flex-1 min-w-0"> {/* Warnings are slightly smaller */}
<div className="flex items-center justify-between mb-0.5"> {warningRooms.map(room => (
<span className="text-[10px] font-bold text-[var(--color-text-tertiary)] uppercase tracking-tight">{alert.source}</span> <AttentionCard key={room.id} room={room} severity="warning" />
<span className="text-[10px] text-[var(--color-text-tertiary)]">{alert.time}</span>
</div>
<p className="text-xs font-semibold text-[var(--color-text-primary)] line-clamp-2">
{alert.message}
</p>
</div>
</div>
</div>
))} ))}
</div> </div>
<button className="w-full py-3 text-[10px] font-bold uppercase tracking-wider text-[var(--color-text-tertiary)] hover:text-[var(--color-primary)] hover:bg-[var(--color-bg-tertiary)] transition-all flex items-center justify-center gap-2"> </section>
Acknowledge All )}
<CheckCircle2 size={12} />
</button> {/* All Systems Status - Compact Table */}
</Card> <section className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-xs font-bold uppercase tracking-wider text-[var(--color-text-tertiary)] flex items-center gap-2">
<Activity size={14} />
All Zones
</h2>
<button className="text-xs text-[var(--color-text-tertiary)] hover:text-[var(--color-primary)] transition-colors">
View All Details
</button>
</div>
{/* Critical Tasks Panel */}
<Card className="bg-[var(--color-bg-elevated)] border-[var(--color-border-subtle)] overflow-hidden"> <Card className="bg-[var(--color-bg-elevated)] border-[var(--color-border-subtle)] overflow-hidden">
<div className="p-4 border-b border-[var(--color-border-subtle)] bg-[var(--color-bg-tertiary)]/50"> {/* Table Header */}
<h4 className="text-xs font-bold uppercase tracking-wider text-[var(--color-text-tertiary)] flex items-center gap-2"> <div className="grid grid-cols-12 gap-4 px-4 py-3 bg-[var(--color-bg-tertiary)]/50 border-b border-[var(--color-border-subtle)] text-[10px] font-bold uppercase tracking-wider text-[var(--color-text-tertiary)]">
<Clock size={14} /> <div className="col-span-3">Zone</div>
Today's Critical <div className="col-span-2">Phase</div>
</h4> <div className="col-span-2 text-center">Status</div>
</div> <div className="col-span-2 text-center">Temp</div>
<div className="p-4 space-y-4"> <div className="col-span-2 text-center">RH</div>
{[ <div className="col-span-1"></div>
{ 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) => (
<div key={i} className="flex items-center gap-4">
<div className="w-10 h-10 rounded-xl bg-[var(--color-bg-tertiary)] flex items-center justify-center shrink-0 border border-[var(--color-border-subtle)] font-bold text-[10px] text-[var(--color-text-tertiary)]">
{task.room.split(' ')[0][0]}{task.room.split(' ')[1] || ''}
</div>
<div className="flex-1 min-w-0">
<p className="text-xs font-bold text-[var(--color-text-primary)] truncate">{task.task}</p>
<p className="text-[10px] text-[var(--color-text-tertiary)] uppercase font-medium">{task.room}</p>
</div>
<button className="p-1.5 rounded-lg hover:bg-[var(--color-primary-soft)] hover:text-[var(--color-primary)] text-[var(--color-text-tertiary)] transition-all">
<ArrowRight size={14} />
</button>
</div> </div>
{/* Table Rows */}
<div className="divide-y divide-[var(--color-border-subtle)]">
{MOCK_ROOMS.map(room => (
<RoomRow
key={room.id}
room={room}
isExpanded={expandedRoom === room.id}
onToggle={() => setExpandedRoom(expandedRoom === room.id ? null : room.id)}
/>
))} ))}
<button className="w-full mt-4 py-2.5 border border-[var(--color-border-subtle)] rounded-xl text-[10px] font-bold uppercase tracking-wider text-[var(--color-text-tertiary)] hover:bg-[var(--color-bg-tertiary)] hover:text-[var(--color-text-primary)] transition-all">
View Task Board
</button>
</div> </div>
</Card> </Card>
</section>
{/* KPI High-Level Summary */} {/* Bottom Stats - Very Minimal */}
<div className="grid grid-cols-2 gap-4"> <section className="grid grid-cols-2 md:grid-cols-4 gap-4">
<div className="stat-card"> <MiniStat label="Active Batches" value="24" trend="stable" />
<p className="text-[10px] font-bold text-[var(--color-text-tertiary)] uppercase tracking-wider mb-1">Total Batches</p> <MiniStat label="Tasks Due Today" value="18" trend="down" />
<div className="text-2xl font-black text-[var(--color-primary)]">24</div> <MiniStat label="Avg Temperature" value="76.4°F" trend="stable" />
</div> <MiniStat label="Avg Humidity" value="58%" trend="stable" />
<div className="stat-card"> </section>
<p className="text-[10px] font-bold text-[var(--color-text-tertiary)] uppercase tracking-wider mb-1">Active Alerts</p>
<div className="text-2xl font-black text-[var(--color-error)]">3</div>
</div>
</div>
</div>
</div>
</div> </div>
); );
} }
// Attention Card - Only for issues that need action
function AttentionCard({ room, severity }: { room: any, severity: 'critical' | 'warning' }) {
const isCritical = severity === 'critical';
return (
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
>
<Card className={cn(
"p-4 cursor-pointer transition-all hover:shadow-lg",
isCritical
? "bg-[var(--color-error)]/5 border-[var(--color-error)]/30 hover:border-[var(--color-error)]/50"
: "bg-[var(--color-warning)]/5 border-[var(--color-warning)]/30 hover:border-[var(--color-warning)]/50"
)}>
<div className="flex items-start justify-between gap-4">
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-1">
<AlertCircle size={16} className={isCritical ? "text-[var(--color-error)]" : "text-[var(--color-warning)]"} />
<span className="font-bold text-[var(--color-text-primary)]">{room.name}</span>
<span className={cn(
"px-1.5 py-0.5 rounded text-[9px] font-bold uppercase",
isCritical ? "bg-[var(--color-error)]/20 text-[var(--color-error)]" : "bg-[var(--color-warning)]/20 text-[var(--color-warning)]"
)}>
{severity}
</span>
</div>
<p className="text-sm text-[var(--color-text-secondary)] mb-3">
{room.issue}
</p>
<div className="flex items-center gap-4 text-xs text-[var(--color-text-tertiary)]">
<span className="flex items-center gap-1">
<Thermometer size={12} />
{room.metrics.temp}°F
</span>
<span className="flex items-center gap-1">
<Droplets size={12} />
{room.metrics.humidity}%
</span>
</div>
</div>
<button className={cn(
"px-3 py-1.5 rounded-lg text-xs font-bold transition-colors",
isCritical
? "bg-[var(--color-error)] text-white hover:bg-[var(--color-error)]/80"
: "bg-[var(--color-warning)] text-[var(--color-text-inverse)] hover:bg-[var(--color-warning)]/80"
)}>
Resolve
</button>
</div>
</Card>
</motion.div>
);
}
// 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 (
<>
<div
className={cn(
"grid grid-cols-12 gap-4 px-4 py-3 items-center cursor-pointer transition-colors",
room.status === 'OK' ? "hover:bg-[var(--color-bg-tertiary)]/30" : "",
room.status === 'WARNING' && "bg-[var(--color-warning)]/5",
room.status === 'CRITICAL' && "bg-[var(--color-error)]/5"
)}
onClick={onToggle}
>
{/* Zone Name */}
<div className="col-span-3 flex items-center gap-2">
<div className={cn("w-2 h-2 rounded-full", statusDot[room.status])} />
<span className="font-medium text-sm text-[var(--color-text-primary)] truncate">{room.name}</span>
</div>
{/* Phase */}
<div className="col-span-2">
<span className={cn(
"px-2 py-0.5 rounded text-[10px] font-bold uppercase",
room.phase === 'FLOWER' ? "bg-purple-500/10 text-purple-400" :
room.phase === 'VEG' ? "bg-[var(--color-primary)]/10 text-[var(--color-primary)]" :
"bg-[var(--color-bg-tertiary)] text-[var(--color-text-tertiary)]"
)}>
{room.phase}
</span>
</div>
{/* Status - Very subtle for OK */}
<div className="col-span-2 text-center">
{room.status === 'OK' ? (
<span className="text-xs text-[var(--color-text-tertiary)]">Normal</span>
) : (
<span className={cn(
"text-xs font-bold",
room.status === 'WARNING' ? "text-[var(--color-warning)]" : "text-[var(--color-error)]"
)}>
{room.status}
</span>
)}
</div>
{/* Temp with trend */}
<div className="col-span-2 text-center flex items-center justify-center gap-1">
<span className={cn(
"text-sm",
room.metrics.temp > 85 ? "text-[var(--color-error)] font-bold" : "text-[var(--color-text-secondary)]"
)}>
{room.metrics.temp}°F
</span>
<TrendIcon size={12} className="text-[var(--color-text-quaternary)]" />
</div>
{/* Humidity */}
<div className="col-span-2 text-center">
<span className={cn(
"text-sm",
room.metrics.humidity > 70 ? "text-[var(--color-warning)] font-bold" : "text-[var(--color-text-secondary)]"
)}>
{room.metrics.humidity}%
</span>
</div>
{/* Expand */}
<div className="col-span-1 text-right">
<ChevronRight size={16} className={cn(
"text-[var(--color-text-quaternary)] transition-transform",
isExpanded && "rotate-90"
)} />
</div>
</div>
{/* Expanded Details */}
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
className="overflow-hidden bg-[var(--color-bg-tertiary)]/30 border-t border-[var(--color-border-subtle)]"
>
<div className="px-4 py-4 grid grid-cols-4 gap-4">
<MetricTile label="Temperature" value={`${room.metrics.temp}°F`} target="75-80°F" />
<MetricTile label="Humidity" value={`${room.metrics.humidity}%`} target="55-65%" />
<MetricTile label="VPD" value={`${room.metrics.vpd} kPa`} target="1.0-1.3" />
<MetricTile label="CO2" value={`${room.metrics.co2} ppm`} target="1000-1400" />
</div>
<div className="px-4 pb-4 flex items-center justify-between">
<div className="flex items-center gap-4 text-xs text-[var(--color-text-tertiary)]">
<span>Strains: {room.strains.join(', ')}</span>
<span>Tasks: {room.tasks.due} pending, {room.tasks.completed} done</span>
</div>
<button className="text-xs text-[var(--color-primary)] hover:underline font-medium flex items-center gap-1">
View Room Details
<ArrowRight size={12} />
</button>
</div>
</motion.div>
)}
</AnimatePresence>
</>
);
}
// 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 (
<div className="stat-card flex items-center justify-between">
<div>
<p className="text-[10px] font-bold text-[var(--color-text-tertiary)] uppercase tracking-wider">{label}</p>
<p className="text-lg font-bold text-[var(--color-text-primary)]">{value}</p>
</div>
<TrendIcon size={16} className="text-[var(--color-text-quaternary)]" />
</div>
);
}
// Detail tile for expanded view
function MetricTile({ label, value, target }: { label: string, value: string, target: string }) {
return (
<div className="text-center">
<p className="text-[10px] font-bold text-[var(--color-text-tertiary)] uppercase tracking-wide mb-1">{label}</p>
<p className="text-lg font-bold text-[var(--color-text-primary)]">{value}</p>
<p className="text-[10px] text-[var(--color-text-quaternary)]">Target: {target}</p>
</div>
);
}