diff --git a/components/EnvironmentalDisplay.tsx b/components/EnvironmentalDisplay.tsx new file mode 100644 index 0000000..92ca3ce --- /dev/null +++ b/components/EnvironmentalDisplay.tsx @@ -0,0 +1,334 @@ +import { useState, useEffect } from 'react'; +import { GrowingEnvironment } from '../lib/environment/types'; +import { EnvironmentalRecommendation } from '../lib/environment/types'; + +interface EnvironmentalDisplayProps { + environment: GrowingEnvironment; + plantId: string; + showRecommendations?: boolean; +} + +export default function EnvironmentalDisplay({ + environment, + plantId, + showRecommendations = true, +}: EnvironmentalDisplayProps) { + const [recommendations, setRecommendations] = useState([]); + const [healthScore, setHealthScore] = useState(null); + const [loading, setLoading] = useState(true); + + useEffect(() => { + if (showRecommendations) { + fetchRecommendations(); + } + }, [plantId]); + + const fetchRecommendations = async () => { + try { + const response = await fetch(`/api/environment/recommendations?plantId=${plantId}`); + const data = await response.json(); + if (data.success) { + setRecommendations(data.recommendations); + setHealthScore(data.environmentalHealth); + } + } catch (error) { + console.error('Error fetching recommendations:', error); + } finally { + setLoading(false); + } + }; + + return ( +
+ {/* Environmental Health Score */} + {healthScore !== null && ( +
+
+
+

+ Environmental Health Score +

+

+ Overall assessment of growing conditions +

+
+
+
+ {healthScore} +
+
/ 100
+
+
+
+
+
+
+

{getScoreInterpretation(healthScore)}

+
+
+ )} + + {/* Recommendations */} + {showRecommendations && recommendations.length > 0 && ( +
+

+ ๐Ÿ“‹ Recommendations ({recommendations.length}) +

+
+ {recommendations.map((rec, idx) => ( + + ))} +
+
+ )} + + {/* Soil Information */} +
+

+ ๐ŸŒฑ Soil Composition +

+
+ + + + + +
+
+ + {/* Climate Conditions */} +
+

+ ๐ŸŒก๏ธ Climate Conditions +

+
+ + + + + + {environment.climate.zone && ( + + )} +
+
+ + {/* Lighting */} +
+

+ โ˜€๏ธ Lighting +

+
+ + {environment.lighting.naturalLight && ( + <> + + + + + )} + {environment.lighting.artificialLight && ( + <> + + + + )} +
+
+ + {/* Location & Container */} +
+
+

๐Ÿ“ Location

+
+ + {environment.location.room && ( + + )} + {environment.location.elevation && ( + + )} +
+
+ + {environment.container && ( +
+

๐Ÿชด Container

+
+ + {environment.container.material && ( + + )} + {environment.container.size && ( + + )} + +
+
+ )} +
+ + {/* Watering */} +
+

๐Ÿ’ง Watering

+
+ + + {environment.watering.frequency && ( + + )} + {environment.watering.waterQuality?.pH && ( + + )} +
+
+ + {/* Nutrients */} + {environment.nutrients && ( +
+

๐Ÿงช Nutrients

+
+ + {environment.nutrients.ec && ( + + )} + {environment.nutrients.tds && ( + + )} +
+
+ )} + + {/* Surroundings */} + {environment.surroundings && ( +
+

๐ŸŒฟ Surroundings

+
+ {environment.surroundings.ecosystem && ( + + )} + {environment.surroundings.windExposure && ( + + )} + {environment.surroundings.companionPlants && environment.surroundings.companionPlants.length > 0 && ( +
+

Companion Plants:

+

{environment.surroundings.companionPlants.join(', ')}

+
+ )} +
+
+ )} +
+ ); +} + +function RecommendationCard({ recommendation }: { recommendation: EnvironmentalRecommendation }) { + const priorityColors = { + critical: 'border-red-500 bg-red-50', + high: 'border-orange-500 bg-orange-50', + medium: 'border-yellow-500 bg-yellow-50', + low: 'border-blue-500 bg-blue-50', + }; + + const priorityIcons = { + critical: '๐Ÿšจ', + high: 'โš ๏ธ', + medium: '๐Ÿ’ก', + low: 'โ„น๏ธ', + }; + + return ( +
+
+ {priorityIcons[recommendation.priority]} +
+
+

{recommendation.issue}

+ + {recommendation.priority} + +
+

+ Recommendation: {recommendation.recommendation} +

+

+ Impact: {recommendation.impact} +

+
+
+
+ ); +} + +function DataPoint({ + label, + value, + highlight, +}: { + label: string; + value: string; + highlight?: 'red' | 'green'; +}) { + const highlightClass = highlight === 'red' ? 'text-red-600 font-semibold' : highlight === 'green' ? 'text-green-600 font-semibold' : ''; + + return ( +
+

{label}

+

{value}

+
+ ); +} + +function formatValue(value: string): string { + return value.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase()); +} + +function getScoreColor(score: number): string { + if (score >= 90) return 'text-green-600'; + if (score >= 75) return 'text-lime-600'; + if (score >= 60) return 'text-yellow-600'; + if (score >= 40) return 'text-orange-600'; + return 'text-red-600'; +} + +function getScoreBgColor(score: number): string { + if (score >= 90) return 'bg-green-600'; + if (score >= 75) return 'bg-lime-600'; + if (score >= 60) return 'bg-yellow-600'; + if (score >= 40) return 'bg-orange-600'; + return 'bg-red-600'; +} + +function getScoreInterpretation(score: number): string { + if (score >= 90) return '๐ŸŒŸ Excellent conditions - your plant should thrive!'; + if (score >= 75) return 'โœ“ Good conditions with minor room for improvement'; + if (score >= 60) return 'โšก Adequate conditions, several optimization opportunities'; + if (score >= 40) return 'โš ๏ธ Suboptimal conditions, address high-priority issues'; + return '๐Ÿšจ Poor conditions, immediate action needed'; +} diff --git a/components/EnvironmentalForm.tsx b/components/EnvironmentalForm.tsx new file mode 100644 index 0000000..3e8e269 --- /dev/null +++ b/components/EnvironmentalForm.tsx @@ -0,0 +1,708 @@ +import { useState } from 'react'; +import { + GrowingEnvironment, + SoilComposition, + ClimateConditions, + LightingConditions, + NutrientProfile, + WateringSchedule, + ContainerInfo, + EnvironmentLocation, + SurroundingEnvironment, +} from '../lib/environment/types'; + +interface EnvironmentalFormProps { + value: Partial; + onChange: (env: Partial) => void; + compact?: boolean; +} + +export default function EnvironmentalForm({ + value, + onChange, + compact = false, +}: EnvironmentalFormProps) { + const [activeSection, setActiveSection] = useState('soil'); + + const updateSection = ( + section: K, + updates: Partial + ) => { + onChange({ + ...value, + [section]: { + ...value[section], + ...updates, + }, + }); + }; + + const sections = [ + { id: 'soil', name: '๐ŸŒฑ Soil', icon: '๐ŸŒฑ' }, + { id: 'nutrients', name: '๐Ÿงช Nutrients', icon: '๐Ÿงช' }, + { id: 'lighting', name: 'โ˜€๏ธ Light', icon: 'โ˜€๏ธ' }, + { id: 'climate', name: '๐ŸŒก๏ธ Climate', icon: '๐ŸŒก๏ธ' }, + { id: 'location', name: '๐Ÿ“ Location', icon: '๐Ÿ“' }, + { id: 'container', name: '๐Ÿชด Container', icon: '๐Ÿชด' }, + { id: 'watering', name: '๐Ÿ’ง Water', icon: '๐Ÿ’ง' }, + { id: 'surroundings', name: '๐ŸŒฟ Surroundings', icon: '๐ŸŒฟ' }, + ]; + + return ( +
+ {/* Section Tabs */} +
+ {sections.map((section) => ( + + ))} +
+ + {/* Section Content */} +
+ {activeSection === 'soil' && ( + updateSection('soil', soil)} + /> + )} + + {activeSection === 'nutrients' && ( + updateSection('nutrients', nutrients)} + /> + )} + + {activeSection === 'lighting' && ( + updateSection('lighting', lighting)} + /> + )} + + {activeSection === 'climate' && ( + updateSection('climate', climate)} + /> + )} + + {activeSection === 'location' && ( + updateSection('location', location)} + /> + )} + + {activeSection === 'container' && ( + updateSection('container', container)} + /> + )} + + {activeSection === 'watering' && ( + updateSection('watering', watering)} + /> + )} + + {activeSection === 'surroundings' && ( + updateSection('surroundings', surroundings)} + /> + )} +
+
+ ); +} + +// Soil Section Component +function SoilSection({ + value, + onChange, +}: { + value?: Partial; + onChange: (soil: Partial) => void; +}) { + const soil = value || {}; + + return ( +
+

