diff --git a/src/App.tsx b/src/App.tsx index 1295cfd..4259897 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect } from 'react'; +import React, { useState, useEffect } from 'react'; import { AppBar, Box, @@ -7,130 +7,532 @@ import { Typography, Card, CardContent, - CircularProgress, - Alert, Button, Stack, + Divider, + Link, + List, + ListItem, + ListItemText, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, + CircularProgress, + Alert, } from '@mui/material'; -import { CloudQueue } from '@mui/icons-material'; -import { getCollection } from './services/edrApi'; -import type { Collection } from './services/edrApi'; +import { CloudQueue, Code, GitHub, BugReport } from '@mui/icons-material'; +import { getPositionData } from './services/edrApi'; + +interface CoverageJSONResponse { + type: string; + domain: { + axes: { + t: { values: string[] }; + [key: string]: unknown; + }; + }; + parameters: { + [key: string]: { + description?: { fi?: string }; + unit?: { + label?: { fi?: string }; + symbol?: { + type?: string; + value?: string; + } | string; + }; + observedProperty?: { + id?: string; + label?: { fi?: string }; + }; + }; + }; + ranges: { + [key: string]: { + values: number[]; + }; + }; +} + +console.log('App.tsx module loaded'); function App() { - const [collection, setCollection] = useState(null); + console.log('App() function called - rendering component'); + const [weatherData, setWeatherData] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); - const fetchCollectionData = async () => { + console.log('App state:', { hasWeatherData: !!weatherData, loading, error }); + + const fetchExampleWeatherData = async () => { setLoading(true); setError(null); try { - const data = await getCollection(); - setCollection(data); + const data = await getPositionData( + 'pal_skandinavia', + 'POINT(10.752 59.913)', + { + f: 'CoverageJSON', + 'parameter-name': 'Temperature,WindSpeedMS,TotalCloudCover', + datetime: '2025-11-28T12:00:00Z/2025-11-28T15:00:00Z', + } + ) as CoverageJSONResponse; + + // Log the actual data structure to understand the format + console.log('Weather data received:', data); + if (data.ranges) { + console.log('Sample range value:', Object.keys(data.ranges)[0], data.ranges[Object.keys(data.ranges)[0]]); + } + + setWeatherData(data); } catch (err) { - setError(err instanceof Error ? err.message : 'Failed to fetch data'); + setError(err instanceof Error ? err.message : 'Failed to fetch weather data'); + console.error('Error fetching weather data:', err); } finally { setLoading(false); } }; useEffect(() => { - fetchCollectionData(); + // Fetch weather data after a short delay to ensure page renders first + const timer = setTimeout(() => { + fetchExampleWeatherData(); + }, 100); + return () => clearTimeout(timer); }, []); return ( - + - FMI Open Data - OGC EDR 1.1 + Vibe Coding Training - Weather App - - - PAL Skandinavia Collection Viewer - + + {/* Welcome Section */} + + + Welcome to Vibe Coding Training! 🚀 + + + Let's build an awesome weather application together using FMI Open Data! + + - - This application demonstrates OGC EDR 1.1 API integration with FMI Open Data. - The default collection is PAL Skandinavia (PAL-AROME model for Scandinavia). - + {/* Getting Started Card */} + + + + 🎯 Getting Started with Vibe Coding + + + + + Vibe Coding is a collaborative development approach where you create issues for features + you want to implement, and then work on them iteratively. Here's how to get started: + - {error && ( - setError(null)}> - {error} - - )} - - {loading && ( - - - - )} - - {collection && !loading && ( - - - - {collection.title} - - - ID: {collection.id} - - - Description: {collection.description} - - {collection.extent?.spatial?.bbox && ( - - Spatial Extent (bbox):{' '} - {JSON.stringify(collection.extent.spatial.bbox)} - - )} - {collection.extent?.temporal?.interval && ( - - Temporal Extent:{' '} - {JSON.stringify(collection.extent.temporal.interval)} - - )} - - - )} + + + + + + + + + + + + + + + + + - - - + + + + + + + + + {/* Weather App Ideas Card */} + + + + 💡 Weather App Feature Ideas + + + + + Not sure what to build? Here are some ideas to get you started: + - + + + + + + + + + + + + + + + + + + + + + + + + + + + {/* FMI Open Data API Card */} + - - About OGC EDR 1.1 + + 🌐 FMI Open Data - OGC EDR 1.1 API + + + + + This application uses the Finnish Meteorological Institute's Open Data API following the + OGC Environmental Data Retrieval (EDR) 1.1 standard. The API provides access to weather + forecasts and observations. + + + + API Endpoint:{' '} + + https://opendata.fmi.fi/edr + + + + + Default Collection: pal_skandinavia (MEPS model for Scandinavia) + - The OGC Environmental Data Retrieval (EDR) API provides a standard way to query - and retrieve environmental data. This application uses the FMI Open Data EDR - endpoint. + Collection Metadata:{' '} + + https://opendata.fmi.fi/edr/collections/pal_skandinavia + - - API Endpoint: https://opendata.fmi.fi/edr + + + Example API Query + + + Here's an example of how to get a 3-hour weather forecast for Oslo, Norway: + + + https://opendata.fmi.fi/edr/collections/pal_skandinavia/position?
+ f=CoverageJSON&
+ parameter-name=Temperature,WindSpeedMS,TotalCloudCover&
+ datetime=2025-11-28T12:00:00Z/2025-11-28T15:00:00Z&
+ coords=POINT(10.752 59.913) +
+ + + Example Response - - Documentation:{' '} - + Live data from the API showing 3-hour forecast for Oslo, Norway: + + + {loading && ( + + + + )} + + {error && ( + + Unable to fetch live data from the API. This could be due to network restrictions or the API being temporarily unavailable. + When the API is accessible, you'll see a table here with real-time weather parameters and their values. + + )} + + {weatherData && !loading && ( + <> + + + + + Time (UTC) + Parameter + Value + Unit + + + + {(() => { + const timeValues = weatherData.domain.axes.t.values; + const parameterKeys = Object.keys(weatherData.ranges); + const rows: React.ReactElement[] = []; + + // Iterate through each time step + timeValues.forEach((time, timeIndex) => { + // For each time, show all parameters + parameterKeys.forEach((paramKey) => { + const param = weatherData.parameters[paramKey]; + const rangeValue = weatherData.ranges[paramKey].values[timeIndex]; + + // Extract unit symbol + let unit = ''; + if (param?.unit?.symbol) { + const symbol = param.unit.symbol; + if (typeof symbol === 'string') { + unit = symbol; + } else if (typeof symbol === 'object' && symbol.value) { + unit = symbol.value; + } + } + + // Format the value + const displayValue = typeof rangeValue === 'number' + ? rangeValue.toFixed(1) + : String(rangeValue ?? 'N/A'); + + // Format parameter name + const displayParamName = paramKey + .replace(/([A-Z])/g, ' $1') + .trim() + .split(' ') + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' '); + + rows.push( + + {new Date(time).toLocaleTimeString('en-GB', { hour: '2-digit', minute: '2-digit' })} + {displayParamName} + {displayValue} + {unit} + + ); + }); + }); + + return rows; + })()} + +
+
+ + + )} + + + Available Parameters + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +
+ + + + {/* Resources Card */} + + + + 📚 Helpful Resources + + + + + + React Documentation + + } + secondary="Learn about React hooks, components, and best practices" + /> + + + + Material UI Documentation + + } + secondary="Explore UI components and design patterns" + /> + + + + TypeScript Documentation + + } + secondary="Master TypeScript types and interfaces" + /> + + + + Vite Documentation + + } + secondary="Understand the build tool and development server" + /> + + + + {/* Footer */} + + + Happy Coding! 🎉 Remember: Start small, iterate often, and have fun building your weather app! + +
diff --git a/src/main.tsx b/src/main.tsx index f842f6d..cbe1741 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -3,6 +3,8 @@ import { createRoot } from 'react-dom/client'; import { CssBaseline, ThemeProvider, createTheme } from '@mui/material'; import App from './App.tsx'; +console.log('main.tsx loaded'); + const theme = createTheme({ palette: { mode: 'light', @@ -15,7 +17,17 @@ const theme = createTheme({ }, }); -createRoot(document.getElementById('root')!).render( +console.log('Attempting to find root element...'); +const rootElement = document.getElementById('root'); +console.log('Root element:', rootElement); + +if (!rootElement) { + document.body.innerHTML = '

ERROR: Root element not found!

'; + throw new Error('Root element not found'); +} + +console.log('Creating React root...'); +createRoot(rootElement).render( @@ -23,3 +35,4 @@ createRoot(document.getElementById('root')!).render( ); +console.log('React app rendered');