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)}
@@ -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)}
+