Soil Composition

+ +
+
+ + +
+ +
+ + onChange({ ...soil, pH: parseFloat(e.target.value) })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+ {soil.pH && soil.pH < 5.5 && 'โš ๏ธ Acidic - may need lime'} + {soil.pH && soil.pH >= 5.5 && soil.pH <= 7.5 && 'โœ“ Good range'} + {soil.pH && soil.pH > 7.5 && 'โš ๏ธ Alkaline - may need sulfur'} +
+
+ +
+ + +
+ +
+ + +
+ +
+ + onChange({ ...soil, organicMatter: parseFloat(e.target.value) })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+
+ +
+

+ ๐Ÿ’ก Tip: Test soil pH with a meter or test kit for accuracy. Most vegetables prefer pH 6.0-7.0. +

+
+
+ ); +} + +// Nutrients Section Component +function NutrientsSection({ + value, + onChange, +}: { + value?: Partial; + onChange: (nutrients: Partial) => void; +}) { + const nutrients = value || {}; + + return ( +
+

Nutrient Profile

+ +
+

Primary Nutrients (NPK)

+
+
+ + onChange({ ...nutrients, nitrogen: parseFloat(e.target.value) })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+
+ + onChange({ ...nutrients, phosphorus: parseFloat(e.target.value) })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+
+ + onChange({ ...nutrients, potassium: parseFloat(e.target.value) })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+
+

