1+ 'use client' ;
2+
3+ import { useEffect , useState } from "react" ;
4+ import { useSupabaseClient } from "@supabase/auth-helpers-react" ;
5+ import { startOfWeek } from "date-fns" ;
6+ import { toZonedTime } from "date-fns-tz" ;
7+
8+ const TIMEZONE = 'Australia/Melbourne' ;
9+
10+ const biomeToStormMap : Record < string , string > = {
11+ "RockyHighlands" : "Dust Storm" ,
12+ "Barren (Pending)" : "Dust Storm" ,
13+ "Barren Wasteland" : "Radiation Storm" ,
14+ "Arid Dunes" : "Sandstorm" ,
15+ "Frigid Expanse" : "Snowstorm" ,
16+ "Volcanic Terrain" : "Ashfall" ,
17+ "Basalt Plains" : "Windstorm" ,
18+ "Sediment Flats" : "Flash Flood" ,
19+ "Cratered Terrain" : "Seismic Shock" ,
20+ "Tundra Basin" : "Ice Storm" ,
21+ "Temperate Highlands" : "Thunderstorm" ,
22+ "Oceanic World" : "Cyclone" ,
23+ "Tropical Jungle" : "Monsoon" ,
24+ "Flood Basin" : "Deluge" ,
25+ "Coral Reefs" : "Supercell" ,
26+ "Dune Fields" : "Heatwave" ,
27+ } ;
28+
29+ function getPlanetType ( density : number ) : "terrestrial" | "gaseous" | "ocean" {
30+ if ( density >= 3.5 ) return "terrestrial" ;
31+ if ( density < 1.5 ) return "gaseous" ;
32+ return "ocean" ;
33+ } ;
34+
35+ interface EventData {
36+ classificationId : number ;
37+ eventCount : number ;
38+ redeemed : boolean ;
39+ nextEventType : string | null ;
40+ }
41+
42+ export default function WeatherEventsOverview ( {
43+ classificationInfo,
44+ } : {
45+ classificationInfo : { id : number ; biome : string ; biomass : number ; density : number } [ ] ;
46+ } ) {
47+ const supabase = useSupabaseClient ( ) ;
48+ const [ eventsData , setEventsData ] = useState < EventData [ ] > ( [ ] ) ;
49+
50+ useEffect ( ( ) => {
51+ const fetchEvents = async ( ) => {
52+ if ( ! classificationInfo || classificationInfo . length === 0 ) return ;
53+
54+ const startOfWeekMelbourne = startOfWeek ( toZonedTime ( new Date ( ) , TIMEZONE ) , { weekStartsOn : 1 } ) ;
55+ startOfWeekMelbourne . setHours ( 0 , 1 , 0 , 0 ) ;
56+
57+ const classificationIds = classificationInfo . map ( c => c . id ) ;
58+
59+ const { data : events , error } = await supabase
60+ . from ( "events" )
61+ . select ( "*" )
62+ . in ( "classification_location" , classificationIds )
63+ . gte ( "time" , startOfWeekMelbourne . toISOString ( ) ) ;
64+
65+ if ( error ) {
66+ console . error ( "Error fetching events:" , error ) ;
67+ return ;
68+ }
69+
70+ const grouped : Record < number , { count : number ; redeemed : boolean ; nextEventType : string | null } > = { } ;
71+
72+ for ( const info of classificationInfo ) {
73+ const { id, biome, biomass, density } = info ;
74+ const planetType = getPlanetType ( density ) ;
75+
76+ grouped [ id ] = { count : 0 , redeemed : false , nextEventType : null } ;
77+
78+ const eventsForLocation = events . filter ( e => e . classification_location === id ) ;
79+
80+ for ( const event of eventsForLocation ) {
81+ grouped [ id ] . count += 1 ;
82+ if ( event . status === "redeemed" ) {
83+ grouped [ id ] . redeemed = true ;
84+ }
85+ }
86+
87+ if ( ! grouped [ id ] . redeemed ) {
88+ const lightningEventExists = eventsForLocation . some ( e =>
89+ e . type ?. toLowerCase ( ) . includes ( "lightning" )
90+ ) ;
91+
92+ let newEventType : string | null = null ;
93+
94+ if (
95+ planetType === "terrestrial" &&
96+ biomass >= 0.000001 &&
97+ biomass <= 0.02 &&
98+ ! lightningEventExists
99+ ) {
100+ newEventType = "lightning-kickoff" ;
101+ } else {
102+ newEventType = biomeToStormMap [ biome ] || "rain-general" ;
103+ }
104+
105+ grouped [ id ] . nextEventType = newEventType ;
106+ }
107+ }
108+
109+ const newData : EventData [ ] = classificationInfo . map ( info => ( {
110+ classificationId : info . id ,
111+ eventCount : grouped [ info . id ] ?. count || 0 ,
112+ redeemed : grouped [ info . id ] ?. redeemed || false ,
113+ nextEventType : grouped [ info . id ] ?. nextEventType || null ,
114+ } ) ) ;
115+
116+ setEventsData ( newData ) ;
117+ } ;
118+
119+ fetchEvents ( ) ;
120+ } , [ classificationInfo ] ) ;
121+
122+ if ( eventsData . length === 0 ) return null ;
123+
124+ return (
125+ < div className = "grid grid-cols-1 md:grid-cols-2 gap-4" >
126+ { eventsData . map ( event => (
127+ < div
128+ key = { event . classificationId }
129+ className = "bg-black/60 text-white p-4 rounded-lg shadow-md flex flex-col items-center"
130+ >
131+ < div className = "text-lg font-semibold" > Location ID: { event . classificationId } </ div >
132+ < div className = "text-sm" > Events this week: { event . eventCount } </ div >
133+ < div className = { `text-sm ${ event . redeemed ? 'text-green-400' : 'text-red-400' } ` } >
134+ { event . redeemed ? 'Redeemed' : 'Not redeemed' }
135+ </ div >
136+ { ! event . redeemed && event . nextEventType && (
137+ < div className = "text-yellow-400 text-sm mt-2" >
138+ Next event: { event . nextEventType }
139+ </ div >
140+ ) }
141+ </ div >
142+ ) ) }
143+ </ div >
144+ ) ;
145+ } ;
0 commit comments