feat(tasks): persist view mode and support ?view=kanban URL parameter
View mode (list/kanban) is now saved to localStorage and restored on page load. URL parameter ?view=kanban takes precedence, enabling tablet kiosk setups to default to Kanban view. Toggle buttons reflect the active view correctly on initial render. Closes #17
This commit is contained in:
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.9.1] - 2026-04-04
|
||||
|
||||
### Added
|
||||
- Persist task view mode (list/kanban) across sessions via localStorage (#17)
|
||||
- Support URL parameter `?view=kanban` to open tasks directly in Kanban view - ideal for tablet kiosk setups
|
||||
- View toggle button reflects the persisted/URL-driven view on page load
|
||||
|
||||
## [0.9.0] - 2026-04-04
|
||||
|
||||
### Added
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "oikos",
|
||||
"version": "0.9.0",
|
||||
"version": "0.9.1",
|
||||
"description": "Self-hosted family planner - calendar, tasks, shopping, meal planning, budget and more. Private, open-source, no subscription.",
|
||||
"main": "server/index.js",
|
||||
"type": "module",
|
||||
|
||||
+15
-5
@@ -356,7 +356,7 @@ let state = {
|
||||
users: [],
|
||||
filters: { status: '', priority: '', assigned_to: '' },
|
||||
groupMode: 'category', // 'category' | 'due'
|
||||
viewMode: 'list', // 'list' | 'kanban'
|
||||
viewMode: 'list', // 'list' | 'kanban' (resolved at render time)
|
||||
expandedTasks: new Set(),
|
||||
dragTaskId: null,
|
||||
};
|
||||
@@ -872,6 +872,7 @@ function wireViewToggle(container) {
|
||||
toggle.querySelectorAll('[data-view]').forEach((btn) => {
|
||||
btn.addEventListener('click', () => {
|
||||
state.viewMode = btn.dataset.view;
|
||||
localStorage.setItem('oikos-tasks-view', state.viewMode);
|
||||
toggle.querySelectorAll('[data-view]').forEach((b) =>
|
||||
b.classList.toggle('group-toggle__btn--active', b.dataset.view === state.viewMode)
|
||||
);
|
||||
@@ -962,23 +963,32 @@ function wireTaskList(container) {
|
||||
// --------------------------------------------------------
|
||||
|
||||
export async function render(container, { user }) {
|
||||
// Initiales Skeleton
|
||||
// View-Mode: URL-Parameter > localStorage > Default 'list'
|
||||
const urlView = new URLSearchParams(window.location.search).get('view');
|
||||
const savedView = localStorage.getItem('oikos-tasks-view');
|
||||
state.viewMode = (urlView === 'kanban' || urlView === 'list') ? urlView
|
||||
: (savedView === 'kanban' || savedView === 'list') ? savedView
|
||||
: 'list';
|
||||
|
||||
const isKanban = state.viewMode === 'kanban';
|
||||
|
||||
// Initiales Skeleton (all values are from i18n keys or hardcoded constants, no user data)
|
||||
container.innerHTML = `
|
||||
<div class="tasks-page">
|
||||
<div class="tasks-toolbar">
|
||||
<h1 class="tasks-toolbar__title">${t('tasks.title')}</h1>
|
||||
<div class="tasks-toolbar__actions">
|
||||
<div class="group-toggle" id="view-toggle">
|
||||
<button class="group-toggle__btn group-toggle__btn--active" data-view="list"
|
||||
<button class="group-toggle__btn ${isKanban ? '' : 'group-toggle__btn--active'}" data-view="list"
|
||||
title="${t('tasks.listView')}" aria-label="${t('tasks.listView')}">
|
||||
<i data-lucide="list" style="width:14px;height:14px;pointer-events:none" aria-hidden="true"></i>
|
||||
</button>
|
||||
<button class="group-toggle__btn" data-view="kanban"
|
||||
<button class="group-toggle__btn ${isKanban ? 'group-toggle__btn--active' : ''}" data-view="kanban"
|
||||
title="${t('tasks.kanbanView')}" aria-label="${t('tasks.kanbanView')}">
|
||||
<i data-lucide="columns" style="width:14px;height:14px;pointer-events:none" aria-hidden="true"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="group-toggle" id="group-mode-toggle">
|
||||
<div class="group-toggle" id="group-mode-toggle" ${isKanban ? 'style="display:none"' : ''}>
|
||||
<button class="group-toggle__btn group-toggle__btn--active" data-mode="category">${t('tasks.categoryLabel')}</button>
|
||||
<button class="group-toggle__btn" data-mode="due">${t('tasks.dueDateLabel')}</button>
|
||||
</div>
|
||||
|
||||
+3
-3
@@ -12,9 +12,9 @@
|
||||
* API: Immer Netzwerk (kein Caching von Nutzerdaten)
|
||||
*/
|
||||
|
||||
const SHELL_CACHE = 'oikos-shell-v24';
|
||||
const PAGES_CACHE = 'oikos-pages-v24';
|
||||
const ASSETS_CACHE = 'oikos-assets-v24';
|
||||
const SHELL_CACHE = 'oikos-shell-v25';
|
||||
const PAGES_CACHE = 'oikos-pages-v25';
|
||||
const ASSETS_CACHE = 'oikos-assets-v25';
|
||||
const ALL_CACHES = [SHELL_CACHE, PAGES_CACHE, ASSETS_CACHE];
|
||||
|
||||
// App-Shell: sofort benötigt für ersten Render
|
||||
|
||||
Reference in New Issue
Block a user