diff --git a/public/locales/ar.json b/public/locales/ar.json index 755fa39..5ab9b24 100644 --- a/public/locales/ar.json +++ b/public/locales/ar.json @@ -112,7 +112,13 @@ "customizeMoveUp": "للأعلى", "customizeMoveDown": "للأسفل", "overdueTasksChip": "{{count}} مهمة متأخرة", - "overdueTasksChipPlural": "{{count}} مهام متأخرة" + "overdueTasksChipPlural": "{{count}} مهام متأخرة", + "customizeManage": "الأدوات", + "customizeExit": "إنهاء التخصيص", + "customizeDrag": "اسحب الأداة", + "customizeSize": "الحجم", + "customizeSizeFor": "حجم {{widget}}", + "customizeHide": "إخفاء {{widget}}" }, "tasks": { "title": "المهام", diff --git a/public/locales/de.json b/public/locales/de.json index 790936b..661f0c0 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -118,7 +118,13 @@ "customizeMoveUp": "Nach oben", "customizeMoveDown": "Nach unten", "overdueTasksChip": "{{count}} überfällige Aufgabe", - "overdueTasksChipPlural": "{{count}} überfällige Aufgaben" + "overdueTasksChipPlural": "{{count}} überfällige Aufgaben", + "customizeManage": "Widgets", + "customizeExit": "Anpassung beenden", + "customizeDrag": "Widget ziehen", + "customizeSize": "Größe", + "customizeSizeFor": "Größe für {{widget}}", + "customizeHide": "{{widget}} ausblenden" }, "tasks": { "title": "Aufgaben", diff --git a/public/locales/el.json b/public/locales/el.json index b2f2ff5..a5a006f 100644 --- a/public/locales/el.json +++ b/public/locales/el.json @@ -112,7 +112,13 @@ "customizeMoveUp": "Πάνω", "customizeMoveDown": "Κάτω", "overdueTasksChip": "{{count}} εκπρόθεσμη εργασία", - "overdueTasksChipPlural": "{{count}} εκπρόθεσμες εργασίες" + "overdueTasksChipPlural": "{{count}} εκπρόθεσμες εργασίες", + "customizeManage": "Widget", + "customizeExit": "Έξοδος από προσαρμογή", + "customizeDrag": "Σύρετε widget", + "customizeSize": "Μέγεθος", + "customizeSizeFor": "Μέγεθος για {{widget}}", + "customizeHide": "Απόκρυψη {{widget}}" }, "tasks": { "title": "Εργασίες", diff --git a/public/locales/en.json b/public/locales/en.json index d11161d..d58122a 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -112,7 +112,13 @@ "customizeMoveUp": "Move up", "customizeMoveDown": "Move down", "overdueTasksChip": "{{count}} overdue task", - "overdueTasksChipPlural": "{{count}} overdue tasks" + "overdueTasksChipPlural": "{{count}} overdue tasks", + "customizeManage": "Widgets", + "customizeExit": "Exit customization", + "customizeDrag": "Drag widget", + "customizeSize": "Size", + "customizeSizeFor": "Size for {{widget}}", + "customizeHide": "Hide {{widget}}" }, "tasks": { "title": "Tasks", diff --git a/public/locales/es.json b/public/locales/es.json index a797df3..a2b438f 100644 --- a/public/locales/es.json +++ b/public/locales/es.json @@ -112,7 +112,13 @@ "customizeMoveUp": "Subir", "customizeMoveDown": "Bajar", "overdueTasksChip": "{{count}} tarea vencida", - "overdueTasksChipPlural": "{{count}} tareas vencidas" + "overdueTasksChipPlural": "{{count}} tareas vencidas", + "customizeManage": "Widgets", + "customizeExit": "Salir de personalización", + "customizeDrag": "Arrastrar widget", + "customizeSize": "Tamaño", + "customizeSizeFor": "Tamaño de {{widget}}", + "customizeHide": "Ocultar {{widget}}" }, "tasks": { "title": "Tareas", diff --git a/public/locales/fr.json b/public/locales/fr.json index c29e954..4e5d188 100644 --- a/public/locales/fr.json +++ b/public/locales/fr.json @@ -112,7 +112,13 @@ "customizeMoveUp": "Monter", "customizeMoveDown": "Descendre", "overdueTasksChip": "{{count}} tâche en retard", - "overdueTasksChipPlural": "{{count}} tâches en retard" + "overdueTasksChipPlural": "{{count}} tâches en retard", + "customizeManage": "Widgets", + "customizeExit": "Quitter la personnalisation", + "customizeDrag": "Faire glisser le widget", + "customizeSize": "Taille", + "customizeSizeFor": "Taille de {{widget}}", + "customizeHide": "Masquer {{widget}}" }, "tasks": { "title": "Tâches", diff --git a/public/locales/hi.json b/public/locales/hi.json index 81b6856..f54f5ff 100644 --- a/public/locales/hi.json +++ b/public/locales/hi.json @@ -112,7 +112,13 @@ "customizeMoveUp": "ऊपर ले जाएं", "customizeMoveDown": "नीचे ले जाएं", "overdueTasksChip": "{{count}} विलंबित कार्य", - "overdueTasksChipPlural": "{{count}} विलंबित कार्य" + "overdueTasksChipPlural": "{{count}} विलंबित कार्य", + "customizeManage": "विजेट", + "customizeExit": "अनुकूलन से बाहर निकलें", + "customizeDrag": "विजेट खींचें", + "customizeSize": "आकार", + "customizeSizeFor": "{{widget}} का आकार", + "customizeHide": "{{widget}} छिपाएँ" }, "tasks": { "title": "कार्य", diff --git a/public/locales/it.json b/public/locales/it.json index c21649a..d61894e 100644 --- a/public/locales/it.json +++ b/public/locales/it.json @@ -112,7 +112,13 @@ "customizeMoveUp": "Su", "customizeMoveDown": "Giù", "overdueTasksChip": "{{count}} compito scaduto", - "overdueTasksChipPlural": "{{count}} compiti scaduti" + "overdueTasksChipPlural": "{{count}} compiti scaduti", + "customizeManage": "Widget", + "customizeExit": "Esci dalla personalizzazione", + "customizeDrag": "Trascina widget", + "customizeSize": "Dimensione", + "customizeSizeFor": "Dimensione di {{widget}}", + "customizeHide": "Nascondi {{widget}}" }, "tasks": { "title": "Compiti", diff --git a/public/locales/ja.json b/public/locales/ja.json index af4b07f..58b8df4 100644 --- a/public/locales/ja.json +++ b/public/locales/ja.json @@ -112,7 +112,13 @@ "customizeMoveUp": "上へ", "customizeMoveDown": "下へ", "overdueTasksChip": "期限超過のタスク {{count}} 件", - "overdueTasksChipPlural": "期限超過のタスク {{count}} 件" + "overdueTasksChipPlural": "期限超過のタスク {{count}} 件", + "customizeManage": "ウィジェット", + "customizeExit": "カスタマイズを終了", + "customizeDrag": "ウィジェットをドラッグ", + "customizeSize": "サイズ", + "customizeSizeFor": "{{widget}} のサイズ", + "customizeHide": "{{widget}} を非表示" }, "tasks": { "title": "タスク", diff --git a/public/locales/pt.json b/public/locales/pt.json index 454feee..f83121f 100644 --- a/public/locales/pt.json +++ b/public/locales/pt.json @@ -112,7 +112,13 @@ "customizeMoveUp": "Mover para cima", "customizeMoveDown": "Mover para baixo", "overdueTasksChip": "{{count}} tarefa vencida", - "overdueTasksChipPlural": "{{count}} tarefas vencidas" + "overdueTasksChipPlural": "{{count}} tarefas vencidas", + "customizeManage": "Widgets", + "customizeExit": "Sair da personalização", + "customizeDrag": "Arrastar widget", + "customizeSize": "Tamanho", + "customizeSizeFor": "Tamanho de {{widget}}", + "customizeHide": "Ocultar {{widget}}" }, "tasks": { "title": "Tarefas", diff --git a/public/locales/ru.json b/public/locales/ru.json index 1cab256..3294bc6 100644 --- a/public/locales/ru.json +++ b/public/locales/ru.json @@ -112,7 +112,13 @@ "customizeMoveUp": "Вверх", "customizeMoveDown": "Вниз", "overdueTasksChip": "{{count}} просроченная задача", - "overdueTasksChipPlural": "{{count}} просроченных задач" + "overdueTasksChipPlural": "{{count}} просроченных задач", + "customizeManage": "Виджеты", + "customizeExit": "Выйти из настройки", + "customizeDrag": "Перетащить виджет", + "customizeSize": "Размер", + "customizeSizeFor": "Размер для {{widget}}", + "customizeHide": "Скрыть {{widget}}" }, "tasks": { "title": "Задачи", diff --git a/public/locales/sv.json b/public/locales/sv.json index f611d3c..72ff18c 100644 --- a/public/locales/sv.json +++ b/public/locales/sv.json @@ -112,7 +112,13 @@ "customizeMoveUp": "Flytta upp", "customizeMoveDown": "Flytta ner", "overdueTasksChip": "{{count}} förfallen uppgift", - "overdueTasksChipPlural": "{{count}} förfallna uppgifter" + "overdueTasksChipPlural": "{{count}} förfallna uppgifter", + "customizeManage": "Widgetar", + "customizeExit": "Avsluta anpassning", + "customizeDrag": "Dra widget", + "customizeSize": "Storlek", + "customizeSizeFor": "Storlek för {{widget}}", + "customizeHide": "Dölj {{widget}}" }, "tasks": { "title": "Uppgifter", diff --git a/public/locales/tr.json b/public/locales/tr.json index 2e97e6a..edc90a8 100644 --- a/public/locales/tr.json +++ b/public/locales/tr.json @@ -112,7 +112,13 @@ "customizeMoveUp": "Yukarı", "customizeMoveDown": "Aşağı", "overdueTasksChip": "{{count}} gecikmiş görev", - "overdueTasksChipPlural": "{{count}} gecikmiş görev" + "overdueTasksChipPlural": "{{count}} gecikmiş görev", + "customizeManage": "Widgetlar", + "customizeExit": "Özelleştirmeden çık", + "customizeDrag": "Widgetı sürükle", + "customizeSize": "Boyut", + "customizeSizeFor": "{{widget}} boyutu", + "customizeHide": "{{widget}} gizle" }, "tasks": { "title": "Görevler", diff --git a/public/locales/uk.json b/public/locales/uk.json index e295193..a5c4872 100644 --- a/public/locales/uk.json +++ b/public/locales/uk.json @@ -112,7 +112,13 @@ "customizeMoveUp": "Перемістити вгору", "customizeMoveDown": "Перемістити вниз", "overdueTasksChip": "{{count}} прострочене завдання", - "overdueTasksChipPlural": "{{count}} прострочених завдань" + "overdueTasksChipPlural": "{{count}} прострочених завдань", + "customizeManage": "Віджети", + "customizeExit": "Вийти з налаштування", + "customizeDrag": "Перетягнути віджет", + "customizeSize": "Розмір", + "customizeSizeFor": "Розмір для {{widget}}", + "customizeHide": "Приховати {{widget}}" }, "tasks": { "title": "Завдання", diff --git a/public/locales/zh.json b/public/locales/zh.json index 6388d87..edbafa1 100644 --- a/public/locales/zh.json +++ b/public/locales/zh.json @@ -112,7 +112,13 @@ "customizeMoveUp": "上移", "customizeMoveDown": "下移", "overdueTasksChip": "{{count}} 个逾期任务", - "overdueTasksChipPlural": "{{count}} 个逾期任务" + "overdueTasksChipPlural": "{{count}} 个逾期任务", + "customizeManage": "小组件", + "customizeExit": "退出自定义", + "customizeDrag": "拖动小组件", + "customizeSize": "大小", + "customizeSizeFor": "{{widget}} 的大小", + "customizeHide": "隐藏 {{widget}}" }, "tasks": { "title": "任务", diff --git a/public/pages/dashboard.js b/public/pages/dashboard.js index ca79eb2..24b3049 100644 --- a/public/pages/dashboard.js +++ b/public/pages/dashboard.js @@ -113,7 +113,52 @@ function showOnboarding(appContainer) { // NEU — primäre Inhalte (tasks, calendar) ganz oben const WIDGET_IDS = ['tasks', 'calendar', 'weather', 'meals', 'shopping', 'birthdays', 'budget', 'family', 'notes']; -const DEFAULT_WIDGET_CONFIG = WIDGET_IDS.map((id, i) => ({ id, visible: true, order: i })); +const WIDGET_SIZE_OPTIONS = ['1x1', '2x1', '2x2', '3x1', '3x2', '4x1', '4x2']; +const WIDGET_SIZE_LABELS = { + '1x1': '1x1', + '2x1': '2x1', + '2x2': '2x2', + '3x1': '3x1', + '3x2': '3x2', + '4x1': '4x1', + '4x2': '4x2', +}; + +function defaultWidgetSize(id) { + if (['tasks', 'calendar'].includes(id)) return '2x2'; + if (['weather', 'shopping'].includes(id)) return '2x1'; + if (id === 'notes') return '2x1'; + return '1x1'; +} + +const DEFAULT_WIDGET_CONFIG = WIDGET_IDS.map((id, i) => ({ id, visible: true, order: i, size: defaultWidgetSize(id) })); + +function normalizeDashboardConfig(input) { + const valid = Array.isArray(input) + ? input + .filter((w) => w && typeof w === 'object' && WIDGET_IDS.includes(w.id)) + .map((w, i) => ({ + id: w.id, + visible: w.visible !== false, + order: Number.isFinite(Number(w.order)) ? Number(w.order) : i, + size: WIDGET_SIZE_OPTIONS.includes(w.size) ? w.size : defaultWidgetSize(w.id), + })) + : []; + const presentIds = new Set(valid.map((w) => w.id)); + for (const id of WIDGET_IDS) { + if (!presentIds.has(id)) { + valid.push({ id, visible: true, order: valid.length, size: defaultWidgetSize(id) }); + } + } + return valid + .sort((a, b) => a.order - b.order) + .map((w, i) => ({ ...w, order: i })); +} + +function setHtml(element, html) { + element.replaceChildren(); + element.insertAdjacentHTML('afterbegin', html); +} function widgetLabel(id) { const map = { @@ -523,7 +568,7 @@ function renderQuickAction({ route, label, icon, tone = '' }) { } -function renderDashboardOverview(user) { +function renderDashboardOverview(user, editing = false) { const dateLabel = formatDate(new Date()); const actions = [ @@ -541,10 +586,21 @@ function renderDashboardOverview(user) {

${greeting(user.display_name)}

-
${actions}
+ ${editing ? ` + ` : `
${actions}
`}
@@ -552,17 +608,34 @@ function renderDashboardOverview(user) { `; } -function widgetTileClass(id) { - // Primär: immer 2 Spalten breit (die wichtigsten Inhalte) - const primaryIds = ['tasks', 'calendar']; - // Sekundär: 2 Spalten ab 3-Spalten-Breakpoint (1024px) - const secondaryIds = ['weather', 'shopping']; - if (primaryIds.includes(id)) return 'widget--wide'; - if (secondaryIds.includes(id)) return 'widget--secondary'; - return ''; +function widgetSizeClass(size) { + return WIDGET_SIZE_OPTIONS.includes(size) ? `widget-size--${size}` : 'widget-size--1x1'; } -function renderDashboardLayout(cfg, data, weather, currency) { +function renderWidgetCustomizeControls(w) { + const sizeOptions = WIDGET_SIZE_OPTIONS.map((size) => ` + + `).join(''); + + return ` +
+ + + +
+ `; +} + +function renderDashboardLayout(cfg, data, weather, currency, { editing = false } = {}) { const widgetById = { tasks: () => renderUrgentTasks(data.urgentTasks ?? []), calendar: () => renderUpcomingEvents(data.upcomingEvents ?? []), @@ -580,11 +653,15 @@ function renderDashboardLayout(cfg, data, weather, currency) { .map((w) => { const html = widgetById[w.id](); if (!html) return ''; - return `
${html}
`; + return `
+ ${editing ? renderWidgetCustomizeControls(w) : ''} + ${html} +
`; }) .join(''); - return `
${tiles}
`; + return `
${tiles}
`; } function renderDashboardSkeleton() { @@ -800,6 +877,9 @@ function openCustomizeModal(currentConfig, onSave) { return draft.map((w, i) => { const isFirst = i === 0; const isLast = i === draft.length - 1; + const sizeOptions = WIDGET_SIZE_OPTIONS.map((size) => ` + + `).join(''); return `
${widgetLabel(w.id)} +