|
import React, { useState, useCallback } from 'react'; |
|
import { LeafIcon, SparklesIcon, SunIcon, WindIcon, SnowflakeIcon, SunriseIcon, AlertTriangleIcon } from '../components/icons'; |
|
import Spinner from '../components/Spinner'; |
|
import { generateSeasonalGuide, isAIConfigured } from '../services/geminiService'; |
|
import type { SeasonalGuide, View } from '../types'; |
|
import { AppStatus } from '../types'; |
|
|
|
const seasonIcons: { [key: string]: React.FC<React.SVGProps<SVGSVGElement>> } = { |
|
Spring: SunriseIcon, |
|
Summer: SunIcon, |
|
Autumn: WindIcon, |
|
Winter: SnowflakeIcon, |
|
}; |
|
|
|
interface SeasonalGuideViewProps { |
|
setActiveView: (view: View) => void; |
|
} |
|
|
|
const SeasonalGuideView: React.FC<SeasonalGuideViewProps> = ({ setActiveView }) => { |
|
const [status, setStatus] = useState<AppStatus>(AppStatus.IDLE); |
|
const [species, setSpecies] = useState<string>(''); |
|
const [location, setLocation] = useState<string>(''); |
|
const [guideData, setGuideData] = useState<SeasonalGuide[] | null>(null); |
|
const [error, setError] = useState<string>(''); |
|
const aiConfigured = isAIConfigured(); |
|
|
|
const handleGenerate = useCallback(async () => { |
|
if (!species.trim()) { |
|
setError('Please enter a bonsai species.'); |
|
return; |
|
} |
|
if (!location.trim()) { |
|
setError('Please enter your city or region.'); |
|
return; |
|
} |
|
setStatus(AppStatus.ANALYZING); |
|
setError(''); |
|
setGuideData(null); |
|
|
|
try { |
|
const result = await generateSeasonalGuide(species, location); |
|
if (result) { |
|
setGuideData(result); |
|
setStatus(AppStatus.SUCCESS); |
|
} else { |
|
throw new Error('Failed to generate the seasonal guide. The AI may be busy. Please try again.'); |
|
} |
|
} catch(e: any) { |
|
setError(e.message); |
|
setStatus(AppStatus.ERROR); |
|
} |
|
}, [species, location]); |
|
|
|
return ( |
|
<div className="space-y-8 max-w-4xl mx-auto"> |
|
<header className="text-center"> |
|
<h2 className="text-3xl font-bold tracking-tight text-stone-900 sm:text-4xl flex items-center justify-center gap-3"> |
|
<LeafIcon className="w-8 h-8 text-green-600" /> |
|
Seasonal Care Guides |
|
</h2> |
|
<p className="mt-4 text-lg leading-8 text-stone-600"> |
|
Get a year-round plan for any bonsai species, tailored to your local climate. |
|
</p> |
|
</header> |
|
|
|
<div className="bg-white p-6 rounded-xl shadow-lg border border-stone-200 space-y-4"> |
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4"> |
|
<input |
|
type="text" |
|
value={species} |
|
onChange={(e) => setSpecies(e.target.value)} |
|
className="block w-full rounded-md border-0 py-2 px-3 text-stone-900 shadow-sm ring-1 ring-inset ring-stone-300 placeholder:text-stone-400 focus:ring-2 focus:ring-inset focus:ring-green-600" |
|
placeholder="e.g., Ficus Retusa" |
|
disabled={status === AppStatus.ANALYZING} |
|
/> |
|
<input |
|
type="text" |
|
value={location} |
|
onChange={(e) => setLocation(e.target.value)} |
|
className="block w-full rounded-md border-0 py-2 px-3 text-stone-900 shadow-sm ring-1 ring-inset ring-stone-300 placeholder:text-stone-400 focus:ring-2 focus:ring-inset focus:ring-green-600" |
|
placeholder="e.g., Miami, Florida" |
|
disabled={status === AppStatus.ANALYZING} |
|
/> |
|
</div> |
|
<button |
|
onClick={handleGenerate} |
|
disabled={status === AppStatus.ANALYZING || !aiConfigured} |
|
className="w-full flex items-center justify-center gap-2 rounded-md bg-green-700 px-4 py-2.5 text-sm font-semibold text-white shadow-sm hover:bg-green-600 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-green-700 disabled:bg-stone-400 disabled:cursor-not-allowed" |
|
> |
|
<SparklesIcon className="w-5 h-5" /> |
|
{status === AppStatus.ANALYZING ? 'Generating...' : 'Generate Guide'} |
|
</button> |
|
{error && <p className="text-sm text-red-600 mt-2">{error}</p>} |
|
{!aiConfigured && ( |
|
<div className="mt-4 p-3 bg-yellow-50 text-yellow-800 rounded-lg border border-yellow-200 text-center"> |
|
<p className="text-sm"> |
|
AI features are disabled. Please set your Gemini API key in the{' '} |
|
<button onClick={() => setActiveView('settings')} className="font-bold underline hover:text-yellow-900"> |
|
Settings page |
|
</button>. |
|
</p> |
|
</div> |
|
)} |
|
</div> |
|
|
|
{status === AppStatus.ANALYZING && <Spinner text="Yuki is reading the almanac..." />} |
|
{status === AppStatus.SUCCESS && guideData && ( |
|
<div className="space-y-6"> |
|
<h3 className="text-2xl font-bold text-stone-800 text-center">Seasonal Guide for a {species} in {location}</h3> |
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6"> |
|
{guideData.sort((a,b) => ['Spring', 'Summer', 'Autumn', 'Winter'].indexOf(a.season) - ['Spring', 'Summer', 'Autumn', 'Winter'].indexOf(b.season)).map(season => { |
|
const Icon = seasonIcons[season.season] || LeafIcon; |
|
return ( |
|
<div key={season.season} className="bg-white rounded-xl shadow-md border border-stone-200 p-6"> |
|
<div className="flex items-center gap-3 mb-4"> |
|
<Icon className="w-7 h-7 text-green-700" /> |
|
<h3 className="text-xl font-semibold text-stone-800">{season.season}</h3> |
|
</div> |
|
<div className="space-y-3 text-stone-600"> |
|
<p className="italic text-stone-600 mb-4">{season.summary}</p> |
|
<ul className="space-y-2"> |
|
{season.tasks.map(task => ( |
|
<li key={task.task} className="flex items-center justify-between text-sm"> |
|
<span>{task.task}</span> |
|
<span className={`px-2 py-0.5 text-xs font-medium rounded-full ${task.importance === 'High' ? 'bg-red-100 text-red-800' : task.importance === 'Medium' ? 'bg-yellow-100 text-yellow-800' : 'bg-blue-100 text-blue-800'}`}>{task.importance}</span> |
|
</li> |
|
))} |
|
</ul> |
|
</div> |
|
</div> |
|
) |
|
})} |
|
</div> |
|
</div> |
|
)} |
|
</div> |
|
); |
|
}; |
|
|
|
export default SeasonalGuideView; |