({ polygon, fieldId }: { polygon: [number, number][]; fieldId: string })
| 141 | const GROWTH_STAGE_CACHE_KEY = "region-growth-stage-cache"; |
| 142 | |
| 143 | function GrowthStageSection({ polygon, fieldId }: { polygon: [number, number][]; fieldId: string }) { |
| 144 | const [data, setData] = useState<any>(null); |
| 145 | const [loading, setLoading] = useState(false); |
| 146 | |
| 147 | useEffect(() => { |
| 148 | const cache = getCache<any>(GROWTH_STAGE_CACHE_KEY); |
| 149 | const cached = cache[fieldId]; |
| 150 | if (cached && Date.now() - cached.timestamp < 3600000) { setData(cached.data); return; } |
| 151 | const fetchGrowthStage = async () => { |
| 152 | setLoading(true); |
| 153 | try { |
| 154 | const result = await invokeWithRetry<any>( |
| 155 | "ndvi-timeseries", |
| 156 | { polygon }, |
| 157 | { retries: 3, isEmpty: (d: any) => !d?.growth_stage } |
| 158 | ); |
| 159 | const gs = result?.growth_stage ? { |
| 160 | stage: result.growth_stage, progress: result.growth_progress, |
| 161 | current_ndvi: result.latest_ndvi, date_range: result.date_range, |
| 162 | } : null; |
| 163 | setData(gs); |
| 164 | if (gs) setCache(GROWTH_STAGE_CACHE_KEY, fieldId, gs); |
| 165 | } catch (e) { console.error("Growth stage error:", e); setData(null); } |
| 166 | finally { setLoading(false); } |
| 167 | }; |
| 168 | fetchGrowthStage(); |
| 169 | }, [fieldId]); |
| 170 | |
| 171 | return ( |
| 172 | <div> |
| 173 | <h3 className="text-xs font-medium text-muted-foreground uppercase tracking-wider mb-3 flex items-center gap-1.5"> |
| 174 | <Leaf className="w-3.5 h-3.5" /> Growth Stage |
| 175 | </h3> |
| 176 | {loading ? ( |
| 177 | <div className="p-4 rounded-xl border border-border bg-accent/15 flex items-center gap-2 text-sm text-muted-foreground"> |
| 178 | <Loader2 className="w-4 h-4 animate-spin" /> Detecting growth stage… |
| 179 | </div> |
| 180 | ) : data ? ( |
| 181 | <div className="p-4 rounded-xl border border-border bg-accent/15 space-y-3"> |
| 182 | <div className="flex items-center justify-between"> |
| 183 | <span className="text-lg font-semibold text-foreground">{data.stage}</span> |
| 184 | <span className="text-xs text-muted-foreground">NDVI: {data.current_ndvi}</span> |
| 185 | </div> |
| 186 | <div className="w-full h-2 rounded-full bg-muted/30 overflow-hidden"> |
| 187 | <div className="h-full rounded-full bg-[#7BC75B] transition-all duration-500" style={{ width: `${data.progress}%` }} /> |
| 188 | </div> |
| 189 | <div className="flex justify-between text-[10px] text-muted-foreground"> |
| 190 | <span>Germination</span><span>Tillering</span><span>Extension</span><span>Heading</span><span>Grain Fill</span> |
| 191 | </div> |
| 192 | <div className="text-[10px] text-muted-foreground">{data.date_range}</div> |
| 193 | </div> |
| 194 | ) : ( |
| 195 | <div className="p-4 rounded-xl border border-border bg-accent/10 text-sm text-muted-foreground"> |
| 196 | No satellite data available for this region. |
| 197 | </div> |
| 198 | )} |
| 199 | </div> |
| 200 | ); |
nothing calls this directly
no test coverage detected