2026-10-02 19:51:13 +02:00
|
|
|
|
/**
|
|
|
|
|
|
* Types et métadonnées partagés entre le serveur et le front.
|
|
|
|
|
|
* Aucun import runtime ici : ce module est consommé tel quel par les deux côtés.
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
|
|
/** Statut météorologique d'une évaluation. */
|
|
|
|
|
|
export type WeatherKind = 'soleil' | 'brouillard' | 'vent' | 'orage' | 'tempete'
|
|
|
|
|
|
|
|
|
|
|
|
export const WEATHER_KINDS: WeatherKind[] = ['soleil', 'brouillard', 'vent', 'orage', 'tempete']
|
|
|
|
|
|
|
|
|
|
|
|
/** Du plus rassurant au plus menaçant. Sert aux tris et au calcul du statut d'une journée. */
|
|
|
|
|
|
export const SEVERITY: Record<WeatherKind, number> = {
|
|
|
|
|
|
soleil: 0,
|
|
|
|
|
|
brouillard: 1,
|
|
|
|
|
|
vent: 2,
|
|
|
|
|
|
orage: 3,
|
|
|
|
|
|
tempete: 4,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface WeatherMeta {
|
|
|
|
|
|
/** Nom affiché, ex. « Alerte Rouge ». */
|
|
|
|
|
|
label: string
|
|
|
|
|
|
/** Titre long pour les modales et la légende. */
|
|
|
|
|
|
title: string
|
|
|
|
|
|
icon: string
|
|
|
|
|
|
/** Description pédagogique affichée dans la légende. */
|
|
|
|
|
|
hint: string
|
|
|
|
|
|
/** Couleur d'accent en dur, pour les SVG et jauges. */
|
|
|
|
|
|
hex: string
|
|
|
|
|
|
/** Classes Tailwind de la pastille (fond / bordure / texte). */
|
|
|
|
|
|
chip: string
|
|
|
|
|
|
/** Classe de couleur du texte seule, pour les libellés sur fond neutre. */
|
|
|
|
|
|
accent: string
|
|
|
|
|
|
/** Classes Tailwind du badge plein. */
|
|
|
|
|
|
badge: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export const WEATHER: Record<WeatherKind, WeatherMeta> = {
|
|
|
|
|
|
soleil: {
|
|
|
|
|
|
label: 'Grand Soleil',
|
|
|
|
|
|
title: 'Grand Soleil',
|
|
|
|
|
|
icon: '☀️',
|
|
|
|
|
|
hint: 'Journée calme. Aucun examen répertorié ni suspecté.',
|
|
|
|
|
|
hex: '#0284c7',
|
|
|
|
|
|
chip: 'bg-sky-500/10 border-sky-500/20 text-sky-400',
|
|
|
|
|
|
accent: 'text-sky-400',
|
|
|
|
|
|
badge: 'bg-sky-500/15 text-sky-300 border-sky-400/30',
|
|
|
|
|
|
},
|
|
|
|
|
|
brouillard: {
|
|
|
|
|
|
label: 'Brouillard',
|
|
|
|
|
|
title: 'Brouillard',
|
|
|
|
|
|
icon: '🌫️',
|
|
|
|
|
|
hint: 'Zone d’incertitude. Rumeur ou sous-entendu d’un professeur.',
|
|
|
|
|
|
hex: '#7c3aed',
|
|
|
|
|
|
chip: 'bg-purple-500/10 border-purple-500/20 text-purple-400',
|
|
|
|
|
|
accent: 'text-purple-400',
|
|
|
|
|
|
badge: 'bg-purple-500/15 text-purple-300 border-purple-400/30',
|
|
|
|
|
|
},
|
|
|
|
|
|
vent: {
|
|
|
|
|
|
label: 'Coup de vent',
|
|
|
|
|
|
title: 'Coup de vent',
|
|
|
|
|
|
icon: '💨',
|
|
|
|
|
|
hint: 'Interrogation surprise potentielle ou petit QCM, coefficient faible.',
|
|
|
|
|
|
hex: '#d97706',
|
|
|
|
|
|
chip: 'bg-amber-500/10 border-amber-500/20 text-amber-400',
|
|
|
|
|
|
accent: 'text-amber-400',
|
|
|
|
|
|
badge: 'bg-amber-500/15 text-amber-300 border-amber-400/30',
|
|
|
|
|
|
},
|
|
|
|
|
|
orage: {
|
|
|
|
|
|
label: 'Orage',
|
|
|
|
|
|
title: 'Orage / Pluie battante',
|
|
|
|
|
|
icon: '🌩️',
|
|
|
|
|
|
hint: 'Devoir surveillé (DS) confirmé ou évaluation de chapitre.',
|
|
|
|
|
|
hex: '#e11d48',
|
|
|
|
|
|
chip: 'bg-rose-500/10 border-rose-500/20 text-rose-400',
|
|
|
|
|
|
accent: 'text-rose-400',
|
|
|
|
|
|
badge: 'bg-rose-500/15 text-rose-300 border-rose-400/30',
|
|
|
|
|
|
},
|
|
|
|
|
|
tempete: {
|
|
|
|
|
|
label: 'Alerte Rouge',
|
|
|
|
|
|
title: 'Alerte Rouge / Tempête',
|
|
|
|
|
|
icon: '🚨',
|
|
|
|
|
|
hint: 'Examen blanc, épreuve commune ou gros devoir de fin de trimestre.',
|
|
|
|
|
|
hex: '#dc2626',
|
|
|
|
|
|
chip: 'bg-red-500/10 border-red-500/20 text-red-500',
|
|
|
|
|
|
accent: 'text-red-500',
|
|
|
|
|
|
badge: 'bg-red-500/15 text-red-300 border-red-400/30',
|
|
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export function isWeatherKind(value: unknown): value is WeatherKind {
|
|
|
|
|
|
return typeof value === 'string' && (WEATHER_KINDS as string[]).includes(value)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Statut par défaut déduit du coefficient, pour pré-remplir le formulaire d'ajout.
|
|
|
|
|
|
* 0 = rien de prévu, 1 = interro surprise, 2-3 = DS, 4+ = alerte rouge.
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function kindFromWeight(weight: number): WeatherKind {
|
|
|
|
|
|
if (weight <= 0) return 'soleil'
|
|
|
|
|
|
if (weight < 2) return 'vent'
|
|
|
|
|
|
if (weight < 4) return 'orage'
|
|
|
|
|
|
return 'tempete'
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Les coefficients proposés dans le formulaire, avec leur statut déduit. */
|
|
|
|
|
|
export const WEIGHT_PRESETS: { value: number; label: string }[] = [
|
|
|
|
|
|
{ value: 0, label: '0 — coursework' },
|
|
|
|
|
|
{ value: 1, label: '1 — interro' },
|
|
|
|
|
|
{ value: 2, label: '2 — contrôle' },
|
|
|
|
|
|
{ value: 3, label: '3 — DS' },
|
|
|
|
|
|
{ value: 4, label: '4 — examen blanc' },
|
|
|
|
|
|
{ value: 5, label: '5 — épreuve commune' },
|
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
/** Statut le plus sévère d'une journée ; `soleil` si la journée est vide. */
|
|
|
|
|
|
export function dominantKind(exams: { kind: WeatherKind }[]): WeatherKind {
|
|
|
|
|
|
let best: WeatherKind = 'soleil'
|
|
|
|
|
|
for (const exam of exams) {
|
|
|
|
|
|
if (SEVERITY[exam.kind] > SEVERITY[best]) best = exam.kind
|
|
|
|
|
|
}
|
|
|
|
|
|
return best
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export type Role = 'admin' | 'student'
|
|
|
|
|
|
|
|
|
|
|
|
export interface Exam {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
classId: number
|
|
|
|
|
|
subject: string
|
|
|
|
|
|
teacher: string | null
|
|
|
|
|
|
room: string | null
|
|
|
|
|
|
chapter: string | null
|
|
|
|
|
|
/** ISO 8601 UTC. */
|
|
|
|
|
|
startsAt: string
|
|
|
|
|
|
durationMin: number
|
|
|
|
|
|
/** Coefficient : alimente la future « pression atmosphérique ». */
|
|
|
|
|
|
weight: number
|
|
|
|
|
|
kind: WeatherKind
|
|
|
|
|
|
note: string | null
|
|
|
|
|
|
createdAt: string
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ClassInfo {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
name: string
|
|
|
|
|
|
level: string | null
|
v0.2.0 — config admin (météo forcée, flash info, édition classe) + PWA
- DB : table day_weather (override admin par jour), flash_message sur classes
- API : PUT/DELETE /api/config/day-weather, PUT /api/config/flash, PATCH /api/config/class
- SSE : day_weather.updated/deleted, flash.updated, class.updated
- Front : bandeau flash info, indicateur 'forcé' sur la frise, modale météo admin
- Onglet Plus : panneau admin (flash info + édition classe), tout admin modifiable
- PWA : manifest.webmanifest, service worker, meta Apple, icônes 192/512
- Web version accessible sur iOS via Safari (Ajouter à l'écran d'accueil)
2026-10-04 17:05:56 +02:00
|
|
|
|
/** Message d'annonce du jour (flash info), lu par tous les membres. */
|
|
|
|
|
|
flashMessage: string | null
|
2026-10-02 19:51:13 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface Member {
|
|
|
|
|
|
id: number
|
|
|
|
|
|
classId: number
|
|
|
|
|
|
nickname: string
|
|
|
|
|
|
role: Role
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Charge utile de `GET /api/me`, renvoyée aussi à la création et à l'adhésion. */
|
|
|
|
|
|
export interface Session {
|
|
|
|
|
|
member: Member
|
|
|
|
|
|
klass: ClassInfo
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
export interface ApiError {
|
|
|
|
|
|
error: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
v0.2.0 — config admin (météo forcée, flash info, édition classe) + PWA
- DB : table day_weather (override admin par jour), flash_message sur classes
- API : PUT/DELETE /api/config/day-weather, PUT /api/config/flash, PATCH /api/config/class
- SSE : day_weather.updated/deleted, flash.updated, class.updated
- Front : bandeau flash info, indicateur 'forcé' sur la frise, modale météo admin
- Onglet Plus : panneau admin (flash info + édition classe), tout admin modifiable
- PWA : manifest.webmanifest, service worker, meta Apple, icônes 192/512
- Web version accessible sur iOS via Safari (Ajouter à l'écran d'accueil)
2026-10-04 17:05:56 +02:00
|
|
|
|
/** Override météo posé par l'admin pour un jour donné (clé locale AAAA-MM-JJ). */
|
|
|
|
|
|
export interface DayWeather {
|
|
|
|
|
|
classId: number
|
|
|
|
|
|
dayKey: string
|
|
|
|
|
|
kind: WeatherKind
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** Flash info : message quotidien rédigé par l'admin. */
|
|
|
|
|
|
export interface FlashInfo {
|
|
|
|
|
|
classId: number
|
|
|
|
|
|
/** null = pas de message publié. */
|
|
|
|
|
|
message: string | null
|
|
|
|
|
|
updatedAt: string
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-10-02 19:51:13 +02:00
|
|
|
|
/** Payloads émis par le flux SSE. */
|
|
|
|
|
|
export type StreamEvent =
|
|
|
|
|
|
| { type: 'exam.created'; exam: Exam }
|
|
|
|
|
|
| { type: 'exam.updated'; exam: Exam }
|
|
|
|
|
|
| { type: 'exam.deleted'; examId: number }
|
v0.2.0 — config admin (météo forcée, flash info, édition classe) + PWA
- DB : table day_weather (override admin par jour), flash_message sur classes
- API : PUT/DELETE /api/config/day-weather, PUT /api/config/flash, PATCH /api/config/class
- SSE : day_weather.updated/deleted, flash.updated, class.updated
- Front : bandeau flash info, indicateur 'forcé' sur la frise, modale météo admin
- Onglet Plus : panneau admin (flash info + édition classe), tout admin modifiable
- PWA : manifest.webmanifest, service worker, meta Apple, icônes 192/512
- Web version accessible sur iOS via Safari (Ajouter à l'écran d'accueil)
2026-10-04 17:05:56 +02:00
|
|
|
|
| { type: 'day_weather.updated'; dayWeather: DayWeather }
|
|
|
|
|
|
| { type: 'day_weather.deleted'; dayKey: string }
|
|
|
|
|
|
| { type: 'flash.updated'; flash: FlashInfo | null }
|
|
|
|
|
|
| { type: 'class.updated'; klass: ClassInfo }
|
2026-10-02 19:51:13 +02:00
|
|
|
|
| { type: 'ping' }
|
|
|
|
|
|
|
|
|
|
|
|
export const CLASS_CODE_LENGTH = 6
|
2026-10-04 17:18:48 +02:00
|
|
|
|
|
|
|
|
|
|
/** Version de l'application. Incrémentée à chaque release APK. */
|
|
|
|
|
|
export const APP_VERSION = '0.3.0'
|