| import React, { useState } from 'react'; | |
| import type { BonsaiAnalysis, ToolRecommendation } from '../types'; | |
| import { | |
| CheckCircleIcon, AlertTriangleIcon, SparklesIcon, ZapIcon, BookOpenIcon, | |
| ClipboardListIcon, ScissorsIcon, SunIcon, DropletIcon, ThermometerIcon, | |
| CalendarIcon, BonsaiIcon, LayersIcon, FlaskConicalIcon, GalleryVerticalEndIcon, | |
| WindIcon, SnowflakeIcon, SunriseIcon, LeafIcon, StethoscopeIcon, BugIcon, WrenchIcon, | |
| BookUserIcon, PaletteIcon, DownloadIcon | |
| } from './icons'; | |
| import Spinner from './Spinner'; | |
| import { generateBonsaiImage } from '../services/geminiService'; | |
| type Tab = 'Overview' | 'Care Plan' | 'Health and Pests' | 'Styling' | 'Fertilizer and Soil' | 'Seasonal Guide' | 'Diagnostics' | 'Pest Library' | 'Tools & Supplies' | 'Knowledge'; | |
| interface AnalysisDisplayProps { | |
| analysis: BonsaiAnalysis; | |
| onReset?: () => void; | |
| onSaveToDiary?: () => void; | |
| isReadonly?: boolean; | |
| treeImageBase64?: string; | |
| } | |
| const TABS: { name: Tab, icon: React.FC<React.SVGProps<SVGSVGElement>> }[] = [ | |
| { name: 'Overview', icon: CheckCircleIcon }, | |
| { name: 'Care Plan', icon: CalendarIcon }, | |
| { name: 'Diagnostics', icon: StethoscopeIcon }, | |
| { name: 'Health and Pests', icon: AlertTriangleIcon }, | |
| { name: 'Pest Library', icon: BugIcon }, | |
| { name: 'Styling', icon: ScissorsIcon }, | |
| { name: 'Fertilizer and Soil', icon: LayersIcon }, | |
| { name: 'Seasonal Guide', icon: LeafIcon }, | |
| { name: 'Tools & Supplies', icon: WrenchIcon }, | |
| { name: 'Knowledge', icon: BookOpenIcon }, | |
| ]; | |
| const InfoCard: React.FC<{ title: string; children: React.ReactNode; icon: React.ReactNode; className?: string }> = ({ title, children, icon, className = '' }) => ( | |
| <div className={`bg-white rounded-xl shadow-md border border-stone-200 p-6 ${className}`}> | |
| <div className="flex items-center gap-3 mb-4"> | |
| {icon} | |
| <h3 className="text-xl font-semibold text-stone-800">{title}</h3> | |
| </div> | |
| <div className="space-y-3 text-stone-600"> | |
| {children} | |
| </div> | |
| </div> | |
| ); | |
| const HealthGauge: React.FC<{ score: number }> = ({ score }) => { | |
| const circumference = 2 * Math.PI * 52; | |
| const offset = circumference - (score / 100) * circumference; | |
| const color = score > 80 ? 'text-green-600' : score > 50 ? 'text-yellow-500' : 'text-red-600'; | |
| return ( | |
| <div className="relative w-32 h-32 flex items-center justify-center"> | |
| <svg className="absolute w-full h-full transform -rotate-90"> | |
| <circle className="text-stone-200" strokeWidth="10" stroke="currentColor" fill="transparent" r="52" cx="64" cy="64" /> | |
| <circle className={color} strokeWidth="10" strokeDasharray={circumference} strokeDashoffset={offset} | |
| strokeLinecap="round" stroke="currentColor" fill="transparent" r="52" cx="64" cy="64" /> | |
| </svg> | |
| <span className={`text-3xl font-bold ${color}`}>{score}</span> | |
| </div> | |
| ); | |
| }; | |
| const TabButton: React.FC<{ name: Tab, icon: React.ReactNode, isActive: boolean, onClick: () => void }> = ({ name, icon, isActive, onClick }) => ( | |
| <button | |
| onClick={onClick} | |
| className={`flex-shrink-0 flex items-center justify-center sm:justify-start gap-2 px-4 py-3 text-sm font-medium rounded-t-lg border-b-2 transition-colors focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 | |
| ${isActive | |
| ? 'border-green-600 text-green-700 bg-white' | |
| : 'border-transparent text-stone-500 hover:text-green-600 hover:bg-stone-100' | |
| }`} | |
| > | |
| {icon} | |
| <span className="hidden sm:inline">{name}</span> | |
| </button> | |
| ); | |
| const PotVisualizerModal: React.FC<{ | |
| isOpen: boolean; | |
| onClose: () => void; | |
| analysis: BonsaiAnalysis; | |
| }> = ({ isOpen, onClose, analysis }) => { | |
| const [isLoading, setIsLoading] = useState(false); | |
| const [generatedImage, setGeneratedImage] = useState<string | null>(null); | |
| const [error, setError] = useState(''); | |
| const [promptUsed, setPromptUsed] = useState(''); | |
| const potStyles = [ | |
| "A shallow, rectangular, unglazed, dark brown ceramic pot.", | |
| "A round, blue-glazed ceramic pot with a soft patina.", | |
| "An oval, cream-colored pot with delicate feet.", | |
| "A modern, minimalist, square, grey concrete pot.", | |
| "A classic, hexagonal, deep red pot.", | |
| "A natural-looking pot carved from rock with rough texture." | |
| ]; | |
| const handleGenerate = async (potStyle: string) => { | |
| setIsLoading(true); | |
| setError(''); | |
| setGeneratedImage(null); | |
| const treeDescription = `A photorealistic image of a healthy ${analysis.species} bonsai tree. ${analysis.healthAssessment.observations.join(' ')}. The trunk is ${analysis.healthAssessment.trunkAndNebariHealth.toLowerCase()}.`; | |
| const fullPrompt = `${treeDescription} The tree is in ${potStyle}`; | |
| setPromptUsed(fullPrompt); | |
| const result = await generateBonsaiImage(fullPrompt); | |
| if (result) { | |
| setGeneratedImage(result); | |
| } else { | |
| setError("Sorry, the AI couldn't generate the image. Please try a different style."); | |
| } | |
| setIsLoading(false); | |
| }; | |
| if (!isOpen) return null; | |
| return ( | |
| <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/60" onClick={onClose}> | |
| <div className="bg-white rounded-2xl shadow-xl w-full max-w-2xl m-4 p-6 relative" onClick={e => e.stopPropagation()}> | |
| <h3 className="text-2xl font-bold text-stone-900 mb-4">AI Pot Visualizer</h3> | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| <div> | |
| <h4 className="font-semibold text-stone-800 mb-2">Choose a Pot Style:</h4> | |
| <div className="space-y-2"> | |
| {potStyles.map(style => ( | |
| <button key={style} onClick={() => handleGenerate(style)} disabled={isLoading} className="w-full text-left p-3 bg-stone-100 hover:bg-green-100 hover:text-green-800 rounded-lg text-sm transition-colors disabled:opacity-50"> | |
| {style} | |
| </button> | |
| ))} | |
| </div> | |
| </div> | |
| <div className="flex flex-col items-center justify-center bg-stone-100 rounded-lg p-4 min-h-[256px]"> | |
| {isLoading ? <Spinner text="Yuki is at the potter's wheel..." /> : | |
| generatedImage ? ( | |
| <div className="space-y-2 text-center"> | |
| <img src={`data:image/jpeg;base64,${generatedImage}`} alt="Generated bonsai" className="rounded-lg shadow-md"/> | |
| <a href={`data:image/jpeg;base64,${generatedImage}`} download="bonsai-pot-visualization.jpg" className="inline-flex items-center gap-2 text-xs text-green-700 hover:underline"> | |
| <DownloadIcon className="w-4 h-4" /> | |
| Download Image | |
| </a> | |
| </div> | |
| ) : | |
| error ? <p className="text-red-600 text-center">{error}</p> : <p className="text-stone-500 text-center">Your generated image will appear here.</p> | |
| } | |
| </div> | |
| </div> | |
| <button onClick={onClose} className="mt-6 w-full bg-stone-200 text-stone-700 font-semibold py-2 px-4 rounded-lg hover:bg-stone-300 transition-colors"> | |
| Close | |
| </button> | |
| </div> | |
| </div> | |
| ); | |
| }; | |
| const AnalysisDisplay: React.FC<AnalysisDisplayProps> = ({ analysis, onReset, onSaveToDiary, isReadonly = false, treeImageBase64 }) => { | |
| const [activeTab, setActiveTab] = useState<Tab>('Overview'); | |
| const [isPotVisualizerOpen, setPotVisualizerOpen] = useState(false); | |
| const { | |
| healthAssessment, careSchedule, pestAndDiseaseAlerts, stylingSuggestions, | |
| environmentalFactors, estimatedAge, species, wateringAnalysis, knowledgeNuggets, | |
| fertilizerRecommendations, soilRecipe, potSuggestion, seasonalGuide, | |
| diagnostics, pestLibrary, toolRecommendations | |
| } = analysis; | |
| const seasonIcons: { [key: string]: React.FC<React.SVGProps<SVGSVGElement>> } = { | |
| Spring: SunriseIcon, | |
| Summer: SunIcon, | |
| Autumn: WindIcon, | |
| Winter: SnowflakeIcon, | |
| }; | |
| const renderContent = () => { | |
| switch (activeTab) { | |
| case 'Overview': | |
| return ( | |
| <div className="grid grid-cols-1 lg:grid-cols-3 gap-6"> | |
| <InfoCard title="At a Glance" icon={<BonsaiIcon className="w-7 h-7 text-green-700" />} className="lg:col-span-1 flex flex-col items-center text-center"> | |
| <HealthGauge score={healthAssessment.healthScore} /> | |
| <p className="text-lg font-medium mt-4"><strong className="text-stone-900">Overall Health:</strong> {healthAssessment.overallHealth}</p> | |
| <p><strong className="text-stone-900">Species:</strong> {species}</p> | |
| <p><strong className="text-stone-900">Estimated Age:</strong> {estimatedAge}</p> | |
| </InfoCard> | |
| <InfoCard title="Key Observations" icon={<CheckCircleIcon className="w-7 h-7 text-green-600" />} className="lg:col-span-2"> | |
| <ul className="list-disc list-inside space-y-2"> | |
| {healthAssessment.observations.map((obs, i) => <li key={i}>{obs}</li>)} | |
| </ul> | |
| </InfoCard> | |
| <InfoCard title="Ideal Environment" icon={<SunIcon className="w-7 h-7 text-yellow-500" />} className="lg:col-span-3 grid grid-cols-1 md:grid-cols-3 gap-4"> | |
| <div className="flex items-center gap-3"> | |
| <SunIcon className="w-8 h-8 text-yellow-500"/> | |
| <div><strong className="block text-stone-800">Light</strong>{environmentalFactors.idealLight}</div> | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| <DropletIcon className="w-8 h-8 text-blue-500"/> | |
| <div><strong className="block text-stone-800">Humidity</strong>{environmentalFactors.idealHumidity}</div> | |
| </div> | |
| <div className="flex items-center gap-3"> | |
| <ThermometerIcon className="w-8 h-8 text-red-500"/> | |
| <div><strong className="block text-stone-800">Temperature</strong>{environmentalFactors.temperatureRange}</div> | |
| </div> | |
| </InfoCard> | |
| </div> | |
| ); | |
| case 'Care Plan': | |
| return ( | |
| <div className="space-y-6"> | |
| <InfoCard title="Watering Analysis" icon={<DropletIcon className="w-7 h-7 text-blue-500" />}> | |
| <p><strong className="text-stone-900">Frequency:</strong> {wateringAnalysis.frequency}</p> | |
| <p><strong className="text-stone-900">Method:</strong> {wateringAnalysis.method}</p> | |
| <p><strong className="text-stone-900">Notes:</strong> {wateringAnalysis.notes}</p> | |
| </InfoCard> | |
| <div> | |
| <h3 className="text-2xl font-semibold text-stone-800 text-center mb-6">4-Week Personalized Care Schedule</h3> | |
| <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5"> | |
| {careSchedule.sort((a,b) => a.week - b.week).map((item, i) => ( | |
| <div key={i} className="bg-white p-5 rounded-lg border border-stone-200 shadow-sm flex flex-col"> | |
| <p className="font-bold text-green-800">Week {item.week}</p> | |
| <p className="font-semibold text-stone-900 mt-1">{item.task}</p> | |
| <p className="text-sm text-stone-600 mt-2 flex-grow">{item.details}</p> | |
| {item.toolsNeeded && item.toolsNeeded.length > 0 && ( | |
| <div className="mt-3 pt-3 border-t border-stone-200"> | |
| <h4 className="text-xs font-bold text-stone-500 uppercase">Tools</h4> | |
| <div className="flex flex-wrap gap-2 mt-1"> | |
| {item.toolsNeeded.map(tool => <span key={tool} className="text-xs bg-stone-100 text-stone-700 px-2 py-1 rounded-full">{tool}</span>)} | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| </div> | |
| </div> | |
| ); | |
| case 'Diagnostics': | |
| return ( | |
| <InfoCard title="Advanced Diagnostics" icon={<StethoscopeIcon className="w-7 h-7 text-blue-700" />}> | |
| <div className="space-y-4"> | |
| {diagnostics.map((diag, i) => ( | |
| <div key={i} className="p-4 bg-stone-50 rounded-lg border border-stone-200"> | |
| <div className="flex justify-between items-baseline"> | |
| <h4 className="font-semibold text-stone-800">{diag.issue}</h4> | |
| <span className={`px-2 py-0.5 text-xs font-medium rounded-full ${diag.confidence === 'High' ? 'bg-red-100 text-red-800' : diag.confidence === 'Medium' ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800'}`}>{diag.confidence} Confidence</span> | |
| </div> | |
| <p className="mt-2"><strong className="font-medium text-stone-700">Symptoms to watch for:</strong> {diag.symptoms}</p> | |
| <p className="mt-1"><strong className="font-medium text-stone-700">Solution/Prevention:</strong> {diag.solution}</p> | |
| </div> | |
| ))} | |
| </div> | |
| </InfoCard> | |
| ); | |
| case 'Health and Pests': | |
| return ( | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
| <InfoCard title="Active Pest & Disease Alerts" icon={<AlertTriangleIcon className="w-7 h-7 text-amber-600" />}> | |
| {pestAndDiseaseAlerts.length > 0 ? ( | |
| pestAndDiseaseAlerts.map((alert, i) => ( | |
| <div key={i} className="py-2 border-b border-stone-200 last:border-b-0"> | |
| <div className="flex justify-between items-baseline"> | |
| <p className="font-semibold text-stone-800">{alert.pestOrDisease}</p> | |
| <span className={`px-2 py-0.5 text-xs font-medium rounded-full ${alert.severity === 'High' ? 'bg-red-100 text-red-800' : alert.severity === 'Medium' ? 'bg-yellow-100 text-yellow-800' : 'bg-green-100 text-green-800'}`}>{alert.severity}</span> | |
| </div> | |
| <p><strong className="font-medium">Symptoms:</strong> {alert.symptoms}</p> | |
| <p><strong className="font-medium">Treatment:</strong> {alert.treatment}</p> | |
| </div> | |
| )) | |
| ) : <p>No active threats detected. Check the 'Pest Library' tab for preventative knowledge on common threats in your area.</p>} | |
| </InfoCard> | |
| <InfoCard title="Detailed Health Breakdown" icon={<CheckCircleIcon className="w-7 h-7 text-green-600" />}> | |
| <p><strong className="font-medium text-stone-900">Foliage:</strong> {healthAssessment.foliageHealth}</p> | |
| <p><strong className="font-medium text-stone-900">Trunk & Nebari:</strong> {healthAssessment.trunkAndNebariHealth}</p> | |
| <p><strong className="font-medium text-stone-900">Pot & Soil:</strong> {healthAssessment.potAndSoilHealth}</p> | |
| </InfoCard> | |
| </div> | |
| ); | |
| case 'Pest Library': | |
| return ( | |
| <InfoCard title="Regional Pest & Disease Library" icon={<BugIcon className="w-7 h-7 text-red-700" />}> | |
| <p className="text-sm mb-4">A reference for the most common threats to a {species} in your region.</p> | |
| <div className="space-y-4"> | |
| {pestLibrary.map((pest, i) => ( | |
| <details key={i} className="p-4 bg-stone-50 rounded-lg border border-stone-200 group"> | |
| <summary className="font-semibold text-stone-800 cursor-pointer flex justify-between items-center"> | |
| {pest.name} ({pest.type}) | |
| <span className="text-xs text-stone-500 group-open:hidden">Show Details</span> | |
| <span className="text-xs text-stone-500 hidden group-open:inline">Hide Details</span> | |
| </summary> | |
| <div className="mt-4 space-y-3 text-sm"> | |
| <p>{pest.description}</p> | |
| <div> | |
| <strong className="font-medium text-stone-700">Symptoms:</strong> | |
| <ul className="list-disc list-inside ml-2"> | |
| {pest.symptoms.map((s, idx) => <li key={idx}>{s}</li>)} | |
| </ul> | |
| </div> | |
| <div> | |
| <strong className="font-medium text-stone-700">Organic Treatment:</strong> | |
| <p>{pest.treatment.organic}</p> | |
| </div> | |
| <div> | |
| <strong className="font-medium text-stone-700">Chemical Treatment:</strong> | |
| <p>{pest.treatment.chemical}</p> | |
| </div> | |
| </div> | |
| </details> | |
| ))} | |
| </div> | |
| </InfoCard> | |
| ); | |
| case 'Styling': | |
| return ( | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
| <InfoCard title="Styling & Shaping Advice" icon={<ScissorsIcon className="w-7 h-7 text-indigo-600" />} className="lg:col-span-1"> | |
| {stylingSuggestions.length > 0 ? ( | |
| stylingSuggestions.map((suggestion, i) => ( | |
| <div key={i} className="py-3 border-b border-stone-200 last:border-b-0"> | |
| <p className="font-semibold text-stone-800">{suggestion.technique} ({suggestion.area})</p> | |
| <p className="mt-1">{suggestion.description}</p> | |
| </div> | |
| )) | |
| ) : <p>No immediate styling is recommended. Focus on health first.</p>} | |
| </InfoCard> | |
| <InfoCard title="Pot Recommendation" icon={<GalleryVerticalEndIcon className="w-7 h-7 text-orange-700" />} className="lg:col-span-1"> | |
| <p><strong className="font-medium text-stone-900">Style:</strong> {potSuggestion.style}</p> | |
| <p><strong className="font-medium text-stone-900">Size:</strong> {potSuggestion.size}</p> | |
| <p><strong className="font-medium text-stone-900">Color Palette:</strong> {potSuggestion.colorPalette}</p> | |
| <p className="mt-3 pt-3 border-t border-stone-200"><strong className="font-medium text-stone-900">Rationale:</strong> {potSuggestion.rationale}</p> | |
| <button onClick={() => setPotVisualizerOpen(true)} className="mt-4 w-full flex items-center justify-center gap-2 rounded-md bg-orange-600 px-4 py-2 text-sm font-semibold text-white shadow-sm hover:bg-orange-500"> | |
| <PaletteIcon className="w-5 h-5"/> | |
| Visualize Pot Pairings | |
| </button> | |
| </InfoCard> | |
| </div> | |
| ); | |
| case 'Fertilizer and Soil': | |
| return ( | |
| <div className="grid grid-cols-1 lg:grid-cols-2 gap-6"> | |
| <InfoCard title="Fertilizer Schedule" icon={<FlaskConicalIcon className="w-7 h-7 text-cyan-600" />}> | |
| {fertilizerRecommendations.map(rec => ( | |
| <div key={rec.phase} className="py-2 border-b border-stone-200 last:border-b-0"> | |
| <p className="font-semibold text-stone-800">{rec.phase}</p> | |
| <p><strong className="font-medium">Type:</strong> {rec.type}</p> | |
| <p><strong className="font-medium">Frequency:</strong> {rec.frequency}</p> | |
| <p className="text-sm italic">Notes: {rec.notes}</p> | |
| </div> | |
| ))} | |
| </InfoCard> | |
| <InfoCard title="Recommended Soil Mix" icon={<LayersIcon className="w-7 h-7 text-amber-700" />}> | |
| <div className="space-y-3"> | |
| {soilRecipe.components.map(comp => ( | |
| <div key={comp.name}> | |
| <div className="flex justify-between items-center mb-1"> | |
| <span className="font-medium text-stone-800">{comp.name}</span> | |
| <span className="font-semibold text-amber-800">{comp.percentage}%</span> | |
| </div> | |
| <div className="w-full bg-stone-200 rounded-full h-2.5"> | |
| <div className="bg-amber-600 h-2.5 rounded-full" style={{ width: `${comp.percentage}%` }}></div> | |
| </div> | |
| <p className="text-xs italic text-stone-500 mt-1">{comp.notes}</p> | |
| </div> | |
| ))} | |
| </div> | |
| <p className="mt-4 pt-4 border-t border-stone-200"><strong className="font-medium text-stone-900">Rationale:</strong> {soilRecipe.rationale}</p> | |
| </InfoCard> | |
| </div> | |
| ); | |
| case 'Seasonal Guide': | |
| return ( | |
| <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> | |
| {seasonalGuide.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 ( | |
| <InfoCard key={season.season} title={season.season} icon={<Icon className="w-7 h-7 text-green-700" />}> | |
| <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> | |
| </InfoCard> | |
| ) | |
| })} | |
| </div> | |
| ); | |
| case 'Tools & Supplies': | |
| const groupedTools = toolRecommendations.reduce((acc, tool) => { | |
| acc[tool.category] = acc[tool.category] || []; | |
| acc[tool.category].push(tool); | |
| return acc; | |
| }, {} as Record<ToolRecommendation['category'], ToolRecommendation[]>); | |
| return ( | |
| <div className="space-y-6"> | |
| {Object.entries(groupedTools).map(([category, tools]) => ( | |
| <InfoCard key={category} title={category} icon={<WrenchIcon className="w-7 h-7 text-gray-700" />}> | |
| <div className="space-y-3"> | |
| {tools.map(tool => ( | |
| <div key={tool.name} className="py-2 border-b border-stone-100 last:border-0"> | |
| <div className="flex justify-between items-baseline"> | |
| <p className="font-semibold text-stone-800">{tool.name}</p> | |
| <span className={`px-2 py-0.5 text-xs font-medium rounded-full ${tool.level === 'Essential' ? 'bg-green-100 text-green-800' : tool.level === 'Recommended' ? 'bg-blue-100 text-blue-800' : 'bg-orange-100 text-orange-800'}`}>{tool.level}</span> | |
| </div> | |
| <p className="text-sm">{tool.description}</p> | |
| </div> | |
| ))} | |
| </div> | |
| </InfoCard> | |
| ))} | |
| </div> | |
| ); | |
| case 'Knowledge': | |
| return ( | |
| <InfoCard title={`Master's Wisdom: ${species}`} icon={<BookOpenIcon className="w-7 h-7 text-purple-600" />}> | |
| <ul className="space-y-4"> | |
| {knowledgeNuggets.map((nugget, i) => ( | |
| <li key={i} className="flex items-start gap-3"> | |
| <SparklesIcon className="w-5 h-5 text-yellow-500 mt-1 flex-shrink-0" /> | |
| <span>{nugget}</span> | |
| </li> | |
| ))} | |
| </ul> | |
| </InfoCard> | |
| ); | |
| default: | |
| return null; | |
| } | |
| }; | |
| return ( | |
| <div className="w-full max-w-6xl mx-auto space-y-8"> | |
| <PotVisualizerModal isOpen={isPotVisualizerOpen} onClose={() => setPotVisualizerOpen(false)} analysis={analysis} /> | |
| <div className="text-center"> | |
| <h2 className="text-3xl font-bold tracking-tight text-stone-900 sm:text-4xl">Your Bonsai Analysis is Ready</h2> | |
| <p className="mt-4 text-lg leading-8 text-stone-600"> | |
| Master Yuki has assessed your <span className="font-semibold text-green-700">{species}</span>. Here is your report. | |
| </p> | |
| </div> | |
| <div className="bg-stone-50 rounded-xl p-1 sm:p-2 sticky top-2 z-20 shadow-sm border border-stone-200"> | |
| <div className="flex flex-nowrap items-center justify-start -mb-px border-b border-stone-200 overflow-x-auto"> | |
| {TABS.map(({name, icon: Icon}) => ( | |
| <TabButton key={name} name={name} icon={<Icon className="w-5 h-5" />} isActive={activeTab === name} onClick={() => setActiveTab(name)} /> | |
| ))} | |
| </div> | |
| </div> | |
| <div className="bg-stone-100 p-4 sm:p-6 lg:p-8 rounded-2xl"> | |
| {renderContent()} | |
| </div> | |
| {!isReadonly && ( | |
| <div className="text-center pt-6 flex flex-col sm:flex-row justify-center items-center gap-4"> | |
| <button | |
| onClick={onReset} | |
| className="w-full sm:w-auto bg-stone-200 text-stone-700 font-semibold py-3 px-6 rounded-lg hover:bg-stone-300 transition-colors" | |
| > | |
| Analyze Another Tree | |
| </button> | |
| {onSaveToDiary && ( | |
| <button | |
| onClick={onSaveToDiary} | |
| className="flex items-center gap-2 w-full sm:w-auto justify-center rounded-md bg-green-700 px-6 py-3 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" | |
| > | |
| <BookUserIcon className="w-5 h-5"/> | |
| Save Tree to My Garden | |
| </button> | |
| )} | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| }; | |
| export default AnalysisDisplay; |