revert(home): restore weather hero with anim + details, drop Countdown from home
All checks were successful
Deploy / deploy (push) Successful in 3m24s
All checks were successful
Deploy / deploy (push) Successful in 3m24s
Focus/Countdown не лежат по high-density. На Home вернул старый weather-hero: WeatherAnimation (фон + иконка), feelsLike/humidity/wind, 76px display-цифра (чуть крупнее прежних 64). FocusCard и CountdownCard файлы оставляем для будущего, но на главной не подключаем. Убрал сопутствующие state/fetch (tramNext, countdowns, nextEvent). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
148
app/page.tsx
148
app/page.tsx
@@ -14,8 +14,6 @@ import WeatherAnimation from '@/components/WeatherAnimation'
|
|||||||
import VoiceOverlay from '@/components/VoiceOverlay'
|
import VoiceOverlay from '@/components/VoiceOverlay'
|
||||||
import TimerWidget from '@/components/TimerWidget'
|
import TimerWidget from '@/components/TimerWidget'
|
||||||
import TimerHomeWidget from '@/components/TimerHomeWidget'
|
import TimerHomeWidget from '@/components/TimerHomeWidget'
|
||||||
import FocusCard from '@/components/FocusCard'
|
|
||||||
import CountdownCard from '@/components/CountdownCard'
|
|
||||||
|
|
||||||
type Tab = 'home' | 'devices' | 'calendar' | 'notes' | 'settings'
|
type Tab = 'home' | 'devices' | 'calendar' | 'notes' | 'settings'
|
||||||
|
|
||||||
@@ -448,8 +446,6 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
|||||||
const [calLoading, setCalLoading] = useState(true)
|
const [calLoading, setCalLoading] = useState(true)
|
||||||
const [pinnedNotes, setPinnedNotes] = useState<any[]>([])
|
const [pinnedNotes, setPinnedNotes] = useState<any[]>([])
|
||||||
const [selectedDay, setSelectedDay] = useState<any>(null)
|
const [selectedDay, setSelectedDay] = useState<any>(null)
|
||||||
const [countdowns, setCountdowns] = useState<{ label: string; date: string }[]>([])
|
|
||||||
const [tramNext, setTramNext] = useState<{ route: string; minutes: number; direction: string } | null>(null)
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('/api/calendar?range=today')
|
fetch('/api/calendar?range=today')
|
||||||
@@ -489,57 +485,8 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
|||||||
})
|
})
|
||||||
.catch(() => {})
|
.catch(() => {})
|
||||||
|
|
||||||
// Countdowns
|
|
||||||
fetch('/api/countdowns')
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(d => setCountdowns((d.countdowns || []).map((c: any) => ({ label: c.label, date: c.date }))))
|
|
||||||
.catch(() => {})
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
// Nearest upcoming tram — refresh every 30s so FocusCard stays current
|
|
||||||
useEffect(() => {
|
|
||||||
let cancelled = false
|
|
||||||
const STOP_IDS = [
|
|
||||||
{ id: '16226', direction: 'в центр' },
|
|
||||||
{ id: '16354', direction: 'от центра' },
|
|
||||||
]
|
|
||||||
const load = async () => {
|
|
||||||
try {
|
|
||||||
const results = await Promise.all(
|
|
||||||
STOP_IDS.map(s =>
|
|
||||||
fetch(`/api/transport?stopId=${s.id}`)
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(j => (j.arrivals || []).map((a: any) => ({ ...a, direction: s.direction })))
|
|
||||||
.catch(() => [])
|
|
||||||
)
|
|
||||||
)
|
|
||||||
const all = results.flat().filter(a => typeof a.minutes === 'number' && a.minutes >= 0)
|
|
||||||
all.sort((a: any, b: any) => a.minutes - b.minutes)
|
|
||||||
if (!cancelled) {
|
|
||||||
setTramNext(all[0] ? {
|
|
||||||
route: all[0].route,
|
|
||||||
minutes: all[0].minutes,
|
|
||||||
direction: all[0].direction,
|
|
||||||
} : null)
|
|
||||||
}
|
|
||||||
} catch {}
|
|
||||||
}
|
|
||||||
load()
|
|
||||||
const t = setInterval(load, 30_000)
|
|
||||||
return () => { cancelled = true; clearInterval(t) }
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
// Next event (today or tomorrow) — сейчас or nearest upcoming within next 24h
|
|
||||||
const nextEvent = (() => {
|
|
||||||
const now = Date.now()
|
|
||||||
const pool = [...todayEvents, ...tomorrowEvents]
|
|
||||||
.filter(e => !e.allDay)
|
|
||||||
.map(e => ({ e, t: new Date(e.start).getTime() }))
|
|
||||||
.filter(({ t }) => t >= now - 5 * 60_000)
|
|
||||||
.sort((a, b) => a.t - b.t)
|
|
||||||
return pool[0]?.e || null
|
|
||||||
})()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -548,19 +495,82 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
|||||||
padding: '18px 22px 24px',
|
padding: '18px 22px 24px',
|
||||||
display: 'flex', flexDirection: 'column', gap: 14,
|
display: 'flex', flexDirection: 'column', gap: 14,
|
||||||
}}>
|
}}>
|
||||||
{/* ───── Bento row: Focus hero + Tram ───── */}
|
{/* ───── Bento row: Hero weather + Tram ───── */}
|
||||||
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1.1fr)', gap: 14, alignItems: 'stretch' }}>
|
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(0, 1fr) minmax(0, 1.1fr)', gap: 14, alignItems: 'stretch' }}>
|
||||||
|
|
||||||
{/* Focus — контекст-hero */}
|
{/* Hero weather card */}
|
||||||
<FocusCard
|
{weather ? (
|
||||||
weather={weather ? { temp: weather.temp, desc: weather.desc, feelsLike: weather.feelsLike } : null}
|
<div
|
||||||
tramNext={tramNext}
|
className="card-hero"
|
||||||
nextEvent={nextEvent ? {
|
onClick={() => weather?.forecast?.[0] && setSelectedDay(weather.forecast[0])}
|
||||||
id: nextEvent.id, title: nextEvent.title, start: nextEvent.start,
|
style={{
|
||||||
allDay: nextEvent.allDay, ownerName: nextEvent.ownerName, color: nextEvent.color,
|
padding: '24px 26px',
|
||||||
} : null}
|
display: 'flex', flexDirection: 'column',
|
||||||
countdowns={countdowns}
|
position: 'relative', overflow: 'hidden',
|
||||||
/>
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Decorative animation, large, behind */}
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute', top: -24, right: -12,
|
||||||
|
opacity: 0.14, pointerEvents: 'none',
|
||||||
|
}}>
|
||||||
|
<WeatherAnimation condition={weather.desc} size={180} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="eyebrow" style={{ marginBottom: 10, position: 'relative', zIndex: 1 }}>
|
||||||
|
Сейчас
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
display: 'flex', alignItems: 'center', gap: 14,
|
||||||
|
position: 'relative', zIndex: 1, marginBottom: 8,
|
||||||
|
}}>
|
||||||
|
<div className="num-display" style={{
|
||||||
|
fontSize: 76, color: 'var(--text-primary)',
|
||||||
|
}}>
|
||||||
|
{weather.temp}°
|
||||||
|
</div>
|
||||||
|
<WeatherAnimation condition={weather.desc} size={60} />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
fontSize: 17, color: 'var(--text-primary)', fontWeight: 600,
|
||||||
|
position: 'relative', zIndex: 1, marginBottom: 16,
|
||||||
|
textTransform: 'capitalize',
|
||||||
|
}}>
|
||||||
|
{weather.desc}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{
|
||||||
|
display: 'flex', gap: 22, flexWrap: 'wrap',
|
||||||
|
marginTop: 'auto', position: 'relative', zIndex: 1,
|
||||||
|
}}>
|
||||||
|
{weather.feelsLike && (
|
||||||
|
<div>
|
||||||
|
<div className="eyebrow">Ощущается</div>
|
||||||
|
<div className="num" style={{ fontSize: 16, color: 'var(--text-primary)', fontWeight: 700, marginTop: 3 }}>{weather.feelsLike}°</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{weather.humidity && (
|
||||||
|
<div>
|
||||||
|
<div className="eyebrow">Влажность</div>
|
||||||
|
<div className="num" style={{ fontSize: 16, color: 'var(--text-primary)', fontWeight: 700, marginTop: 3 }}>{weather.humidity}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{weather.windSpeed && (
|
||||||
|
<div>
|
||||||
|
<div className="eyebrow">Ветер</div>
|
||||||
|
<div className="num" style={{ fontSize: 16, color: 'var(--text-primary)', fontWeight: 700, marginTop: 3 }}>{weather.windSpeed}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="card-hero" style={{ padding: '24px 26px', display: 'flex', alignItems: 'center', justifyContent: 'center', color: 'var(--text-tertiary)', fontSize: 13 }}>
|
||||||
|
Загрузка погоды...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Tram */}
|
{/* Tram */}
|
||||||
<TransportWidget />
|
<TransportWidget />
|
||||||
@@ -701,16 +711,6 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
|||||||
<TimerHomeWidget />
|
<TimerHomeWidget />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* ───── Row 4: Countdown ───── */}
|
|
||||||
<div style={{
|
|
||||||
display: 'grid',
|
|
||||||
gridTemplateColumns: 'minmax(260px, 1fr) minmax(0, 2fr)',
|
|
||||||
gap: 14,
|
|
||||||
}}>
|
|
||||||
<CountdownCard />
|
|
||||||
<div /> {/* место под будущий виджет */}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Weather day detail modal */}
|
{/* Weather day detail modal */}
|
||||||
{selectedDay && (
|
{selectedDay && (
|
||||||
<WeatherDayModal
|
<WeatherDayModal
|
||||||
|
|||||||
Reference in New Issue
Block a user