|
- // assets/scripts/calendar.js
- // Strings aus window.TT.i18n – keine hardcodierten deutschen Texte mehr
-
- function t(key) {
- return window.TT?.i18n?.[key] ?? key;
- }
-
- class WeekCalendar {
- constructor() {
- this.nav = document.querySelector('.week-nav');
- this.daysContainer = document.querySelector('.week-nav__days');
- this.calBtn = document.querySelector('.week-nav__cal');
- this.prevBtn = document.querySelector('.week-nav__arrow--prev');
- this.nextBtn = document.querySelector('.week-nav__arrow--next');
- this.header = document.querySelector('.tt-header');
-
- const raw = this.nav?.dataset.activeDate;
- this.activeDate = raw ? new Date(raw + 'T00:00:00') : new Date();
- this.today = new Date();
- this.today.setHours(0, 0, 0, 0);
-
- this.monthOpen = false;
- this.monthDate = new Date(this.activeDate);
- this.monthEl = null;
-
- if (!this.nav) return;
- this.init();
- }
-
- get months() { return t('months') || []; }
- get monthsShort() { return t('monthsShort') || []; }
- get weekdays() { return t('weekdays') || []; }
- get weekdaysShort() { return t('weekdaysShort') || []; }
-
- init() {
- this.prevBtn?.addEventListener('click', e => { e.preventDefault(); this.navigateWeek(-1); });
- this.nextBtn?.addEventListener('click', e => { e.preventDefault(); this.navigateWeek(1); });
- this.calBtn?.addEventListener('click', e => { e.preventDefault(); this.toggleMonth(); });
-
- this.daysContainer?.addEventListener('click', e => {
- const dayEl = e.target.closest('.week-nav__day');
- if (!dayEl) return;
- e.preventDefault();
- const dateStr = dayEl.dataset.date;
- if (dateStr) this.goToDate(new Date(dateStr + 'T00:00:00'));
- });
- }
-
- // ── Wochen-Navigation ─────────────────────────────────────────────────────
-
- navigateWeek(direction) {
- const slideOut = direction > 0 ? 'slide-out-left' : 'slide-out-right';
- const slideIn = direction > 0 ? 'slide-in-right' : 'slide-in-left';
-
- this.daysContainer.classList.add(slideOut);
-
- setTimeout(() => {
- const newDate = new Date(this.activeDate);
- newDate.setDate(newDate.getDate() + direction * 7);
- this.activeDate = newDate;
- this.renderWeekDays();
- this.updateHeaderMeta();
-
- this.daysContainer.classList.remove(slideOut);
- this.daysContainer.classList.add(slideIn);
- requestAnimationFrame(() => requestAnimationFrame(() => {
- this.daysContainer.classList.remove(slideIn);
- }));
-
- window.history.pushState({}, '', `/week/${this.formatDate(this.getMonday(this.activeDate))}`);
- window.entryManager?.loadEntriesForDate(this.formatDate(this.activeDate));
- }, 180);
- }
-
- renderWeekDays() {
- const monday = this.getMonday(this.activeDate);
- this.daysContainer.innerHTML = '';
-
- for (let i = 0; i < 7; i++) {
- const d = new Date(monday);
- d.setDate(d.getDate() + i);
- const isActive = this.isSameDay(d, this.activeDate);
- const isToday = this.isSameDay(d, this.today);
-
- // Führungsnull: padStart(2, '0')
- const dayNum = String(d.getDate()).padStart(2, '0');
- const monthShort = this.monthsShort[d.getMonth()] ?? '';
-
- const a = document.createElement('a');
- a.href = `/week/${this.formatDate(d)}`;
- a.className = 'week-nav__day'
- + (isActive ? ' week-nav__day--active' : '')
- + (isToday ? ' week-nav__day--today' : '');
- a.dataset.date = this.formatDate(d);
- a.innerHTML = `
- <span class="week-nav__day-name">${this.weekdaysShort[i] ?? ''}</span>
- <span class="week-nav__day-date">${dayNum}. ${monthShort}</span>
- `;
- this.daysContainer.appendChild(a);
- }
- }
-
- goToDate(date) {
- this.activeDate = date;
- this.renderWeekDays();
- this.updateHeaderMeta();
- window.history.pushState({}, '', `/week/${this.formatDate(date)}`);
- window.entryManager?.loadEntriesForDate(this.formatDate(date));
- if (this.monthOpen) this.closeMonth();
- }
-
- updateHeaderMeta() {
- const dateEl = document.querySelector('.tt-header__date');
- const kwEl = document.querySelector('.tt-header__kw');
-
- if (dateEl) {
- const d = this.activeDate;
- const day = d.getDate();
- const month = this.months[d.getMonth()] ?? '';
- const tomorrow = new Date(this.today); tomorrow.setDate(this.today.getDate() + 1);
- const yesterday = new Date(this.today); yesterday.setDate(this.today.getDate() - 1);
-
- // JS getDay(): 0=So, 1=Mo...6=Sa → weekdays[0]=Montag, also index = getDay()-1, So=6
- const jsDay = d.getDay();
- const isoIdx = jsDay === 0 ? 6 : jsDay - 1;
- const weekday = this.weekdays[isoIdx] ?? '';
-
- let prefix;
- if (this.isSameDay(d, this.today)) prefix = t('today');
- else if (this.isSameDay(d, tomorrow)) prefix = t('tomorrow');
- else if (this.isSameDay(d, yesterday)) prefix = t('yesterday');
- else prefix = weekday;
-
- dateEl.textContent = `${prefix}, ${day}. ${month}`;
- document.title = `${prefix}, ${day}. ${month}`;
- }
-
- if (kwEl) kwEl.textContent = `${t('weekLabel')} ${this.getWeekNumber(this.activeDate)}`;
- }
-
- // ── Monats-Ansicht ────────────────────────────────────────────────────────
-
- toggleMonth() { this.monthOpen ? this.closeMonth() : this.openMonth(); }
-
- openMonth() {
- this.monthOpen = true;
- this.monthDate = new Date(this.activeDate);
- this.monthEl = document.createElement('div');
- this.monthEl.className = 'month-calendar month-calendar--hidden';
- this.header.appendChild(this.monthEl);
- this.renderMonthGrid();
- requestAnimationFrame(() => requestAnimationFrame(() => {
- this.monthEl.classList.remove('month-calendar--hidden');
- this.monthEl.classList.add('month-calendar--visible');
- }));
- this.calBtn.classList.add('week-nav__cal--active');
- }
-
- closeMonth() {
- if (!this.monthEl) return;
- this.monthEl.classList.remove('month-calendar--visible');
- this.monthEl.classList.add('month-calendar--hidden');
- setTimeout(() => { this.monthEl?.remove(); this.monthEl = null; }, 280);
- this.monthOpen = false;
- this.calBtn.classList.remove('week-nav__cal--active');
- }
-
- renderMonthGrid() {
- if (!this.monthEl) return;
-
- const year = this.monthDate.getFullYear();
- const month = this.monthDate.getMonth();
-
- const firstDay = new Date(year, month, 1);
- let startDow = firstDay.getDay();
- startDow = startDow === 0 ? 6 : startDow - 1;
- const daysInMonth = new Date(year, month + 1, 0).getDate();
- const daysInPrev = new Date(year, month, 0).getDate();
-
- let html = `
- <div class="month-calendar__header">
- <button class="month-calendar__arrow month-nav-prev" title="${t('prevMonth')}">
- <svg viewBox="0 0 8 14" fill="none"><path d="M7 1L1 7L7 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
- </button>
- <span class="month-calendar__title">${this.months[month] ?? ''} ${year}</span>
- <button class="month-calendar__arrow month-nav-next" title="${t('nextMonth')}">
- <svg viewBox="0 0 8 14" fill="none"><path d="M1 1L7 7L1 13" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/></svg>
- </button>
- <button class="month-calendar__close week-nav__cal" title="${t('monthView')}">
- <svg viewBox="0 0 18 18" fill="none">
- <rect x="1" y="3" width="16" height="14" rx="2" stroke="currentColor" stroke-width="1.5"/>
- <path d="M1 7h16" stroke="currentColor" stroke-width="1.5"/>
- <path d="M5 1v4M13 1v4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
- <rect x="4" y="10" width="2" height="2" rx="0.5" fill="currentColor"/>
- <rect x="8" y="10" width="2" height="2" rx="0.5" fill="currentColor"/>
- <rect x="12" y="10" width="2" height="2" rx="0.5" fill="currentColor"/>
- </svg>
- </button>
- </div>
- <div class="month-calendar__grid">
- <div class="month-calendar__weekdays">
- ${this.weekdaysShort.map(d => `<span>${d}</span>`).join('')}
- </div>
- <div class="month-calendar__days">`;
-
- for (let i = startDow - 1; i >= 0; i--)
- html += `<span class="month-day month-day--other">${daysInPrev - i}</span>`;
-
- for (let d = 1; d <= daysInMonth; d++) {
- const date = new Date(year, month, d);
- const isToday = this.isSameDay(date, this.today);
- const isActive= this.isSameDay(date, this.activeDate);
- const cls = 'month-day'
- + (isToday ? ' month-day--today' : '')
- + (isActive ? ' month-day--active' : '');
- html += `<span class="${cls}" data-date="${this.formatDate(date)}">${d}</span>`;
- }
-
- const totalCells = Math.ceil((startDow + daysInMonth) / 7) * 7;
- for (let d = 1; d <= totalCells - startDow - daysInMonth; d++)
- html += `<span class="month-day month-day--other">${d}</span>`;
-
- html += `</div></div>`;
- this.monthEl.innerHTML = html;
-
- this.monthEl.querySelector('.month-nav-prev')?.addEventListener('click', () => this.navigateMonth(-1));
- this.monthEl.querySelector('.month-nav-next')?.addEventListener('click', () => this.navigateMonth(1));
- this.monthEl.querySelector('.month-calendar__close')?.addEventListener('click', () => this.closeMonth());
- this.monthEl.querySelectorAll('.month-day[data-date]').forEach(el => {
- el.addEventListener('click', () => this.goToDate(new Date(el.dataset.date + 'T00:00:00')));
- });
- }
-
- navigateMonth(direction) {
- const grid = this.monthEl?.querySelector('.month-calendar__grid');
- if (!grid) return;
- const slideOut = direction > 0 ? 'slide-out-left' : 'slide-out-right';
- grid.classList.add(slideOut);
- setTimeout(() => {
- this.monthDate.setMonth(this.monthDate.getMonth() + direction);
- this.renderMonthGrid();
- }, 160);
- }
-
- // ── Hilfsfunktionen ───────────────────────────────────────────────────────
-
- getMonday(date) {
- const d = new Date(date);
- const day = d.getDay();
- d.setDate(d.getDate() - day + (day === 0 ? -6 : 1));
- d.setHours(0, 0, 0, 0);
- return d;
- }
-
- isSameDay(a, b) {
- return a.getFullYear() === b.getFullYear()
- && a.getMonth() === b.getMonth()
- && a.getDate() === b.getDate();
- }
-
- formatDate(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}`;
- }
-
- getWeekNumber(date) {
- const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
- d.setUTCDate(d.getUTCDate() + 4 - (d.getUTCDay() || 7));
- const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
- return Math.ceil((((d - yearStart) / 86400000) + 1) / 7);
- }
- }
-
- document.addEventListener('DOMContentLoaded', () => { new WeekCalendar(); });
|