meteo-exam/web/src/main.ts
Mathis fed02e55bd fix : bouton Plus fonctionnel, try-catch sur le rendu des vues
- try-catch autour du switch de rendu pour capturer les erreurs silencieuses
- Nav flottante V2 vérifiée sur émulateur : bouton Plus accessible au-dessus de la barre système
- Settings page fonctionne (thème, serveur, session, identité visuelle)
2026-10-05 07:53:52 +02:00

124 lines
4.2 KiB
TypeScript

import './styles.css'
import { h, mount } from './dom'
import { bootstrap, getState, subscribe } from './store'
import { renderJoin } from './views/join'
import { renderWeek } from './views/week'
import { renderRadar } from './views/radar'
import { renderJournal } from './views/journal'
import { renderSettings } from './views/settings'
import { getBaseUrl } from './config'
import { checkForUpdate } from './update'
import { showUpdateModal } from './update-modal'
type Tab = 'meteo' | 'radar' | 'bilan' | 'parametres'
const app = document.getElementById('app')!
let activeTab: Tab = 'meteo'
const TABS: { id: Tab; label: string; icon: string }[] = [
{ id: 'meteo', label: 'Météo', icon: 'cloud-sun' },
{ id: 'radar', label: 'Radar', icon: 'satellite-dish' },
{ id: 'bilan', label: 'Bilan', icon: 'journal-bookmark' },
{ id: 'parametres', label: 'Plus', icon: 'gear' },
]
function svgIcon(name: string): string {
const icons: Record<string, string> = {
'cloud-sun': '<path d="M4 15a4 4 0 0 1 4-4 5 5 0 0 1 9.6 1.8A3.2 3.2 0 1 1 18 19H7a3 3 0 0 1-3-4Z"/><path d="M8 5V3M4.5 6.5 3 5M12 6l1.5-1.5"/>',
'satellite-dish': '<circle cx="12" cy="12" r="8"/><circle cx="12" cy="12" r="4"/><path d="M12 12 18 6M12 2v2M12 20v2M2 12h2M20 12h2"/>',
'journal-bookmark': '<path d="M6 3h12v18H6z"/><path d="M9 7h6M9 11h6M9 15h4"/>',
'gear': '<circle cx="12" cy="12" r="3"/><path d="M19 12a7 7 0 0 0-.1-1l2-1.5-2-3.4-2.4 1A7 7 0 0 0 15 6l-.3-2.6h-4L10.4 6A7 7 0 0 0 8.9 7.1l-2.4-1-2 3.4 2 1.5a7 7 0 0 0 0 2l-2 1.5 2 3.4 2.4-1A7 7 0 0 0 10.4 18l.3 2.6h4L15 18a7 7 0 0 0 1.5-1.1l2.4 1 2-3.4-2-1.5c.1-.3.1-.7.1-1Z"/>',
}
return `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">${icons[name] ?? ''}</svg>`
}
function renderApp(): void {
const state = getState()
// Écran d'accueil
if (!state.session) {
const container = h('div', { class: 'mobile-container' })
const main = h('main', { class: 'app-main' })
renderJoin(main, renderApp)
container.append(main)
mount(app, container)
return
}
const container = h('div', { class: 'mobile-container' })
// Header
const header = h(
'header',
{ class: 'app-header' },
h(
'div',
{ class: 'flex items-center gap-3' },
h('div', { class: 'app-mark paper-grid' }, h('div', { class: 'zero' }, '0')),
h(
'div',
{},
h('h1', { class: 'font-bold text-lg leading-none' }, 'Météo Exam'),
h('p', { class: 'text-[10px] font-black tracking-widest uppercase mt-1', style: 'color:var(--blue2)' },
`${state.klass?.name ?? state.session.klass.name}${state.klass?.level ? ` • ${state.klass.level}` : ''}`),
),
),
h(
'button',
{ class: 'live-btn', onclick: () => {} },
h('span', { class: 'status-dot', style: 'background:var(--good)' }),
),
)
// Main content
const main = h('main', { class: 'app-main' })
const rerender = (): void => renderApp()
try {
switch (activeTab) {
case 'meteo': renderWeek(main, rerender); break
case 'radar': renderRadar(main, rerender); break
case 'bilan': renderJournal(main, rerender); break
case 'parametres': renderSettings(main, rerender); break
}
} catch (err) {
console.error('Render error:', activeTab, err)
main.replaceChildren(
h('div', { style: 'padding:20px;text-align:center' },
h('p', { style: 'color:#FB7185;font-weight:700;font-size:14px' }, 'Erreur de rendu'),
h('p', { style: 'color:var(--muted);font-size:12px;margin-top:8px' }, String(err)),
),
)
}
// Bottom nav
const nav = h(
'nav',
{ class: 'bottom-nav' },
...TABS.map((tab) => {
const active = tab.id === activeTab
return h(
'button',
{
class: active ? 'active' : '',
onclick: () => { activeTab = tab.id; renderApp() },
},
h('span', { html: svgIcon(tab.icon) }),
h('span', {}, tab.label),
)
}),
)
container.append(header, main, nav)
mount(app, container)
}
subscribe(renderApp)
void bootstrap().then(() => {
renderApp()
console.log(`Météo Exam — v${__APP_VERSION__} — serveur : ${getBaseUrl()}`)
void checkForUpdate().then((update) => {
if (update) showUpdateModal(update, () => renderApp())
})
})