From 99a6b7437b0caa42ae5573d88878ab884617f32a Mon Sep 17 00:00:00 2001 From: "hendrik_gebhardt@gmx.de" Date: Sun, 11 Jan 2026 17:37:57 +0000 Subject: [PATCH] fix Coding/UI --- CHANGELOG.txt | 35 +++++++++++ data/taskmate.db | Bin 520192 -> 528384 bytes frontend/css/board.css | 89 ++++++++++++++++++++++++++++ frontend/index.html | 14 ++++- frontend/js/board.js | 131 +++++++++++++++++++++++++++++++++++++++++ frontend/js/coding.js | 28 +++------ frontend/sw.js | 2 +- 7 files changed, 277 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.txt b/CHANGELOG.txt index 9e241a1..ebb126d 100644 --- a/CHANGELOG.txt +++ b/CHANGELOG.txt @@ -1,6 +1,41 @@ TASKMATE - CHANGELOG ==================== +================================================================================ +11.01.2025 - FEATURE: Coding-Modul - Freie Pfad-Eingabe +================================================================================ + +## ÄNDERUNG +Ordnerpfad für Coding-Anwendungen kann jetzt frei definiert werden + +## IMPLEMENTIERUNG +✅ Neues Eingabefeld für Ordnerpfad im Modal +✅ Pfad wird nicht mehr automatisch aus Namen generiert +✅ Volle Flexibilität für beliebige Pfadangaben +✅ Validierung auf leeren Pfad + +================================================================================ +10.01.2026 - FEATURE: Optionales zweispaltiges Layout für Aufgabenansicht +================================================================================ + +## NEUE FUNKTION +Möglichkeit, Aufgaben in der Board-Ansicht mehrspaltig anzuzeigen + +## IMPLEMENTIERUNG +✅ Toggle-Button in der Filter-Leiste hinzugefügt +✅ Dynamisches Grid-Layout: Spalten erweitern sich nur bei Bedarf +✅ Automatische Erkennung wenn Scrollen nötig wäre +✅ Layout-Präferenz wird in localStorage gespeichert +✅ 2 Spalten wenn Inhalt > 120% der Höhe (ab 1400px Breite) +✅ 3 Spalten wenn Inhalt > 250% der Höhe (ab 1800px Breite) +✅ Spalten-Breite passt sich dynamisch an +✅ Cache-Version erhöht auf 303 + +## BEDIENUNG +- Button mit Raster-Icon in der Filter-Leiste schaltet Layout um +- Layout bleibt über Browser-Sessions erhalten +- Aufgaben werden bei aktivem Layout nebeneinander statt untereinander angezeigt + ================================================================================ 10.01.2026 - BUGFIX: Download von Dateianhängen funktioniert wieder ================================================================================ diff --git a/data/taskmate.db b/data/taskmate.db index bb1c53d2f9f63da0076ecc141e92af56a6a4bfe1..4fe697ee83370099f689158714bfdf9ad171292d 100644 GIT binary patch delta 14257 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 delta 8031 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 diff --git a/frontend/css/board.css b/frontend/css/board.css index 5720a5d..0c6c714 100644 --- a/frontend/css/board.css +++ b/frontend/css/board.css @@ -648,6 +648,7 @@ min-height: 0; overflow-x: auto; padding: var(--spacing-4); + align-items: flex-start; } /* Column */ @@ -662,6 +663,7 @@ border-radius: var(--radius-xl); box-shadow: var(--shadow-sm); flex-shrink: 0; + transition: width var(--transition-default), min-width var(--transition-default); } .column-header { @@ -1259,3 +1261,90 @@ font-size: var(--text-2xs); flex-shrink: 0; } + +/* ======================================== + MULTI-COLUMN LAYOUT + ======================================== */ + +/* Standard Layout (einspalltig) */ +.board .column-body { + flex: 1; + padding: var(--spacing-2); + overflow-y: auto; + display: flex; + flex-direction: column; + gap: var(--spacing-2); +} + +/* Base Multi-Column Layout - aktiviert das Feature, aber zeigt noch einspaltig */ +.board.multi-column-layout .column-body { + /* Bleibt erstmal bei flex layout bis Inhalt zu lang wird */ + display: flex; + flex-direction: column; + gap: var(--spacing-2); +} + +/* Dynamisch aktivierte 2-spaltige Ansicht (wenn Scrollen nötig wäre) */ +.board.multi-column-layout .column-body.dynamic-2-columns { + display: grid; + grid-template-columns: 1fr 1fr; + grid-auto-flow: row; + gap: var(--spacing-2); + align-content: start; + overflow-x: hidden; + overflow-y: auto; +} + +/* Dynamisch aktivierte 3-spaltige Ansicht (wenn viel Inhalt) */ +.board.multi-column-layout .column-body.dynamic-3-columns { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + grid-auto-flow: row; + gap: var(--spacing-2); + align-content: start; + overflow-x: hidden; + overflow-y: auto; +} + +/* Spalten-Breite wenn erweitert */ +.board.multi-column-layout .column { + transition: width var(--transition-default), min-width var(--transition-default); +} + +.board.multi-column-layout .column.expanded-2x { + width: auto; + min-width: 560px; + max-width: 640px; +} + +.board.multi-column-layout .column.expanded-3x { + width: auto; + min-width: 840px; + max-width: 960px; +} + +/* Task Cards im Multi-Column Layout */ +.board.multi-column-layout .task-card { + width: 100%; + box-sizing: border-box; +} + +/* Hover-Effekt für Layout-Toggle Button */ +#btn-toggle-layout { + transition: all var(--transition-fast); +} + +#btn-toggle-layout:hover { + transform: rotate(90deg); +} + +/* Active state indicator - korrigiert für richtige Selektion */ +.view-board.active .board.multi-column-layout ~ * { + /* Dummy rule to ensure the layout class is applied */ +} + +/* Layout toggle button active state */ +#btn-toggle-layout.active { + color: var(--primary); + background: var(--primary-light); +} diff --git a/frontend/index.html b/frontend/index.html index 6f8c463..af53c46 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -357,6 +357,14 @@
+
@@ -1696,7 +1704,11 @@
- Ordner: /home/claude-dev/... +
+
+ + + Vollständiger Pfad zum Projekt-Ordner
diff --git a/frontend/js/board.js b/frontend/js/board.js index 19ac77b..8f93f0e 100644 --- a/frontend/js/board.js +++ b/frontend/js/board.js @@ -25,6 +25,9 @@ class BoardManager { // Week strip calendar this.weekStripDate = this.getMonday(new Date()); this.tooltip = null; + + // Layout preferences + this.multiColumnLayout = this.loadLayoutPreference(); this.init(); } @@ -52,6 +55,21 @@ class BoardManager { this.loadStats(); this.renderWeekStrip(); }); + + // Apply initial layout preference + setTimeout(() => { + this.applyLayoutClass(); + if (this.multiColumnLayout) { + this.checkAndApplyDynamicLayout(); + } + }, 100); + + // Re-check layout on window resize + window.addEventListener('resize', debounce(() => { + if (this.multiColumnLayout) { + this.checkAndApplyDynamicLayout(); + } + }, 250)); } // ===================== @@ -144,6 +162,13 @@ class BoardManager { // Keyboard navigation document.addEventListener('keydown', (e) => this.handleKeyboard(e)); + + // Layout toggle button - use delegated event handling + document.addEventListener('click', (e) => { + if (e.target.closest('#btn-toggle-layout')) { + this.toggleLayout(); + } + }); } // ===================== @@ -155,6 +180,9 @@ class BoardManager { const columns = store.get('columns'); clearElement(this.boardElement); + + // Apply layout class + this.applyLayoutClass(); columns.forEach(column => { const columnElement = this.createColumnElement(column); @@ -170,6 +198,9 @@ class BoardManager { 'Statuskarte hinzufügen' ]); this.boardElement.appendChild(addColumnBtn); + + // Check dynamic layout after render + setTimeout(() => this.checkAndApplyDynamicLayout(), 100); } createColumnElement(column) { @@ -475,6 +506,9 @@ class BoardManager { countElement.textContent = filteredTasks.length.toString(); } }); + + // Check if dynamic layout adjustment is needed + setTimeout(() => this.checkAndApplyDynamicLayout(), 100); } // ===================== @@ -1461,6 +1495,103 @@ class BoardManager { div.textContent = str; return div.innerHTML; } + + // ===================== + // LAYOUT PREFERENCES + // ===================== + + loadLayoutPreference() { + const stored = localStorage.getItem('taskmate:boardLayout'); + return stored === 'multiColumn'; + } + + saveLayoutPreference(multiColumn) { + localStorage.setItem('taskmate:boardLayout', multiColumn ? 'multiColumn' : 'single'); + } + + toggleLayout() { + this.multiColumnLayout = !this.multiColumnLayout; + this.saveLayoutPreference(this.multiColumnLayout); + this.applyLayoutClass(); + this.checkAndApplyDynamicLayout(); + + this.showSuccess(this.multiColumnLayout + ? 'Mehrspalten-Layout aktiviert' + : 'Einspalten-Layout aktiviert' + ); + } + + applyLayoutClass() { + const toggleBtn = $('#btn-toggle-layout'); + + if (this.multiColumnLayout) { + this.boardElement?.classList.add('multi-column-layout'); + toggleBtn?.classList.add('active'); + } else { + this.boardElement?.classList.remove('multi-column-layout'); + toggleBtn?.classList.remove('active'); + + // Remove all dynamic classes when disabled + const columns = this.boardElement?.querySelectorAll('.column'); + columns?.forEach(column => { + const columnBody = column.querySelector('.column-body'); + columnBody?.classList.remove('dynamic-2-columns', 'dynamic-3-columns'); + column.classList.remove('expanded-2x', 'expanded-3x'); + }); + } + } + + checkAndApplyDynamicLayout() { + if (!this.multiColumnLayout || !this.boardElement) return; + + // Debug logging + console.log('[Layout Check] Checking dynamic layout...'); + + // Check each column to see if scrolling is needed + const columns = this.boardElement.querySelectorAll('.column'); + + columns.forEach(column => { + const columnBody = column.querySelector('.column-body'); + if (!columnBody) return; + + // Remove dynamic classes first + columnBody.classList.remove('dynamic-2-columns', 'dynamic-3-columns'); + column.classList.remove('expanded-2x', 'expanded-3x'); + + // Force reflow to get accurate measurements + columnBody.offsetHeight; + + const scrollHeight = columnBody.scrollHeight; + const clientHeight = columnBody.clientHeight; + const hasOverflow = scrollHeight > clientHeight; + + console.log('[Layout Check]', { + column: column.dataset.columnId, + scrollHeight, + clientHeight, + hasOverflow, + ratio: scrollHeight / clientHeight + }); + + // Check if content overflows + if (hasOverflow && clientHeight > 0) { + // Calculate how many columns we need based on content height + const ratio = scrollHeight / clientHeight; + + if (ratio > 2.5 && window.innerWidth >= 1800) { + // Need 3 columns + console.log('[Layout] Applying 3 columns'); + columnBody.classList.add('dynamic-3-columns'); + column.classList.add('expanded-3x'); + } else if (ratio > 1.1 && window.innerWidth >= 1400) { + // Need 2 columns (reduced threshold to 1.1) + console.log('[Layout] Applying 2 columns'); + columnBody.classList.add('dynamic-2-columns'); + column.classList.add('expanded-2x'); + } + } + }); + } } // Create and export singleton diff --git a/frontend/js/coding.js b/frontend/js/coding.js index b48ff09..770f71f 100644 --- a/frontend/js/coding.js +++ b/frontend/js/coding.js @@ -83,11 +83,8 @@ class CodingManager { // Farb-Presets this.renderColorPresets(); - // Name-Eingabe für Pfad-Preview + // Name-Eingabe (kein Pfad-Preview mehr nötig) const nameInput = document.getElementById('coding-name'); - if (nameInput) { - nameInput.addEventListener('input', () => this.updatePathPreview()); - } // CLAUDE.md Link Event const claudeLink = document.getElementById('coding-claude-link'); @@ -165,17 +162,6 @@ class CodingManager { }); } - /** - * Pfad-Preview aktualisieren - */ - updatePathPreview() { - const nameInput = document.getElementById('coding-name'); - const preview = document.getElementById('coding-path-preview'); - if (!nameInput || !preview) return; - - const name = nameInput.value.trim(); - preview.textContent = name || '...'; - } // switchClaudeTab entfernt - CLAUDE.md ist jetzt nur readonly @@ -602,6 +588,7 @@ class CodingManager { // Felder füllen document.getElementById('coding-name').value = directory?.name || ''; + document.getElementById('coding-path').value = directory?.localPath || ''; document.getElementById('coding-description').value = directory?.description || ''; document.getElementById('coding-branch').value = directory?.defaultBranch || 'main'; @@ -609,8 +596,6 @@ class CodingManager { const claudeContent = directory?.claudeMdFromDisk || ''; this.updateClaudeLink(claudeContent, directory?.name); - // Pfad-Preview aktualisieren - this.updatePathPreview(); // Farbe setzen const color = directory?.color || '#4F46E5'; @@ -663,13 +648,11 @@ class CodingManager { */ async handleSave() { const name = document.getElementById('coding-name').value.trim(); + const localPath = document.getElementById('coding-path').value.trim(); const description = document.getElementById('coding-description').value.trim(); // CLAUDE.md wird nicht mehr gespeichert - nur readonly const defaultBranch = document.getElementById('coding-branch').value.trim() || 'main'; - // Pfad automatisch aus Name generieren - const localPath = `${BASE_PATH}/${name}`; - // Farbe ermitteln const selectedPreset = document.querySelector('.color-preset.selected'); const color = selectedPreset?.dataset.color || document.getElementById('coding-color-custom').value; @@ -685,6 +668,11 @@ class CodingManager { return; } + if (!localPath) { + showToast('Ordnerpfad ist erforderlich', 'error'); + return; + } + const data = { name, localPath, diff --git a/frontend/sw.js b/frontend/sw.js index 1175f6a..fba1440 100644 --- a/frontend/sw.js +++ b/frontend/sw.js @@ -4,7 +4,7 @@ * Offline support and caching */ -const CACHE_VERSION = '300'; +const CACHE_VERSION = '306'; const CACHE_NAME = 'taskmate-v' + CACHE_VERSION; const STATIC_CACHE_NAME = 'taskmate-static-v' + CACHE_VERSION; const DYNAMIC_CACHE_NAME = 'taskmate-dynamic-v' + CACHE_VERSION;