11'use client' ;
22
33import { useEffect , useState } from "react" ;
4- import { useSupabaseClient } from "@supabase/auth-helpers-react" ;
5- import { startOfWeek } from "date-fns" ;
4+ import { useSupabaseClient , useSession } from "@supabase/auth-helpers-react" ;
5+ import { startOfWeek , addWeeks , differenceInSeconds } from "date-fns" ;
66import { toZonedTime } from "date-fns-tz" ;
7+ import { Button } from "@/components/ui/button" ;
78
89const TIMEZONE = 'Australia/Melbourne' ;
910
@@ -30,67 +31,92 @@ function getPlanetType(density: number): "terrestrial" | "gaseous" | "ocean" {
3031 if ( density >= 3.5 ) return "terrestrial" ;
3132 if ( density < 1.5 ) return "gaseous" ;
3233 return "ocean" ;
33- } ;
34+ }
3435
3536interface EventData {
3637 classificationId : number ;
3738 eventCount : number ;
39+ hasEvent : boolean ;
3840 redeemed : boolean ;
3941 nextEventType : string | null ;
40- } ;
42+ anomalyId : number ;
43+ countdown : string ;
44+ }
4145
42- export default function WeatherEventsOverview ( {
43- classificationInfo,
44- } : {
45- classificationInfo : { id : number ; biome : string ; biomass : number ; density : number } [ ] ;
46- } ) {
46+ function formatCountdown ( seconds : number ) : string {
47+ const h = Math . floor ( seconds / 3600 ) ;
48+ const m = Math . floor ( ( seconds % 3600 ) / 60 ) ;
49+ const s = seconds % 60 ;
50+ return `${ h } h ${ m } m ${ s } s` ;
51+ }
52+
53+ export default function WeatherEventsOverview ( ) {
4754 const supabase = useSupabaseClient ( ) ;
55+ const session = useSession ( ) ;
4856 const [ eventsData , setEventsData ] = useState < EventData [ ] > ( [ ] ) ;
57+ const [ loading , setLoading ] = useState < boolean > ( true ) ;
4958
5059 useEffect ( ( ) => {
51- const fetchEvents = async ( ) => {
52- if ( ! classificationInfo || classificationInfo . length === 0 ) return ;
60+ async function fetchData ( ) {
61+ if ( ! session ) return ;
62+
63+ // Step 1: Fetch classifications
64+ const { data : classifications , error : classError } = await supabase
65+ . from ( "classifications" )
66+ . select ( "id, anomaly" )
67+ . eq ( "author" , session . user . id )
68+ . in ( "classificationtype" , [ "planet" , "telescope-minorPlanet" ] ) ;
69+
70+ if ( classError || ! classifications ) {
71+ console . error ( "Error fetching classifications:" , classError ) ;
72+ setLoading ( false ) ;
73+ return ;
74+ }
5375
54- const startOfWeekMelbourne = startOfWeek ( toZonedTime ( new Date ( ) , TIMEZONE ) , { weekStartsOn : 1 } ) ;
76+ const classificationInfo = classifications . map ( ( item ) => ( {
77+ id : item . id ,
78+ biome : "RockyHighlands" , // Placeholder
79+ biomass : 0.01 ,
80+ density : 3.5 ,
81+ anomaly_id : item . anomaly ,
82+ } ) ) ;
83+
84+ // Step 2: Fetch events
85+ const now = new Date ( ) ;
86+ const startOfWeekMelbourne = startOfWeek ( toZonedTime ( now , TIMEZONE ) , { weekStartsOn : 1 } ) ;
5587 startOfWeekMelbourne . setHours ( 0 , 1 , 0 , 0 ) ;
88+ const endOfWeekMelbourne = addWeeks ( startOfWeekMelbourne , 1 ) ;
5689
5790 const classificationIds = classificationInfo . map ( c => c . id ) ;
5891
59- const { data : events , error } = await supabase
92+ const { data : events , error : eventsError } = await supabase
6093 . from ( "events" )
6194 . select ( "*" )
6295 . in ( "classification_location" , classificationIds )
6396 . gte ( "time" , startOfWeekMelbourne . toISOString ( ) ) ;
6497
65- if ( error ) {
66- console . error ( "Error fetching events:" , error ) ;
98+ if ( eventsError ) {
99+ console . error ( "Error fetching events:" , eventsError ) ;
100+ setLoading ( false ) ;
67101 return ;
68- } ;
102+ }
69103
70- const grouped : Record < number , { count : number ; redeemed : boolean ; nextEventType : string | null } > = { } ;
104+ const grouped : Record < number , EventData > = { } ;
71105
72106 for ( const info of classificationInfo ) {
73- const { id, biome, biomass, density } = info ;
107+ const { id, biome, biomass, density, anomaly_id } = info ;
74108 const planetType = getPlanetType ( density ) ;
75109
76- grouped [ id ] = { count : 0 , redeemed : false , nextEventType : null } ;
77-
78110 const eventsForLocation = events . filter ( e => e . classification_location === id ) ;
111+ const hasEventThisWeek = eventsForLocation . length > 0 ;
112+ const redeemed = eventsForLocation . some ( e => e . status === "redeemed" ) ;
113+ const lightningEventExists = eventsForLocation . some ( e =>
114+ e . type ?. toLowerCase ( ) . includes ( "lightning" )
115+ ) ;
79116
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 ;
117+ let newEventType : string | null = null ;
93118
119+ if ( ! hasEventThisWeek ) {
94120 if (
95121 planetType === "terrestrial" &&
96122 biomass >= 0.000001 &&
@@ -101,45 +127,88 @@ export default function WeatherEventsOverview({
101127 } else {
102128 newEventType = biomeToStormMap [ biome ] || "rain-general" ;
103129 }
104-
105- grouped [ id ] . nextEventType = newEventType ;
106130 }
131+
132+ const secondsLeft = differenceInSeconds ( endOfWeekMelbourne , toZonedTime ( now , TIMEZONE ) ) ;
133+ grouped [ id ] = {
134+ classificationId : id ,
135+ eventCount : eventsForLocation . length ,
136+ hasEvent : hasEventThisWeek ,
137+ redeemed,
138+ nextEventType : newEventType ,
139+ anomalyId : anomaly_id ,
140+ countdown : formatCountdown ( secondsLeft ) ,
141+ } ;
107142 }
108143
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- } ) ) ;
144+ setEventsData ( Object . values ( grouped ) ) ;
145+ setLoading ( false ) ;
146+ }
147+
148+ fetchData ( ) ;
149+ } , [ session ] ) ;
115150
116- setEventsData ( newData ) ;
117- } ;
151+ const handleCreateEvent = async ( classificationId : number , anomalyId : number , type : string ) => {
152+ const { error } = await supabase . from ( "events" ) . insert ( {
153+ classification_location : classificationId ,
154+ location : anomalyId ,
155+ type,
156+ configuration : { } ,
157+ completed : false ,
158+ } ) ;
118159
119- fetchEvents ( ) ;
120- } , [ classificationInfo ] ) ;
160+ if ( error ) {
161+ console . error ( "Error creating event:" , error ) ;
162+ } else {
163+ window . location . reload ( ) ;
164+ }
165+ } ;
121166
122- if ( eventsData . length === 0 ) return null ;
167+ if ( loading ) return < div className = "text-white p-4" > Loading weather events...</ div > ;
168+ if ( eventsData . length === 0 ) return < div className = "text-white p-4" > No planets found.</ div > ;
123169
124170 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' }
171+ < div className = "min-h-screen bg-black text-white p-4 space-y-8" >
172+ < div className = "grid grid-cols-1 md:grid-cols-2 gap-6" >
173+ { eventsData . map ( event => (
174+ < div
175+ key = { event . classificationId }
176+ className = "bg-gradient-to-br from-cyan-900 to-black text-white p-6 rounded-2xl shadow-lg space-y-2 border border-white/10"
177+ >
178+ < h2 className = "text-xl font-bold text-cyan-300" >
179+ Planet #{ event . classificationId }
180+ </ h2 >
181+ < p className = "text-sm text-gray-300" >
182+ Events this week: < span className = "font-semibold" > { event . eventCount } </ span >
183+ </ p >
184+ < p className = { `text-sm font-semibold ${ event . redeemed ? 'text-green-400' : 'text-red-400' } ` } >
185+ { event . redeemed ? 'Redeemed' : 'Not Redeemed' }
186+ </ p >
187+ { event . hasEvent ? (
188+ < p className = "text-sm text-yellow-200" >
189+ Next event in: < span className = "font-mono" > { event . countdown } </ span >
190+ </ p >
191+ ) : (
192+ event . nextEventType && (
193+ < >
194+ < p className = "text-sm text-yellow-300" >
195+ Next Suggested Event: < strong > { event . nextEventType } </ strong >
196+ </ p >
197+ < Button
198+ variant = "secondary"
199+ className = "mt-2 text-sm"
200+ onClick = { ( ) =>
201+ handleCreateEvent ( event . classificationId , event . anomalyId , event . nextEventType ! )
202+ }
203+ >
204+ Create Event
205+ </ Button >
206+ </ >
207+ )
208+ ) }
135209 </ 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- ) ) }
210+ ) ) }
211+ </ div >
143212 </ div >
144213 ) ;
145214} ;
0 commit comments