diff --git a/src/App.jsx b/src/App.jsx index 74be53f..404167f 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -44,6 +44,7 @@ export default function App() { const [categoryFilter, setCategoryFilter] = useState('Todos'); const [colorFilter, setColorFilter] = useState(''); const [ageFilter, setAgeFilter] = useState('any'); + const [favoriteFilter, setFavoriteFilter] = useState(false); const [showClosetFilters, setShowClosetFilters] = useState(false); // Estado para criação de Looks @@ -315,9 +316,20 @@ export default function App() { return () => { unsubClothes(); unsubLooks(); unsubSections(); unsubProfile(); unsubNotif(); unsubPlans(); }; }, [user]); + const getWeatherEmoji = (code) => { + if (code === 0) return '☀️'; + if ([1, 2, 3].includes(code)) return '⛅'; + if ([45, 48].includes(code)) return '🌫️'; + if ([51, 53, 55, 56, 57, 61, 63, 65, 66, 67].includes(code)) return '🌧️'; + if ([71, 73, 75, 77, 85, 86].includes(code)) return '❄️'; + if ([80, 81, 82].includes(code)) return '🌦️'; + if ([95, 96, 99].includes(code)) return '⛈️'; + return '☀️'; + }; + // Fetch Weather Data useEffect(() => { - if (view !== 'dashboard') return; + if (!user) return; const fetchWeather = async () => { try { const locName = userProfile?.location || 'Lisboa, Portugal'; @@ -326,16 +338,24 @@ export default function App() { if (geoData.results && geoData.results.length > 0) { const { latitude, longitude, name, country } = geoData.results[0]; - const weatherRes = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true&daily=temperature_2m_max,temperature_2m_min&timezone=auto`); + const weatherRes = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&longitude=${longitude}¤t_weather=true&daily=temperature_2m_max,temperature_2m_min,weathercode&timezone=auto`); const weatherRaw = await weatherRes.json(); if (weatherRaw.current_weather && weatherRaw.daily) { + const dailyForecast = weatherRaw.daily.time.map((dateStr, idx) => ({ + date: dateStr, + min: Math.round(weatherRaw.daily.temperature_2m_min[idx]), + max: Math.round(weatherRaw.daily.temperature_2m_max[idx]), + weathercode: weatherRaw.daily.weathercode[idx] + })); + setWeatherData({ name: `${name}, ${country || ''}`.replace(/,\s*$/, ''), currentTemp: Math.round(weatherRaw.current_weather.temperature), minTemp: Math.round(weatherRaw.daily.temperature_2m_min[0]), maxTemp: Math.round(weatherRaw.daily.temperature_2m_max[0]), - avgTemp: Math.round((weatherRaw.daily.temperature_2m_min[0] + weatherRaw.daily.temperature_2m_max[0]) / 2) + avgTemp: Math.round((weatherRaw.daily.temperature_2m_min[0] + weatherRaw.daily.temperature_2m_max[0]) / 2), + forecast: dailyForecast }); } } @@ -344,7 +364,7 @@ export default function App() { } }; fetchWeather(); - }, [userProfile?.location, view]); + }, [userProfile?.location, user]); // --- Lógicas de Negócio --- @@ -491,9 +511,11 @@ export default function App() { else if (ageFilter === 'older') matchesAge = days > 365; } - return matchesSearch && matchesCategory && matchesColor && matchesAge && matchesSection; + const matchesFavorite = !favoriteFilter || c.favorite; + + return matchesSearch && matchesCategory && matchesColor && matchesAge && matchesSection && matchesFavorite; }); - }, [baseClothes, searchTerm, categoryFilter, colorFilter, ageFilter, t, activeSectionFilter]); + }, [baseClothes, searchTerm, categoryFilter, colorFilter, ageFilter, t, activeSectionFilter, favoriteFilter]); // Ações de Itens const handleItemAction = async (action, item) => { @@ -510,6 +532,15 @@ export default function App() { } }; + const handleLookAction = async (action, look) => { + if (!user) return; + const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'looks', look.id || look); + + switch (action) { + case 'favorite': await updateDoc(docRef, { favorite: !look.favorite }); break; + } + }; + const saveItem = async (e) => { e.preventDefault(); if (!user) return; @@ -1103,12 +1134,11 @@ export default function App() { {/* DASHBOARD */} {view === 'dashboard' && (
-
+
{[ { label: t('readyClothes'), val: activeClothes.length, icon: Shirt, col: 'primary' }, { label: t('inLaundry'), val: laundryClothes.length, icon: Droplets, col: 'blue' }, { label: t('myLooks'), val: looks.length, icon: Sparkles, col: 'purple' }, - { label: t('favorites'), val: activeClothes.filter(c => c.favorite).length, icon: Heart, col: 'rose' }, ].map((s, i) => (
@@ -1190,7 +1220,7 @@ export default function App() { className="flex items-center gap-3 px-8 py-4 bg-primary-600 text-white rounded-[2rem] font-black uppercase text-[10px] tracking-widest shadow-xl shadow-primary-600/30 hover:scale-105 transition-all" > {t('advancedFilters')} - {(colorFilter || ageFilter !== 'any' || (categoryFilter !== 'Todos' && categoryFilter !== t('all'))) && ( + {(colorFilter || favoriteFilter || ageFilter !== 'any' || (categoryFilter !== 'Todos' && categoryFilter !== t('all'))) && ( )} @@ -1478,7 +1508,8 @@ export default function App() { return item && item.color && item.color.includes(colorFilter); }); } - return matchesSection && matchesColor; + const matchesFavorite = !favoriteFilter || look.favorite; + return matchesSection && matchesColor && matchesFavorite; }); const availableLooks = filteredBySectionLooks.filter(look => @@ -1517,6 +1548,13 @@ export default function App() { {copiedLookId === look.id ? t('linkCopied') : t('share')} + @@ -1668,6 +1706,12 @@ export default function App() { const dayLooks = getLooksForDayGlobal(ds); const isToday = ds === todayStrGlobal; const isWeek = plannerMode === 'week'; + + let dayWeather = null; + if (weatherAlerts && weatherData && weatherData.forecast) { + dayWeather = weatherData.forecast.find(f => f.date === ds); + } + return (
{ setPlannerPickerDate(ds); setShowPlannerPicker(true); }} @@ -1675,7 +1719,14 @@ export default function App() { style={{ minHeight: isWeek ? '180px' : '100px' }} >
- {date.getDate()} +
+ {date.getDate()} + {dayWeather && ( + + {getWeatherEmoji(dayWeather.weathercode)} + + )} +
{isToday && {t('today')}}
{dayLooks.length > 0 ? ( @@ -2412,10 +2463,24 @@ export default function App() { {availableColors.map(c => )}
+ +
+ + +
- +
diff --git a/src/lib/i18n.js b/src/lib/i18n.js index a2ba7e3..5b41459 100644 --- a/src/lib/i18n.js +++ b/src/lib/i18n.js @@ -21,6 +21,7 @@ export const translations = { dailyOutfit: "Outfit Diário", noOutfitPlanned: "Nenhum Outfit Planeado", goToPlanning: "Vá ao planeamento para adicionar", + onlyFavorites: "Apenas Favoritos", logout: "Sair", overview: "Visão Geral", myCloset: "O Meu Armário", @@ -223,6 +224,7 @@ export const translations = { dailyOutfit: "Daily Outfit", noOutfitPlanned: "No Outfit Planned", goToPlanning: "Go to planning to add one", + onlyFavorites: "Favorites Only", logout: "Logout", overview: "Overview", myCloset: "My Closet", @@ -425,6 +427,7 @@ export const translations = { dailyOutfit: "Outfit Diario", noOutfitPlanned: "Sin Outfit Planeado", goToPlanning: "Ve a planificación para añadir", + onlyFavorites: "Solo Favoritos", logout: "Cerrar Sesión", overview: "Visión General", myCloset: "Mi Armario", @@ -627,6 +630,7 @@ export const translations = { dailyOutfit: "Tenue du Jour", noOutfitPlanned: "Aucune Tenue Prévue", goToPlanning: "Allez dans planification pour ajouter", + onlyFavorites: "Favoris Uniquement", logout: "Déconnexion", overview: "Vue d'ensemble", myCloset: "Mon Placard", @@ -829,6 +833,7 @@ export const translations = { dailyOutfit: "Tägliches Outfit", noOutfitPlanned: "Kein Outfit Geplant", goToPlanning: "Gehen Sie zur Planung, um eins hinzuzufügen", + onlyFavorites: "Nur Favoriten", logout: "Abmelden", overview: "Übersicht", myCloset: "Mein Schrank",