Files
calendrier_republicain/public/js/app.js
T
2026-09-15 23:12:57 +02:00

231 lines
7.9 KiB
JavaScript

'use strict';
(() => {
const monthImage = document.getElementById('month-image');
const monthPeriod = document.getElementById('month-period');
const decadeDay = document.getElementById('decade-day');
const republicanDate = document.getElementById('republican-date');
const republicanYear = document.getElementById('republican-year');
const sextileBadge = document.getElementById('sextile-badge');
const gregorianEquivalentValue = document.getElementById('gregorian-equivalent-value');
const monthName = document.getElementById('month-name');
const monthSeasonSuffix = document.getElementById('month-season-suffix');
const monthEtymology = document.getElementById('month-etymology');
const monthDescription = document.getElementById('month-description');
const dateInput = document.getElementById('date-input');
const convertForm = document.getElementById('convert-form');
const todayButton = document.getElementById('today-button');
const formError = document.getElementById('form-error');
const viewHome = document.getElementById('view-home');
const viewAbout = document.getElementById('view-about');
const aboutTitle = document.getElementById('about-title');
const aboutContent = document.getElementById('about-content');
const menuToggle = document.getElementById('menu-toggle');
const mainNav = document.getElementById('main-nav');
const navBackdrop = document.getElementById('nav-backdrop');
const langSwitch = document.getElementById('lang-switch');
const ABOUT_ROUTES = {
'about/history': 'history',
'about/leap-years': 'leapYears',
'about/complementary-days': 'complementaryDays',
};
let lastRepublicanResult = null;
function pad(value) {
return String(value).padStart(2, '0');
}
function toIsoDate({ year, month, day }) {
return `${String(year).padStart(4, '0')}-${pad(month)}-${pad(day)}`;
}
function formatGregorian({ year, month, day }, weekdayKey) {
const weekdayLabel = I18N.t(`weekdays.${weekdayKey}`, '');
return `${weekdayLabel} ${pad(day)}/${pad(month)}/${year}`.trim();
}
function closeMenu() {
mainNav.classList.remove('open');
mainNav.setAttribute('aria-hidden', 'true');
navBackdrop.hidden = true;
menuToggle.setAttribute('aria-expanded', 'false');
}
function openMenu() {
mainNav.classList.add('open');
mainNav.setAttribute('aria-hidden', 'false');
navBackdrop.hidden = false;
menuToggle.setAttribute('aria-expanded', 'true');
}
function renderRepublicanResult(result) {
lastRepublicanResult = result;
const months = I18N.months.months || {};
const decadeDays = I18N.months.decadeDays || {};
const complementary = I18N.months.complementaryDays || { days: {} };
sextileBadge.hidden = !result.isSextile;
gregorianEquivalentValue.textContent = formatGregorian(result.gregorianDate, result.gregorianWeekdayKey);
republicanYear.textContent = `${I18N.t('home.yearLabel')} ${result.republicanYearRoman} (${result.republicanYear})`;
if (result.isComplementaryDay) {
const day = complementary.days[result.complementaryDayKey] || {};
decadeDay.textContent = complementary.title || '';
republicanDate.textContent = day.name || '';
monthImage.src = 'assets/months/complementary.svg';
monthImage.alt = day.name || '';
monthPeriod.textContent = '';
monthName.textContent = day.name || '';
monthSeasonSuffix.textContent = '';
monthEtymology.textContent = '';
monthDescription.textContent = day.description || '';
} else {
const month = months[result.monthKey] || {};
const seasonSuffix = (I18N.months.seasonSuffix || {})[month.season] || '';
decadeDay.textContent = decadeDays[result.decadeDayKey] || '';
republicanDate.textContent = `${result.dayOfMonth} ${month.name || ''}`.trim();
monthImage.src = `assets/months/${result.monthKey}.svg`;
monthImage.alt = month.name || '';
monthPeriod.textContent = month.period || '';
monthName.textContent = month.name || '';
monthSeasonSuffix.textContent = seasonSuffix;
monthEtymology.textContent = month.etymology || '';
monthDescription.textContent = month.description || '';
}
}
function showFormError(messageKey) {
formError.textContent = I18N.t(messageKey);
formError.hidden = false;
}
function hideFormError() {
formError.hidden = true;
formError.textContent = '';
}
async function fetchAndRender(url) {
hideFormError();
const response = await fetch(url);
const payload = await response.json();
if (!payload.ok) {
if (payload.error === 'BEFORE_EPOCH') {
showFormError('form.errorBeforeEpoch');
} else if (payload.error === 'INVALID_DATE') {
showFormError('form.errorInvalid');
} else {
showFormError('form.errorGeneric');
}
return;
}
renderRepublicanResult(payload.republicanDate);
dateInput.value = toIsoDate(payload.republicanDate.gregorianDate);
}
async function loadToday() {
try {
await fetchAndRender('/api/today');
} catch (error) {
showFormError('form.errorGeneric');
}
}
async function handleConvertSubmit(event) {
event.preventDefault();
const value = dateInput.value; // yyyy-mm-dd from <input type="date">
if (!value) return;
const [year, month, day] = value.split('-').map((part) => Number.parseInt(part, 10));
try {
await fetchAndRender(`/api/convert?year=${year}&month=${month}&day=${day}`);
} catch (error) {
showFormError('form.errorGeneric');
}
}
function renderAboutView(sectionKey) {
const section = I18N.about[sectionKey];
if (!section) return;
aboutTitle.textContent = section.title;
aboutContent.innerHTML = '';
(section.sections || []).forEach((block) => {
const heading = document.createElement('h2');
heading.textContent = block.heading;
aboutContent.appendChild(heading);
(block.paragraphs || []).forEach((paragraph) => {
const p = document.createElement('p');
p.textContent = paragraph;
aboutContent.appendChild(p);
});
});
}
function route() {
const hash = window.location.hash.replace(/^#\/?/, '');
closeMenu();
document.querySelectorAll('.nav-link').forEach((link) => link.classList.remove('active'));
if (hash in ABOUT_ROUTES) {
viewHome.hidden = true;
viewAbout.hidden = false;
renderAboutView(ABOUT_ROUTES[hash]);
const activeLink = document.querySelector(`.nav-link[href="#/${hash}"]`);
if (activeLink) activeLink.classList.add('active');
} else {
viewHome.hidden = false;
viewAbout.hidden = true;
const activeLink = document.querySelector('.nav-link[href="#/"]');
if (activeLink) activeLink.classList.add('active');
}
}
function bindEvents() {
menuToggle.addEventListener('click', () => {
if (mainNav.classList.contains('open')) {
closeMenu();
} else {
openMenu();
}
});
navBackdrop.addEventListener('click', closeMenu);
window.addEventListener('hashchange', route);
convertForm.addEventListener('submit', handleConvertSubmit);
todayButton.addEventListener('click', () => {
loadToday();
});
langSwitch.addEventListener('click', async () => {
const nextLang = I18N.currentLang === 'fr' ? 'en' : 'fr';
await I18N.setLang(nextLang);
});
I18N.onChange((lang) => {
langSwitch.textContent = lang.toUpperCase();
if (lastRepublicanResult) {
renderRepublicanResult(lastRepublicanResult);
}
if (!viewAbout.hidden) {
const hash = window.location.hash.replace(/^#\/?/, '');
if (hash in ABOUT_ROUTES) renderAboutView(ABOUT_ROUTES[hash]);
}
});
}
async function init() {
bindEvents();
const initialLang = I18N.detectInitialLang();
await I18N.load(initialLang);
langSwitch.textContent = initialLang.toUpperCase();
route();
await loadToday();
}
init();
})();