mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-20 22:01:45 +00:00
384d583628
The biggest NOMAD update yet. Introduces a modular addon architecture and three major new features. Addon System: - Admin panel addon management with enable/disable toggles - Trip addons (Packing List, Budget, Documents) dynamically show/hide in trip tabs - Global addons appear in the main navigation for all users Vacay — Vacation Day Planner (Global Addon): - Monthly calendar view with international public holidays (100+ countries via Nager.Date API) - Company holidays with auto-cleanup of conflicting entries - User-based system: each NOMAD user is a person in the calendar - Fusion system: invite other users to share a combined calendar with real-time WebSocket sync - Vacation entitlement tracking with automatic carry-over to next year - Full settings: block weekends, public holidays, company holidays, carry-over toggle - Invite/accept/decline flow with forced confirmation modal - Color management per user with collision detection on fusion - Dissolve fusion with preserved entries Atlas — Travel World Map (Global Addon): - Fullscreen Leaflet world map with colored country polygons (GeoJSON) - Glass-effect bottom panel with stats, continent breakdown, streak tracking - Country tooltips with trip count, places visited, first/last visit dates - Liquid glass hover effect on the stats panel - Canvas renderer with tile preloading for maximum performance - Responsive: mobile stats bars, no zoom controls on touch Dashboard Widgets: - Currency converter with 50 currencies, CustomSelect dropdowns, localStorage persistence - Timezone widget with customizable city list, live updating clock - Per-user toggle via settings button, bottom sheet on mobile Admin Panel: - Consistent dark mode across all tabs (CSS variable overrides) - Online/offline status badges on user list via WebSocket - Unified heading sizes and subtitles across all sections - Responsive tab grid on mobile Mobile Improvements: - Vacay: slide-in sidebar drawer, floating toolbar, responsive calendar grid - Atlas: top/bottom glass stat bars, no popups - Trip Planner: fixed position content container prevents overscroll, portal-based sidebar buttons - Dashboard: fixed viewport container, mobile widget bottom sheet - Admin: responsive tab grid, compact buttons - Global: overscroll-behavior fixes, modal scroll containment Other: - Trip tab labels: Planung→Karte, Packliste→Liste, Buchungen→Buchung (DE mobile) - Reservation form responsive layout - Backup panel responsive buttons
147 lines
4.2 KiB
JavaScript
147 lines
4.2 KiB
JavaScript
// German public holidays (Feiertage) calculation per Bundesland
|
|
// Includes fixed and Easter-dependent movable holidays
|
|
|
|
const BUNDESLAENDER = {
|
|
BW: 'Baden-Württemberg',
|
|
BY: 'Bayern',
|
|
BE: 'Berlin',
|
|
BB: 'Brandenburg',
|
|
HB: 'Bremen',
|
|
HH: 'Hamburg',
|
|
HE: 'Hessen',
|
|
MV: 'Mecklenburg-Vorpommern',
|
|
NI: 'Niedersachsen',
|
|
NW: 'Nordrhein-Westfalen',
|
|
RP: 'Rheinland-Pfalz',
|
|
SL: 'Saarland',
|
|
SN: 'Sachsen',
|
|
ST: 'Sachsen-Anhalt',
|
|
SH: 'Schleswig-Holstein',
|
|
TH: 'Thüringen',
|
|
};
|
|
|
|
// Gauss Easter algorithm
|
|
function easterSunday(year) {
|
|
const a = year % 19;
|
|
const b = Math.floor(year / 100);
|
|
const c = year % 100;
|
|
const d = Math.floor(b / 4);
|
|
const e = b % 4;
|
|
const f = Math.floor((b + 8) / 25);
|
|
const g = Math.floor((b - f + 1) / 3);
|
|
const h = (19 * a + b - d - g + 15) % 30;
|
|
const i = Math.floor(c / 4);
|
|
const k = c % 4;
|
|
const l = (32 + 2 * e + 2 * i - h - k) % 7;
|
|
const m = Math.floor((a + 11 * h + 22 * l) / 451);
|
|
const month = Math.floor((h + l - 7 * m + 114) / 31);
|
|
const day = ((h + l - 7 * m + 114) % 31) + 1;
|
|
return new Date(year, month - 1, day);
|
|
}
|
|
|
|
function addDays(date, days) {
|
|
const d = new Date(date);
|
|
d.setDate(d.getDate() + days);
|
|
return d;
|
|
}
|
|
|
|
function fmt(date) {
|
|
const y = date.getFullYear();
|
|
const m = String(date.getMonth() + 1).padStart(2, '0');
|
|
const d = String(date.getDate()).padStart(2, '0');
|
|
return `${y}-${m}-${d}`;
|
|
}
|
|
|
|
export function getHolidays(year, bundesland = 'NW') {
|
|
const easter = easterSunday(year);
|
|
const holidays = {};
|
|
|
|
// Fixed holidays (nationwide)
|
|
holidays[`${year}-01-01`] = 'Neujahr';
|
|
holidays[`${year}-05-01`] = 'Tag der Arbeit';
|
|
holidays[`${year}-10-03`] = 'Tag der Deutschen Einheit';
|
|
holidays[`${year}-12-25`] = '1. Weihnachtsfeiertag';
|
|
holidays[`${year}-12-26`] = '2. Weihnachtsfeiertag';
|
|
|
|
// Easter-dependent (nationwide)
|
|
holidays[fmt(addDays(easter, -2))] = 'Karfreitag';
|
|
holidays[fmt(addDays(easter, 1))] = 'Ostermontag';
|
|
holidays[fmt(addDays(easter, 39))] = 'Christi Himmelfahrt';
|
|
holidays[fmt(addDays(easter, 50))] = 'Pfingstmontag';
|
|
|
|
// State-specific
|
|
const bl = bundesland.toUpperCase();
|
|
|
|
// Heilige Drei Könige (6. Jan) — BW, BY, ST
|
|
if (['BW', 'BY', 'ST'].includes(bl)) {
|
|
holidays[`${year}-01-06`] = 'Heilige Drei Könige';
|
|
}
|
|
|
|
// Internationaler Frauentag (8. März) — BE, MV
|
|
if (['BE', 'MV'].includes(bl)) {
|
|
holidays[`${year}-03-08`] = 'Internationaler Frauentag';
|
|
}
|
|
|
|
// Fronleichnam — BW, BY, HE, NW, RP, SL, SN (teilweise), TH (teilweise)
|
|
if (['BW', 'BY', 'HE', 'NW', 'RP', 'SL'].includes(bl)) {
|
|
holidays[fmt(addDays(easter, 60))] = 'Fronleichnam';
|
|
}
|
|
|
|
// Mariä Himmelfahrt (15. Aug) — SL, BY (teilweise)
|
|
if (['SL'].includes(bl)) {
|
|
holidays[`${year}-08-15`] = 'Mariä Himmelfahrt';
|
|
}
|
|
|
|
// Weltkindertag (20. Sep) — TH
|
|
if (bl === 'TH') {
|
|
holidays[`${year}-09-20`] = 'Weltkindertag';
|
|
}
|
|
|
|
// Reformationstag (31. Okt) — BB, HB, HH, MV, NI, SN, ST, SH, TH
|
|
if (['BB', 'HB', 'HH', 'MV', 'NI', 'SN', 'ST', 'SH', 'TH'].includes(bl)) {
|
|
holidays[`${year}-10-31`] = 'Reformationstag';
|
|
}
|
|
|
|
// Allerheiligen (1. Nov) — BW, BY, NW, RP, SL
|
|
if (['BW', 'BY', 'NW', 'RP', 'SL'].includes(bl)) {
|
|
holidays[`${year}-11-01`] = 'Allerheiligen';
|
|
}
|
|
|
|
// Buß- und Bettag — SN (Mittwoch vor dem 23. November)
|
|
if (bl === 'SN') {
|
|
const nov23 = new Date(year, 10, 23);
|
|
let bbt = new Date(nov23);
|
|
while (bbt.getDay() !== 3) bbt.setDate(bbt.getDate() - 1);
|
|
holidays[fmt(bbt)] = 'Buß- und Bettag';
|
|
}
|
|
|
|
return holidays;
|
|
}
|
|
|
|
export function isWeekend(dateStr) {
|
|
const d = new Date(dateStr + 'T00:00:00');
|
|
const day = d.getDay();
|
|
return day === 0 || day === 6;
|
|
}
|
|
|
|
export function getWeekday(dateStr) {
|
|
const d = new Date(dateStr + 'T00:00:00');
|
|
return ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'][d.getDay()];
|
|
}
|
|
|
|
export function getWeekdayFull(dateStr) {
|
|
const d = new Date(dateStr + 'T00:00:00');
|
|
return ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'][d.getDay()];
|
|
}
|
|
|
|
export function daysInMonth(year, month) {
|
|
return new Date(year, month, 0).getDate();
|
|
}
|
|
|
|
export function formatDate(dateStr) {
|
|
const d = new Date(dateStr + 'T00:00:00');
|
|
return d.toLocaleDateString('de-DE', { weekday: 'short', day: '2-digit', month: '2-digit', year: 'numeric' });
|
|
}
|
|
|
|
export { BUNDESLAENDER };
|