diff --git a/frontend/src/components/layout/UserMenu.tsx b/frontend/src/components/layout/UserMenu.tsx
index 0f72e67..ba9d6aa 100644
--- a/frontend/src/components/layout/UserMenu.tsx
+++ b/frontend/src/components/layout/UserMenu.tsx
@@ -3,6 +3,7 @@ import { Link } from 'react-router-dom';
import { LogOut, ChevronDown, User } from 'lucide-react';
import { useAuth } from '../../context/AuthContext';
import { RoleBadge } from '../ui/RoleBadge';
+import { cn } from '../../lib/utils';
/**
* User menu dropdown for desktop sidebar
diff --git a/frontend/src/pages/DashboardPage.tsx b/frontend/src/pages/DashboardPage.tsx
index f176fd8..352a736 100644
--- a/frontend/src/pages/DashboardPage.tsx
+++ b/frontend/src/pages/DashboardPage.tsx
@@ -18,16 +18,24 @@ import { Card } from '../components/ui/card';
import { motion } from 'framer-motion';
// Mock Data for Facility Overview
-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 }, 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 } },
-] as const;
+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' },
@@ -121,7 +129,7 @@ export default function DashboardPage() {
diff --git a/frontend/src/pages/RoomDetailPage.tsx b/frontend/src/pages/RoomDetailPage.tsx
index 5062902..d874892 100644
--- a/frontend/src/pages/RoomDetailPage.tsx
+++ b/frontend/src/pages/RoomDetailPage.tsx
@@ -3,7 +3,7 @@ import { useParams, useNavigate, Link } from 'react-router-dom';
import {
ArrowLeft, Thermometer, Droplets, Wind, Zap, Sun,
Sprout, Calendar, Edit2, Layers, Clock, CheckCircle2,
- Activity, History, ClipboardList, Filter, MoreHorizontal
+ Activity, History, ClipboardList, Filter, MoreHorizontal, Plus
} from 'lucide-react';
import api from '../lib/api';
import { Card } from '../components/ui/card';
diff --git a/frontend/src/pages/RoomsPage.tsx b/frontend/src/pages/RoomsPage.tsx
index 28a5738..ba5c0a3 100644
--- a/frontend/src/pages/RoomsPage.tsx
+++ b/frontend/src/pages/RoomsPage.tsx
@@ -1,9 +1,10 @@
import { useEffect, useState } from 'react';
import { Link } from 'react-router-dom';
-import { Home, Plus, Thermometer, Droplets, ChevronRight } from 'lucide-react';
+import { Home, Plus, Thermometer, Droplets, ChevronRight, Activity, Leaf, Flower, ShieldCheck, ArrowRight } from 'lucide-react';
import api from '../lib/api';
import { usePermissions } from '../hooks/usePermissions';
-import { PageHeader, EmptyState, CardSkeleton } from '../components/ui/LinearPrimitives';
+import { Card } from '../components/ui/card';
+import { cn } from '../lib/utils';
export default function RoomsPage() {
const { isManager } = usePermissions();
@@ -26,112 +27,179 @@ export default function RoomsPage() {
}
};
- // Header background colors per room type
- const getHeaderStyle = (type: string) => {
- const styles: Record
= {
- VEG: { bg: 'bg-green-500/10', text: 'text-green-600', border: 'border-green-500/20' },
- FLOWER: { bg: 'bg-purple-500/10', text: 'text-purple-600', border: 'border-purple-500/20' },
- DRY: { bg: 'bg-amber-500/10', text: 'text-amber-600', border: 'border-amber-500/20' },
- CURE: { bg: 'bg-orange-500/10', text: 'text-orange-600', border: 'border-orange-500/20' },
- MOTHER: { bg: 'bg-pink-500/10', text: 'text-pink-600', border: 'border-pink-500/20' },
- TRIM: { bg: 'bg-slate-500/10', text: 'text-slate-600', border: 'border-slate-500/20' },
- CLONE: { bg: 'bg-teal-500/10', text: 'text-teal-600', border: 'border-teal-500/20' },
+ const getRoomStyle = (type: string) => {
+ const styles: Record = {
+ VEG: { accent: 'emerald', icon: Leaf },
+ FLOWER: { accent: 'purple', icon: Flower },
+ DRY: { accent: 'amber', icon: Activity },
+ CURE: { accent: 'orange', icon: Activity },
+ MOTHER: { accent: 'pink', icon: Leaf },
+ TRIM: { accent: 'slate', icon: Activity },
+ CLONE: { accent: 'teal', icon: Leaf },
};
- return styles[type] || { bg: 'bg-tertiary', text: 'text-secondary', border: 'border-subtle' };
+ return styles[type] || { accent: 'slate', icon: Home };
};
- return (
-
-
-
- Add Room
-
- )
- }
- />
+ const vegCount = rooms.filter(r => r.type === 'VEG').length;
+ const flowerCount = rooms.filter(r => r.type === 'FLOWER').length;
+ const totalBatches = rooms.reduce((acc, r) => acc + (r.batches?.length || 0), 0);
+ return (
+
+ {/* Page Header */}
+
+
+
+ Cultivation Zones
+
+
+
+ {rooms.length} Active Zones • Environmental Controls Active
+
+
+
+ {isManager && (
+
+ )}
+
+
+ {/* KPI Strip */}
+
+
+
+
Total Zones
+
{rooms.length}
+
+
+
+
+
+
+
+
Veg Rooms
+
{vegCount}
+
+
+
+
+
+
+
+
Flower Rooms
+
{flowerCount}
+
+
+
+
+
+
+
+
Active Batches
+
{totalBatches}
+
+
+
+
+
+ {/* Room Grid */}
{isLoading ? (
- {Array.from({ length: 6 }).map((_, i) =>
)}
+ {Array.from({ length: 6 }).map((_, i) => (
+
+
+
+
+
+ ))}
) : rooms.length === 0 ? (
-
-
- Create First Room
-
- )
- }
- />
+
+
+
No Cultivation Zones Configured
+ {isManager && (
+
+ )}
+
) : (
-
+
{rooms.map(room => {
- const style = getHeaderStyle(room.type);
+ const { accent, icon: Icon } = getRoomStyle(room.type);
+ const accentClasses = {
+ emerald: 'bg-emerald-500/10 text-emerald-500 border-emerald-500/20',
+ purple: 'bg-purple-500/10 text-purple-500 border-purple-500/20',
+ amber: 'bg-amber-500/10 text-amber-500 border-amber-500/20',
+ orange: 'bg-orange-500/10 text-orange-500 border-orange-500/20',
+ pink: 'bg-pink-500/10 text-pink-500 border-pink-500/20',
+ slate: 'bg-slate-500/10 text-slate-500 border-slate-500/20',
+ teal: 'bg-teal-500/10 text-teal-500 border-teal-500/20',
+ };
+
return (
- {/* Color-coded Header */}
-
-
-
- {room.name?.replace('[DEMO] ', '')}
-
- {room.sqft?.toLocaleString()} sqft • {room.capacity || '—'} cap
-
-
-
- {room.type}
-
-
-
-
-
- {/* Sensor Data - Prominent */}
-
-
-
-
-
- {room.targetTemp || '—'}
- °F
-
-
Temp
-
-
-
-
-
- {room.targetHumidity || '—'}
- %
-
-
Humidity
+
+ {/* Header */}
+
- {/* Batch Count */}
-
-
-
0 ? 'bg-success' : 'bg-subtle'}`} />
-
- {room.batches?.length || 0} {room.batches?.length === 1 ? 'batch' : 'batches'}
+ {/* Body */}
+
+
+
+ {room.name?.replace('[DEMO] ', '')}
+
+
+ {room.sqft?.toLocaleString()} sqft • Capacity {room.capacity || '—'}
+
+
+
+ {/* Environmental Vitals */}
+
+
+
+
+ {room.targetTemp || '—'}°F
+
+
Target Temp
+
+
+
+
+ {room.targetHumidity || '—'}%
+
+
Target RH
+
+
+
+ {/* Batch Status */}
+
+
+
0 ? 'bg-emerald-500' : 'bg-slate-300 dark:bg-slate-700')} />
+
+ {room.batches?.length || 0} Active Batch{room.batches?.length === 1 ? '' : 'es'}
+
+
+
+ Enter →
-
View →
-
+
);
})}
@@ -140,3 +208,4 @@ export default function RoomsPage() {
);
}
+