fix: PDF export visibility and Light/Dark mode theming for Environment Report
Some checks are pending
Test / backend-test (push) Waiting to run
Test / frontend-test (push) Waiting to run

This commit is contained in:
fullsizemalt 2026-01-06 03:15:45 -08:00
parent 2998b90fe0
commit dc403c29f5
3 changed files with 2834 additions and 248 deletions

355
QUICK-REFERENCE.md Normal file
View file

@ -0,0 +1,355 @@
# VERIDIAN HARDWARE TIER REPORT - QUICK REFERENCE
**Deployment:** 5 Greenhouses + 3 Drying Containers
**Date:** January 6, 2026
**Report:** [VERIDIAN-HARDWARE-TIERS-REPORT.md](./VERIDIAN-HARDWARE-TIERS-REPORT.md)
---
## 🎯 TL;DR (Too Long; Didn't Read)
### Three Hardware Tiers for 5 GH + 3 Containers
| Tier | Total Cost | What You Get | Best For |
|------|------------|--------------|----------|
| **Baseline** | **$2,200** | SensorPush HT1, Kasa EP10, Pi 4, Zigbee soil sensors (optional) | Initial testing, budget-conscious |
| **Premium** | **$10,500** | HT.w sensors, CO2, cameras, Pi 5, Soil Scout | Production, small commercial |
| **Enterprise** | **$36,600** | PAR sensors, PTZ cameras, Pi cluster, PLC | Commercial scale, maximum uptime |
**Real 2026 Pricing Applied** - Actual prices are LOWER than estimated!
---
## 📦 What's Included
### Common to All Tiers:
- **26 Air Sensors** (20 greenhouses + 6 containers)
- **10 Smart Plugs** (power control for fans, heaters, dehumidifiers)
- **Raspberry Pi Edge Device** (runs HomeAssistant + Veridian Edge Agent)
- **Network Infrastructure** (WiFi, ethernet, switches)
- **SQLite Offline Buffer** (data survives internet outages)
### Tier-Specific Additions:
**Baseline ($2,200):**
- SensorPush HT1 sensors (±0.36°F accuracy)
- Raspberry Pi 4 (4GB RAM)
- Kasa EP10 smart plugs (no energy monitoring)
- Optional: Zigbee soil sensors ($198) or DIY capacitive ($255)
- 30-min UPS battery backup
**Premium ($10,500):**
- SensorPush HT.w sensors (±0.18°F accuracy - 2x better!)
- Raspberry Pi 5 (8GB RAM) + NVMe SSD
- Kasa EP25 smart plugs (with energy monitoring)
- CO2 monitoring (8 zones)
- 4MP cameras (8 cameras, 30-day retention)
- Soil Scout LoRa sensors (professional-grade)
- E-ink displays (8 zones)
- 2-hour UPS + 4G LTE failover
**Enterprise ($36,600):**
- Everything in Premium, plus:
- PAR light sensors (Apogee SQ-500)
- 4K PTZ cameras (90-day retention)
- Pi 5 cluster (3 nodes, high availability)
- PLC control (industrial automation)
- Multi-depth soil sensors (moisture + EC + pH)
- Voice annunciation system
- 4-6 hour UPS + generator hook-up
- Dual 4G LTE internet redundancy
---
## 🔧 Key Hardware Specifications
### Environmental Sensors
**Air Sensors:**
- **SensorPush HT1** (Baseline): $54.95 each
- Temperature: -40°F to 185°F (±0.36°F)
- Humidity: 0-100% RH (±2%)
- Battery: 12 months
- Data: Every 1 minute
- **SensorPush HT.w** (Premium/Enterprise): $110 each
- Temperature: -40°F to 185°F (±0.18°F) - **2x more accurate!**
- Humidity: 0-100% RH (±1%)
- Battery: 18 months
- Data: Every 30 seconds
**Soil Sensors (Optional):**
| Option | Price | Accuracy | Battery | Integration |
|--------|-------|----------|---------|-------------|
| **Zigbee (Tuya TS0601)** | $18 each | ±5% | 6-12 mo (AAA) | ZHA / Zigbee2MQTT |
| **DIY Capacitive** | $8 each | ±5% | N/A (wired) | ADC + Python/Bun |
| **Soil Scout (LoRa)** | $150-250 each | ±3% | 5-10 years | MQTT via LoRa gateway |
| **Soil Scout Pro** | $250 each | ±2% | 10 years | Multi-depth (6", 12", 18") |
**CO2 Sensors (Premium/Enterprise):**
- **SenseAir S8** (Premium): $200 each
- Range: 0-2000 ppm
- Accuracy: ±30 ppm ±3%
- Output: Modbus RTU
- **SenseAir S8 Pro** (Enterprise): $350 each
- Range: 0-10,000 ppm
- Accuracy: ±20 ppm ±2%
- Pressure compensation: Yes
**PAR Sensors (Enterprise only):**
- **Apogee SQ-500**: $600 each
- Spectral range: 389-683 nm (plant-focused)
- Output: 0-2.5V
- Weatherproof: IP68
### Smart Plugs
| Model | Max Load | Energy Monitor | Price | Best For |
|-------|----------|----------------|-------|----------|
| **Kasa EP10** | 15A / 1800W | No | $7.50 each | Small loads, basic control |
| **Kasa EP25** | 15A / 1800W | Yes | $11.25 each | Medium loads, energy tracking |
**Note:** Actual prices from 4-packs! EP10 4-pack = $29.99, EP25 4-pack = $44.99
### Edge Device
| Model | RAM | Performance | Storage | Price |
|-------|-----|-------------|---------|-------|
| **Raspberry Pi 4** | 4GB | 1x baseline | 32GB SD | $60 |
| **Raspberry Pi 5** | 8GB | 2-3x faster | 500GB NVMe | $95 |
**Recommendation:** Pi 5 if budget allows (2-3x faster, true Gigabit Ethernet, NVMe support)
---
## 🏗️ Deployment Architecture
```
Veridian Cloud Backend (VPS)
↓ HTTPS/WebSocket
Edge Device (Raspberry Pi)
├── Home Assistant
├── Veridian Edge Agent (Bun)
├── SQLite Buffer (offline cache)
└── MQTT Broker (optional)
┌────┴────┬─────────┬────────────┐
│ │ │ │
SensorPush Kasa Zigbee Cameras/Other
Gateway Plugs (soil) (Tier 2/3)
│ │ │ │
└────┬────┴─────────┴────────────┘
26 Sensors + 10 Plugs
```
---
## 📍 Sensor Placement
### Greenhouses (5 total)
- **4 air sensors each** (N, S, E, W walls at canopy height)
- **2 soil sensors each** (optional - Tier 1: Zigbee/DIY, Tier 2+: Soil Scout)
### Containers (3 total)
- **2 air sensors each** (front and back, eye level)
- **No soil sensors** (drying environment)
**Total Air Sensors:** 26 (5 GH × 4 + 3 Containers × 2)
**Total Soil Sensors (optional):** 10 (5 GH × 2)
---
## ⚙️ Software Stack
### Edge Device (Raspberry Pi):
- **Home Assistant** (home automation platform)
- **Veridian Edge Agent** (Bun runtime, polls SensorPush API)
- **SQLite** (offline data buffering)
- **MQTT Broker** (optional - for soil sensors, e-ink displays)
- **go2rtc** (Tier 2/3 - camera streaming)
### Integrations:
- **SensorPush** (air sensors) - Native HA integration
- **TP-Link Kasa** (smart plugs) - Native HA integration
- **ZHA** or **Zigbee2MQTT** (soil sensors)
- **Modbus** (CO2 sensors - Tier 2/3)
- **Veridian Cloud** (backend API, WebSocket)
---
## 🔌 Example Automations
### Emergency Ventilation (High Temp)
```yaml
# If temperature > 85°F for 1 minute → Turn on fan
trigger:
- platform: numeric_state
entity_id: sensor.greenhouse_1_temperature
above: 85
for:
minutes: 1
action:
- service: switch.turn_on
entity_id: switch.greenhouse_1_fan
```
### Low Soil Moisture Alert
```yaml
# If soil moisture < 30% Send notification
trigger:
- platform: numeric_state
entity_id: sensor.greenhouse_1_soil_moisture
below: 30
for:
minutes: 30
action:
- service: notify.mobile_app
data:
title: "💧 GH1 Low Soil Moisture"
message: "Soil moisture at 25%. Watering needed."
```
### Container Dehumidifier
```yaml
# If humidity > 65% → Turn on dehumidifier
trigger:
- platform: numeric_state
entity_id: sensor.container_1_humidity
above: 65
action:
- service: switch.turn_on
entity_id: switch.container_1_dehumidifier
```
---
## 📅 Installation Timeline
### Tier 1 (Baseline)
- **Procurement:** 1 week (Amazon, fast shipping)
- **Installation:** 1 weekend (DIY)
- **Configuration:** 1-2 days
- **Testing:** 1 week
- **Total:** 3-4 weeks to full deployment
### Tier 2 (Premium)
- **Procurement:** 2 weeks (some specialty items)
- **Installation:** 1 week
- **Configuration:** 3-5 days
- **Testing:** 1 week
- **Total:** 5-6 weeks to full deployment
### Tier 3 (Enterprise)
- **Procurement:** 3-4 weeks (industrial equipment)
- **Installation:** 2-3 weeks (professional)
- **Configuration:** 1-2 weeks
- **Testing:** 2 weeks
- **Total:** 10-12 weeks to full deployment
---
## 💰 Cost Comparison Over 5 Years
| Tier | Initial Cost | Maintenance | Total 5-Year | ROI Timeline |
|------|--------------|-------------|--------------|--------------|
| **Baseline** | $2,200 | $324 | $2,524 | 6-12 months |
| **Premium** | $10,500 | $760 | $11,260 | 4-6 months |
| **Enterprise** | $36,600 | $8,560 | $45,160 | 6-9 months |
**ROI Calculation:**
- Energy savings from smart HVAC control: 20-30%
- Reduced crop loss from environmental alerts: 10-20%
- Increased yields from optimized environment: 10-20%
- Labor savings from automation: 5-10 hours/week
---
## 🚀 Recommendations
### For Initial Testing → **Start with Tier 1 ($2,200)**
**Why:**
- Low risk investment
- Quick deployment (1 weekend)
- Proven consumer hardware
- Validates system before scaling
- Upgrade path to higher tiers
**Deployment Strategy:**
1. Pilot 1 greenhouse + 1 container (Week 1-2)
2. Test for 2-4 weeks (Week 3-6)
3. Expand to all zones (Week 7-8)
4. Total: 2 months to full deployment
### For Production → **Tier 2 ($10,500)**
**Why:**
- Production-grade reliability
- Camera surveillance (security + monitoring)
- CO2 monitoring (yield optimization)
- Professional soil sensors (Soil Scout)
- Good balance of cost vs capability
**ROI:** 200-300% over 5 years
### For Commercial Scale → **Tier 3 ($36,600)**
**Why:**
- Maximum uptime (99.99%)
- High availability (automatic failover)
- PAR light monitoring (professional cultivation)
- Enterprise support and SLAs
- 7-10 year lifespan
**ROI:** 400-600% over 5 years, 1000%+ over 10 years
---
## 📚 Additional Resources
### Documentation
- **Full Report:** [VERIDIAN-HARDWARE-TIERS-REPORT.md](./VERIDIAN-HARDWARE-TIERS-REPORT.md)
- **Veridian Docs:** https://docs.veridian.runfoo.run
- **Home Assistant Docs:** https://www.home-assistant.io/docs
### Hardware Vendors
- **SensorPush:** https://sensorpush.com
- **Raspberry Pi:** https://www.raspberrypi.com
- **Kasa Smart Plugs:** https://www.kasasmart.com
- **Soil Scout:** https://soilscout.com
- **RAK Wireless (LoRa):** https://store.rakwireless.com
### Community Support
- **Home Assistant Community:** https://community.home-assistant.io
- **Veridian Edge Repo:** https://git.runfoo.run/malty/veridian-edge
- **GitHub Issues:** https://github.com/your-org/veridian/issues
---
## 🏷️ Tags (for Web Search / SEO)
`#Veridian #CultivationPlatform #GreenhouseAutomation #IoT #HomeAssistant #SensorPush #SoilMoistureSensors #SmartFarming #CannabisCultivation #EnvironmentalMonitoring #RaspberryPi #EdgeComputing #Zigbee #LoRaWAN #WirelessSensors #ClimateControl #AgricultureTechnology #AgTech #PrecisionAgriculture #GrowAutomation #HVACControl #CO2Monitoring #PARSensors #VPDMonitoring #SmartPlugs #Kasa #TPLink #DIYGreenhouse #ContainerFarming #DryingContainers #HomeAutomation #SmartHome #IndustrialIoT #IIoT #AgriculturalSensors #CapacitiveSoilSensors #Modbus #MQTT #ZHA #Zigbee2MQTT #SoilScout #SenseAir #ApogeeInstruments #PTZCameras #Surveillance #RemoteMonitoring #OfflineResilience #DataBuffering #CloudSync #HardwareTier #BillOfMaterials #BOM #DeploymentGuide #InstallationGuide #MaintenanceSchedule #Troubleshooting #ROI #CostAnalysis #2026Pricing`
---
## 📞 Contact & Support
**Veridian Platform:**
- **Documentation:** https://docs.veridian.runfoo.run
- **GitHub:** https://github.com/your-org/veridian
- **GitLab:** https://git.runfoo.run/malty/veridian-edge
- **Support:** support@veridian.runfoo.run
**Report Issues:**
- **GitHub Issues:** https://github.com/your-org/veridian/issues
- **Email:** support@veridian.runfoo.run
---
**Quick Reference Created:** January 6, 2026
**Full Report:** [VERIDIAN-HARDWARE-TIERS-REPORT.md](./VERIDIAN-HARDWARE-TIERS-REPORT.md)
**Version:** 1.0

File diff suppressed because it is too large Load diff

View file

@ -308,18 +308,18 @@ export default function EnvironmentReportPage() {
} }
`}</style> `}</style>
<div className="min-h-screen bg-gradient-to-br from-slate-900 via-slate-800 to-slate-900 p-8"> <div className="min-h-screen bg-slate-50 dark:bg-slate-950 p-8 transition-colors duration-300">
<div className="max-w-5xl mx-auto"> <div className="max-w-5xl mx-auto">
{/* --- WEB DASHBOARD VIEW --- */} {/* --- WEB DASHBOARD VIEW --- */}
<div className="no-print mb-8 flex items-center justify-between"> <div className="no-print mb-8 flex items-center justify-between">
<div> <div>
<h1 className="text-3xl font-bold text-white flex items-center gap-3"> <h1 className="text-3xl font-bold text-slate-900 dark:text-white flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-gradient-to-br from-blue-500 to-indigo-600 shadow-lg shadow-blue-500/25"> <div className="p-2.5 rounded-xl bg-blue-600 shadow-lg shadow-blue-500/25">
<FileText className="w-6 h-6 text-white" /> <FileText className="w-6 h-6 text-white" />
</div> </div>
Environment Report Environment Report
</h1> </h1>
<p className="text-slate-400 ml-14 mt-1"> <p className="text-slate-500 dark:text-slate-400 ml-14 mt-1">
Generate and export environmental monitoring reports Generate and export environmental monitoring reports
</p> </p>
</div> </div>
@ -330,7 +330,7 @@ export default function EnvironmentReportPage() {
<select <select
value={dateRange} value={dateRange}
onChange={(e) => setDateRange(e.target.value as any)} onChange={(e) => setDateRange(e.target.value as any)}
className="appearance-none bg-slate-800 border border-slate-700 rounded-xl px-4 py-2.5 pr-10 text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" className="appearance-none bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 rounded-xl px-4 py-2.5 pr-10 text-slate-900 dark:text-white text-sm focus:outline-none focus:ring-2 focus:ring-blue-500 shadow-sm"
> >
<option value="24h">Last 24 Hours</option> <option value="24h">Last 24 Hours</option>
<option value="7d">Last 7 Days</option> <option value="7d">Last 7 Days</option>
@ -362,20 +362,19 @@ export default function EnvironmentReportPage() {
</div> </div>
</div> </div>
{/* Dashboard Content (Web View) */}
<div className={`space-y-6 no-print transition-opacity duration-300 ${pdfExporting ? 'opacity-50 pointer-events-none grayscale' : ''}`}> <div className={`space-y-6 no-print transition-opacity duration-300 ${pdfExporting ? 'opacity-50 pointer-events-none grayscale' : ''}`}>
{/* Dashboard Header */} {/* Dashboard Header */}
<div className="p-8 rounded-2xl bg-slate-800/50 border border-slate-700/50 text-center"> <div className="p-8 rounded-2xl bg-white dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700/50 text-center shadow-sm dark:shadow-none">
<div className="inline-flex items-center gap-2 px-4 py-1.5 bg-emerald-500/20 text-emerald-400 rounded-full text-sm font-medium mb-4"> <div className="inline-flex items-center gap-2 px-4 py-1.5 bg-emerald-500/10 dark:bg-emerald-500/20 text-emerald-600 dark:text-emerald-400 rounded-full text-sm font-medium mb-4">
<Activity className="w-4 h-4" /> <Activity className="w-4 h-4" />
Environment Monitoring Report Environment Monitoring Report
</div> </div>
<h2 className="text-2xl font-bold text-white mb-2">Veridian Grow Operations</h2> <h2 className="text-2xl font-bold text-slate-900 dark:text-white mb-2">Veridian Grow Operations</h2>
<p className="text-slate-400"> <p className="text-slate-500 dark:text-slate-400">
<Calendar className="w-4 h-4 inline mr-2" /> <Calendar className="w-4 h-4 inline mr-2" />
{reportData?.period.start} {reportData?.period.end} {reportData?.period.start} {reportData?.period.end}
</p> </p>
<p className="text-xs text-slate-500 mt-2">Generated: {formatDate()}</p> <p className="text-xs text-slate-400 mt-2">Generated: {formatDate()}</p>
</div> </div>
{/* Summary Stats */} {/* Summary Stats */}
@ -383,13 +382,13 @@ export default function EnvironmentReportPage() {
<motion.div <motion.div
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
className="p-5 rounded-2xl bg-gradient-to-br from-red-500/20 to-orange-500/10 border border-red-500/20" className="p-5 rounded-2xl bg-white dark:bg-gradient-to-br dark:from-red-500/20 dark:to-orange-500/10 border border-red-200 dark:border-red-500/20 shadow-sm dark:shadow-none"
> >
<div className="flex items-center gap-2 mb-2 text-slate-400"> <div className="flex items-center gap-2 mb-2 text-slate-500 dark:text-slate-400">
<Thermometer className="w-5 h-5 text-red-400" /> <Thermometer className="w-5 h-5 text-red-500 dark:text-red-400" />
<span className="text-sm">Avg Temperature</span> <span className="text-sm">Avg Temperature</span>
</div> </div>
<p className="text-3xl font-bold text-white"> <p className="text-3xl font-bold text-slate-900 dark:text-white">
{reportData?.devices[0]?.stats.temperature.avg.toFixed(1) || '--'}°F {reportData?.devices[0]?.stats.temperature.avg.toFixed(1) || '--'}°F
</p> </p>
<p className="text-xs text-slate-500 mt-1"> <p className="text-xs text-slate-500 mt-1">
@ -401,13 +400,13 @@ export default function EnvironmentReportPage() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 }} transition={{ delay: 0.1 }}
className="p-5 rounded-2xl bg-gradient-to-br from-blue-500/20 to-cyan-500/10 border border-blue-500/20" className="p-5 rounded-2xl bg-white dark:bg-gradient-to-br dark:from-blue-500/20 dark:to-cyan-500/10 border border-blue-200 dark:border-blue-500/20 shadow-sm dark:shadow-none"
> >
<div className="flex items-center gap-2 mb-2 text-slate-400"> <div className="flex items-center gap-2 mb-2 text-slate-500 dark:text-slate-400">
<Droplets className="w-5 h-5 text-blue-400" /> <Droplets className="w-5 h-5 text-blue-500 dark:text-blue-400" />
<span className="text-sm">Avg Humidity</span> <span className="text-sm">Avg Humidity</span>
</div> </div>
<p className="text-3xl font-bold text-white"> <p className="text-3xl font-bold text-slate-900 dark:text-white">
{reportData?.devices[0]?.stats.humidity.avg.toFixed(0) || '--'}% {reportData?.devices[0]?.stats.humidity.avg.toFixed(0) || '--'}%
</p> </p>
<p className="text-xs text-slate-500 mt-1"> <p className="text-xs text-slate-500 mt-1">
@ -419,13 +418,13 @@ export default function EnvironmentReportPage() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.2 }} transition={{ delay: 0.2 }}
className="p-5 rounded-2xl bg-gradient-to-br from-purple-500/20 to-pink-500/10 border border-purple-500/20" className="p-5 rounded-2xl bg-white dark:bg-gradient-to-br dark:from-purple-500/20 dark:to-pink-500/10 border border-purple-200 dark:border-purple-500/20 shadow-sm dark:shadow-none"
> >
<div className="flex items-center gap-2 mb-2 text-slate-400"> <div className="flex items-center gap-2 mb-2 text-slate-500 dark:text-slate-400">
<Wind className="w-5 h-5 text-purple-400" /> <Wind className="w-5 h-5 text-purple-500 dark:text-purple-400" />
<span className="text-sm">Avg VPD</span> <span className="text-sm">Avg VPD</span>
</div> </div>
<p className="text-3xl font-bold text-white"> <p className="text-3xl font-bold text-slate-900 dark:text-white">
{reportData?.devices[0]?.stats.vpd.avg.toFixed(2) || '--'} kPa {reportData?.devices[0]?.stats.vpd.avg.toFixed(2) || '--'} kPa
</p> </p>
<p className="text-xs text-slate-500 mt-1"> <p className="text-xs text-slate-500 mt-1">
@ -437,26 +436,26 @@ export default function EnvironmentReportPage() {
initial={{ opacity: 0, y: 20 }} initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }} animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.3 }} transition={{ delay: 0.3 }}
className="p-5 rounded-2xl bg-gradient-to-br from-amber-500/20 to-yellow-500/10 border border-amber-500/20" className="p-5 rounded-2xl bg-white dark:bg-gradient-to-br dark:from-amber-500/20 dark:to-yellow-500/10 border border-amber-200 dark:border-amber-500/20 shadow-sm dark:shadow-none"
> >
<div className="flex items-center gap-2 mb-2 text-slate-400"> <div className="flex items-center gap-2 mb-2 text-slate-500 dark:text-slate-400">
<AlertTriangle className="w-5 h-5 text-amber-400" /> <AlertTriangle className="w-5 h-5 text-amber-500 dark:text-amber-400" />
<span className="text-sm">Alerts</span> <span className="text-sm">Alerts</span>
</div> </div>
<p className="text-3xl font-bold text-white"> <p className="text-3xl font-bold text-slate-900 dark:text-white">
{reportData?.alerts.total || 0} {reportData?.alerts.total || 0}
</p> </p>
<p className="text-xs text-slate-500 mt-1"> <p className="text-xs text-slate-500 mt-1">
<CheckCircle className="w-3 h-3 inline mr-1 text-green-400" /> <CheckCircle className="w-3 h-3 inline mr-1 text-green-500 dark:text-green-400" />
{reportData?.alerts.resolved || 0} resolved {reportData?.alerts.resolved || 0} resolved
</p> </p>
</motion.div> </motion.div>
</div> </div>
{/* Temperature Trend Chart */} {/* Temperature Trend Chart */}
<div className="p-6 rounded-2xl bg-slate-800/50 border border-slate-700/50"> <div className="p-6 rounded-2xl bg-white dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700/50 shadow-sm dark:shadow-none">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2"> <h3 className="text-lg font-semibold text-slate-900 dark:text-white mb-4 flex items-center gap-2">
<TrendingUp className="w-5 h-5 text-emerald-400" /> <TrendingUp className="w-5 h-5 text-emerald-500 dark:text-emerald-400" />
Temperature Trend Temperature Trend
</h3> </h3>
<div className="h-64"> <div className="h-64">
@ -468,7 +467,7 @@ export default function EnvironmentReportPage() {
<stop offset="95%" stopColor="#ef4444" stopOpacity={0} /> <stop offset="95%" stopColor="#ef4444" stopOpacity={0} />
</linearGradient> </linearGradient>
</defs> </defs>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" /> <CartesianGrid strokeDasharray="3 3" stroke="#334155" opacity={0.3} />
<XAxis dataKey="timestamp" stroke="#64748b" fontSize={10} tickLine={false} /> <XAxis dataKey="timestamp" stroke="#64748b" fontSize={10} tickLine={false} />
<YAxis stroke="#64748b" fontSize={10} tickLine={false} /> <YAxis stroke="#64748b" fontSize={10} tickLine={false} />
<Tooltip contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }} /> <Tooltip contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }} />
@ -482,9 +481,9 @@ export default function EnvironmentReportPage() {
{/* Humidity and VPD Charts */} {/* Humidity and VPD Charts */}
<div className="grid grid-cols-2 gap-6"> <div className="grid grid-cols-2 gap-6">
<div className="p-6 rounded-2xl bg-slate-800/50 border border-slate-700/50"> <div className="p-6 rounded-2xl bg-white dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700/50 shadow-sm dark:shadow-none">
<h3 className="text-sm font-semibold text-white mb-4 flex items-center gap-2"> <h3 className="text-sm font-semibold text-slate-900 dark:text-white mb-4 flex items-center gap-2">
<Droplets className="w-4 h-4 text-blue-400" /> <Droplets className="w-4 h-4 text-blue-500 dark:text-blue-400" />
Humidity Trend Humidity Trend
</h3> </h3>
<div className="h-48"> <div className="h-48">
@ -496,7 +495,7 @@ export default function EnvironmentReportPage() {
<stop offset="95%" stopColor="#3b82f6" stopOpacity={0} /> <stop offset="95%" stopColor="#3b82f6" stopOpacity={0} />
</linearGradient> </linearGradient>
</defs> </defs>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" /> <CartesianGrid strokeDasharray="3 3" stroke="#334155" opacity={0.3} />
<XAxis dataKey="timestamp" stroke="#64748b" fontSize={9} tickLine={false} /> <XAxis dataKey="timestamp" stroke="#64748b" fontSize={9} tickLine={false} />
<YAxis stroke="#64748b" fontSize={9} tickLine={false} domain={[0, 100]} /> <YAxis stroke="#64748b" fontSize={9} tickLine={false} domain={[0, 100]} />
<Tooltip contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }} /> <Tooltip contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }} />
@ -506,9 +505,9 @@ export default function EnvironmentReportPage() {
</div> </div>
</div> </div>
<div className="p-6 rounded-2xl bg-slate-800/50 border border-slate-700/50"> <div className="p-6 rounded-2xl bg-white dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700/50 shadow-sm dark:shadow-none">
<h3 className="text-sm font-semibold text-white mb-4 flex items-center gap-2"> <h3 className="text-sm font-semibold text-slate-900 dark:text-white mb-4 flex items-center gap-2">
<Wind className="w-4 h-4 text-purple-400" /> <Wind className="w-4 h-4 text-purple-500 dark:text-purple-400" />
VPD Trend VPD Trend
</h3> </h3>
<div className="h-48"> <div className="h-48">
@ -520,7 +519,7 @@ export default function EnvironmentReportPage() {
<stop offset="95%" stopColor="#a855f7" stopOpacity={0} /> <stop offset="95%" stopColor="#a855f7" stopOpacity={0} />
</linearGradient> </linearGradient>
</defs> </defs>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" /> <CartesianGrid strokeDasharray="3 3" stroke="#334155" opacity={0.3} />
<XAxis dataKey="timestamp" stroke="#64748b" fontSize={9} tickLine={false} /> <XAxis dataKey="timestamp" stroke="#64748b" fontSize={9} tickLine={false} />
<YAxis stroke="#64748b" fontSize={9} tickLine={false} domain={[0, 2]} /> <YAxis stroke="#64748b" fontSize={9} tickLine={false} domain={[0, 2]} />
<Tooltip contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }} /> <Tooltip contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }} />
@ -535,15 +534,15 @@ export default function EnvironmentReportPage() {
{/* Hourly Breakdown */} {/* Hourly Breakdown */}
{reportData?.hourlyBreakdown && reportData.hourlyBreakdown.length > 0 && ( {reportData?.hourlyBreakdown && reportData.hourlyBreakdown.length > 0 && (
<div className="p-6 rounded-2xl bg-slate-800/50 border border-slate-700/50 print-break"> <div className="p-6 rounded-2xl bg-white dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700/50 shadow-sm dark:shadow-none print-break">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2"> <h3 className="text-lg font-semibold text-slate-900 dark:text-white mb-4 flex items-center gap-2">
<Clock className="w-5 h-5 text-cyan-400" /> <Clock className="w-5 h-5 text-cyan-500 dark:text-cyan-400" />
Hourly Average Temperature Hourly Average Temperature
</h3> </h3>
<div className="h-64"> <div className="h-64">
<ResponsiveContainer width="100%" height="100%"> <ResponsiveContainer width="100%" height="100%">
<BarChart data={reportData.hourlyBreakdown}> <BarChart data={reportData.hourlyBreakdown}>
<CartesianGrid strokeDasharray="3 3" stroke="#334155" /> <CartesianGrid strokeDasharray="3 3" stroke="#334155" opacity={0.3} />
<XAxis dataKey="hour" stroke="#64748b" fontSize={10} tickLine={false} /> <XAxis dataKey="hour" stroke="#64748b" fontSize={10} tickLine={false} />
<YAxis stroke="#64748b" fontSize={10} tickLine={false} /> <YAxis stroke="#64748b" fontSize={10} tickLine={false} />
<Tooltip contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }} /> <Tooltip contentStyle={{ backgroundColor: '#1e293b', border: '1px solid #334155', borderRadius: '8px' }} />
@ -555,12 +554,12 @@ export default function EnvironmentReportPage() {
)} )}
{/* Device Summary Table */} {/* Device Summary Table */}
<div className="p-6 rounded-2xl bg-slate-800/50 border border-slate-700/50"> <div className="p-6 rounded-2xl bg-white dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700/50 shadow-sm dark:shadow-none">
<h3 className="text-lg font-semibold text-white mb-4">Device Summary</h3> <h3 className="text-lg font-semibold text-slate-900 dark:text-white mb-4">Device Summary</h3>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="text-left text-xs text-slate-400 uppercase tracking-wider"> <tr className="text-left text-xs text-slate-500 dark:text-slate-400 uppercase tracking-wider">
<th className="pb-3">Device</th> <th className="pb-3">Device</th>
<th className="pb-3 text-center">Readings</th> <th className="pb-3 text-center">Readings</th>
<th className="pb-3 text-center">Temp (Avg)</th> <th className="pb-3 text-center">Temp (Avg)</th>
@ -568,19 +567,19 @@ export default function EnvironmentReportPage() {
<th className="pb-3 text-center">VPD (Avg)</th> <th className="pb-3 text-center">VPD (Avg)</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-700/50"> <tbody className="divide-y divide-slate-200 dark:divide-slate-700/50">
{reportData?.devices.map(device => ( {reportData?.devices.map(device => (
<tr key={device.id} className="text-sm"> <tr key={device.id} className="text-sm">
<td className="py-3 text-white font-medium">{device.name}</td> <td className="py-3 text-slate-900 dark:text-white font-medium">{device.name}</td>
<td className="py-3 text-slate-300 text-center">{device.readings}</td> <td className="py-3 text-slate-600 dark:text-slate-300 text-center">{device.readings}</td>
<td className="py-3 text-center"> <td className="py-3 text-center">
<span className="text-red-400 font-medium">{device.stats.temperature.avg.toFixed(1)}°F</span> <span className="text-red-500 dark:text-red-400 font-medium">{device.stats.temperature.avg.toFixed(1)}°F</span>
</td> </td>
<td className="py-3 text-center"> <td className="py-3 text-center">
<span className="text-blue-400 font-medium">{device.stats.humidity.avg.toFixed(0)}%</span> <span className="text-blue-500 dark:text-blue-400 font-medium">{device.stats.humidity.avg.toFixed(0)}%</span>
</td> </td>
<td className="py-3 text-center"> <td className="py-3 text-center">
<span className="text-purple-400 font-medium">{device.stats.vpd.avg.toFixed(2)}</span> <span className="text-purple-500 dark:text-purple-400 font-medium">{device.stats.vpd.avg.toFixed(2)}</span>
</td> </td>
</tr> </tr>
))} ))}
@ -591,16 +590,16 @@ export default function EnvironmentReportPage() {
{/* Alert Summary */} {/* Alert Summary */}
{reportData?.alerts.byType && reportData.alerts.byType.length > 0 && ( {reportData?.alerts.byType && reportData.alerts.byType.length > 0 && (
<div className="p-6 rounded-2xl bg-slate-800/50 border border-slate-700/50"> <div className="p-6 rounded-2xl bg-white dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700/50 shadow-sm dark:shadow-none">
<h3 className="text-lg font-semibold text-white mb-4 flex items-center gap-2"> <h3 className="text-lg font-semibold text-slate-900 dark:text-white mb-4 flex items-center gap-2">
<AlertTriangle className="w-5 h-5 text-amber-400" /> <AlertTriangle className="w-5 h-5 text-amber-500 dark:text-amber-400" />
Alert Summary Alert Summary
</h3> </h3>
<div className="grid grid-cols-2 md:grid-cols-4 gap-4"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4">
{reportData.alerts.byType.map(alert => ( {reportData.alerts.byType.map(alert => (
<div key={alert.type} className="p-4 rounded-xl bg-slate-900/50 border border-slate-700/50"> <div key={alert.type} className="p-4 rounded-xl bg-slate-50 dark:bg-slate-900/50 border border-slate-200 dark:border-slate-700/50">
<p className="text-xs text-slate-400 mb-1">{alert.type.replace('_', ' ')}</p> <p className="text-xs text-slate-500 dark:text-slate-400 mb-1">{alert.type.replace('_', ' ')}</p>
<p className="text-2xl font-bold text-white">{alert.count}</p> <p className="text-2xl font-bold text-slate-900 dark:text-white">{alert.count}</p>
</div> </div>
))} ))}
</div> </div>
@ -609,17 +608,17 @@ export default function EnvironmentReportPage() {
{/* Response Time Analytics */} {/* Response Time Analytics */}
{reportData?.alertAnalytics && ( {reportData?.alertAnalytics && (
<div className="p-6 rounded-2xl bg-slate-800/50 border border-slate-700/50 print-break"> <div className="p-6 rounded-2xl bg-white dark:bg-slate-800/50 border border-slate-200 dark:border-slate-700/50 shadow-sm dark:shadow-none print-break">
<h3 className="text-lg font-semibold text-white mb-6 flex items-center gap-2"> <h3 className="text-lg font-semibold text-slate-900 dark:text-white mb-6 flex items-center gap-2">
<Clock className="w-5 h-5 text-cyan-400" /> <Clock className="w-5 h-5 text-cyan-500 dark:text-cyan-400" />
Alert Response Time Analytics Alert Response Time Analytics
</h3> </h3>
{/* KPIs */} {/* KPIs */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6"> <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
<div className="p-4 rounded-xl bg-gradient-to-br from-emerald-500/20 to-green-500/10 border border-emerald-500/20"> <div className="p-4 rounded-xl bg-white dark:bg-gradient-to-br dark:from-emerald-500/20 dark:to-green-500/10 border border-emerald-200 dark:border-emerald-500/20 shadow-sm dark:shadow-none">
<p className="text-xs text-slate-400 mb-1">Resolution Rate</p> <p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Resolution Rate</p>
<p className="text-3xl font-bold text-emerald-400"> <p className="text-3xl font-bold text-emerald-600 dark:text-emerald-400">
{reportData.alertAnalytics.summary.resolutionRate}% {reportData.alertAnalytics.summary.resolutionRate}%
</p> </p>
<p className="text-xs text-slate-500 mt-1"> <p className="text-xs text-slate-500 mt-1">
@ -627,25 +626,25 @@ export default function EnvironmentReportPage() {
</p> </p>
</div> </div>
<div className="p-4 rounded-xl bg-gradient-to-br from-blue-500/20 to-cyan-500/10 border border-blue-500/20"> <div className="p-4 rounded-xl bg-white dark:bg-gradient-to-br dark:from-blue-500/20 dark:to-cyan-500/10 border border-blue-200 dark:border-blue-500/20 shadow-sm dark:shadow-none">
<p className="text-xs text-slate-400 mb-1">Avg Resolution Time</p> <p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Avg Resolution Time</p>
<p className="text-3xl font-bold text-blue-400"> <p className="text-3xl font-bold text-blue-600 dark:text-blue-400">
{reportData.alertAnalytics.resolutionTimes.avgMinutes || '--'} {reportData.alertAnalytics.resolutionTimes.avgMinutes || '--'}
</p> </p>
<p className="text-xs text-slate-500 mt-1">minutes</p> <p className="text-xs text-slate-500 mt-1">minutes</p>
</div> </div>
<div className="p-4 rounded-xl bg-gradient-to-br from-amber-500/20 to-orange-500/10 border border-amber-500/20"> <div className="p-4 rounded-xl bg-white dark:bg-gradient-to-br dark:from-amber-500/20 dark:to-orange-500/10 border border-amber-200 dark:border-amber-500/20 shadow-sm dark:shadow-none">
<p className="text-xs text-slate-400 mb-1">Fastest Resolution</p> <p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Fastest Resolution</p>
<p className="text-3xl font-bold text-amber-400"> <p className="text-3xl font-bold text-amber-600 dark:text-amber-400">
{reportData.alertAnalytics.resolutionTimes.minMinutes || '--'} {reportData.alertAnalytics.resolutionTimes.minMinutes || '--'}
</p> </p>
<p className="text-xs text-slate-500 mt-1">minutes</p> <p className="text-xs text-slate-500 mt-1">minutes</p>
</div> </div>
<div className="p-4 rounded-xl bg-gradient-to-br from-red-500/20 to-rose-500/10 border border-red-500/20"> <div className="p-4 rounded-xl bg-white dark:bg-gradient-to-br dark:from-red-500/20 dark:to-rose-500/10 border border-red-200 dark:border-red-500/20 shadow-sm dark:shadow-none">
<p className="text-xs text-slate-400 mb-1">Slowest Resolution</p> <p className="text-xs text-slate-500 dark:text-slate-400 mb-1">Slowest Resolution</p>
<p className="text-3xl font-bold text-red-400"> <p className="text-3xl font-bold text-red-600 dark:text-red-400">
{reportData.alertAnalytics.resolutionTimes.maxMinutes || '--'} {reportData.alertAnalytics.resolutionTimes.maxMinutes || '--'}
</p> </p>
<p className="text-xs text-slate-500 mt-1">minutes</p> <p className="text-xs text-slate-500 mt-1">minutes</p>
@ -655,11 +654,11 @@ export default function EnvironmentReportPage() {
{/* By Alert Type */} {/* By Alert Type */}
{reportData.alertAnalytics.byType.length > 0 && ( {reportData.alertAnalytics.byType.length > 0 && (
<div className="mb-6"> <div className="mb-6">
<h4 className="text-sm font-medium text-slate-400 mb-3">Resolution Time by Alert Type</h4> <h4 className="text-sm font-medium text-slate-500 dark:text-slate-400 mb-3">Resolution Time by Alert Type</h4>
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<table className="w-full"> <table className="w-full">
<thead> <thead>
<tr className="text-left text-xs text-slate-400 uppercase tracking-wider"> <tr className="text-left text-xs text-slate-500 dark:text-slate-400 uppercase tracking-wider">
<th className="pb-3">Alert Type</th> <th className="pb-3">Alert Type</th>
<th className="pb-3 text-center">Total</th> <th className="pb-3 text-center">Total</th>
<th className="pb-3 text-center">Resolved</th> <th className="pb-3 text-center">Resolved</th>
@ -667,25 +666,25 @@ export default function EnvironmentReportPage() {
<th className="pb-3 text-center">Resolution Rate</th> <th className="pb-3 text-center">Resolution Rate</th>
</tr> </tr>
</thead> </thead>
<tbody className="divide-y divide-slate-700/50"> <tbody className="divide-y divide-slate-200 dark:divide-slate-700/50">
{reportData.alertAnalytics.byType.map(alertType => ( {reportData.alertAnalytics.byType.map(alertType => (
<tr key={alertType.type} className="text-sm"> <tr key={alertType.type} className="text-sm">
<td className="py-3 text-white font-medium">{alertType.type.replace('_', ' ')}</td> <td className="py-3 text-slate-900 dark:text-white font-medium">{alertType.type.replace('_', ' ')}</td>
<td className="py-3 text-slate-300 text-center">{alertType.count}</td> <td className="py-3 text-slate-600 dark:text-slate-300 text-center">{alertType.count}</td>
<td className="py-3 text-center"> <td className="py-3 text-center">
<span className="text-green-400">{alertType.resolved}</span> <span className="text-green-600 dark:text-green-400">{alertType.resolved}</span>
</td> </td>
<td className="py-3 text-center"> <td className="py-3 text-center">
<span className="text-cyan-400 font-medium"> <span className="text-cyan-600 dark:text-cyan-400 font-medium">
{alertType.avgResolutionMin ? `${alertType.avgResolutionMin} min` : '--'} {alertType.avgResolutionMin ? `${alertType.avgResolutionMin} min` : '--'}
</span> </span>
</td> </td>
<td className="py-3 text-center"> <td className="py-3 text-center">
<span className={`px-2 py-0.5 rounded-full text-xs font-medium ${alertType.count > 0 && alertType.resolved / alertType.count >= 0.9 <span className={`px-2 py-0.5 rounded-full text-xs font-medium ${alertType.count > 0 && alertType.resolved / alertType.count >= 0.9
? 'bg-green-500/20 text-green-400' ? 'bg-green-100 dark:bg-green-500/20 text-green-700 dark:text-green-400'
: alertType.count > 0 && alertType.resolved / alertType.count >= 0.5 : alertType.count > 0 && alertType.resolved / alertType.count >= 0.5
? 'bg-amber-500/20 text-amber-400' ? 'bg-amber-100 dark:bg-amber-500/20 text-amber-700 dark:text-amber-400'
: 'bg-red-500/20 text-red-400' : 'bg-red-100 dark:bg-red-500/20 text-red-700 dark:text-red-400'
}`}> }`}>
{alertType.count > 0 ? Math.round(alertType.resolved / alertType.count * 100) : 0}% {alertType.count > 0 ? Math.round(alertType.resolved / alertType.count * 100) : 0}%
</span> </span>
@ -701,39 +700,39 @@ export default function EnvironmentReportPage() {
{/* Recent Alerts Timeline */} {/* Recent Alerts Timeline */}
{reportData.alertAnalytics.recentAlerts.length > 0 && ( {reportData.alertAnalytics.recentAlerts.length > 0 && (
<div> <div>
<h4 className="text-sm font-medium text-slate-400 mb-3">Recent Alert Timeline</h4> <h4 className="text-sm font-medium text-slate-500 dark:text-slate-400 mb-3">Recent Alert Timeline</h4>
<div className="space-y-2"> <div className="space-y-2">
{reportData.alertAnalytics.recentAlerts.slice(0, 5).map(alert => ( {reportData.alertAnalytics.recentAlerts.slice(0, 5).map(alert => (
<div <div
key={alert.id} key={alert.id}
className={`p-4 rounded-xl border ${alert.resolvedAt className={`p-4 rounded-xl border ${alert.resolvedAt
? 'bg-green-500/5 border-green-500/20' ? 'bg-green-50 dark:bg-green-500/5 border-green-200 dark:border-green-500/20'
: 'bg-amber-500/5 border-amber-500/20' : 'bg-amber-50 dark:bg-amber-500/5 border-amber-200 dark:border-amber-500/20'
}`} }`}
> >
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className={`px-2 py-0.5 rounded text-xs font-medium ${alert.severity === 'CRITICAL' ? 'bg-red-500/20 text-red-400' : <span className={`px-2 py-0.5 rounded text-xs font-medium ${alert.severity === 'CRITICAL' ? 'bg-red-100 dark:bg-red-500/20 text-red-700 dark:text-red-400' :
alert.severity === 'WARNING' ? 'bg-amber-500/20 text-amber-400' : alert.severity === 'WARNING' ? 'bg-amber-100 dark:bg-amber-500/20 text-amber-700 dark:text-amber-400' :
'bg-blue-500/20 text-blue-400' 'bg-blue-100 dark:bg-blue-500/20 text-blue-700 dark:text-blue-400'
}`}> }`}>
{alert.severity} {alert.severity}
</span> </span>
<span className="text-sm text-white font-medium">{alert.type.replace('_', ' ')}</span> <span className="text-sm text-slate-900 dark:text-white font-medium">{alert.type.replace('_', ' ')}</span>
</div> </div>
<span className={`flex items-center gap-1.5 text-xs font-medium ${alert.resolvedAt ? 'text-green-400' : 'text-amber-400' <span className={`flex items-center gap-1.5 text-xs font-medium ${alert.resolvedAt ? 'text-green-600 dark:text-green-400' : 'text-amber-600 dark:text-amber-400'
}`}> }`}>
<CheckCircle className="w-3 h-3" /> <CheckCircle className="w-3 h-3" />
{alert.resolvedAt ? 'Resolved' : 'Pending'} {alert.resolvedAt ? 'Resolved' : 'Pending'}
</span> </span>
</div> </div>
<p className="text-sm text-slate-300 mb-2">{alert.message}</p> <p className="text-sm text-slate-600 dark:text-slate-300 mb-2">{alert.message}</p>
<div className="flex items-center gap-4 text-xs text-slate-500"> <div className="flex items-center gap-4 text-xs text-slate-500">
<span> <span>
Triggered: {new Date(alert.createdAt).toLocaleString()} Triggered: {new Date(alert.createdAt).toLocaleString()}
</span> </span>
{alert.resolutionTimeMin && ( {alert.resolutionTimeMin && (
<span className="text-cyan-400"> <span className="text-cyan-600 dark:text-cyan-400">
Return to Normal: {alert.resolutionTimeMin.toFixed(1)} min Return to Normal: {alert.resolutionTimeMin.toFixed(1)} min
</span> </span>
)} )}
@ -747,7 +746,7 @@ export default function EnvironmentReportPage() {
)} )}
{/* Footer */} {/* Footer */}
<div className="text-center py-6 text-xs text-slate-500 border-t border-slate-700/50"> <div className="text-center py-6 text-xs text-slate-500 border-t border-slate-200 dark:border-slate-700/50">
<p>Veridian Grow Operations Manager Environment Monitoring Report</p> <p>Veridian Grow Operations Manager Environment Monitoring Report</p>
<p className="mt-1">Generated automatically Data subject to sensor accuracy</p> <p className="mt-1">Generated automatically Data subject to sensor accuracy</p>
</div> </div>
@ -755,10 +754,19 @@ export default function EnvironmentReportPage() {
{/* --- CLEAN DOCUMENT LAYOUT (For PDF Generation) --- */} {/* --- CLEAN DOCUMENT LAYOUT (For PDF Generation) --- */}
{/* This div is referenced by reportRef for html2canvas */} {/* Wrapper for off-screen positioning - MUST be visible for html2canvas */}
<div
className="fixed top-0"
style={{
left: pdfExporting ? '0' : '-9999px',
zIndex: -50,
visibility: 'visible',
width: '210mm'
}}
>
<div <div
ref={reportRef} ref={reportRef}
className={`bg-white text-black p-12 mx-auto shadow-2xl ${pdfExporting ? 'block' : 'hidden'}`} className="bg-white text-black p-12 mx-auto shadow-2xl"
style={{ style={{
width: '210mm', // A4 Width width: '210mm', // A4 Width
minHeight: '297mm', // A4 Height minHeight: '297mm', // A4 Height
@ -934,6 +942,7 @@ export default function EnvironmentReportPage() {
</div> </div>
</div> </div>
</div> </div>
</div>
</> </>
); );
} }