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
This commit is contained in:
Ulas
2026-04-04 22:13:51 +02:00
parent 2508473265
commit 2c36fa0307
11 changed files with 64 additions and 11 deletions
+9
View File
@@ -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
+1 -1
View File
@@ -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",
+1
View File
@@ -100,6 +100,7 @@
"priorityHigh": "Hoch",
"priorityMedium": "Mittel",
"priorityLow": "Niedrig",
"priorityNone": "Keine",
"statusOpen": "Offen",
"statusInProgress": "In Bearbeitung",
"statusDone": "Erledigt",
+1
View File
@@ -100,6 +100,7 @@
"priorityHigh": "High",
"priorityMedium": "Medium",
"priorityLow": "Low",
"priorityNone": "None",
"statusOpen": "Open",
"statusInProgress": "In Progress",
"statusDone": "Done",
+1
View File
@@ -100,6 +100,7 @@
"priorityHigh": "Alta",
"priorityMedium": "Media",
"priorityLow": "Bassa",
"priorityNone": "Nessuna",
"statusOpen": "Aperto",
"statusInProgress": "In corso",
"statusDone": "Completato",
+1 -1
View File
@@ -167,7 +167,7 @@ function renderUrgentTasks(tasks) {
const due = formatDueDate(t.due_date);
return `
<div class="task-item" data-route="/tasks" role="button" tabindex="0">
<div class="task-item__priority task-item__priority--${t.priority}" aria-hidden="true"></div>
${t.priority !== 'none' ? `<div class="task-item__priority task-item__priority--${t.priority}" aria-hidden="true"></div>` : ''}
<span class="sr-only">${PRIORITY_LABELS()[t.priority] ?? t.priority}</span>
<div class="task-item__content">
<div class="task-item__title">${esc(t.title)}</div>
+3 -1
View File
@@ -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 `<span class="priority-badge priority-badge--${priority}">
<span class="priority-dot priority-dot--${priority}"></span>
${PRIORITY_LABELS()[priority] ?? priority}
@@ -254,7 +256,7 @@ function renderModalContent({ task = null, users = [] } = {}) {
).join('');
const priorityOptions = PRIORITIES().map((p) =>
`<option value="${p.value}" ${(task?.priority ?? 'medium') === p.value ? 'selected' : ''}>${p.label}</option>`
`<option value="${p.value}" ${(task?.priority ?? 'none') === p.value ? 'selected' : ''}>${p.label}</option>`
).join('');
return `
+4
View File
@@ -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);
+3 -3
View File
@@ -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
+37 -2
View File
@@ -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);
`,
},
];
/**
+3 -3
View File
@@ -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,