From 2c36fa0307c4f0d5e452976a45347961292aa3f8 Mon Sep 17 00:00:00 2001 From: Ulas Date: Sat, 4 Apr 2026 22:13:51 +0200 Subject: [PATCH] feat(tasks): add optional "none" priority level for tasks without urgency New tasks default to "none" priority instead of "medium". Tasks with no priority hide the badge in list and dashboard views, reducing visual noise for routine items. Includes DB migration v4 and i18n keys (de, en, it). Closes #15 --- CHANGELOG.md | 9 +++++++++ package.json | 2 +- public/locales/de.json | 1 + public/locales/en.json | 1 + public/locales/it.json | 1 + public/pages/dashboard.js | 2 +- public/pages/tasks.js | 4 +++- public/styles/tokens.css | 4 ++++ public/sw.js | 6 +++--- server/db.js | 39 +++++++++++++++++++++++++++++++++++++-- server/routes/tasks.js | 6 +++--- 11 files changed, 64 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95ac2e0..0c24a51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.9.0] - 2026-04-04 + +### Added +- Optional task priority: new "None" level allows tasks without urgency, reducing visual noise for routine tasks (#15) +- "None" is now the default priority for new tasks +- Tasks with no priority hide the priority badge entirely in list and dashboard views +- DB migration v4 extends priority CHECK constraint to include 'none' +- i18n keys for "None" priority in de, en, it locales + ## [0.8.2] - 2026-04-04 ### Fixed diff --git a/package.json b/package.json index dd6944b..09b4f6e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.8.2", + "version": "0.9.0", "description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.", "main": "server/index.js", "type": "module", diff --git a/public/locales/de.json b/public/locales/de.json index a6f7341..9a15f32 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -100,6 +100,7 @@ "priorityHigh": "Hoch", "priorityMedium": "Mittel", "priorityLow": "Niedrig", + "priorityNone": "Keine", "statusOpen": "Offen", "statusInProgress": "In Bearbeitung", "statusDone": "Erledigt", diff --git a/public/locales/en.json b/public/locales/en.json index be1d695..1a4d49b 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -100,6 +100,7 @@ "priorityHigh": "High", "priorityMedium": "Medium", "priorityLow": "Low", + "priorityNone": "None", "statusOpen": "Open", "statusInProgress": "In Progress", "statusDone": "Done", diff --git a/public/locales/it.json b/public/locales/it.json index 283e5b2..a7d8c17 100644 --- a/public/locales/it.json +++ b/public/locales/it.json @@ -100,6 +100,7 @@ "priorityHigh": "Alta", "priorityMedium": "Media", "priorityLow": "Bassa", + "priorityNone": "Nessuna", "statusOpen": "Aperto", "statusInProgress": "In corso", "statusDone": "Completato", diff --git a/public/pages/dashboard.js b/public/pages/dashboard.js index d5a2cdd..90ce761 100644 --- a/public/pages/dashboard.js +++ b/public/pages/dashboard.js @@ -167,7 +167,7 @@ function renderUrgentTasks(tasks) { const due = formatDueDate(t.due_date); return `
- + ${t.priority !== 'none' ? `` : ''} ${PRIORITY_LABELS()[t.priority] ?? t.priority}
${esc(t.title)}
diff --git a/public/pages/tasks.js b/public/pages/tasks.js index 54427a3..29f6237 100644 --- a/public/pages/tasks.js +++ b/public/pages/tasks.js @@ -20,6 +20,7 @@ const PRIORITIES = () => [ { value: 'high', label: t('tasks.priorityHigh'), color: 'var(--color-priority-high)' }, { value: 'medium', label: t('tasks.priorityMedium'), color: 'var(--color-priority-medium)' }, { value: 'low', label: t('tasks.priorityLow'), color: 'var(--color-priority-low)' }, + { value: 'none', label: t('tasks.priorityNone'), color: 'var(--color-priority-none)' }, ]; const STATUSES = () => [ @@ -110,6 +111,7 @@ function groupBy(tasks, mode) { // -------------------------------------------------------- function renderPriorityBadge(priority) { + if (priority === 'none') return ''; return ` ${PRIORITY_LABELS()[priority] ?? priority} @@ -254,7 +256,7 @@ function renderModalContent({ task = null, users = [] } = {}) { ).join(''); const priorityOptions = PRIORITIES().map((p) => - `` + `` ).join(''); return ` diff --git a/public/styles/tokens.css b/public/styles/tokens.css index 5eb8ef6..05420f2 100644 --- a/public/styles/tokens.css +++ b/public/styles/tokens.css @@ -114,12 +114,14 @@ /* -------------------------------------------------------- * 6. Farben - Prioritäten * -------------------------------------------------------- */ + --color-priority-none: var(--neutral-400); --color-priority-low: var(--neutral-500); --color-priority-medium: #B45309; --color-priority-high: #D4511E; --color-priority-urgent: #DC2626; /* Hintergrundfarben für Priority-Badges */ + --color-priority-none-bg: rgba(142, 141, 137, 0.08); --color-priority-low-bg: rgba(142, 141, 137, 0.12); --color-priority-medium-bg: rgba(180, 83, 9, 0.12); --color-priority-high-bg: rgba(212, 81, 30, 0.12); @@ -328,6 +330,7 @@ --meal-snack-light: #3D2010; /* Priority-Badge Hintergründe */ + --color-priority-none-bg: rgba(142, 141, 137, 0.12); --color-priority-low-bg: rgba(142, 141, 137, 0.18); --color-priority-medium-bg: rgba(230, 147, 10, 0.18); --color-priority-high-bg: rgba(212, 81, 30, 0.18); @@ -408,6 +411,7 @@ --meal-dinner-light: #1A2D4D; --meal-snack-light: #3D2010; + --color-priority-none-bg: rgba(142, 141, 137, 0.12); --color-priority-low-bg: rgba(142, 141, 137, 0.18); --color-priority-medium-bg: rgba(230, 147, 10, 0.18); --color-priority-high-bg: rgba(212, 81, 30, 0.18); diff --git a/public/sw.js b/public/sw.js index 67b618b..8ce6352 100644 --- a/public/sw.js +++ b/public/sw.js @@ -12,9 +12,9 @@ * API: Immer Netzwerk (kein Caching von Nutzerdaten) */ -const SHELL_CACHE = 'oikos-shell-v23'; -const PAGES_CACHE = 'oikos-pages-v23'; -const ASSETS_CACHE = 'oikos-assets-v23'; +const SHELL_CACHE = 'oikos-shell-v24'; +const PAGES_CACHE = 'oikos-pages-v24'; +const ASSETS_CACHE = 'oikos-assets-v24'; const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE]; // App-Shell: sofort benötigt für ersten Render diff --git a/server/db.js b/server/db.js index b72399c..1a1269c 100644 --- a/server/db.js +++ b/server/db.js @@ -88,8 +88,8 @@ const MIGRATIONS = [ title TEXT NOT NULL, description TEXT, category TEXT NOT NULL DEFAULT 'Sonstiges', - priority TEXT NOT NULL DEFAULT 'medium' - CHECK(priority IN ('low', 'medium', 'high', 'urgent')), + priority TEXT NOT NULL DEFAULT 'none' + CHECK(priority IN ('none', 'low', 'medium', 'high', 'urgent')), status TEXT NOT NULL DEFAULT 'open' CHECK(status IN ('open', 'in_progress', 'done')), due_date TEXT, @@ -296,6 +296,41 @@ const MIGRATIONS = [ CREATE INDEX IF NOT EXISTS idx_budget_parent ON budget_entries(recurrence_parent_id); `, }, + { + version: 4, + description: 'Priorität "none" erlauben und als Default setzen', + up: ` + -- SQLite erlaubt kein ALTER CHECK, daher Tabelle neu erstellen + CREATE TABLE tasks_new ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + description TEXT, + category TEXT NOT NULL DEFAULT 'Sonstiges', + priority TEXT NOT NULL DEFAULT 'none' + CHECK(priority IN ('none', 'low', 'medium', 'high', 'urgent')), + status TEXT NOT NULL DEFAULT 'open' + CHECK(status IN ('open', 'in_progress', 'done')), + due_date TEXT, + due_time TEXT, + assigned_to INTEGER REFERENCES users(id) ON DELETE SET NULL, + created_by INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE, + is_recurring INTEGER NOT NULL DEFAULT 0, + recurrence_rule TEXT, + parent_task_id INTEGER REFERENCES tasks(id) ON DELETE CASCADE, + created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')), + updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')) + ); + + INSERT INTO tasks_new SELECT * FROM tasks; + DROP TABLE tasks; + ALTER TABLE tasks_new RENAME TO tasks; + + CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status); + CREATE INDEX IF NOT EXISTS idx_tasks_assigned ON tasks(assigned_to); + CREATE INDEX IF NOT EXISTS idx_tasks_parent ON tasks(parent_task_id); + CREATE INDEX IF NOT EXISTS idx_tasks_due ON tasks(due_date); + `, + }, ]; /** diff --git a/server/routes/tasks.js b/server/routes/tasks.js index 6492e87..bbd156e 100644 --- a/server/routes/tasks.js +++ b/server/routes/tasks.js @@ -18,7 +18,7 @@ const router = express.Router(); // Konstanten // -------------------------------------------------------- -const VALID_PRIORITIES = ['low', 'medium', 'high', 'urgent']; +const VALID_PRIORITIES = ['none', 'low', 'medium', 'high', 'urgent']; const VALID_STATUSES = ['open', 'in_progress', 'done']; const VALID_CATEGORIES = ['Haushalt', 'Schule', 'Einkauf', 'Reparatur', 'Gesundheit', 'Finanzen', 'Freizeit', 'Sonstiges']; @@ -96,7 +96,7 @@ router.get('/', (req, res) => { ORDER BY CASE t.status WHEN 'done' THEN 1 ELSE 0 END, CASE t.priority WHEN 'urgent' THEN 0 WHEN 'high' THEN 1 - WHEN 'medium' THEN 2 ELSE 3 END, + WHEN 'medium' THEN 2 WHEN 'low' THEN 3 ELSE 4 END, t.due_date ASC NULLS LAST, t.created_at DESC `; @@ -148,7 +148,7 @@ router.post('/', (req, res) => { title, description = null, category = 'Sonstiges', - priority = 'medium', + priority = 'none', due_date = null, due_time = null, assigned_to = null,