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:
@@ -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"
|
||||
]
|
||||
}
|
||||
}]
|
||||
}]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
}
|
||||
|
||||
.view-list.active {
|
||||
display: flex;
|
||||
display: flex !important;
|
||||
}
|
||||
|
||||
/* List Header */
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>×</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>
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
// =====================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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;
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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');
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
In neuem Issue referenzieren
Einen Benutzer sperren