+ NPK ratio: {nutrients.nitrogen || 0}-{nutrients.phosphorus || 0}-{nutrients.potassium || 0} +

+
+ +
+

+ ๐Ÿ’ก Tip: Leave at 0 if unknown. Use soil test kit for accurate NPK values. +

+
+
+ ); +} + +// Lighting Section Component +function LightingSection({ + value, + onChange, +}: { + value?: Partial; + onChange: (lighting: Partial) => void; +}) { + const lighting = value || { type: 'natural' }; + + return ( +
+

Lighting Conditions

+ +
+ + +
+ + {(lighting.type === 'natural' || lighting.type === 'mixed') && ( +
+
+ + +
+ +
+ + + onChange({ + ...lighting, + naturalLight: { + ...lighting.naturalLight, + hoursPerDay: parseInt(e.target.value), + } as any, + }) + } + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+
+ )} + +
+

+ ๐Ÿ’ก Tip: Most vegetables need 6+ hours of direct sunlight. Herbs can do well with 4-6 hours. +

+
+
+ ); +} + +// Climate Section Component +function ClimateSection({ + value, + onChange, +}: { + value?: Partial; + onChange: (climate: Partial) => void; +}) { + const climate = value || {}; + + return ( +
+

Climate Conditions

+ +
+
+ + onChange({ ...climate, temperatureDay: parseFloat(e.target.value) })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+ +
+ + onChange({ ...climate, temperatureNight: parseFloat(e.target.value) })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+ +
+ + onChange({ ...climate, humidityAverage: parseFloat(e.target.value) })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+ +
+ + +
+
+ +
+

