diff --git a/package.json b/package.json index a8e0f9d..23cbf13 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "test:tasks": "node --experimental-sqlite test-tasks.js", "test:shopping": "node --experimental-sqlite test-shopping.js", "test:meals": "node --experimental-sqlite test-meals.js", - "test": "node --experimental-sqlite test-db.js && node --experimental-sqlite test-dashboard.js && node --experimental-sqlite test-tasks.js && node --experimental-sqlite test-shopping.js && node --experimental-sqlite test-meals.js" + "test:calendar": "node --experimental-sqlite test-calendar.js", + "test": "node --experimental-sqlite test-db.js && node --experimental-sqlite test-dashboard.js && node --experimental-sqlite test-tasks.js && node --experimental-sqlite test-shopping.js && node --experimental-sqlite test-meals.js && node --experimental-sqlite test-calendar.js" }, "dependencies": { "bcrypt": "^5.1.1", diff --git a/public/index.html b/public/index.html index 9f4b52f..0c15883 100644 --- a/public/index.html +++ b/public/index.html @@ -20,6 +20,7 @@ + diff --git a/public/pages/calendar.js b/public/pages/calendar.js index 46fc333..2af845f 100644 --- a/public/pages/calendar.js +++ b/public/pages/calendar.js @@ -1,25 +1,957 @@ /** - * Modul: Calendar - * Zweck: Seite für das Calendar-Modul - * Abhängigkeiten: /api.js + * Modul: Kalender (Calendar) + * Zweck: Monats-/Wochen-/Tages-/Agenda-Ansicht mit vollem Termin-CRUD + * Abhängigkeiten: /api.js, /router.js (window.oikos) */ import { api } from '/api.js'; -/** - * @param {HTMLElement} container - * @param {{ user: object }} context - */ +// -------------------------------------------------------- +// Konstanten +// -------------------------------------------------------- + +const VIEWS = ['month', 'week', 'day', 'agenda']; +const VIEW_LABELS = { month: 'Monat', week: 'Woche', day: 'Tag', agenda: 'Agenda' }; +const DAY_NAMES_SHORT = ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa']; +const DAY_NAMES_LONG = ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag']; +const MONTH_NAMES = ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', + 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember']; + +const EVENT_COLORS = [ + '#007AFF', '#34C759', '#FF9500', '#FF3B30', + '#AF52DE', '#FF6B35', '#5AC8FA', '#FFCC00', + '#8E8E93', '#30B0C7', +]; + +const HOUR_HEIGHT = 56; // px pro Stunde in Wochen-/Tagesansicht + +// -------------------------------------------------------- +// State +// -------------------------------------------------------- + +let state = { + view: 'month', + today: '', + cursor: null, // aktuell angezeigte Referenz-Datum (YYYY-MM-DD) + events: [], + users: [], + rangeFrom: '', + rangeTo: '', +}; + +// -------------------------------------------------------- +// Datumshelfer +// -------------------------------------------------------- + +function pad(n) { return String(n).padStart(2, '0'); } +function isoDate(d) { return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; } + +function addMonths(dateStr, n) { + const d = new Date(dateStr + 'T00:00:00'); + d.setMonth(d.getMonth() + n); + return isoDate(d); +} + +function addDays(dateStr, n) { + const d = new Date(dateStr + 'T00:00:00'); + d.setDate(d.getDate() + n); + return isoDate(d); +} + +function getMondayOf(dateStr) { + const d = new Date(dateStr + 'T00:00:00'); + const day = d.getDay(); + const diff = (day === 0 ? -6 : 1 - day); + d.setDate(d.getDate() + diff); + return isoDate(d); +} + +function formatDate(dateStr, { long = false, weekday = false } = {}) { + const d = new Date(dateStr + 'T00:00:00'); + const day = d.getDate(); + const mon = MONTH_NAMES[d.getMonth()]; + if (weekday) { + const wd = long ? DAY_NAMES_LONG[d.getDay()] : DAY_NAMES_SHORT[d.getDay()]; + return `${wd}, ${day}. ${mon}`; + } + return `${day}. ${mon} ${d.getFullYear()}`; +} + +function formatTime(datetimeStr) { + if (!datetimeStr) return ''; + const t = datetimeStr.slice(11, 16); + return t || ''; +} + +function formatDateTime(datetimeStr) { + if (!datetimeStr) return ''; + const date = datetimeStr.slice(0, 10); + const time = datetimeStr.slice(11, 16); + return time ? `${formatDate(date)} ${time} Uhr` : formatDate(date); +} + +function getMonthRange(dateStr) { + const d = new Date(dateStr + 'T00:00:00'); + const year = d.getFullYear(); + const month = d.getMonth(); + const from = `${year}-${pad(month + 1)}-01`; + // Extra Tage für Kalenderraster (6 Wochen × 7 = 42 Tage) + const to = addDays(from, 41); + return { from, to }; +} + +function getWeekRange(dateStr) { + const monday = getMondayOf(dateStr); + return { from: monday, to: addDays(monday, 6) }; +} + +function getAgendaRange(dateStr) { + return { from: dateStr, to: addDays(dateStr, 30) }; +} + +function eventsOnDay(dateStr) { + return state.events.filter((e) => { + const start = e.start_datetime.slice(0, 10); + const end = e.end_datetime ? e.end_datetime.slice(0, 10) : start; + return start <= dateStr && end >= dateStr; + }); +} + +// -------------------------------------------------------- +// API +// -------------------------------------------------------- + +async function loadRange(from, to) { + const res = await api.get(`/calendar?from=${from}&to=${to}`); + state.events = res.data; + state.rangeFrom = from; + state.rangeTo = to; +} + +async function loadUsers() { + try { + const res = await api.get('/users'); + state.users = res.data; + } catch { + state.users = []; + } +} + +// -------------------------------------------------------- +// Entry Point +// -------------------------------------------------------- + export async function render(container, { user }) { + state.today = isoDate(new Date()); + state.cursor = state.today; + state.view = 'month'; + container.innerHTML = ` -
-