diff --git a/src/hooks/useGeolocation.js b/src/hooks/useGeolocation.js new file mode 100644 index 00000000..0dfc7129 --- /dev/null +++ b/src/hooks/useGeolocation.js @@ -0,0 +1,76 @@ +import { useEffect, useState } from 'react' + +const GEOLOCATION_KEY = 'userGeolocation' +const GEOLOCATION_MANUAL_KEY = 'userGeolocationManual' + +export const useGeolocation = () => { + const [coords, setCoords] = useState(null) + const [loading, setLoading] = useState(true) + const [error, setError] = useState(null) + const [isManualOverride, setIsManualOverride] = useState(true) + + // Load from localStorage on mount + useEffect(() => { + const stored = localStorage.getItem(GEOLOCATION_KEY) + const isManual = localStorage.getItem(GEOLOCATION_MANUAL_KEY) === 'true' + + if (stored) { + try { + setCoords(JSON.parse(stored)) + setIsManualOverride(isManual) + setLoading(false) + return + } catch (e) { + console.error('Failed to parse stored geolocation:', e) + } + } + + // Auto-detect geolocation + if ('geolocation' in navigator) { + navigator.geolocation.getCurrentPosition( + position => { + const { latitude, longitude } = position.coords + const newCoords = { latitude, longitude } + setCoords(newCoords) + localStorage.setItem(GEOLOCATION_KEY, JSON.stringify(newCoords)) + localStorage.setItem(GEOLOCATION_MANUAL_KEY, 'false') + setLoading(false) + }, + err => { + console.warn('Geolocation error:', err) + setError(err.message) + // Use default (New York) if geolocation fails + const defaultCoords = { latitude: 40.7128, longitude: -74.006 } + setCoords(defaultCoords) + localStorage.setItem(GEOLOCATION_KEY, JSON.stringify(defaultCoords)) + setLoading(false) + }, + { + timeout: 5000, + enableHighAccuracy: false, + }, + ) + } else { + setError('Geolocation not supported') + const defaultCoords = { latitude: 40.7128, longitude: -74.006 } + setCoords(defaultCoords) + setLoading(false) + } + }, []) + + const setManualLocation = (latitude, longitude) => { + const newCoords = { latitude, longitude } + setCoords(newCoords) + setIsManualOverride(true) + localStorage.setItem(GEOLOCATION_KEY, JSON.stringify(newCoords)) + localStorage.setItem(GEOLOCATION_MANUAL_KEY, 'true') + } + + return { + coords, + loading, + error, + isManualOverride, + setManualLocation, + } +} diff --git a/src/hooks/useWeather.js b/src/hooks/useWeather.js new file mode 100644 index 00000000..73f89418 --- /dev/null +++ b/src/hooks/useWeather.js @@ -0,0 +1,84 @@ +import { useQuery } from '@tanstack/react-query' + +const OPEN_METEO_API = 'https://api.open-meteo.com/v1' + +// Map WMO weather codes to icons and descriptions +const weatherCodeMap = { + 0: { icon: '☀️', desc: 'Clear' }, + 1: { icon: '🌤️', desc: 'Mostly Clear' }, + 2: { icon: '⛅', desc: 'Partly Cloudy' }, + 3: { icon: '☁️', desc: 'Cloudy' }, + 45: { icon: '🌫️', desc: 'Foggy' }, + 48: { icon: '🌫️', desc: 'Foggy' }, + 51: { icon: '🌦️', desc: 'Light Drizzle' }, + 53: { icon: '🌦️', desc: 'Drizzle' }, + 55: { icon: '🌧️', desc: 'Heavy Drizzle' }, + 61: { icon: '🌧️', desc: 'Light Rain' }, + 63: { icon: '🌧️', desc: 'Rain' }, + 65: { icon: '⛈️', desc: 'Heavy Rain' }, + 71: { icon: '❄️', desc: 'Light Snow' }, + 73: { icon: '❄️', desc: 'Snow' }, + 75: { icon: '❄️', desc: 'Heavy Snow' }, + 77: { icon: '🌨️', desc: 'Snow Grains' }, + 80: { icon: '🌧️', desc: 'Light Showers' }, + 81: { icon: '🌧️', desc: 'Showers' }, + 82: { icon: '⛈️', desc: 'Heavy Showers' }, + 85: { icon: '🌨️', desc: 'Light Snow Showers' }, + 86: { icon: '🌨️', desc: 'Snow Showers' }, + 95: { icon: '⛈️', desc: 'Thunderstorm' }, + 96: { icon: '⛈️', desc: 'Thunderstorm with Hail' }, + 99: { icon: '⛈️', desc: 'Thunderstorm with Hail' }, +} + +export const useWeather = (latitude, longitude, date) => { + return useQuery({ + queryKey: ['weather', latitude, longitude, date?.toISOString()?.split('T')[0]], + enabled: !!latitude && !!longitude && !!date, + staleTime: 1000 * 60 * 60, // 1 hour + gcTime: 1000 * 60 * 60 * 24, // 24 hours + queryFn: async () => { + const dateStr = date.toISOString().split('T')[0] + + const params = new URLSearchParams({ + latitude, + longitude, + start_date: dateStr, + end_date: dateStr, + daily: 'weather_code,temperature_2m_max,temperature_2m_min', + temperature_unit: 'celsius', + timezone: 'auto', + }) + + const response = await fetch( + `${OPEN_METEO_API}/forecast?${params.toString()}`, + ) + + if (!response.ok) { + throw new Error('Failed to fetch weather data') + } + + const data = await response.json() + const dailyData = data.daily + + if (!dailyData || dailyData.weather_code[0] == null) { + return null + } + + const weatherCode = dailyData.weather_code[0] + const weatherInfo = weatherCodeMap[weatherCode] || { + icon: '🌡️', + desc: 'Unknown', + } + const tempMax = dailyData.temperature_2m_max[0] + const tempMin = dailyData.temperature_2m_min[0] + + return { + icon: weatherInfo.icon, + description: weatherInfo.desc, + tempMax: Math.round(tempMax), + tempMin: Math.round(tempMin), + code: weatherCode, + } + }, + }) +} diff --git a/src/views/Chores/CompactChoreCard.jsx b/src/views/Chores/CompactChoreCard.jsx index 59c050b9..ba888112 100644 --- a/src/views/Chores/CompactChoreCard.jsx +++ b/src/views/Chores/CompactChoreCard.jsx @@ -23,6 +23,9 @@ import { getPriorityColor, getTextColorFromBackgroundColor, } from '../../utils/Colors.jsx' +import AssigneeAvatarGroup, { + getChoreAssigneeNames, +} from '../components/AssigneeAvatarGroup' import ChoreActionMenu from '../components/ChoreActionMenu' const CompactChoreCard = ({ @@ -84,15 +87,7 @@ const CompactChoreCard = ({ parts.push(getRecurrentChipText(chore)) // Assignee - if (chore.assignedTo) { - const assignee = performers.find( - p => p.userId === chore.assignedTo, - )?.displayName - if (assignee) parts.push(assignee) - } - if (chore.assignedTo === null) { - parts.push('Anyone') - } + parts.push(getChoreAssigneeNames(chore, performers).join(', ')) // Points if (chore.points > 0) { @@ -124,6 +119,10 @@ const CompactChoreCard = ({ bgcolor: 'background.level1', boxShadow: 'sm', }, + ...(chore.frontendCompleted && { + opacity: 0.72, + bgcolor: 'success.softBg', + }), '&::before': { content: '""', position: 'absolute', @@ -373,6 +372,10 @@ const CompactChoreCard = ({ sx={{ fontWeight: 600, fontSize: 14, + textDecoration: chore.frontendCompleted ? 'line-through' : 'none', + color: chore.frontendCompleted + ? 'text.tertiary' + : 'text.primary', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', @@ -403,6 +406,7 @@ const CompactChoreCard = ({ {/* Line 2: Metadata */} + {getFrequencyIcon(chore)} { } const toggleViewMode = () => { - const modes = ['default', 'compact', 'calendar'] + const modes = ['default', 'compact', 'weekly', 'calendar'] const currentIndex = modes.indexOf(viewMode) const nextIndex = (currentIndex + 1) % modes.length const newMode = modes[nextIndex] setViewMode(newMode) localStorage.setItem('choreCardViewMode', newMode) - if (newMode !== 'calendar') { + if (newMode !== 'calendar' && newMode !== 'weekly') { setSelectedCalendarDate(null) } } @@ -817,6 +820,8 @@ const MyChores = () => { setSearchFilter('All') } + const isCalendarView = viewMode === 'calendar' || viewMode === 'weekly' + // Show error state when API is unreachable if (choresError || membersError) { return ( @@ -962,13 +967,17 @@ const MyChores = () => { viewMode === 'default' ? 'Switch to Compact View' : viewMode === 'compact' - ? 'Switch to Calendar View' - : 'Switch to Card View' + ? 'Switch to Weekly View' + : viewMode === 'weekly' + ? 'Switch to Monthly View' + : 'Switch to Card View' } > {viewMode === 'default' ? ( ) : viewMode === 'compact' ? ( + + ) : viewMode === 'weekly' ? ( ) : ( @@ -1256,7 +1265,7 @@ const MyChores = () => { ? getFilteredChores.length === 0 : projectFilteredChores.length === 0) && // only if not in calendar view: - viewMode !== 'calendar' && ( + !isCalendarView && ( { )} {searchTerm?.length > 0 && - viewMode !== 'calendar' && ( + !isCalendarView && ( { toggleChoreSelection={toggleChoreSelection} /> )} - {viewMode === 'calendar' && ( + {isCalendarView && ( <> {/* Summary Chips when no date selected */} {/* { */} {/* Calendar Monthly View */} - {isLargeScreen ? ( + {viewMode === 'weekly' ? ( + { + setSelectedCalendarDate(date) + }} + /> + ) : isLargeScreen ? ( { @@ -1493,7 +1511,7 @@ const MyChores = () => { )} {searchTerm.length === 0 && - viewMode !== 'calendar' && ( + !isCalendarView && ( {choreSections.map((section, index) => { if (section.content.length === 0) return null diff --git a/src/views/Chores/hooks/useChoreActions.js b/src/views/Chores/hooks/useChoreActions.js index 68120e26..63a62885 100644 --- a/src/views/Chores/hooks/useChoreActions.js +++ b/src/views/Chores/hooks/useChoreActions.js @@ -150,21 +150,39 @@ export const useChoreActions = ({ [chores, filteredChores, setChores, setFilteredChores, queryClient, showSuccess, showError, showWarning, showUndo, refetchChores], ) + const markChoreCompletedInState = useCallback( + chore => { + const completedChore = { + ...chore, + frontendCompleted: true, + frontendCompletedAt: new Date().toISOString(), + } + + setChores(prev => + prev.map(c => (c.id === chore.id ? completedChore : c)), + ) + setFilteredChores(prev => + prev.map(c => (c.id === chore.id ? completedChore : c)), + ) + + queryClient.setQueriesData({ queryKey: ['chores'] }, oldData => { + if (!oldData || !oldData.res) return oldData + return { + ...oldData, + res: oldData.res.map(c => (c.id === chore.id ? completedChore : c)), + } + }) + + return completedChore + }, + [queryClient, setChores, setFilteredChores], + ) + const handleChoreAction = useCallback( async (action, chore, extraData = {}) => { switch (action) { case 'complete': - // 1. Instantly hide the chore from the UI and Cache - setChores(prev => prev.filter(c => c.id !== chore.id)) - setFilteredChores(prev => prev.filter(c => c.id !== chore.id)) - - queryClient.setQueriesData({ queryKey: ['chores'] }, oldData => { - if (!oldData || !oldData.res) return oldData; - return { - ...oldData, - res: oldData.res.filter(c => c.id !== chore.id), - } - }); + markChoreCompletedInState(chore) try { const response = await MarkChoreComplete( @@ -196,9 +214,8 @@ export const useChoreActions = ({ }, }) - // 3. Fetch the fresh active list from the server silently - // (This brings in the next occurrence if recurring, without showing the completed one) - queryClient.invalidateQueries({ queryKey: ['chores'] }) + // Keep the completed occurrence visible in the current view. + // A later refetch/navigation will pick up the server's next occurrence. } else { refetchChores() // Network failed, revert to truth } @@ -402,6 +419,7 @@ export const useChoreActions = ({ archiveChore, startChore, pauseChore, + markChoreCompletedInState, ], ) diff --git a/src/views/components/AssigneeAvatarGroup.jsx b/src/views/components/AssigneeAvatarGroup.jsx new file mode 100644 index 00000000..a9efaa17 --- /dev/null +++ b/src/views/components/AssigneeAvatarGroup.jsx @@ -0,0 +1,131 @@ +import { Avatar, Box } from '@mui/joy' + +const ASSIGNEE_COLORS = [ + '#1a73e8', + '#d93025', + '#188038', + '#f9ab00', + '#9334e6', + '#e8710a', + '#00796b', + '#c2185b', + '#5f6368', + '#3949ab', +] + +const hashString = value => { + return String(value || '') + .split('') + .reduce((hash, char) => (hash * 31 + char.charCodeAt(0)) >>> 0, 0) +} + +const getUserName = user => + user?.displayName || user?.name || user?.username || 'Anyone' + +const getUserInitial = user => getUserName(user).trim().charAt(0).toUpperCase() + +const getUserColor = user => { + const seed = user?.userId || user?.id || getUserName(user) + return ASSIGNEE_COLORS[hashString(seed) % ASSIGNEE_COLORS.length] +} + +const getChoreAssignees = (chore, performers = []) => { + const usersById = new Map( + performers.map(user => [Number(user.userId || user.id), user]), + ) + + const assignedUsers = [] + + if (chore?.assignees?.length > 0) { + chore.assignees.forEach(assignee => { + const userId = Number(assignee.userId || assignee.id) + assignedUsers.push(usersById.get(userId) || assignee) + }) + } else if (chore?.assignedTo) { + const userId = Number(chore.assignedTo) + assignedUsers.push(usersById.get(userId) || { userId }) + } + + const deduped = [] + const seen = new Set() + assignedUsers.forEach(user => { + const key = user?.userId || user?.id || getUserName(user) + if (!seen.has(key)) { + seen.add(key) + deduped.push(user) + } + }) + + return deduped.length > 0 + ? deduped + : [{ userId: 'anyone', displayName: 'Anyone' }] +} + +export const getChoreAssigneeNames = (chore, performers = []) => + getChoreAssignees(chore, performers).map(getUserName) + +const AssigneeAvatarGroup = ({ + chore, + performers = [], + size = 20, + max = 3, + sx, +}) => { + const assignees = getChoreAssignees(chore, performers) + const visibleAssignees = assignees.slice(0, max) + const overflowCount = assignees.length - visibleAssignees.length + + return ( + + {visibleAssignees.map((user, index) => ( + + {getUserInitial(user)} + + ))} + {overflowCount > 0 && ( + + +{overflowCount} + + )} + + ) +} + +export default AssigneeAvatarGroup diff --git a/src/views/components/CalendarWeekly.jsx b/src/views/components/CalendarWeekly.jsx new file mode 100644 index 00000000..c88d4068 --- /dev/null +++ b/src/views/components/CalendarWeekly.jsx @@ -0,0 +1,231 @@ +import { ArrowBackIosNew, ArrowForwardIos, CheckCircle, LocationOn, Today } from '@mui/icons-material' +import { Box, Chip, IconButton, Typography } from '@mui/joy' +import { useMemo, useState } from 'react' +import { useGeolocation } from '../../hooks/useGeolocation' +import { useLocalization } from '../../contexts/LocalizationContext' +import { getPriorityColor } from '../../utils/Colors' +import AssigneeAvatarGroup from './AssigneeAvatarGroup' +import LocationOverrideDialog from './LocationOverrideDialog' +import WeatherDisplay from './WeatherDisplay' +import styles from './CalendarWeekly.module.css' + +const isSameDate = (left, right) => + left.getFullYear() === right.getFullYear() && + left.getMonth() === right.getMonth() && + left.getDate() === right.getDate() + +const addDays = (date, days) => { + const next = new Date(date) + next.setDate(next.getDate() + days) + return next +} + +const startOfWeek = (date, firstDayOfWeek) => { + const start = new Date(date) + start.setHours(0, 0, 0, 0) + const diff = (start.getDay() - firstDayOfWeek + 7) % 7 + start.setDate(start.getDate() - diff) + return start +} + +const CalendarWeekly = ({ chores, performers = [], selectedDate, onDateChange }) => { + const { firstDayOfWeek, fmt } = useLocalization() + const [anchorDate, setAnchorDate] = useState(selectedDate || new Date()) + const [expandedDate, setExpandedDate] = useState(null) + const [locationDialogOpen, setLocationDialogOpen] = useState(false) + const { coords, isManualOverride, setManualLocation } = useGeolocation() + + const weekStart = useMemo( + () => startOfWeek(anchorDate, firstDayOfWeek), + [anchorDate, firstDayOfWeek], + ) + + const weekDays = useMemo( + () => Array.from({ length: 7 }, (_, index) => addDays(weekStart, index)), + [weekStart], + ) + + const choresByDay = useMemo(() => { + return weekDays.map(day => + chores.filter(chore => { + if (!chore.nextDueDate) return false + const choreDate = new Date(chore.nextDueDate) + if (Number.isNaN(choreDate.getTime())) return false + return isSameDate(choreDate, day) + }), + ) + }, [chores, weekDays]) + + const handleSelectDate = date => { + const nextDate = new Date(date) + setAnchorDate(nextDate) + setExpandedDate( + expandedDate && isSameDate(expandedDate, nextDate) ? null : nextDate, + ) + onDateChange(nextDate) + } + + const goToCurrentWeek = () => { + const today = new Date() + setAnchorDate(today) + setExpandedDate(today) + onDateChange(today) + } + + const weekEnd = weekDays[6] + + return ( +
+
+ + setAnchorDate(addDays(anchorDate, -7))} + title='Previous week' + > + + + + + + setAnchorDate(addDays(anchorDate, 7))} + title='Next week' + > + + + {isManualOverride && ( + setLocationDialogOpen(true)} + title='Change weather location' + > + + + )} + + + + {fmt.date(weekStart, 'MMM D')} - {fmt.date(weekEnd, 'MMM D, YYYY')} + + + + + {choresByDay.reduce( + (total, dayChores) => total + dayChores.length, + 0, + )}{' '} + tasks + + +
+ +
+ {weekDays.map((day, index) => { + const dayChores = choresByDay[index] + const isSelected = selectedDate && isSameDate(day, selectedDate) + const isExpanded = expandedDate && isSameDate(day, expandedDate) + const isToday = isSameDate(day, new Date()) + + return ( + + ) + })} +
+ + setLocationDialogOpen(false)} + onSetLocation={setManualLocation} + /> +
+ ) +} + +export default CalendarWeekly diff --git a/src/views/components/CalendarWeekly.module.css b/src/views/components/CalendarWeekly.module.css new file mode 100644 index 00000000..b357bab1 --- /dev/null +++ b/src/views/components/CalendarWeekly.module.css @@ -0,0 +1,206 @@ +.weekCalendar { + width: 100%; +} + +.weekHeader { + display: grid; + grid-template-columns: auto 1fr auto; + gap: 8px; + align-items: center; + margin-bottom: 12px; +} + +.weekGrid { + display: grid; + grid-template-columns: repeat(7, minmax(0, 1fr)); + gap: 8px; +} + +.dayColumn { + min-height: 180px; + border: 1px solid var(--joy-palette-divider); + border-radius: 8px; + background: var(--joy-palette-background-surface); + color: inherit; + font: inherit; + padding: 8px; + display: flex; + flex-direction: column; + gap: 8px; + cursor: pointer; + text-align: left; + position: relative; + transition: + flex-grow 0.2s ease, + transform 0.2s ease, + box-shadow 0.2s ease, + border-color 0.2s ease, + background-color 0.2s ease; +} + +.dayColumn:hover { + border-color: var(--joy-palette-primary-outlinedBorder); + background: var(--joy-palette-primary-softBg); +} + +.activeDay { + border-color: var(--joy-palette-primary-solidBg); + box-shadow: 0 0 0 1px var(--joy-palette-primary-solidBg); +} + +.today { + border-color: var(--joy-palette-primary-outlinedBorder); +} + +.dayHeader { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 6px; + min-height: 44px; +} + +.dateBlock { + min-width: 0; + display: flex; + align-items: flex-start; + gap: 6px; +} + +.dateText { + min-width: 0; +} + +.dayBadges { + flex-shrink: 0; +} + +.weatherDisplay { + flex-shrink: 0; +} + +.dayName { + text-transform: uppercase; + color: var(--joy-palette-text-tertiary); + font-size: 0.7rem; + line-height: 1; +} + +.dayNumber { + font-weight: 600; + line-height: 1.2; +} + +.taskList { + display: flex; + flex-direction: column; + gap: 6px; + min-height: 0; +} + +.taskPill { + border-left: 3px solid; + border-radius: 6px; + background: var(--joy-palette-background-body); + padding: 5px 6px; + min-width: 0; + display: flex; + align-items: center; + gap: 5px; +} + +.completedTaskPill { + opacity: 0.72; + background: var(--joy-palette-success-softBg); +} + +.taskName { + min-width: 0; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + font-size: 0.78rem; + line-height: 1.2; +} + +.completedTaskPill .taskName { + text-decoration: line-through; + color: var(--joy-palette-text-tertiary); +} + +.completedIcon { + width: 14px; + height: 14px; + flex-shrink: 0; + color: var(--joy-palette-success-600); +} + +.moreTasks { + color: var(--joy-palette-text-tertiary); + font-size: 0.75rem; + line-height: 1.2; +} + +.emptyDay { + color: var(--joy-palette-text-tertiary); + font-size: 0.75rem; + margin-top: auto; +} + +@media (max-width: 700px) { + .weekHeader { + grid-template-columns: 1fr; + } + + .weekGrid { + grid-template-columns: 1fr; + } + + .dayColumn { + min-height: 96px; + } +} + +@media (min-width: 701px) and (max-width: 900px) and (orientation: portrait) { + .weekGrid { + display: flex; + gap: 0; + align-items: stretch; + overflow: visible; + padding: 4px 0 8px; + } + + .dayColumn { + flex: 1 1 0; + min-height: 210px; + min-width: 0; + margin-left: -22px; + box-shadow: var(--joy-shadow-sm); + overflow: visible; + } + + .dayColumn:first-child { + margin-left: 0; + } + + .expandedDay { + flex-grow: 2.1; + z-index: 20; + transform: translateY(-3px); + box-shadow: var(--joy-shadow-md); + } + + .weatherDisplay { + width: 32px; + } + + .dayColumn:not(.expandedDay):hover { + z-index: 10; + transform: translateY(-2px); + } + + .expandedDay .dayBadges { + align-self: flex-start; + justify-content: flex-end; + } +} diff --git a/src/views/components/LocationOverrideDialog.jsx b/src/views/components/LocationOverrideDialog.jsx new file mode 100644 index 00000000..5659628b --- /dev/null +++ b/src/views/components/LocationOverrideDialog.jsx @@ -0,0 +1,138 @@ +import { Close } from '@mui/icons-material' +import { Button, FormControl, FormLabel, Input, Modal, ModalClose, Stack } from '@mui/joy' +import { useState } from 'react' + +const LocationOverrideDialog = ({ open, onClose, onSetLocation }) => { + const [city, setCity] = useState('') + const [loading, setLoading] = useState(false) + const [error, setError] = useState('') + + const handleSubmit = async e => { + e.preventDefault() + setError('') + setLoading(true) + + try { + // Use Open-Meteo Geocoding API (free, no API key needed) + const response = await fetch( + `https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&count=1&language=en&format=json`, + ) + const data = await response.json() + + if (!data.results || data.results.length === 0) { + setError('City not found. Please try again.') + return + } + + const result = data.results[0] + onSetLocation(result.latitude, result.longitude) + setCity('') + onClose() + } catch (err) { + setError('Failed to search location. Please try again.') + console.error(err) + } finally { + setLoading(false) + } + } + + return ( + +
+ + +
+ +
+

+ Override Location +

+

+ Enter a city name to update your weather location +

+
+ + + City + setCity(e.target.value)} + disabled={loading} + /> + + + {error && ( +
+ {error} +
+ )} + + + + + +
+
+
+
+ ) +} + +export default LocationOverrideDialog diff --git a/src/views/components/WeatherDisplay.jsx b/src/views/components/WeatherDisplay.jsx new file mode 100644 index 00000000..782f4775 --- /dev/null +++ b/src/views/components/WeatherDisplay.jsx @@ -0,0 +1,38 @@ +import { Box, Tooltip, Typography } from '@mui/joy' +import { useWeather } from '../../hooks/useWeather' + +const WeatherDisplay = ({ date, latitude, longitude, className }) => { + const { data: weather, isLoading } = useWeather(latitude, longitude, date) + + if (isLoading || !weather) { + return null + } + + return ( + + + + {weather.icon} + + + {weather.tempMax}° + + + + ) +} + +export default WeatherDisplay