UI-Redesign: AegisSight Design, Filter-Popover, Header-Umbau

- Session-Timeout auf 60 Minuten erhöht (ACCESS_TOKEN_EXPIRY + SESSION_TIMEOUT)
- AegisSight Light Theme: Gold-Akzent (#C8A851) statt Indigo
- Navigation-Tabs in eigene Zeile unter Header verschoben (HTML-Struktur)
- Filter-Bar durch kompaktes Popover mit Checkboxen ersetzt (Mehrfachauswahl)
- Archiv-Funktion repariert (lädt jetzt per API statt leerem Store)
- Filter-Bugs behoben: Reset-Button ID, Default-Werte, Ohne-Datum-Filter
- Mehrspalten-Layout Feature entfernt
- Online-Status vom Header an User-Avatar verschoben (grüner Punkt)
- Lupen-Icon entfernt
- CLAUDE.md: Docker-Deploy und CSS-Tricks Regeln aktualisiert

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Dieser Commit ist enthalten in:
Server Deploy
2026-03-19 18:49:38 +01:00
Ursprung 99a6b7437b
Commit 4bd57d653f
36 geänderte Dateien mit 5027 neuen und 2897 gelöschten Zeilen

Datei anzeigen

@@ -4,7 +4,7 @@
"namespace": "android_app",
"package_name": "de.aegissight.taskmate",
"sha256_cert_fingerprints": [
"TO_BE_REPLACED_WITH_YOUR_APP_SIGNING_KEY_FINGERPRINT"
"75:8D:45:A0:FF:33:7E:39:69:A7:E2:13:CE:6A:3C:A4:C8:67:2F:6A:21:91:42:4C:74:B1:BC:B9:C5:B8:74:F2"
]
}
}]
}]

Datei anzeigen

