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 TimerWidget from '@/components/TimerWidget'
|
||||
import TimerHomeWidget from '@/components/TimerHomeWidget'
|
||||
import FocusCard from '@/components/FocusCard'
|
||||
import CountdownCard from '@/components/CountdownCard'
|
||||
|
||||
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 [pinnedNotes, setPinnedNotes] = useState<any[]>([])
|
||||
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(() => {
|
||||
fetch('/api/calendar?range=today')
|
||||
@@ -489,57 +485,8 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
||||
})
|
||||
.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 (
|
||||
@@ -548,19 +495,82 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
||||
padding: '18px 22px 24px',
|
||||
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' }}>
|
||||
|
||||
{/* Focus — контекст-hero */}
|
||||
<FocusCard
|
||||
weather={weather ? { temp: weather.temp, desc: weather.desc, feelsLike: weather.feelsLike } : null}
|
||||
tramNext={tramNext}
|
||||
nextEvent={nextEvent ? {
|
||||
id: nextEvent.id, title: nextEvent.title, start: nextEvent.start,
|
||||
allDay: nextEvent.allDay, ownerName: nextEvent.ownerName, color: nextEvent.color,
|
||||
} : null}
|
||||
countdowns={countdowns}
|
||||
/>
|
||||
{/* Hero weather card */}
|
||||
{weather ? (
|
||||
<div
|
||||
className="card-hero"
|
||||
onClick={() => weather?.forecast?.[0] && setSelectedDay(weather.forecast[0])}
|
||||
style={{
|
||||
padding: '24px 26px',
|
||||
display: 'flex', flexDirection: 'column',
|
||||
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 */}
|
||||
<TransportWidget />
|
||||
@@ -701,16 +711,6 @@ function HomeTab({ weather, sensors }: { weather: WeatherData | null; sensors: S
|
||||
<TimerHomeWidget />
|
||||
</div>
|
||||
|
||||
{/* ───── Row 4: Countdown ───── */}
|
||||
<div style={{
|
||||
display: 'grid',
|
||||
gridTemplateColumns: 'minmax(260px, 1fr) minmax(0, 2fr)',
|
||||
gap: 14,
|
||||
}}>
|
||||
<CountdownCard />
|
||||
<div /> {/* место под будущий виджет */}
|
||||
</div>
|
||||
|
||||
{/* Weather day detail modal */}
|
||||
{selectedDay && (
|
||||
<WeatherDayModal
|
||||
|
||||
Reference in New Issue
Block a user