add weather display
This commit is contained in:
14
next.config.js
Normal file
14
next.config.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/** @type {import('next').NextConfig} */
|
||||||
|
const nextConfig = {
|
||||||
|
images: {
|
||||||
|
remotePatterns: [
|
||||||
|
{
|
||||||
|
protocol: 'https',
|
||||||
|
hostname: 'cdn.weatherapi.com',
|
||||||
|
pathname: '/weather/**',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = nextConfig
|
||||||
82
package-lock.json
generated
82
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"@react-pdf/renderer": "^4.3.0",
|
"@react-pdf/renderer": "^4.3.0",
|
||||||
"framer-motion": "^12.12.1",
|
"framer-motion": "^12.12.1",
|
||||||
"mongodb": "^6.3.0",
|
"mongodb": "^6.3.0",
|
||||||
|
"mongoose": "^8.15.0",
|
||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfkit": "^0.17.1",
|
"pdfkit": "^0.17.1",
|
||||||
@@ -1171,6 +1172,23 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/debug": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/decimal.js-light": {
|
"node_modules/decimal.js-light": {
|
||||||
"version": "2.5.1",
|
"version": "2.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
|
||||||
@@ -1663,6 +1681,15 @@
|
|||||||
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/kareem": {
|
||||||
|
"version": "2.6.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
|
||||||
|
"integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lilconfig": {
|
"node_modules/lilconfig": {
|
||||||
"version": "3.1.3",
|
"version": "3.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||||
@@ -1845,6 +1872,28 @@
|
|||||||
"whatwg-url": "^14.1.0 || ^13.0.0"
|
"whatwg-url": "^14.1.0 || ^13.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/mongoose": {
|
||||||
|
"version": "8.15.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.15.0.tgz",
|
||||||
|
"integrity": "sha512-WFKsY1q12ScGabnZWUB9c/QzZmz/ESorrV27OembB7Gz6rrh9m3GA4Srsv1uvW1s9AHO5DeZ6DdUTyF9zyNERQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bson": "^6.10.3",
|
||||||
|
"kareem": "2.6.3",
|
||||||
|
"mongodb": "~6.16.0",
|
||||||
|
"mpath": "0.9.0",
|
||||||
|
"mquery": "5.0.0",
|
||||||
|
"ms": "2.1.3",
|
||||||
|
"sift": "17.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=16.20.1"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/mongoose"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/motion-dom": {
|
"node_modules/motion-dom": {
|
||||||
"version": "12.12.1",
|
"version": "12.12.1",
|
||||||
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.12.1.tgz",
|
"resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.12.1.tgz",
|
||||||
@@ -1860,6 +1909,33 @@
|
|||||||
"integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==",
|
"integrity": "sha512-f9qiqUHm7hWSLlNW8gS9pisnsN7CRFRD58vNjptKdsqFLpkVnX00TNeD6Q0d27V9KzT7ySFyK1TZ/DShfVOv6w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/mpath": {
|
||||||
|
"version": "0.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
|
||||||
|
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mquery": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"debug": "4.x"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/mz": {
|
"node_modules/mz": {
|
||||||
"version": "2.7.0",
|
"version": "2.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
|
||||||
@@ -2614,6 +2690,12 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/sift": {
|
||||||
|
"version": "17.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
|
||||||
|
"integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/signal-exit": {
|
"node_modules/signal-exit": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
"@react-pdf/renderer": "^4.3.0",
|
"@react-pdf/renderer": "^4.3.0",
|
||||||
"framer-motion": "^12.12.1",
|
"framer-motion": "^12.12.1",
|
||||||
"mongodb": "^6.3.0",
|
"mongodb": "^6.3.0",
|
||||||
|
"mongoose": "^8.15.0",
|
||||||
"next": "14.1.0",
|
"next": "14.1.0",
|
||||||
"pdf-lib": "^1.17.1",
|
"pdf-lib": "^1.17.1",
|
||||||
"pdfkit": "^0.17.1",
|
"pdfkit": "^0.17.1",
|
||||||
|
|||||||
74
src/app/api/weather/route.js
Normal file
74
src/app/api/weather/route.js
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import { NextResponse } from 'next/server'
|
||||||
|
import { connectToDatabase } from '@/lib/mongodb'
|
||||||
|
|
||||||
|
const CACHE_DURATION = 2 * 60 * 60 * 1000 // 2 hours in milliseconds
|
||||||
|
const COORDS = {
|
||||||
|
lat: 47.06149235737582,
|
||||||
|
lon: 28.86683069635403
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchWeatherFromAPI() {
|
||||||
|
const response = await fetch(
|
||||||
|
`http://api.weatherapi.com/v1/forecast.json?key=${process.env.WEATHER_API_KEY}&q=${COORDS.lat},${COORDS.lon}&days=7&aqi=no`
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error('Failed to fetch weather data')
|
||||||
|
}
|
||||||
|
|
||||||
|
return response.json()
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function GET() {
|
||||||
|
try {
|
||||||
|
// Ensure database connection
|
||||||
|
const { db } = await connectToDatabase()
|
||||||
|
|
||||||
|
// Check if we have cached data
|
||||||
|
const cachedWeather = await db.collection('weather').findOne({
|
||||||
|
'location.lat': COORDS.lat,
|
||||||
|
'location.lon': COORDS.lon
|
||||||
|
})
|
||||||
|
|
||||||
|
const now = new Date()
|
||||||
|
const isCacheValid = cachedWeather &&
|
||||||
|
(now - new Date(cachedWeather.lastUpdated)) < CACHE_DURATION
|
||||||
|
|
||||||
|
// If cache is valid, return cached data
|
||||||
|
if (isCacheValid) {
|
||||||
|
return NextResponse.json(cachedWeather)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no cache or cache is invalid, fetch fresh data
|
||||||
|
const weatherData = await fetchWeatherFromAPI()
|
||||||
|
|
||||||
|
// Add metadata to the weather data
|
||||||
|
const weatherWithMetadata = {
|
||||||
|
...weatherData,
|
||||||
|
location: COORDS,
|
||||||
|
lastUpdated: now
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update or create weather document
|
||||||
|
await db.collection('weather').updateOne(
|
||||||
|
{
|
||||||
|
'location.lat': COORDS.lat,
|
||||||
|
'location.lon': COORDS.lon
|
||||||
|
},
|
||||||
|
{ $set: weatherWithMetadata },
|
||||||
|
{ upsert: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
return NextResponse.json(weatherWithMetadata)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Weather API Error:', error)
|
||||||
|
// If we have cached data but failed to fetch new data, return cached data
|
||||||
|
if (cachedWeather) {
|
||||||
|
return NextResponse.json(cachedWeather)
|
||||||
|
}
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Failed to fetch weather data' },
|
||||||
|
{ status: 500 }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@ import { LastRefresh } from '@/components/LastRefresh'
|
|||||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'
|
||||||
import { motion, AnimatePresence } from 'framer-motion'
|
import { motion, AnimatePresence } from 'framer-motion'
|
||||||
import { Navbar } from '@/components/Navbar'
|
import { Navbar } from '@/components/Navbar'
|
||||||
|
import WeatherForecast from '@/components/WeatherForecast'
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
const [data, setData] = useState([])
|
const [data, setData] = useState([])
|
||||||
@@ -153,6 +154,15 @@ export default function Dashboard() {
|
|||||||
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 text-white">
|
<div className="min-h-screen bg-gradient-to-br from-gray-900 via-gray-800 to-gray-900 text-white">
|
||||||
<Navbar lastUpdateTime={lastUpdateTime} />
|
<Navbar lastUpdateTime={lastUpdateTime} />
|
||||||
<div className="max-w-[1400px] mx-auto p-4 sm:p-6 space-y-6">
|
<div className="max-w-[1400px] mx-auto p-4 sm:p-6 space-y-6">
|
||||||
|
<motion.div
|
||||||
|
variants={containerVariants}
|
||||||
|
initial="hidden"
|
||||||
|
animate="visible"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<WeatherForecast />
|
||||||
|
</motion.div>
|
||||||
|
|
||||||
<motion.div
|
<motion.div
|
||||||
variants={containerVariants}
|
variants={containerVariants}
|
||||||
initial="hidden"
|
initial="hidden"
|
||||||
|
|||||||
125
src/components/WeatherForecast.js
Normal file
125
src/components/WeatherForecast.js
Normal file
@@ -0,0 +1,125 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import { Card } from '@/components/ui/card'
|
||||||
|
import { motion } from 'framer-motion'
|
||||||
|
|
||||||
|
const WEATHER_ICONS = {
|
||||||
|
'01d': '☀️', // clear sky
|
||||||
|
'01n': '🌙', // clear sky night
|
||||||
|
'02d': '⛅', // few clouds
|
||||||
|
'02n': '☁️', // few clouds night
|
||||||
|
'03d': '☁️', // scattered clouds
|
||||||
|
'03n': '☁️', // scattered clouds night
|
||||||
|
'04d': '☁️', // broken clouds
|
||||||
|
'04n': '☁️', // broken clouds night
|
||||||
|
'09d': '🌧️', // shower rain
|
||||||
|
'09n': '🌧️', // shower rain night
|
||||||
|
'10d': '🌦️', // rain
|
||||||
|
'10n': '🌧️', // rain night
|
||||||
|
'11d': '⛈️', // thunderstorm
|
||||||
|
'11n': '⛈️', // thunderstorm night
|
||||||
|
'13d': '🌨️', // snow
|
||||||
|
'13n': '🌨️', // snow night
|
||||||
|
'50d': '🌫️', // mist
|
||||||
|
'50n': '🌫️', // mist night
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function WeatherForecast() {
|
||||||
|
const [weatherData, setWeatherData] = useState(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [error, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchWeather = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/weather');
|
||||||
|
if (!response.ok) throw new Error('Failed to fetch weather data');
|
||||||
|
const data = await response.json();
|
||||||
|
setWeatherData(data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchWeather();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-24 bg-gray-100 rounded-lg animate-pulse flex items-center justify-center">
|
||||||
|
<div className="text-gray-400">Loading weather data...</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div className="w-full h-24 bg-red-50 rounded-lg flex items-center justify-center">
|
||||||
|
<div className="text-red-500">Error loading weather data</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!weatherData?.forecast?.forecastday) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="w-full p-3 bg-gray-800/50 backdrop-blur-sm border-gray-700 hover:bg-gray-800/70 transition-all duration-300">
|
||||||
|
<div className="flex justify-between items-center mb-2">
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold text-white">7-Day Forecast</h2>
|
||||||
|
<div className="text-xs text-gray-400">
|
||||||
|
Chisinau, UTM
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400">
|
||||||
|
Last updated: {new Date(weatherData.lastUpdated).toLocaleTimeString()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="grid grid-cols-7 gap-2">
|
||||||
|
{weatherData.forecast.forecastday.map((day, index) => (
|
||||||
|
<motion.div
|
||||||
|
key={day.date}
|
||||||
|
initial={{ opacity: 0, y: 20 }}
|
||||||
|
animate={{ opacity: 1, y: 0 }}
|
||||||
|
transition={{ delay: index * 0.1 }}
|
||||||
|
className="flex flex-col items-center p-2 rounded-lg bg-gray-700/30 hover:bg-gray-700/50 transition-all duration-300"
|
||||||
|
>
|
||||||
|
<div className="text-xs text-gray-400">
|
||||||
|
{index === 0 ? 'Today' : new Date(day.date).toLocaleDateString('en-US', { weekday: 'short' })}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<div className="relative w-10 h-10">
|
||||||
|
<Image
|
||||||
|
src={`https:${day.day.condition.icon}`}
|
||||||
|
alt={day.day.condition.text}
|
||||||
|
fill
|
||||||
|
className="object-contain"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<span className="text-sm font-semibold text-white">
|
||||||
|
{Math.round(day.day.maxtemp_c)}°
|
||||||
|
</span>
|
||||||
|
<span className="text-xs text-gray-400">
|
||||||
|
{Math.round(day.day.mintemp_c)}°
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-500 text-center mt-0.5 line-clamp-1">
|
||||||
|
{day.day.condition.text}
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-gray-400">
|
||||||
|
{day.day.daily_chance_of_rain}%
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
88
src/lib/weather.js
Normal file
88
src/lib/weather.js
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
import { connectToDatabase } from './mongodb'
|
||||||
|
|
||||||
|
const WEATHER_API_KEY = process.env.OPENWEATHER_API_KEY
|
||||||
|
const LAT = 47.06149235737582
|
||||||
|
const LON = 28.86683069635403
|
||||||
|
|
||||||
|
const WEATHER_ICONS = {
|
||||||
|
'01d': '☀️', // clear sky
|
||||||
|
'01n': '🌙', // clear sky night
|
||||||
|
'02d': '⛅', // few clouds
|
||||||
|
'02n': '☁️', // few clouds night
|
||||||
|
'03d': '☁️', // scattered clouds
|
||||||
|
'03n': '☁️', // scattered clouds night
|
||||||
|
'04d': '☁️', // broken clouds
|
||||||
|
'04n': '☁️', // broken clouds night
|
||||||
|
'09d': '🌧️', // shower rain
|
||||||
|
'09n': '🌧️', // shower rain night
|
||||||
|
'10d': '🌦️', // rain
|
||||||
|
'10n': '🌧️', // rain night
|
||||||
|
'11d': '⛈️', // thunderstorm
|
||||||
|
'11n': '⛈️', // thunderstorm night
|
||||||
|
'13d': '🌨️', // snow
|
||||||
|
'13n': '🌨️', // snow night
|
||||||
|
'50d': '🌫️', // mist
|
||||||
|
'50n': '🌫️', // mist night
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function fetchWeatherData() {
|
||||||
|
try {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.openweathermap.org/data/2.5/forecast?lat=${LAT}&lon=${LON}&appid=${WEATHER_API_KEY}&units=metric`
|
||||||
|
)
|
||||||
|
const data = await response.json()
|
||||||
|
|
||||||
|
// Process the data to get daily forecasts
|
||||||
|
const dailyForecasts = data.list.reduce((acc, item) => {
|
||||||
|
const date = new Date(item.dt * 1000)
|
||||||
|
const day = date.toISOString().split('T')[0]
|
||||||
|
|
||||||
|
if (!acc[day]) {
|
||||||
|
acc[day] = {
|
||||||
|
date: day,
|
||||||
|
temp_min: item.main.temp_min,
|
||||||
|
temp_max: item.main.temp_max,
|
||||||
|
humidity: item.main.humidity,
|
||||||
|
wind_speed: item.wind.speed,
|
||||||
|
icon: item.weather[0].icon,
|
||||||
|
description: item.weather[0].description,
|
||||||
|
timestamp: date
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
acc[day].temp_min = Math.min(acc[day].temp_min, item.main.temp_min)
|
||||||
|
acc[day].temp_max = Math.max(acc[day].temp_max, item.main.temp_max)
|
||||||
|
}
|
||||||
|
|
||||||
|
return acc
|
||||||
|
}, {})
|
||||||
|
|
||||||
|
return Object.values(dailyForecasts)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching weather data:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getWeatherData() {
|
||||||
|
const { db } = await connectToDatabase()
|
||||||
|
const collection = db.collection('weather_data')
|
||||||
|
|
||||||
|
// Get the latest weather data
|
||||||
|
const latestData = await collection.findOne({}, { sort: { timestamp: -1 } })
|
||||||
|
|
||||||
|
// If no data exists or data is older than 2 hours, fetch new data
|
||||||
|
if (!latestData || (Date.now() - latestData.timestamp.getTime() > 2 * 60 * 60 * 1000)) {
|
||||||
|
const newData = await fetchWeatherData()
|
||||||
|
await collection.insertOne({
|
||||||
|
forecasts: newData,
|
||||||
|
timestamp: new Date()
|
||||||
|
})
|
||||||
|
return newData
|
||||||
|
}
|
||||||
|
|
||||||
|
return latestData.forecasts
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWeatherIcon(iconCode) {
|
||||||
|
return WEATHER_ICONS[iconCode] || '❓'
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user