@@ -112,13 +112,15 @@
display: flex;
align-items: center;
justify-content: space-between;
height: var(--header-height);
min-height: var(--header-height);
padding: 0 var(--spacing-4);
background: var(--bg-card);
border-bottom: 1px solid var(--border-light);
position: sticky;
top: 0;
z-index: var(--z-sticky);
width: 100%;
box-sizing: border-box;
}
.header-left,
@@ -126,11 +128,20 @@
display: flex;
align-items: center;
gap: var(--spacing-3);
flex-shrink: 0;
flex-wrap: nowrap;
}
.header-center {
.view-tabs-bar {
display: flex;
justify-content: space-between;
align-items: center;
padding: var(--spacing-1) var(--spacing-4);
background: var(--bg-card);
border-bottom: 1px solid var(--border-light);
position: sticky;
top: var(--header-height);
z-index: calc(var(--z-sticky) - 1);
}
.logo {
@@ -150,7 +161,8 @@
}
.project-select {
min-width: 160px;
width: 100%;
min-width: 120px;
max-width: 200px;
font-weight: var(--font-medium);
font-size: var(--text-sm);
@@ -166,15 +178,17 @@
position: relative;
backdrop-filter: blur(10px);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1);
justify-content: center;
}
.view-tab {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
gap: var(--spacing-2);
padding: 12px 20px;
font-size: var(--text-sm);
gap: 2px;
padding: 8px 12px;
font-size: 11px;
font-weight: var(--font-medium);
color: var(--text-tertiary);
background: none;
@@ -183,6 +197,7 @@
cursor: pointer;
transition: all var(--transition-default);
white-space: nowrap;
flex-shrink: 0;
}
/* Tab Icon */
@@ -207,7 +222,7 @@
/* Active State with Underline */
.view-tab.active {
color: var(--primary);
background: rgba(59, 130, 246, 0.1);
background: rgba(200, 168, 81, 0.1);
border-radius: var(--radius-md);
}
@@ -256,6 +271,9 @@
display: flex;
align-items: center;
gap: var(--spacing-2);
flex: 1 1 auto;
min-width: 150px;
max-width: 450px;
}
.search-icon {
@@ -266,8 +284,8 @@
}
.search-input {
width: 450px;
min-width: 350px;
width: 100%;
max-width: 450px;
padding: 10px 16px;
background: var(--bg-tertiary);
border: 1px solid transparent;
@@ -439,6 +457,23 @@
border-radius: var(--radius-full);
cursor: pointer;
transition: all var(--transition-fast);
position: relative;
}
.user-avatar::after {
content: '';
position: absolute;
bottom: 0;
right: 0;
width: 10px;
height: 10px;
background: var(--success);
border-radius: 50%;
border: 2px solid var(--bg-card);
}
.offline .user-avatar::after {
background: var(--error);
}
.user-avatar:hover {
@@ -447,16 +482,16 @@
}
.user-dropdown {
position: absolute;
top: calc(100% + 8px);
right: 0;
position: fixed;
top: 60px;
right: 20px;
min-width: 220px;
padding: var(--spacing-2);
background: var(--bg-card);
border: 1px solid var(--border-default);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
z-index: var(--z-dropdown);
z-index: 9999;
}
.user-info {
@@ -524,34 +559,125 @@
FILTER BAR
======================================== */
.filter-bar {
.filter-bar-actions {
display: flex;
gap: var(--spacing-2);
align-items: center;
justify-content: flex-start;
gap: var(--spacing-4);
padding: var(--spacing-3) var(--spacing-4);
background: var(--bg-card);
border-bottom: 1px solid var(--border-light);
flex-shrink: 0;
width: 100%;
}
.filter-group {
.filter-toggle-btn {
display: flex;
align-items: center;
gap: var(--spacing-2);
}
.filter-toggle-btn.has-filters {
color: var(--primary);
border-color: var(--primary);
background: var(--primary-light);
}
.filter-popover {
position: absolute;
top: 100%;
right: var(--spacing-4);
z-index: var(--z-dropdown);
min-width: 280px;
background: var(--bg-card);
border: 1px solid var(--border-light);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-lg);
padding: var(--spacing-4);
}
.filter-popover.hidden {
display: none;
}
.filter-popover-content {
display: flex;
flex-direction: column;
gap: var(--spacing-3);
}
.filter-group label {
font-size: var(--text-sm);
font-weight: var(--font-medium);
color: var(--text-tertiary);
.filter-popover-row {
display: flex;
flex-direction: column;
gap: var(--spacing-1);
}
.filter-actions {
.filter-popover-row label {
font-size: var(--text-xs);
font-weight: var(--font-semibold);
color: var(--text-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.filter-checkbox-list {
display: flex;
flex-direction: column;
gap: 2px;
max-height: 200px;
overflow-y: auto;
}
.filter-checkbox-item {
display: flex;
align-items: center;
gap: var(--spacing-2);
margin-left: auto;
padding: 6px 8px;
border-radius: var(--radius-md);
cursor: pointer;
font-size: var(--text-sm);
color: var(--text-primary);
}
.filter-checkbox-item:hover {
background: var(--bg-hover);
}
.filter-checkbox-item input[type="checkbox"] {
appearance: none;
-webkit-appearance: none;
width: 20px;
height: 20px;
min-width: 20px;
border: 1px solid var(--border-default);
border-radius: 4px;
background: var(--bg-tertiary);
cursor: pointer;
position: relative;
}
.filter-checkbox-item input[type="checkbox"]:hover {
border-color: var(--primary);
}
.filter-checkbox-item input[type="checkbox"]:checked {
background: var(--primary);
border-color: var(--primary);
}
.filter-checkbox-item input[type="checkbox"]:checked::after {
content: '';
position: absolute;
left: 6px;
top: 2px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 2px 2px 0;
transform: rotate(45deg);
}
.filter-popover-footer {
padding-top: var(--spacing-2);
border-top: 1px solid var(--border-light);
display: flex;
justify-content: flex-end;
margin-top: var(--spacing-2);
}
/* ========================================
@@ -637,26 +763,26 @@
background: var(--bg-main);
}
.view-board .filter-bar {
flex-shrink: 0;
}
.board {
display: flex;
gap: var(--spacing-3);
flex: 1;
min-height: 0;
overflow-x: auto;
overflow-y: hidden;
padding: var(--spacing-4);
align-items: flex-start;
width: 100%;
box-sizing: border-box;
}
/* Column */
.column {
display: flex;
flex-direction: column;
width: 280px;
min-width: 260px;
width: var(--column-width, 280px);
min-width: var(--column-min-width, 260px);
max-width: 100%;
max-height: calc(100vh - var(--header-height) - 160px);
background: var(--bg-card);
border: 1px solid var(--border-light);
@@ -697,7 +823,10 @@
text-transform: uppercase;
letter-spacing: 0.05em;
flex: 1;
padding-right: var(--spacing-8);
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.column-count {
@@ -716,12 +845,12 @@
.column-actions {
display: flex;
align-items: center;
gap: var(--spacing-1);
opacity: 0;
transition: opacity var(--transition-fast);
position: absolute;
top: var(--spacing-3);
right: var(--spacing-3);
flex-shrink: 0;
margin-left: var(--spacing-2);
}
.column-actions .btn-icon {
@@ -1276,75 +1405,3 @@
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);
}

Datei anzeigen

@@ -9,7 +9,7 @@
======================================== */
.view-calendar {
display: flex;
display: none;
flex-direction: column;
padding: var(--spacing-6);
gap: var(--spacing-4);
@@ -17,6 +17,10 @@
height: 100%;
}
.view-calendar.active {
display: flex !important;
}
/* Calendar Header */
.calendar-header {
display: flex;

Datei anzeigen

@@ -175,8 +175,8 @@
}
.git-status-badge.ahead {
background: rgba(59, 130, 246, 0.15);
color: #3B82F6;
background: rgba(124, 141, 181, 0.15);
color: var(--info);
}
.git-status-badge.behind {

Datei anzeigen

@@ -170,7 +170,7 @@ select {
width: 100%;
padding: 10px 14px;
font-family: var(--font-primary);
font-size: var(--text-sm);
font-size: var(--text-base); /* 16px - verhindert iOS Zoom bei Focus */
color: var(--text-primary);
background: var(--bg-input);
border: 1px solid var(--border-default);
@@ -604,7 +604,8 @@ input[type="color"] {
align-items: center;
gap: var(--spacing-3);
width: 100%;
padding: var(--spacing-2) var(--spacing-3);
padding: var(--spacing-3) var(--spacing-4);
min-height: 44px;
font-size: var(--text-sm);
color: var(--text-secondary);
background: none;

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@@ -796,7 +796,7 @@
.drop-zone.drag-over {
border-color: var(--primary);
background: rgba(59, 130, 246, 0.05);
background: rgba(200, 168, 81, 0.08);
color: var(--primary);
}

Datei anzeigen

@@ -287,10 +287,10 @@
background-color: transparent;
}
25%, 75% {
background-color: var(--primary-100, rgba(59, 130, 246, 0.1));
background-color: var(--primary-100, rgba(200, 168, 81, 0.1));
}
50% {
background-color: var(--primary-200, rgba(59, 130, 246, 0.2));
background-color: var(--primary-200, rgba(200, 168, 81, 0.2));
}
}

Datei anzeigen

@@ -13,7 +13,7 @@
}
.view-list.active {
display: flex;
display: flex !important;
}
/* List Header */

Datei anzeigen

@@ -71,6 +71,82 @@
transform: translateY(-8px) rotate(-45deg);
}
/* ========================================
MOBILE COLUMN INDICATOR
======================================== */
.mobile-column-indicator {
position: fixed;
bottom: 20px;
bottom: calc(20px + env(safe-area-inset-bottom, 0px));
left: 50%;
transform: translateX(-50%);
z-index: 100;
text-align: center;
pointer-events: none;
}
.column-dots {
display: flex;
gap: 8px;
justify-content: center;
margin-bottom: 8px;
}
.column-dots .dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--text-tertiary);
opacity: 0.3;
transition: all 0.3s ease;
}
.column-dots .dot.active {
background: var(--primary);
opacity: 1;
transform: scale(1.2);
}
.column-name {
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 8px 16px;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
}
/* Mobile view hint */
.mobile-view-hint {
position: fixed;
top: 50%;
transform: translateY(-50%);
z-index: 100;
background: rgba(0, 0, 0, 0.8);
color: white;
padding: 12px 20px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
opacity: 0;
pointer-events: none;
transition: all 0.3s ease;
}
.mobile-view-hint.visible {
opacity: 1;
}
.mobile-view-hint.left {
left: 20px;
}
.mobile-view-hint.right {
right: 20px;
}
/* ========================================
MOBILE SLIDE-IN MENU
======================================== */
@@ -436,6 +512,221 @@
min-height: 44px;
min-width: 44px;
}
/* ========================================
MOBILE BOARD OPTIMIERUNG
======================================== */
/* Task Cards - Groesser und besser lesbar */
.task-card {
padding: var(--spacing-4);
border-radius: var(--radius-xl);
}
.task-title {
font-size: 1rem;
line-height: 1.4;
font-weight: var(--font-semibold);
}
.task-card-meta {
font-size: var(--text-sm);
gap: var(--spacing-4);
margin-top: var(--spacing-4);
}
.task-meta-item {
gap: 6px;
}
.task-meta-item .icon {
width: 18px;
height: 18px;
}
.task-card-labels {
gap: 8px;
margin-top: var(--spacing-4);
}
.task-label {
padding: 6px 12px;
font-size: var(--text-sm);
}
.task-card-footer {
margin-top: var(--spacing-4);
padding-top: var(--spacing-4);
}
.task-assignee-avatar {
width: 32px;
height: 32px;
font-size: 12px;
}
.task-counts {
font-size: var(--text-sm);
gap: var(--spacing-4);
}
/* Priority Stars groesser */
.priority-stars {
font-size: 16px;
}
/* Column Header */
.column-header {
padding: var(--spacing-4);
}
.column-title {
font-size: var(--text-base);
}
.column-count {
min-width: 28px;
height: 28px;
font-size: var(--text-sm);
}
/* Column Body */
.column-body {
padding: var(--spacing-3);
gap: var(--spacing-3);
}
/* Add Task Button */
.btn-add-task {
padding: var(--spacing-4);
font-size: var(--text-base);
min-height: 52px;
}
/* Filter Bar */
.filter-bar {
padding: var(--spacing-4);
gap: var(--spacing-3);
}
.filter-select {
min-height: 44px;
font-size: var(--text-base);
padding: var(--spacing-3) var(--spacing-4);
}
/* Stats Bar */
.board-stats-bar {
padding: var(--spacing-3) var(--spacing-4);
gap: var(--spacing-4);
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.board-stat {
flex-shrink: 0;
}
.board-stat-icon {
width: 36px;
height: 36px;
}
.board-stat-icon svg {
width: 20px;
height: 20px;
}
.board-stat-value {
font-size: var(--text-lg);
}
.board-stat-label {
font-size: var(--text-sm);
}
/* Week Strip */
.week-strip {
padding: var(--spacing-3) var(--spacing-4);
}
.week-strip-nav {
width: 40px;
height: 40px;
}
.week-strip-nav svg {
width: 20px;
height: 20px;
}
.week-strip-day {
padding: var(--spacing-2) var(--spacing-3);
}
.week-strip-day-name {
font-size: 12px;
}
.week-strip-day-number {
font-size: var(--text-lg);
}
.week-strip-today {
font-size: var(--text-sm);
padding: var(--spacing-2) var(--spacing-3);
min-height: 40px;
}
/* Board Padding */
.board {
padding: var(--spacing-3);
gap: var(--spacing-3);
}
/* Modal auf Mobile */
.modal-body {
padding: var(--spacing-5);
}
.form-group label {
font-size: var(--text-base);
margin-bottom: var(--spacing-3);
}
/* Subtasks groesser */
.subtask-item {
padding: var(--spacing-3) var(--spacing-4);
min-height: 48px;
}
.subtask-title {
font-size: var(--text-base);
}
/* Comments groesser */
.comment-content {
padding: var(--spacing-4);
}
.comment-text {
font-size: var(--text-base);
line-height: 1.6;
}
/* Attachments */
.attachment-name {
font-size: var(--text-base);
}
/* Links */
.link-item {
padding: var(--spacing-4);
}
.link-title {
font-size: var(--text-base);
}
}
/* ========================================
@@ -469,4 +760,150 @@
.hamburger-btn.active .hamburger-line:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
}
/* ========================================
EXTRA SMALL: NOCH GROESSERE ELEMENTE
======================================== */
/* Task Cards noch groesser */
.task-card {
padding: var(--spacing-5);
}
.task-title {
font-size: 1.125rem;
line-height: 1.5;
}
.task-card-meta {
font-size: var(--text-base);
}
.task-label {
padding: 8px 14px;
font-size: var(--text-base);
}
/* Column */
.column-header {
padding: var(--spacing-4) var(--spacing-5);
}
.column-title {
font-size: var(--text-lg);
}
.column-body {
padding: var(--spacing-4);
gap: var(--spacing-4);
}
/* Filter - vertikal auf sehr kleinen Screens */
.filter-bar {
flex-direction: column;
align-items: stretch;
}
.filter-group {
flex-direction: column;
align-items: stretch;
gap: var(--spacing-3);
}
.filter-group label {
display: none;
}
.filter-select {
width: 100%;
min-height: 48px;
}
.filter-actions {
margin-left: 0;
justify-content: center;
flex-wrap: wrap;
}
/* Stats Bar - scrollbar horizontal */
.board-stats-bar {
justify-content: flex-start;
gap: var(--spacing-5);
padding: var(--spacing-4);
}
.board-stat-icon {
width: 40px;
height: 40px;
}
.board-stat-value {
font-size: var(--text-xl);
}
/* Week Strip kompakter */
.week-strip {
padding: var(--spacing-2) var(--spacing-3);
}
.week-strip-day {
padding: var(--spacing-2);
}
.week-strip-day-name {
font-size: 11px;
}
.week-strip-day-number {
font-size: var(--text-base);
}
/* Login Screen */
.login-container {
padding: var(--spacing-6);
}
.login-header h1 {
font-size: var(--text-xl);
}
.login-form .form-group label {
font-size: var(--text-base);
}
/* Modals */
.modal {
width: 100%;
max-width: 100%;
height: 100%;
max-height: 100%;
border-radius: 0;
}
.modal-header {
padding: var(--spacing-4);
}
.modal-header h2 {
font-size: var(--text-lg);
}
.modal-body {
padding: var(--spacing-4);
}
.modal-footer {
padding: var(--spacing-4);
}
/* Form Groups */
.form-row {
grid-template-columns: 1fr;
}
/* Add Task Button */
.btn-add-task {
min-height: 56px;
font-size: var(--text-lg);
}
}

Datei anzeigen

@@ -77,8 +77,8 @@
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
width: 44px;
height: 44px;
font-size: 20px;
color: var(--text-muted);
background: none;
@@ -770,7 +770,9 @@
.lightbox-close {
position: absolute;
top: var(--spacing-6);
top: calc(var(--spacing-6) + env(safe-area-inset-top, 0px));
right: var(--spacing-6);
right: calc(var(--spacing-6) + env(safe-area-inset-right, 0px));
width: 48px;
height: 48px;
font-size: 32px;

Datei anzeigen

@@ -59,16 +59,16 @@
============================================================================= */
.notification-dropdown {
position: absolute;
top: calc(100% + 8px);
right: 0;
position: fixed;
top: 60px;
right: 20px;
width: 380px;
max-height: 520px;
background: var(--bg-card);
border: 1px solid var(--border-default);
border-radius: var(--radius-xl);
box-shadow: var(--shadow-xl);
z-index: var(--z-dropdown);
z-index: 10001;
overflow: hidden;
display: flex;
flex-direction: column;

Datei anzeigen

@@ -175,7 +175,7 @@
.reminder-number-input:focus {
border-color: var(--primary);
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
box-shadow: var(--shadow-focus);
}
.reminder-unit-select {
@@ -200,7 +200,7 @@
.reminder-unit-select:focus {
border-color: var(--primary);
outline: none;
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
box-shadow: var(--shadow-focus);
}
.reminder-advance-suffix {
@@ -293,7 +293,7 @@
.custom-select-trigger.active {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
box-shadow: var(--shadow-focus);
}
.custom-select-value {
@@ -477,7 +477,7 @@
border-color: #1d4ed8;
color: white !important;
transform: translateY(-1px);
box-shadow: 0 3px 8px rgba(59, 130, 246, 0.4);
box-shadow: 0 3px 8px rgba(200, 168, 81, 0.4);
}
#reminder-modal .btn-secondary {

Datei anzeigen

@@ -4,6 +4,57 @@
* Mobile und Tablet Anpassungen
*/
/* ========================================
GLOBALE RESPONSIVE ANPASSUNGEN
======================================== */
/* Verhindere horizontales Scrollen */
html {
overflow-x: hidden;
width: 100%;
}
body {
overflow-x: hidden;
width: 100%;
margin: 0;
padding: 0;
}
/* App Container */
.app {
width: 100%;
max-width: 100%;
overflow-x: hidden;
position: relative;
}
/* Hauptcontainer */
.main {
width: 100%;
max-width: 100%;
overflow-x: hidden;
position: relative;
}
/* Alle Views */
.view {
width: 100%;
max-width: 100%;
overflow-x: hidden;
box-sizing: border-box;
}
/* Verhindere Überlauf bei allen Elementen */
* {
max-width: 100%;
}
/* Responsive Box-Sizing für alle Elemente */
* {
box-sizing: border-box;
}
/* ========================================
TABLET (max 1024px)
======================================== */
@@ -31,33 +82,91 @@
}
}
/* ========================================
MEDIUM SCREENS (max 1200px)
======================================== */
@media (max-width: 1200px) {
/* Header-Anpassungen für mittlere Bildschirme */
.header {
padding: var(--spacing-sm) var(--spacing-md);
}
.header-left {
gap: var(--spacing-sm);
}
.header-right {
gap: var(--spacing-sm);
}
/* Project Selector schmaler */
.project-select {
max-width: 200px;
}
/* Search Container schmaler */
.search-container {
max-width: 200px;
}
/* Navigation tabs - kompakter und zentriert bei mittleren Bildschirmen */
.view-tabs {
gap: var(--spacing-xs);
flex-wrap: nowrap;
justify-content: center;
}
.view-tab {
padding: 6px 8px;
font-size: 11px;
flex-shrink: 0;
}
/* Modals anpassen */
.modal {
margin: var(--spacing-md);
}
.modal-large {
max-width: calc(100vw - 32px);
}
/* Tabellen responsiv */
table {
display: block;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
}
/* ========================================
SMALL TABLET (max 768px)
======================================== */
@media (max-width: 768px) {
.header {
flex-wrap: wrap;
height: auto;
padding: var(--spacing-sm) var(--spacing-md);
gap: var(--spacing-sm);
display: flex;
align-items: center;
height: 60px;
padding: 0 var(--spacing-sm);
gap: var(--spacing-xs);
overflow: hidden;
}
.header-left {
flex: 1;
order: 1;
flex: 0 0 auto;
display: flex;
align-items: center;
gap: var(--spacing-xs);
}
.header-center {
order: 3;
width: 100%;
justify-content: center;
padding-top: var(--spacing-sm);
border-top: 1px solid var(--border-default);
.view-tabs-bar {
display: none; /* Navigation wird ins Mobile Menu verschoben */
}
.header-right {
order: 2;
flex: 0 0 auto;
}
.logo {
@@ -72,28 +181,18 @@
display: none;
}
.view-tabs {
width: 100%;
justify-content: center;
/* Mobile Navigation wird über Hamburger Menu gesteuert */
.mobile-menu {
display: block;
}
/* Logo kleiner auf Mobile */
.logo {
font-size: var(--text-lg);
}
.view-tab {
flex: 1;
text-align: center;
}
.filter-bar {
flex-direction: column;
gap: var(--spacing-md);
}
.filter-group {
flex-wrap: wrap;
justify-content: center;
}
.filter-actions {
justify-content: center;
.filter-bar-actions {
display: none;
}
.view-board {
@@ -101,16 +200,119 @@
gap: var(--spacing-md);
}
/* Mobile Board - One column at a time */
.board-container {
overflow-x: hidden !important;
scroll-snap-type: none !important;
width: 100%;
padding: 0;
}
.column {
width: calc(100vw - 32px);
min-width: calc(100vw - 32px);
max-height: none;
width: 100%;
min-width: unset;
max-width: 100%;
max-height: calc(100vh - 200px);
margin: 0;
padding: var(--spacing-sm);
box-sizing: border-box;
}
.column.mobile-active {
display: flex !important;
}
.btn-add-column {
width: calc(100vw - 32px);
min-width: calc(100vw - 32px);
width: 100%;
min-width: unset;
min-height: 100px;
margin: 0;
box-sizing: border-box;
}
/* Hide scrollbar in mobile board */
.board-container::-webkit-scrollbar {
display: none;
}
/* Fix for views in mobile */
.view.active {
display: flex !important;
}
/* List view mobile adjustments */
.view-list.active {
padding: var(--spacing-sm);
}
.list-header {
flex-direction: column;
align-items: stretch;
gap: var(--spacing-md);
padding: var(--spacing-md);
}
.list-controls {
flex-direction: column;
align-items: stretch;
}
.list-view-toggle {
justify-content: center;
}
.list-sort {
margin-left: 0;
justify-content: space-between;
}
/* Table responsive */
.list-table-container {
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
.list-table {
min-width: 600px;
}
/* Calendar view mobile adjustments */
.view-calendar {
padding: var(--spacing-sm);
}
.calendar-header {
flex-direction: column;
gap: var(--spacing-md);
padding: var(--spacing-md);
}
.calendar-nav {
width: 100%;
justify-content: space-between;
}
.calendar-actions {
width: 100%;
justify-content: center;
}
.calendar-grid {
font-size: var(--text-xs);
}
.calendar-day-header {
padding: var(--spacing-sm);
}
.calendar-day {
min-height: 60px;
padding: var(--spacing-xs);
}
.calendar-task {
font-size: 9px;
padding: 1px 2px;
}
.modal {
@@ -264,6 +466,7 @@
left: var(--spacing-md);
right: var(--spacing-md);
bottom: var(--spacing-md);
bottom: calc(var(--spacing-md) + env(safe-area-inset-bottom, 0px));
}
.toast {
@@ -291,7 +494,7 @@
@media print {
.header,
.filter-bar,
.view-tabs-bar,
.btn-add-column,
.column-actions,
.btn-add-task,

Datei anzeigen

@@ -9,26 +9,26 @@
FARBEN - Modernes Light Theme
======================================== */
/* Primärfarben */
--primary: #4F46E5;
--primary-hover: #4338CA;
--primary-light: #EEF2FF;
--accent: #06B6D4;
--accent-hover: #0891B2;
/* Primärfarben - AegisSight Light Theme */
--primary: #C8A851;
--primary-hover: #B5923E;
--primary-light: rgba(200, 168, 81, 0.12);
--accent: #C8A851;
--accent-hover: #B5923E;
/* Hintergründe */
--bg-main: #F8FAFC;
--bg-main: #E8EDF6;
--bg-secondary: #FFFFFF;
--bg-tertiary: #F1F5F9;
--bg-hover: #E2E8F0;
--bg-active: #CBD5E1;
--bg-tertiary: #F1F3F9;
--bg-hover: #F8F9FC;
--bg-active: #D0D6DE;
--bg-sidebar: #FFFFFF;
--bg-card: #FFFFFF;
--bg-input: #FFFFFF;
/* Textfarben */
--text-primary: #0F172A;
--text-secondary: #475569;
--text-primary: #0A1832;
--text-secondary: #64748B;
--text-tertiary: #64748B;
--text-muted: #94A3B8;
--text-placeholder: #94A3B8;
@@ -44,9 +44,9 @@
--error: #EF4444;
--error-bg: #FEE2E2;
--error-text: #991B1B;
--info: #3B82F6;
--info-bg: #DBEAFE;
--info-text: #1E40AF;
--info: #7C8DB5;
--info-bg: rgba(124, 141, 181, 0.12);
--info-text: #4A5568;
/* Prioritätsfarben */
--priority-high: #EF4444;
@@ -60,7 +60,7 @@
--border-light: #F1F5F9;
--border-default: #E2E8F0;
--border-dark: #CBD5E1;
--border-focus: #4F46E5;
--border-focus: #C8A851;
/* Schatten */
--shadow-xs: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
@@ -68,12 +68,12 @@
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -2px rgba(0, 0, 0, 0.1);
--shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
--shadow-xl: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
--shadow-focus: 0 0 0 3px rgba(79, 70, 229, 0.2);
--shadow-focus: 0 0 0 3px rgba(200, 168, 81, 0.25);
/* Scrollbar */
--scrollbar-bg: #F1F5F9;
--scrollbar-thumb: #CBD5E1;
--scrollbar-thumb-hover: #94A3B8;
--scrollbar-bg: #E8EDF6;
--scrollbar-thumb: #94A3B8;
--scrollbar-thumb-hover: #64748B;
/* Overlay */
--overlay-bg: rgba(15, 23, 42, 0.5);
@@ -158,7 +158,7 @@
Z-INDEX
======================================== */
--z-dropdown: 100;
--z-dropdown: 300;
--z-sticky: 200;
--z-modal-overlay: 900;
--z-modal: 1000;

Datei anzeigen

@@ -38,6 +38,7 @@
<link rel="stylesheet" href="css/knowledge.css">
<link rel="stylesheet" href="css/reminders.css">
<link rel="stylesheet" href="css/contacts.css">
<link rel="stylesheet" href="css/contacts-modern.css">
<link rel="stylesheet" href="css/responsive.css">
<link rel="stylesheet" href="css/mobile.css">
<link rel="stylesheet" href="css/pwa.css">
@@ -182,77 +183,9 @@
</div>
</div>
<div class="header-center">
<!-- View Tabs -->
<nav class="view-tabs">
<button class="view-tab active" data-view="board">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7" rx="1"/>
<rect x="14" y="3" width="7" height="7" rx="1"/>
<rect x="3" y="14" width="7" height="7" rx="1"/>
<rect x="14" y="14" width="7" height="7" rx="1"/>
</svg>
Board
</button>
<button class="view-tab" data-view="list">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="8" y1="6" x2="21" y2="6"/>
<line x1="8" y1="12" x2="21" y2="12"/>
<line x1="8" y1="18" x2="21" y2="18"/>
<circle cx="3.5" cy="6" r="1" fill="currentColor"/>
<circle cx="3.5" cy="12" r="1" fill="currentColor"/>
<circle cx="3.5" cy="18" r="1" fill="currentColor"/>
</svg>
Liste
</button>
<button class="view-tab" data-view="calendar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
<circle cx="12" cy="16" r="1" fill="currentColor"/>
</svg>
Kalender
</button>
<button class="view-tab" data-view="proposals">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 11l3 3L22 4"/>
<path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>
</svg>
Genehmigung
</button>
<button class="view-tab" data-view="coding">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="16 18 22 12 16 6"/>
<polyline points="8 6 2 12 8 18"/>
<line x1="12" y1="20" x2="12" y2="4" transform="rotate(-15 12 12)"/>
</svg>
Coding
</button>
<button class="view-tab" data-view="knowledge">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
</svg>
Wissen
</button>
<button class="view-tab" data-view="contacts">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
Kontakte
</button>
</nav>
</div>
<div class="header-right">
<!-- Search -->
<div class="search-container">
<svg class="search-icon" viewBox="0 0 24 24"><circle cx="11" cy="11" r="8" stroke="currentColor" stroke-width="2" fill="none"/><path d="m21 21-4.35-4.35" stroke="currentColor" stroke-width="2"/></svg>
<input type="text" id="search-input" class="search-input" placeholder="Suchen...">
<button type="button" id="search-clear" class="search-clear hidden" title="Suche zurücksetzen (Esc)">
<svg viewBox="0 0 24 24" width="16" height="16"><path d="M18 6L6 18M6 6l12 12" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
@@ -260,12 +193,6 @@
<div id="search-spinner" class="search-spinner hidden"></div>
</div>
<!-- Connection Status -->
<div id="connection-status" class="connection-status online" title="Verbunden">
<span class="status-dot"></span>
<span class="status-text">Online</span>
</div>
<!-- Notification Bell -->
<div class="notification-bell" id="notification-bell">
<button id="notification-btn" class="btn btn-icon" title="Benachrichtigungen">
@@ -321,6 +248,135 @@
</div>
</header>
<nav class="view-tabs-bar">
<nav class="view-tabs">
<button class="view-tab active" data-view="board">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7" rx="1"/>
<rect x="14" y="3" width="7" height="7" rx="1"/>
<rect x="3" y="14" width="7" height="7" rx="1"/>
<rect x="14" y="14" width="7" height="7" rx="1"/>
</svg>
Board
</button>
<button class="view-tab" data-view="list">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<line x1="8" y1="6" x2="21" y2="6"/>
<line x1="8" y1="12" x2="21" y2="12"/>
<line x1="8" y1="18" x2="21" y2="18"/>
<circle cx="3.5" cy="6" r="1" fill="currentColor"/>
<circle cx="3.5" cy="12" r="1" fill="currentColor"/>
<circle cx="3.5" cy="18" r="1" fill="currentColor"/>
</svg>
Liste
</button>
<button class="view-tab" data-view="calendar">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="4" width="18" height="18" rx="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
<circle cx="12" cy="16" r="1" fill="currentColor"/>
</svg>
Kalender
</button>
<button class="view-tab" data-view="proposals">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M9 11l3 3L22 4"/>
<path d="M21 12v7a2 2 0 01-2 2H5a2 2 0 01-2-2V5a2 2 0 012-2h11"/>
</svg>
Genehmigung
</button>
<button class="view-tab" data-view="coding">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polyline points="16 18 22 12 16 6"/>
<polyline points="8 6 2 12 8 18"/>
<line x1="12" y1="20" x2="12" y2="4" transform="rotate(-15 12 12)"/>
</svg>
Coding
</button>
<button class="view-tab" data-view="knowledge">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/>
<path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
</svg>
Wissen
</button>
<button class="view-tab" data-view="contacts">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/>
<circle cx="9" cy="7" r="4"/>
<path d="M23 21v-2a4 4 0 0 0-3-3.87"/>
<path d="M16 3.13a4 4 0 0 1 0 7.75"/>
</svg>
Kontakte
</button>
</nav>
<div class="filter-bar-actions">
<button id="btn-filter-toggle" class="btn btn-outline filter-toggle-btn">
<svg class="icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="22 3 2 3 10 12.46 10 19 14 21 14 12.46 22 3"></polygon></svg>
<span>Filter</span>
</button>
<button id="btn-show-archived" class="btn btn-outline filter-toggle-btn">
<svg class="icon" viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="3" width="20" height="5" rx="1"></rect><path d="M4 8v11a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8"></path><path d="M10 12h4"></path></svg>
<span>Archiv</span>
</button>
</div>
<div id="filter-popover" class="filter-popover hidden">
<div class="filter-popover-content">
<div class="filter-popover-row">
<label>Bearbeiter</label>
<div id="filter-assignee-list" class="filter-checkbox-list"></div>
</div>
<div class="filter-popover-row">
<label>Prioritaet</label>
<div id="filter-priority-list" class="filter-checkbox-list">
<label class="filter-checkbox-item">
<input type="checkbox" name="filter-priority" value="high">
<span>Hoch</span>
</label>
<label class="filter-checkbox-item">
<input type="checkbox" name="filter-priority" value="medium">
<span>Mittel</span>
</label>
<label class="filter-checkbox-item">
<input type="checkbox" name="filter-priority" value="low">
<span>Niedrig</span>
</label>
</div>
</div>
<div class="filter-popover-row">
<label>Labels</label>
<div id="filter-labels-list" class="filter-checkbox-list"></div>
</div>
<div class="filter-popover-row">
<label>Faelligkeit</label>
<div id="filter-due-list" class="filter-checkbox-list">
<label class="filter-checkbox-item">
<input type="checkbox" name="filter-due" value="overdue">
<span>Ueberfaellig</span>
</label>
<label class="filter-checkbox-item">
<input type="checkbox" name="filter-due" value="today">
<span>Heute</span>
</label>
<label class="filter-checkbox-item">
<input type="checkbox" name="filter-due" value="week">
<span>Diese Woche</span>
</label>
<label class="filter-checkbox-item">
<input type="checkbox" name="filter-due" value="none">
<span>Ohne Datum</span>
</label>
</div>
</div>
</div>
<div class="filter-popover-footer">
<button id="btn-clear-filters" class="btn btn-text">Filter zurücksetzen</button>
</div>
</div>
</nav>
<!-- Offline Banner -->
<div id="offline-banner" class="offline-banner hidden">
<svg class="icon" viewBox="0 0 24 24"><path d="M1 1l22 22M16.72 11.06A10.94 10.94 0 0 1 19 12.55M5 12.55a10.94 10.94 0 0 1 5.17-2.39M10.71 5.05A16 16 0 0 1 22.58 9M1.42 9a15.91 15.91 0 0 1 4.7-2.88M8.53 16.11a6 6 0 0 1 6.95 0M12 20h.01" stroke="currentColor" stroke-width="2" fill="none"/></svg>
@@ -332,43 +388,8 @@
<!-- Board View -->
<div id="view-board" class="view view-board active">
<!-- Filter Bar -->
<div class="filter-bar">
<div class="filter-group">
<label>Filter:</label>
<select id="filter-assignee" class="filter-select">
<option value="">Alle Benutzer</option>
</select>
<select id="filter-priority" class="filter-select">
<option value="">Alle Prioritäten</option>
<option value="high">Hoch</option>
<option value="medium">Mittel</option>
<option value="low">Niedrig</option>
</select>
<select id="filter-labels" class="filter-select">
<option value="">Alle Labels</option>
</select>
<select id="filter-due" class="filter-select">
<option value="">Alle Fälligkeiten</option>
<option value="overdue">Überfällig</option>
<option value="today">Heute</option>
<option value="week">Diese Woche</option>
<option value="none">Ohne Datum</option>
</select>
</div>
<div class="filter-actions">
<button id="btn-toggle-layout" class="btn btn-icon" title="Layout umschalten">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<rect x="3" y="3" width="7" height="7"/>
<rect x="14" y="3" width="7" height="7"/>
<rect x="3" y="14" width="7" height="7"/>
<rect x="14" y="14" width="7" height="7"/>
</svg>
</button>
<button id="btn-clear-filters" class="btn btn-text">Filter zurücksetzen</button>
<button id="btn-show-archived" class="btn btn-text">Archiv anzeigen</button>
</div>
</div>
<!-- Week Strip Calendar -->
<div id="week-strip" class="week-strip">
@@ -1051,6 +1072,26 @@
</div>
</div>
<!-- Column Select Modal -->
<div id="column-select-modal" class="modal modal-small hidden">
<div class="modal-header">
<h2>Spalte auswählen</h2>
<button class="modal-close" data-close-modal>&times;</button>
</div>
<div class="modal-body">
<p id="column-select-message"></p>
<div class="form-group" style="margin-top: 1rem;">
<select id="column-select-dropdown" class="form-control">
<!-- Spalten werden dynamisch eingefügt -->
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" id="column-select-cancel" class="btn btn-secondary">Abbrechen</button>
<button type="button" id="column-select-ok" class="btn btn-primary">Wiederherstellen</button>
</div>
</div>
<!-- Archive Modal -->
<div id="archive-modal" class="modal modal-large hidden">
<div class="modal-header">
@@ -2093,6 +2134,9 @@
<!-- Socket.io Client -->
<script src="/socket.io/socket.io.js"></script>
<!-- Auth Fix -->
<script src="js/auth-fix.js"></script>
<!-- Main App (ES Module) -->
<script type="module" src="js/app.js"></script>

Datei anzeigen

@@ -570,8 +570,10 @@ class ApiClient {
return this.put(`/tasks/${taskId}/archive`, { archived: true });
}
async restoreTask(projectId, taskId) {
return this.put(`/tasks/${taskId}/archive`, { archived: false });
async restoreTask(projectId, taskId, columnId = null) {
const data = { archived: false };
if (columnId) data.columnId = columnId;
return this.put(`/tasks/${taskId}/archive`, data);
}
async getTaskHistory(projectId, taskId) {

Datei anzeigen

@@ -263,27 +263,33 @@ class App {
// Search - Hybrid search with client-side filtering and server search
this.setupSearch();
// Filter changes
$('#filter-priority')?.addEventListener('change', (e) => {
store.setFilter('priority', e.target.value);
// Filter toggle popover
$('#btn-filter-toggle')?.addEventListener('click', (e) => {
e.stopPropagation();
$('#filter-popover')?.classList.toggle('hidden');
});
$('#filter-assignee')?.addEventListener('change', (e) => {
store.setFilter('assignee', e.target.value);
// Close popover on click outside
document.addEventListener('click', (e) => {
const popover = $('#filter-popover');
const toggleBtn = $('#btn-filter-toggle');
if (popover && !popover.contains(e.target) && !toggleBtn?.contains(e.target)) {
popover.classList.add('hidden');
}
});
$('#filter-labels')?.addEventListener('change', (e) => {
store.setFilter('label', e.target.value);
});
$('#filter-due')?.addEventListener('change', (e) => {
store.setFilter('dueDate', e.target.value);
});
// Filter changes - checkbox listeners for static filters
$('#filter-priority-list')?.addEventListener('change', () => this.applyCheckboxFilters());
$('#filter-due-list')?.addEventListener('change', () => this.applyCheckboxFilters());
$('#filter-assignee-list')?.addEventListener('change', () => this.applyCheckboxFilters());
$('#filter-labels-list')?.addEventListener('change', () => this.applyCheckboxFilters());
// Reset filters
$('#btn-reset-filters')?.addEventListener('click', () => {
$('#btn-clear-filters')?.addEventListener('click', () => {
store.resetFilters();
this.resetFilterInputs();
this.updateFilterButtonState();
$('#filter-popover')?.classList.add('hidden');
});
// Open archive modal
@@ -301,6 +307,9 @@ class App {
// Confirm dialog events
window.addEventListener('confirm:show', (e) => this.showConfirmDialog(e.detail));
// Column select dialog events
window.addEventListener('column-select:show', (e) => this.showColumnSelectDialog(e.detail));
// Lightbox events
window.addEventListener('lightbox:open', (e) => this.openLightbox(e.detail));
@@ -702,11 +711,13 @@ class App {
// Initialize contacts view when switching to it
if (view === 'contacts') {
window.initContactsPromise = window.initContactsPromise || import('./contacts.js').then(module => {
if (module.initContacts) {
return module.initContacts();
if (module.contactsManager && !module.contactsManager.isInitialized) {
return module.contactsManager.init();
}
});
window.initContactsPromise.catch(console.error);
window.initContactsPromise.catch(error => {
console.error('[App] Contacts module error:', error);
});
}
// Render list view when switching to it (delayed to ensure store is updated)
@@ -752,65 +763,114 @@ class App {
}
populateAssigneeFilter() {
const select = $('#filter-assignee');
if (!select) return;
const list = $('#filter-assignee-list');
if (!list) return;
const users = store.get('users');
select.innerHTML = '<option value="all">Alle Bearbeiter</option>';
list.innerHTML = '';
users.forEach(user => {
const option = document.createElement('option');
option.value = user.id;
option.textContent = user.displayName || user.email || 'Unbekannt';
select.appendChild(option);
const label = document.createElement('label');
label.className = 'filter-checkbox-item';
label.innerHTML = `<input type="checkbox" name="filter-assignee" value="${user.id}"><span>${user.displayName || user.email || 'Unbekannt'}</span>`;
list.appendChild(label);
});
}
populateLabelFilter() {
const select = $('#filter-labels');
if (!select) return;
const list = $('#filter-labels-list');
if (!list) return;
const labels = store.get('labels');
select.innerHTML = '<option value="all">Alle Labels</option>';
list.innerHTML = '';
labels.forEach(label => {
const option = document.createElement('option');
option.value = label.id;
option.textContent = label.name;
select.appendChild(option);
const item = document.createElement('label');
item.className = 'filter-checkbox-item';
item.innerHTML = `<input type="checkbox" name="filter-labels" value="${label.id}"><span>${label.name}</span>`;
list.appendChild(item);
});
}
applyCheckboxFilters() {
const getCheckedValues = (name) => {
const checkboxes = document.querySelectorAll(`input[name="${name}"]:checked`);
return Array.from(checkboxes).map(cb => cb.value);
};
const priority = getCheckedValues('filter-priority');
const assignee = getCheckedValues('filter-assignee');
const label = getCheckedValues('filter-labels');
const dueDate = getCheckedValues('filter-due');
store.setFilter('priority', priority.length > 0 ? priority : '');
store.setFilter('assignee', assignee.length > 0 ? assignee : '');
store.setFilter('label', label.length > 0 ? label : '');
store.setFilter('dueDate', dueDate.length > 0 ? dueDate : '');
this.updateFilterButtonState();
}
resetFilterInputs() {
const priority = $('#filter-priority');
const assignee = $('#filter-assignee');
const labels = $('#filter-labels');
const dueDate = $('#filter-due');
const checkboxes = document.querySelectorAll('#filter-popover input[type="checkbox"]');
checkboxes.forEach(cb => cb.checked = false);
const search = $('#search-input');
const searchClear = $('#search-clear');
const searchContainer = $('.search-container');
if (priority) priority.value = 'all';
if (assignee) assignee.value = 'all';
if (labels) labels.value = 'all';
if (dueDate) dueDate.value = '';
if (search) search.value = '';
if (searchClear) searchClear.classList.add('hidden');
if (searchContainer) searchContainer.classList.remove('has-search');
}
openArchiveModal() {
this.handleModalOpen({ modalId: 'archive-modal' });
this.renderArchiveList();
updateFilterButtonState() {
const btn = $('#btn-filter-toggle');
if (!btn) return;
const groups = ['filter-priority', 'filter-assignee', 'filter-labels', 'filter-due'];
const activeCount = groups.filter(name => {
return document.querySelectorAll(`input[name="${name}"]:checked`).length > 0;
}).length;
const label = btn.querySelector('span');
if (label) {
label.textContent = activeCount > 0 ? `Filter (${activeCount})` : 'Filter';
}
btn.classList.toggle('has-filters', activeCount > 0);
}
renderArchiveList() {
async openArchiveModal() {
this.handleModalOpen({ modalId: 'archive-modal' });
const archiveList = $('#archive-list');
const archiveEmpty = $('#archive-empty');
// Ladeindikator anzeigen
if (archiveList) {
archiveList.classList.remove('hidden');
archiveList.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--text-secondary)">Lade archivierte Aufgaben...</div>';
}
if (archiveEmpty) archiveEmpty.classList.add('hidden');
try {
const projectId = store.get('currentProjectId');
const allTasks = await api.getTasks(projectId, { archived: true });
const archivedTasks = allTasks.filter(t => t.archived);
this.renderArchiveList(archivedTasks);
} catch (error) {
console.error('Failed to load archived tasks:', error);
if (archiveList) {
archiveList.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--danger)">Fehler beim Laden der archivierten Aufgaben</div>';
}
}
}
renderArchiveList(tasks) {
const archiveList = $('#archive-list');
const archiveEmpty = $('#archive-empty');
if (!archiveList) return;
// Get all archived tasks
const tasks = store.get('tasks').filter(t => t.archived);
const columns = store.get('columns');
if (tasks.length === 0) {
@@ -855,13 +915,16 @@ class App {
const projectId = store.get('currentProjectId');
await api.restoreTask(projectId, taskId);
store.updateTask(taskId, { archived: false });
this.renderArchiveList();
this.showSuccess('Aufgabe wiederhergestellt');
// Re-render board
if (this.board) {
this.board.render();
}
// Archivliste neu laden
const allTasks = await api.getTasks(projectId, { archived: true });
this.renderArchiveList(allTasks.filter(t => t.archived));
} catch (error) {
console.error('Restore error:', error);
this.showError('Fehler beim Wiederherstellen');
@@ -889,8 +952,11 @@ class App {
const projectId = store.get('currentProjectId');
await api.deleteTask(projectId, taskId);
store.removeTask(taskId);
this.renderArchiveList();
this.showSuccess('Aufgabe gelöscht');
// Archivliste neu laden
const allTasks = await api.getTasks(projectId, { archived: true });
this.renderArchiveList(allTasks.filter(t => t.archived));
} catch (error) {
this.showError('Fehler beim Löschen');
}
@@ -944,9 +1010,9 @@ class App {
import('./contacts.js').then(module => {
if (module.contactsManager) {
module.contactsManager.searchQuery = '';
module.contactsManager.filterContacts();
module.contactsManager.loadContacts();
}
});
}).catch(() => {});
// Cancel any pending server search
if (searchAbortController) {
@@ -1027,9 +1093,9 @@ class App {
import('./contacts.js').then(module => {
if (module.contactsManager) {
module.contactsManager.searchQuery = value;
module.contactsManager.filterContacts();
module.contactsManager.loadContacts();
}
});
}).catch(() => {});
} else {
// Immediate client-side filtering for tasks
store.setFilter('search', value);
@@ -1283,6 +1349,55 @@ class App {
cancelBtn?.addEventListener('click', handleCancel);
}
// =====================
// COLUMN SELECT DIALOG
// =====================
showColumnSelectDialog({ message, columns, onSelect }) {
const modal = $('#column-select-modal');
const messageEl = $('#column-select-message');
const dropdown = $('#column-select-dropdown');
const confirmBtn = $('#column-select-ok');
const cancelBtn = $('#column-select-cancel');
if (messageEl) messageEl.textContent = message;
// Spalten in Dropdown einfügen
if (dropdown) {
dropdown.innerHTML = '';
columns.forEach(column => {
const option = document.createElement('option');
option.value = column.id;
option.textContent = column.name;
dropdown.appendChild(option);
});
}
// Show modal
this.handleModalOpen({ modalId: 'column-select-modal' });
// One-time event handlers
const handleConfirm = () => {
const selectedColumnId = parseInt(dropdown?.value);
this.handleModalClose({ modalId: 'column-select-modal' });
if (onSelect && selectedColumnId) onSelect(selectedColumnId);
cleanup();
};
const handleCancel = () => {
this.handleModalClose({ modalId: 'column-select-modal' });
cleanup();
};
const cleanup = () => {
confirmBtn?.removeEventListener('click', handleConfirm);
cancelBtn?.removeEventListener('click', handleCancel);
};
confirmBtn?.addEventListener('click', handleConfirm);
cancelBtn?.addEventListener('click', handleCancel);
}
// =====================
// LIGHTBOX
// =====================

Datei anzeigen

@@ -26,9 +26,6 @@ class BoardManager {
this.weekStripDate = this.getMonday(new Date());
this.tooltip = null;
// Layout preferences
this.multiColumnLayout = this.loadLayoutPreference();
this.init();
}
@@ -56,20 +53,6 @@ class BoardManager {
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));
}
// =====================
@@ -163,12 +146,6 @@ 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();
}
});
}
// =====================
@@ -180,9 +157,6 @@ class BoardManager {
const columns = store.get('columns');
clearElement(this.boardElement);
// Apply layout class
this.applyLayoutClass();
columns.forEach(column => {
const columnElement = this.createColumnElement(column);
@@ -198,9 +172,9 @@ class BoardManager {
'Statuskarte hinzufügen'
]);
this.boardElement.appendChild(addColumnBtn);
// Check dynamic layout after render
setTimeout(() => this.checkAndApplyDynamicLayout(), 100);
// Emit event for mobile column navigation
document.dispatchEvent(new CustomEvent('columns:updated'));
}
createColumnElement(column) {
@@ -506,9 +480,6 @@ class BoardManager {
countElement.textContent = filteredTasks.length.toString();
}
});
// Check if dynamic layout adjustment is needed
setTimeout(() => this.checkAndApplyDynamicLayout(), 100);
}
// =====================
@@ -1496,102 +1467,6 @@ class BoardManager {
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

Datei anzeigen

@@ -712,7 +712,11 @@ class CalendarViewManager {
filteredTasks = filteredTasks.filter(task => {
const filterCategory = columnFilterMap[task.columnId];
// If no filter state exists for this category, default to showing "in_progress"
// If column has 'in_progress' filter category, check if 'in_progress' filter is enabled
if (filterCategory === 'in_progress') {
return this.enabledFilters['in_progress'] !== false;
}
// For other categories, check their specific filter state
if (this.enabledFilters[filterCategory] === undefined) {
// Default: show in_progress, hide open and completed
return filterCategory === 'in_progress';

Datei-Diff unterdrückt, da er zu groß ist Diff laden

Datei anzeigen

@@ -5,6 +5,7 @@
*/
import { $, $$ } from './utils.js';
import { enhanceMobileSwipe } from './mobile-swipe.js';
class MobileManager {
constructor() {
@@ -21,6 +22,8 @@ class MobileManager {
this.touchStartTime = 0;
this.isSwiping = false;
this.swipeDirection = null;
this.currentColumnIndex = 0;
this.swipeTarget = null; // 'board' or 'header'
// Touch drag & drop state
this.touchDraggedElement = null;
@@ -87,6 +90,9 @@ class MobileManager {
this.updateUserInfo();
});
// Enhance with new swipe functionality
enhanceMobileSwipe(this);
console.log('[Mobile] Initialized');
}
@@ -220,8 +226,18 @@ class MobileManager {
$$('.view').forEach(v => {
const viewName = v.id.replace('view-', '');
const isActive = viewName === view;
v.classList.toggle('active', isActive);
v.classList.toggle('hidden', !isActive);
if (isActive) {
v.classList.add('active');
v.classList.remove('hidden');
// Force display for mobile
if (this.isMobile) {
v.style.display = '';
}
} else {
v.classList.remove('active');
v.classList.add('hidden');
}
});
// Update mobile nav
@@ -300,41 +316,42 @@ class MobileManager {
bindSwipeEvents() {
if (!this.mainContent) return;
this.mainContent.addEventListener('touchstart', (e) => this.handleSwipeStart(e), { passive: true });
this.mainContent.addEventListener('touchmove', (e) => this.handleSwipeMove(e), { passive: false });
this.mainContent.addEventListener('touchend', (e) => this.handleSwipeEnd(e), { passive: true });
this.mainContent.addEventListener('touchcancel', () => this.resetSwipe(), { passive: true });
// Swipe für Board-Container (Spalten-Navigation)
const boardContainer = document.querySelector('.board-container');
if (boardContainer) {
boardContainer.addEventListener('touchstart', (e) => this.handleBoardSwipeStart(e), { passive: true });
boardContainer.addEventListener('touchmove', (e) => this.handleBoardSwipeMove(e), { passive: false });
boardContainer.addEventListener('touchend', (e) => this.handleBoardSwipeEnd(e), { passive: true });
boardContainer.addEventListener('touchcancel', () => this.resetSwipe(), { passive: true });
}
// Swipe für Header (View-Navigation)
const header = document.querySelector('.header');
if (header) {
header.addEventListener('touchstart', (e) => this.handleHeaderSwipeStart(e), { passive: true });
header.addEventListener('touchmove', (e) => this.handleHeaderSwipeMove(e), { passive: false });
header.addEventListener('touchend', (e) => this.handleHeaderSwipeEnd(e), { passive: true });
header.addEventListener('touchcancel', () => this.resetSwipe(), { passive: true });
}
}
/**
* Handle swipe start
* Handle board swipe start (für Spalten-Navigation)
*/
handleSwipeStart(e) {
if (!this.isMobile) return;
handleBoardSwipeStart(e) {
if (!this.isMobile || this.currentView !== 'board') return;
if (this.isMenuOpen || $('.modal-overlay:not(.hidden)')) return;
// Don't swipe if menu is open
if (this.isMenuOpen) return;
// Don't swipe if modal is open
if ($('.modal-overlay:not(.hidden)')) return;
// Don't swipe on specific interactive elements, but allow swipe in column-body
// Don't swipe on interactive elements
const target = e.target;
if (target.closest('.modal') ||
target.closest('.calendar-grid') ||
target.closest('.knowledge-entry-list') ||
target.closest('.list-table') ||
target.closest('input') ||
target.closest('textarea') ||
if (target.closest('button') ||
target.closest('input') ||
target.closest('textarea') ||
target.closest('select') ||
target.closest('button') ||
target.closest('a[href]') ||
target.closest('.task-card .priority-stars') ||
target.closest('.task-card .task-counts')) {
target.closest('.task-card')) {
return;
}
// Only single touch
if (e.touches.length !== 1) return;
this.touchStartX = e.touches[0].clientX;
@@ -342,12 +359,79 @@ class MobileManager {
this.touchStartTime = Date.now();
this.isSwiping = false;
this.swipeDirection = null;
this.swipeTarget = 'board';
}
/**
* Handle swipe move
* Handle header swipe start (für View-Navigation)
*/
handleSwipeMove(e) {
handleHeaderSwipeStart(e) {
if (!this.isMobile) return;
if (this.isMenuOpen || $('.modal-overlay:not(.hidden)')) return;
// Nur in der Header-Region swipen
if (!e.target.closest('.header')) return;
// Nicht auf Buttons swipen
if (e.target.closest('button') || e.target.closest('.view-tab')) return;
if (e.touches.length !== 1) return;
this.touchStartX = e.touches[0].clientX;
this.touchStartY = e.touches[0].clientY;
this.touchStartTime = Date.now();
this.isSwiping = false;
this.swipeDirection = null;
this.swipeTarget = 'header';
}
/**
* Handle board swipe move
*/
handleBoardSwipeMove(e) {
if (!this.isMobile || this.touchStartX === 0 || this.swipeTarget !== 'board') return;
const touch = e.touches[0];
this.touchCurrentX = touch.clientX;
this.touchCurrentY = touch.clientY;
const deltaX = this.touchCurrentX - this.touchStartX;
const deltaY = this.touchCurrentY - this.touchStartY;
// Determine direction
if (!this.swipeDirection && (Math.abs(deltaX) > 10 || Math.abs(deltaY) > 10)) {
if (Math.abs(deltaX) > Math.abs(deltaY) * 1.5) {
this.swipeDirection = 'horizontal';
this.isSwiping = true;
document.body.classList.add('is-swiping');
} else {
this.swipeDirection = 'vertical';
this.resetSwipe();
return;
}
}
if (this.swipeDirection !== 'horizontal') return;
e.preventDefault();
// Visual feedback für Spalten-Navigation
const columns = $$('.column');
if (deltaX > this.SWIPE_THRESHOLD && this.currentColumnIndex > 0) {
this.swipeIndicatorLeft?.classList.add('visible');
this.swipeIndicatorRight?.classList.remove('visible');
} else if (deltaX < -this.SWIPE_THRESHOLD && this.currentColumnIndex < columns.length - 1) {
this.swipeIndicatorRight?.classList.add('visible');
this.swipeIndicatorLeft?.classList.remove('visible');
} else {
this.swipeIndicatorLeft?.classList.remove('visible');
this.swipeIndicatorRight?.classList.remove('visible');
}
}
/**
* Handle header swipe move
*/
handleHeaderSwipeMove(e) {
if (!this.isMobile || this.touchStartX === 0) return;
const touch = e.touches[0];
@@ -412,10 +496,11 @@ class MobileManager {
* Handle swipe end
*/
handleSwipeEnd() {
if (!this.isSwiping || this.swipeDirection !== 'horizontal') {
this.resetSwipe();
return;
}
if (!this.isMobile || this.touchStartX === 0 || this.swipeTarget !== 'header') return;
const touch = e.touches[0];
this.touchCurrentX = touch.clientX;
this.touchCurrentY = touch.clientY;
const deltaX = this.touchCurrentX - this.touchStartX;
const deltaTime = Date.now() - this.touchStartTime;

Datei anzeigen

@@ -32,10 +32,10 @@ class Store {
// Filters
filters: {
search: '',
priority: 'all',
assignee: 'all',
label: 'all',
dueDate: 'all',
priority: '',
assignee: '',
label: '',
dueDate: '',
archived: false
},
@@ -155,10 +155,10 @@ class Store {
labels: [],
filters: {
search: '',
priority: 'all',
assignee: 'all',
label: 'all',
dueDate: 'all',
priority: '',
assignee: '',
label: '',
dueDate: '',
archived: false
},
searchResultIds: [],
@@ -412,10 +412,10 @@ class Store {
this.setState({
filters: {
search: '',
priority: 'all',
assignee: 'all',
label: 'all',
dueDate: 'all',
priority: '',
assignee: '',
label: '',
dueDate: '',
archived: false
}
}, 'RESET_FILTERS');

Datei anzeigen

@@ -49,6 +49,16 @@ class SyncManager {
});
this.setupEventListeners();
// Update body offline class based on sync status
store.subscribe('syncStatus', () => {
const status = store.get('syncStatus');
if (status === 'offline' || status === 'error') {
document.body.classList.add('offline');
} else {
document.body.classList.remove('offline');
}
});
} catch (error) {
console.error('[Sync] Failed to connect:', error);
store.setSyncStatus('error');

Datei anzeigen

@@ -587,10 +587,38 @@ class TaskModalManager {
this.close();
this.showSuccess('Aufgabe wiederhergestellt');
} catch (error) {
this.showError('Fehler beim Wiederherstellen');
// Prüfen ob Spaltenauswahl erforderlich ist
if (error.data?.requiresColumn) {
this.showColumnSelectDialog();
} else {
this.showError('Fehler beim Wiederherstellen');
}
}
}
showColumnSelectDialog() {
const columns = store.get('columns');
// Modal für Spaltenauswahl erstellen
window.dispatchEvent(new CustomEvent('column-select:show', {
detail: {
message: 'Die ursprüngliche Spalte existiert nicht mehr. Bitte wählen Sie eine Spalte:',
columns: columns,
onSelect: async (columnId) => {
try {
const projectId = store.get('currentProjectId');
await api.restoreTask(projectId, this.taskId, columnId);
store.updateTask(this.taskId, { archived: false, columnId: columnId });
this.close();
this.showSuccess('Aufgabe wiederhergestellt');
} catch (error) {
this.showError('Fehler beim Wiederherstellen');
}
}
}
}));
}
async autoSaveDescription() {
// Deprecated - use autoSaveTask instead
await this.autoSaveTask();

Datei anzeigen

@@ -492,37 +492,55 @@ export function filterTasks(tasks, filters, searchResultIds = [], columns = [])
}
}
// Priority filter
if (filters.priority && filters.priority !== 'all') {
if (task.priority !== filters.priority) return false;
// Priority filter (string or array)
if (filters.priority && filters.priority.length > 0) {
const priorityFilter = Array.isArray(filters.priority) ? filters.priority : [filters.priority];
if (!priorityFilter.includes(task.priority)) return false;
}
// Assignee filter
if (filters.assignee && filters.assignee !== 'all') {
if (task.assignedTo !== parseInt(filters.assignee)) return false;
// Assignee filter (string or array)
if (filters.assignee && filters.assignee.length > 0) {
const assigneeFilter = Array.isArray(filters.assignee) ? filters.assignee : [filters.assignee];
const assigneeIds = assigneeFilter.map(id => parseInt(id));
const taskAssigneeId = task.assignedTo;
const taskAssignees = task.assignees?.map(a => a.id || a) || [];
const matches = assigneeIds.some(id => id === taskAssigneeId || taskAssignees.includes(id));
if (!matches) return false;
}
// Label filter
if (filters.label && filters.label !== 'all') {
const hasLabel = task.labels?.some(l => l.id === parseInt(filters.label));
// Label filter (string or array)
if (filters.label && filters.label.length > 0) {
const labelFilter = Array.isArray(filters.label) ? filters.label : [filters.label];
const labelIds = labelFilter.map(id => parseInt(id));
const hasLabel = task.labels?.some(l => labelIds.includes(l.id));
if (!hasLabel) return false;
}
// Due date filter
if (filters.dueDate && filters.dueDate !== 'all' && filters.dueDate !== '') {
const status = getDueDateStatus(task.dueDate);
// Due date filter (string or array)
if (filters.dueDate && filters.dueDate.length > 0) {
const dueDateFilter = Array.isArray(filters.dueDate) ? filters.dueDate : [filters.dueDate];
let matches = false;
// Bei "überfällig" erledigte Aufgaben ausschließen
if (filters.dueDate === 'overdue') {
if (status !== 'overdue' || isTaskCompleted(task)) return false;
}
if (filters.dueDate === 'today' && status !== 'today') return false;
if (filters.dueDate === 'week') {
const due = new Date(task.dueDate);
const weekFromNow = new Date();
weekFromNow.setDate(weekFromNow.getDate() + 7);
if (!task.dueDate || due > weekFromNow) return false;
for (const filterVal of dueDateFilter) {
if (filterVal === 'none') {
if (!task.dueDate) { matches = true; break; }
} else if (filterVal === 'overdue') {
const status = getDueDateStatus(task.dueDate);
if (status === 'overdue' && !isTaskCompleted(task)) { matches = true; break; }
} else if (filterVal === 'today') {
const status = getDueDateStatus(task.dueDate);
if (status === 'today') { matches = true; break; }
} else if (filterVal === 'week') {
if (task.dueDate) {
const due = new Date(task.dueDate);
const weekFromNow = new Date();
weekFromNow.setDate(weekFromNow.getDate() + 7);
if (due <= weekFromNow) { matches = true; break; }
}
}
}
if (!matches) return false;
}
return true;

Datei anzeigen

@@ -4,7 +4,7 @@
* Offline support and caching
*/
const CACHE_VERSION = '306';
const CACHE_VERSION = '390';
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;