110 lines
4.1 KiB
TypeScript
110 lines
4.1 KiB
TypeScript
|
|
import './styles.css'
|
||
|
|
import { h, mount } from './dom'
|
||
|
|
import { bootstrap, getState, isAdmin, subscribe } from './store'
|
||
|
|
import { renderJoin } from './views/join'
|
||
|
|
import { renderWeek } from './views/week'
|
||
|
|
import { renderRadar } from './views/radar'
|
||
|
|
import { renderMap } from './views/map'
|
||
|
|
import { renderPlus } from './views/plus'
|
||
|
|
import { getBaseUrl } from './config'
|
||
|
|
|
||
|
|
type Tab = 'meteo' | 'radar' | 'carte' | 'plus'
|
||
|
|
|
||
|
|
const app = document.getElementById('app')!
|
||
|
|
let activeTab: Tab = 'meteo'
|
||
|
|
|
||
|
|
const TABS: { id: Tab; label: string; icon: string }[] = [
|
||
|
|
{ id: 'meteo', label: 'Météo', icon: 'fa-cloud-sun' },
|
||
|
|
{ id: 'radar', label: 'Radar', icon: 'fa-satellite-dish' },
|
||
|
|
{ id: 'carte', label: 'La Carte', icon: 'fa-map-marked-alt' },
|
||
|
|
{ id: 'plus', label: 'Plus', icon: 'fa-ellipsis-h' },
|
||
|
|
]
|
||
|
|
|
||
|
|
function renderApp(): void {
|
||
|
|
const state = getState()
|
||
|
|
|
||
|
|
// Écran d'accueil : pas de session valide.
|
||
|
|
if (!state.session) {
|
||
|
|
const container = h('div', { class: 'mobile-container' })
|
||
|
|
const main = h('main', { class: 'flex-1 overflow-y-auto no-scrollbar p-5' })
|
||
|
|
renderJoin(main, renderApp)
|
||
|
|
container.append(main)
|
||
|
|
mount(app, container)
|
||
|
|
return
|
||
|
|
}
|
||
|
|
|
||
|
|
// App pleine : header + contenu + nav.
|
||
|
|
const container = h('div', { class: 'mobile-container' })
|
||
|
|
|
||
|
|
const header = h(
|
||
|
|
'header',
|
||
|
|
{ class: 'pt-8 pb-3 px-6 bg-slate-900/80 backdrop-blur-md flex justify-between items-center z-20 border-b border-slate-800/60' },
|
||
|
|
h(
|
||
|
|
'div',
|
||
|
|
{ class: 'flex items-center gap-3' },
|
||
|
|
h('div', { class: 'w-9 h-9 rounded-2xl bg-gradient-to-tr from-sky-400 to-indigo-500 flex items-center justify-center text-white font-black shadow-md text-sm' }, 'ME'),
|
||
|
|
h('div', {},
|
||
|
|
h('h1', { class: 'text-lg font-extrabold text-white tracking-tight leading-none' }, 'Météo Exam'),
|
||
|
|
h('p', { class: 'text-xs font-bold text-sky-400 mt-0.5' }, `${state.session.klass.name}${state.session.klass.level ? ` · ${state.session.klass.level}` : ''}`),
|
||
|
|
),
|
||
|
|
),
|
||
|
|
h(
|
||
|
|
'div',
|
||
|
|
{ class: 'flex items-center gap-2' },
|
||
|
|
isAdmin()
|
||
|
|
? h('span', { class: 'inline-flex items-center px-2.5 py-1 rounded-full text-xs font-extrabold bg-sky-500/10 text-sky-400 border border-sky-500/20' },
|
||
|
|
h('i', { class: 'fa-solid fa-user-shield mr-1' }), 'Admin')
|
||
|
|
: null,
|
||
|
|
h('span', { class: 'inline-flex items-center px-2.5 py-1 rounded-full text-xs font-extrabold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20' },
|
||
|
|
h('span', { class: 'w-2 h-2 rounded-full bg-emerald-500 animate-ping mr-1.5' }),
|
||
|
|
'Direct'),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
|
||
|
|
const main = h('main', { class: 'flex-1 overflow-y-auto no-scrollbar p-5 pb-28 relative' })
|
||
|
|
const rerender = (): void => renderApp()
|
||
|
|
|
||
|
|
switch (activeTab) {
|
||
|
|
case 'meteo': renderWeek(main, rerender); break
|
||
|
|
case 'radar': renderRadar(main, rerender); break
|
||
|
|
case 'carte': renderMap(main, rerender); break
|
||
|
|
case 'plus': renderPlus(main, rerender); break
|
||
|
|
}
|
||
|
|
|
||
|
|
const nav = h(
|
||
|
|
'nav',
|
||
|
|
{ class: 'absolute bottom-0 left-0 right-0 bg-slate-900/95 backdrop-blur-xl border-t border-slate-800 px-4 py-3 flex justify-around items-center z-30' },
|
||
|
|
...TABS.map((tab) => {
|
||
|
|
const active = tab.id === activeTab
|
||
|
|
return h(
|
||
|
|
'button',
|
||
|
|
{
|
||
|
|
class: `flex flex-col items-center gap-1 transition-colors ${active ? 'text-sky-400' : 'text-slate-500'}`,
|
||
|
|
onclick: () => {
|
||
|
|
activeTab = tab.id
|
||
|
|
renderApp()
|
||
|
|
},
|
||
|
|
},
|
||
|
|
tab.id === 'radar'
|
||
|
|
? h('div', { class: 'relative' },
|
||
|
|
h('i', { class: `fa-solid ${tab.icon} text-xl` }),
|
||
|
|
h('span', { class: 'absolute -top-1 -right-1 w-2 h-2 bg-rose-500 rounded-full' }))
|
||
|
|
: h('i', { class: `fa-solid ${tab.icon} text-xl` }),
|
||
|
|
h('span', { class: 'text-[10px] font-black tracking-wide' }, tab.label),
|
||
|
|
)
|
||
|
|
}),
|
||
|
|
)
|
||
|
|
|
||
|
|
container.append(header, main, nav)
|
||
|
|
mount(app, container)
|
||
|
|
}
|
||
|
|
|
||
|
|
// --- Bootstrapping -----------------------------------------------------------
|
||
|
|
|
||
|
|
subscribe(renderApp)
|
||
|
|
|
||
|
|
void bootstrap().then(() => {
|
||
|
|
renderApp()
|
||
|
|
console.log(`Météo Exam — serveur : ${getBaseUrl()}`)
|
||
|
|
})
|