Skip to content

Commit 8fcffb9

Browse files
committed
️🗳️🗼 ↝ [SSG-179]: You can now create/monitor events from your weather balloon structure
1 parent 38871f7 commit 8fcffb9

File tree

3 files changed

+137
-67
lines changed

3 files changed

+137
-67
lines changed

app/tests/page.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,15 @@ import { useEffect, useState } from "react";
1414
export default function TestPage() {
1515
const supabase = useSupabaseClient();
1616
const session = useSession();
17-
const [locationIds, setLocationIds] = useState<{ id: number; biome: string; biomass: number; density: number }[]>([]);
17+
const [locationIds, setLocationIds] = useState<{ id: number; biome: string; biomass: number; density: number; anomaly_id: number }[]>([]);
1818
const [loading, setLoading] = useState<boolean>(true);
1919

2020
useEffect(() => {
2121
async function fetchLocationIds() {
2222
if (!session) return;
2323
const { data, error } = await supabase
2424
.from("classifications")
25-
.select("id")
25+
.select("id, anomaly")
2626
.eq("author", session.user.id)
2727
.in("classificationtype", ["planet", "telescope-minorPlanet"]);
2828

@@ -32,6 +32,7 @@ export default function TestPage() {
3232
biome: "RockyHighlands",// biome: item.biome ?? "Unknown",
3333
biomass: 0.01, //item.biomass ?? 0,
3434
density: 3.5, // item.density ?? 1,
35+
anomaly_id: item.anomaly,
3536
}));
3637
setLocationIds(parsedData);
3738
}
@@ -54,7 +55,7 @@ export default function TestPage() {
5455
density={3.5}
5556
/>
5657
<MyLocationIds /> */}
57-
<WeatherEventsOverview classificationInfo={locationIds} />
58+
<WeatherEventsOverview />
5859
</div>
5960
);
6061
};

components/Data/Generator/Weather/EventsAcrossMyLocations.tsx

Lines changed: 132 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
'use client';
22

33
import { 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";
66
import { toZonedTime } from "date-fns-tz";
7+
import { Button } from "@/components/ui/button";
78

89
const 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

3536
interface 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
};

constants/Structures/Properties.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -307,7 +307,7 @@ export const StructuresConfig: StructureConfig = {
307307
{
308308
icon: <UmbrellaIcon className="w-6 h-6 text-[#5e81ac]" />,
309309
text: "Upcoming weather events",
310-
dynamicComponent: <WeatherEventsOverview classificationInfo={[]} />,
310+
dynamicComponent: <WeatherEventsOverview />,
311311
}
312312
// {
313313
// icon: <BookAudioIcon className="w-6 h-6 text-[#5e81ac]" />,

0 commit comments

Comments
 (0)