+ ๐Ÿ’ก Tip: Most plants thrive at 18-25ยฐC. Good airflow prevents disease. +

+
+
+ ); +} + +// Location Section Component +function LocationSection({ + value, + onChange, +}: { + value?: Partial; + onChange: (location: Partial) => void; +}) { + const location = value || {}; + + return ( +
+

Growing Location

+ +
+ + +
+ +
+

+ ๐Ÿ’ก Tip: Location type affects climate control and pest exposure. +

+
+
+ ); +} + +// Container Section Component +function ContainerSection({ + value, + onChange, +}: { + value?: Partial; + onChange: (container: Partial) => void; +}) { + const container = value || {}; + + return ( +
+

Container Information

+ +
+
+ + +
+ +
+ + + {container.drainage === 'no' && ( +

+ โš ๏ธ WARNING: No drainage will likely kill your plant! +

+ )} +
+ +
+ + onChange({ ...container, size: e.target.value })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+
+ +
+

+ ๐Ÿ’ก Tip: Always ensure drainage! Sitting water = root rot = dead plant. +

+
+
+ ); +} + +// Watering Section Component +function WateringSection({ + value, + onChange, +}: { + value?: Partial; + onChange: (watering: Partial) => void; +}) { + const watering = value || {}; + + return ( +
+

Watering Schedule

+ +
+
+ + +
+ +
+ + +
+ +
+ + onChange({ ...watering, frequency: e.target.value })} + className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-green-500" + /> +
+
+ +
+

+ ๐Ÿ’ก Tip: Water when top inch of soil is dry. Overwatering kills more plants than underwatering! +

+
+
+ ); +} + +// Surroundings Section Component +function SurroundingsSection({ + value, + onChange, +}: { + value?: Partial; + onChange: (surroundings: Partial) => void; +}) { + const surroundings = value || {}; + + return ( +
+

Surrounding Environment

+ +
+ + +
+ +
+ + +
+ +
+

+ ๐Ÿ’ก Tip: Track companion plants and pests to learn what works in your ecosystem. +

+
+
+ ); +} diff --git a/pages/plants/[id].tsx b/pages/plants/[id].tsx index c167b39..cecfd61 100644 --- a/pages/plants/[id].tsx +++ b/pages/plants/[id].tsx @@ -2,6 +2,8 @@ import { useState, useEffect } from 'react'; import { useRouter } from 'next/router'; import Link from 'next/link'; import Head from 'next/head'; +import EnvironmentalDisplay from '../../components/EnvironmentalDisplay'; +import { GrowingEnvironment } from '../../lib/environment/types'; interface Plant { id: string; @@ -28,6 +30,7 @@ interface Plant { email: string; }; childPlants: string[]; + environment?: GrowingEnvironment; notes?: string; registeredAt: string; updatedAt: string; @@ -48,7 +51,7 @@ export default function PlantDetail() { const [lineage, setLineage] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); - const [activeTab, setActiveTab] = useState<'details' | 'lineage'>('details'); + const [activeTab, setActiveTab] = useState<'details' | 'lineage' | 'environment'>('details'); useEffect(() => { if (id) { @@ -219,6 +222,21 @@ export default function PlantDetail() { > ๐ŸŒณ Family Tree + {/* Details Tab */} @@ -362,6 +380,69 @@ export default function PlantDetail() { )} )} + + {/* Environment Tab */} + {activeTab === 'environment' && ( +
+ {plant.environment ? ( + + ) : ( +
+
๐ŸŒ
+

+ No Environmental Data Yet +

+

+ Track soil, climate, nutrients, and growing conditions to: +

+
+
+
๐Ÿ’ก
+

Get Recommendations

+

+ Receive personalized advice to optimize conditions +

+
+
+
๐Ÿ”
+

Compare & Learn

+

+ Find what works for similar plants +

+
+
+
๐Ÿ“ˆ
+

Track Success

+

+ Monitor growth and health over time +

+
+
+

+ ๐Ÿ“˜ Learn more in the{' '} + + Environmental Tracking Guide + +

+ +
+ )} +
+ )} );