diff --git a/CHANGELOG.md b/CHANGELOG.md index 6c62530..94a1fa6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [0.11.7] - 2026-04-05 + +### Added +- Kanban view: quick-status button on each card to advance status without drag-and-drop (open → in progress → done → open) - useful for touch devices and kiosk browsers (#24) + ## [0.11.6] - 2026-04-05 ### Fixed diff --git a/package.json b/package.json index d07cd43..0e675ba 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "oikos", - "version": "0.11.6", + "version": "0.11.7", "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 22c1dff..78b6799 100644 --- a/public/locales/de.json +++ b/public/locales/de.json @@ -142,6 +142,9 @@ "kanbanOpen": "Offen", "kanbanInProgress": "In Bearbeitung", "kanbanDone": "Erledigt", + "kanbanMoveToInProgress": "In Bearbeitung setzen", + "kanbanMoveToDone": "Als erledigt markieren", + "kanbanMoveToOpen": "Erneut öffnen", "recurring": "Wiederkehrend", "listView": "Listenansicht", "kanbanView": "Kanban-Ansicht" diff --git a/public/locales/en.json b/public/locales/en.json index b216731..fdeab0a 100644 --- a/public/locales/en.json +++ b/public/locales/en.json @@ -142,6 +142,9 @@ "kanbanOpen": "Open", "kanbanInProgress": "In Progress", "kanbanDone": "Done", + "kanbanMoveToInProgress": "Set to in progress", + "kanbanMoveToDone": "Mark as done", + "kanbanMoveToOpen": "Reopen", "recurring": "Recurring", "listView": "List view", "kanbanView": "Kanban view" diff --git a/public/locales/it.json b/public/locales/it.json index 70818a2..eb99f2f 100644 --- a/public/locales/it.json +++ b/public/locales/it.json @@ -142,6 +142,9 @@ "kanbanOpen": "Da fare", "kanbanInProgress": "In corso", "kanbanDone": "Completato", + "kanbanMoveToInProgress": "Imposta in corso", + "kanbanMoveToDone": "Segna come completato", + "kanbanMoveToOpen": "Riapri", "recurring": "Ricorrente", "listView": "Vista elenco", "kanbanView": "Vista Kanban" diff --git a/public/locales/sv.json b/public/locales/sv.json index 8ffc7e5..661de94 100644 --- a/public/locales/sv.json +++ b/public/locales/sv.json @@ -142,6 +142,9 @@ "kanbanOpen": "Öppna", "kanbanInProgress": "Pågår", "kanbanDone": "Slutfört", + "kanbanMoveToInProgress": "Sätt till pågår", + "kanbanMoveToDone": "Markera som klar", + "kanbanMoveToOpen": "Öppna igen", "recurring": "Återkommande", "listView": "Listvy", "kanbanView": "Kanban-vy" diff --git a/public/pages/tasks.js b/public/pages/tasks.js index 7eb44c5..dc7af1d 100644 --- a/public/pages/tasks.js +++ b/public/pages/tasks.js @@ -506,8 +506,21 @@ const KANBAN_COLS = () => [ { status: 'done', label: t('tasks.kanbanDone'), colorVar: '--color-success' }, ]; +function kanbanNextStatus(status) { + if (status === 'open') return 'in_progress'; + if (status === 'in_progress') return 'done'; + return 'open'; +} + function renderKanbanCard(task) { - const due = formatDueDate(task.due_date); + const due = formatDueDate(task.due_date); + const next = kanbanNextStatus(task.status); + const icon = next === 'done' ? 'check' : next === 'in_progress' ? 'circle-play' : 'rotate-ccw'; + const nextLabel = next === 'done' + ? t('tasks.kanbanMoveToDone') + : next === 'in_progress' + ? t('tasks.kanbanMoveToInProgress') + : t('tasks.kanbanMoveToOpen'); return `
@@ -516,13 +529,17 @@ function renderKanbanCard(task) { ${renderPriorityBadge(task.priority)} ${due ? ` ${due.label}` : ''}
- ${task.assigned_color ? ` - ` : ''} + + `; } @@ -625,8 +642,30 @@ function wireKanbanDrag(container) { } }); - // Klick auf Kanban-Card öffnet Edit-Modal + // Klick auf Status-Button: Status ohne Modal wechseln board.addEventListener('click', async (e) => { + const statusBtn = e.target.closest('[data-next-status]'); + if (statusBtn) { + e.stopPropagation(); + const card = statusBtn.closest('.kanban-card[data-task-id]'); + if (!card) return; + const taskId = card.dataset.taskId; + const newStatus = statusBtn.dataset.nextStatus; + const task = state.tasks.find((t) => String(t.id) === String(taskId)); + if (!task) return; + task.status = newStatus; + renderKanban(container); + try { + await api.patch(`/tasks/${taskId}/status`, { status: newStatus }); + await loadTasks(container); + } catch (err) { + window.oikos.showToast(err.message, 'danger'); + await loadTasks(container); + } + return; + } + + // Klick auf Kanban-Card öffnet Edit-Modal if (e.target.closest('[draggable]')) { const card = e.target.closest('.kanban-card[data-task-id]'); if (!card) return; diff --git a/public/styles/tasks.css b/public/styles/tasks.css index 8ed6a7c..0ec768b 100644 --- a/public/styles/tasks.css +++ b/public/styles/tasks.css @@ -581,10 +581,40 @@ .kanban-card__footer { display: flex; - justify-content: flex-end; + align-items: center; + justify-content: space-between; margin-top: var(--space-2); } +.kanban-card__status-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + padding: 0; + border: 1px solid var(--color-border); + border-radius: var(--radius-sm); + background: transparent; + color: var(--color-text-secondary); + cursor: pointer; + transition: background-color var(--transition-fast), color var(--transition-fast), + border-color var(--transition-fast); + flex-shrink: 0; +} + +.kanban-card__status-btn:hover { + background-color: var(--color-accent-light); + border-color: var(--color-accent); + color: var(--color-accent); +} + +.kanban-card__status-btn svg { + width: 12px; + height: 12px; + pointer-events: none; +} + .kanban-drop-placeholder { height: 60px; border: 2px dashed var(--color-accent);