Fadikkop commited on
Commit
a12aab2
·
verified ·
1 Parent(s): a890ba8

Promote version 4549c0d to main

Browse files

Promoted commit 4549c0d2bdafddf68bbb5a3ac5890cefe286b3ca to main branch

Files changed (7) hide show
  1. components/tag-item.js +109 -0
  2. components/task-item.js +303 -49
  3. components/timeline-sidebar.js +133 -252
  4. components/timeline-tasks.js +50 -179
  5. index.html +210 -797
  6. script.js +738 -179
  7. style.css +186 -769
components/tag-item.js CHANGED
@@ -1,3 +1,112 @@
 
 
 
 
 
 
1
 
 
 
 
 
2
 
 
 
 
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Tag Item Component
2
+ class TagItem extends HTMLElement {
3
+ constructor() {
4
+ super();
5
+ this.attachShadow({ mode: 'open' });
6
+ }
7
 
8
+ connectedCallback() {
9
+ this.render();
10
+ this.setupEventListeners();
11
+ }
12
 
13
+ static get observedAttributes() {
14
+ return ['tag-id', 'tag-name', 'tag-color'];
15
+ }
16
 
17
+ attributeChangedCallback() {
18
+ this.render();
19
+ }
20
+
21
+ render() {
22
+ const tagName = this.getAttribute('tag-name') || '';
23
+ const tagColor = this.getAttribute('tag-color') || '#f97316';
24
+ const tagId = this.getAttribute('tag-id') || '';
25
+
26
+ this.shadowRoot.innerHTML = `
27
+ <style>
28
+ :host {
29
+ display: inline-flex;
30
+ align-items: center;
31
+ gap: 6px;
32
+ padding: 4px 12px;
33
+ border-radius: 16px;
34
+ font-size: 14px;
35
+ font-weight: 500;
36
+ transition: all 0.2s ease;
37
+ }
38
+
39
+ .tag-content {
40
+ background-color: ${tagColor}20;
41
+ color: ${tagColor};
42
+ padding: 4px 12px;
43
+ border-radius: 16px;
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 6px;
47
+ }
48
+
49
+ .tag-name {
50
+ user-select: none;
51
+ }
52
+
53
+ .delete-btn {
54
+ background: none;
55
+ border: none;
56
+ color: inherit;
57
+ cursor: pointer;
58
+ padding: 2px;
59
+ border-radius: 50%;
60
+ display: flex;
61
+ align-items: center;
62
+ justify-content: center;
63
+ transition: all 0.2s ease;
64
+ }
65
+
66
+ .delete-btn:hover {
67
+ background-color: rgba(0, 0, 0, 0.1);
68
+ transform: scale(1.1);
69
+ }
70
+
71
+ .delete-btn i {
72
+ width: 14px;
73
+ height: 14px;
74
+ }
75
+ </style>
76
+
77
+ <div class="tag-content">
78
+ <span class="tag-name">${tagName}</span>
79
+ <button class="delete-btn" @click="${this.handleDelete}">
80
+ <i data-feather="x"></i>
81
+ </button>
82
+ </div>
83
+ `;
84
+
85
+ // Replace feather icon
86
+ const iconEl = this.shadowRoot.querySelector('i[data-feather]');
87
+ if (iconEl) {
88
+ iconEl.outerHTML = feather.icons.x.toSvg({ width: 14, height: 14 });
89
+ }
90
+ }
91
+
92
+ setupEventListeners() {
93
+ this.shadowRoot.addEventListener('click', (e) => {
94
+ const deleteBtn = e.target.closest('.delete-btn');
95
+ if (deleteBtn) {
96
+ e.stopPropagation();
97
+ this.handleDelete(e);
98
+ }
99
+ });
100
+ }
101
+
102
+ handleDelete(e) {
103
+ if (confirm('Tag wirklich löschen?')) {
104
+ this.dispatchEvent(new CustomEvent('delete', {
105
+ bubbles: true,
106
+ detail: { tagId: this.getAttribute('tag-id') }
107
+ }));
108
+ }
109
+ }
110
+ }
111
+
112
+ customElements.define('tag-item', TagItem);
components/task-item.js CHANGED
@@ -1,101 +1,355 @@
1
-
2
- // Tag Item Component
3
- class TagItem extends HTMLElement {
4
  constructor() {
5
  super();
6
  this.attachShadow({ mode: 'open' });
7
  }
8
-
9
  connectedCallback() {
 
 
 
10
  this.render();
11
  this.setupEventListeners();
12
  }
13
 
14
  static get observedAttributes() {
15
- return ['tag-id', 'tag-name', 'tag-color'];
16
  }
17
 
18
- attributeChangedCallback() {
 
 
 
 
 
 
19
  this.render();
20
  }
21
 
22
- render() {
23
- const tagName = this.getAttribute('tag-name') || '';
24
- const tagColor = this.getAttribute('tag-color') || '#f97316';
25
- const tagId = this.getAttribute('tag-id') || '';
 
 
26
 
27
- this.shadowRoot.innerHTML = `
 
 
 
 
 
 
 
 
 
 
 
28
  <style>
29
  :host {
30
- display: inline-flex;
31
- align-items: center;
32
- gap: 6px;
33
- padding: 4px 12px;
34
- border-radius: 16px;
35
- font-size: 14px;
36
- font-weight: 500;
37
- transition: all 0.2s ease;
38
  }
39
 
40
- .tag-content {
41
- background-color: ${tagColor}20;
42
- color: ${tagColor};
43
- padding: 4px 12px;
44
- border-radius: 16px;
 
 
 
 
 
45
  display: flex;
46
  align-items: center;
47
- gap: 6px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  }
49
 
50
- .tag-name {
51
- user-select: none;
 
52
  }
53
 
54
- .delete-btn {
55
  background: none;
56
  border: none;
57
- color: inherit;
58
  cursor: pointer;
59
- padding: 2px;
60
- border-radius: 50%;
 
 
 
 
 
 
 
61
  display: flex;
62
  align-items: center;
63
- justify-content: center;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  transition: all 0.2s ease;
65
  }
66
 
67
- .delete-btn:hover {
68
- background-color: rgba(0, 0, 0, 0.1);
69
- transform: scale(1.1);
 
 
 
 
 
 
 
 
 
 
 
 
 
70
  }
71
  </style>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
- <div class="tag-content">
74
- <span class="tag-name">${tagName}</span>
75
- <button class="delete-btn">
76
- <svg xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"></line><line x1="6" y1="6" x2="18" y2="18"></line></svg>
77
- </button>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
78
  </div>
79
  `;
 
 
 
 
 
 
 
 
 
 
 
80
  }
81
- setupEventListeners() {
 
82
  this.shadowRoot.addEventListener('click', (e) => {
83
- const deleteBtn = e.target.closest('.delete-btn');
84
- if (deleteBtn) {
85
  e.stopPropagation();
86
- this.handleDelete(e);
 
 
 
 
 
 
 
87
  }
88
  });
89
  }
90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  handleDelete(e) {
92
- if (confirm('Tag wirklich löschen?')) {
93
- this.dispatchEvent(new CustomEvent('delete', {
94
- bubbles: true,
95
- detail: { tagId: this.getAttribute('tag-id') }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
  }));
97
  }
98
  }
99
  }
100
 
101
- customElements.define('tag-item', TagItem);
 
1
+ // Task Item Component
2
+ class TaskItem extends HTMLElement {
 
3
  constructor() {
4
  super();
5
  this.attachShadow({ mode: 'open' });
6
  }
 
7
  connectedCallback() {
8
+ this.taskId = this.getAttribute('task-id');
9
+ this.completed = this.getAttribute('completed') === 'true';
10
+ this.loadTaskData();
11
  this.render();
12
  this.setupEventListeners();
13
  }
14
 
15
  static get observedAttributes() {
16
+ return ['task-id', 'completed'];
17
  }
18
 
19
+ attributeChangedCallback(name, oldValue, newValue) {
20
+ if (name === 'task-id' && oldValue !== newValue) {
21
+ this.taskId = newValue;
22
+ this.loadTaskData();
23
+ } else if (name === 'completed') {
24
+ this.completed = newValue === 'true';
25
+ }
26
  this.render();
27
  }
28
 
29
+ loadTaskData() {
30
+ if (!window.state || !window.state.tasks) return;
31
+
32
+ this.task = window.state.tasks.find(t => t.id === this.taskId);
33
+ this.tags = window.state.tags || [];
34
+ }
35
 
36
+ render() {
37
+ if (!this.task) {
38
+ this.shadowRoot.innerHTML = `<div class="loading" data-task-id="${this.taskId}">Lädt...</div>`;
39
+ return;
40
+ }
41
+ const { title, estimatedTime, spentTime, tags } = this.task;
42
+ const progress = estimatedTime > 0 ? (spentTime / estimatedTime) * 100 : 0;
43
+ const tagElements = (tags || []).map(tagId => {
44
+ const tag = this.tags?.find(t => t.id === tagId);
45
+ return tag ? `<span class="tag-badge" style="background-color: ${tag.color}20; color: ${tag.color}">${tag.name}</span>` : '';
46
+ }).join('');
47
+ this.shadowRoot.innerHTML = `
48
  <style>
49
  :host {
50
+ display: block;
51
+ background: #1f2937;
52
+ border-radius: 8px;
53
+ padding: 12px;
54
+ margin-bottom: 8px;
55
+ transition: all 0.3s ease;
56
+ cursor: pointer;
 
57
  }
58
 
59
+ :host(:hover) {
60
+ transform: translateY(-2px);
61
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
62
+ }
63
+
64
+ :host(.completed) {
65
+ opacity: 0.6;
66
+ }
67
+
68
+ .task-header {
69
  display: flex;
70
  align-items: center;
71
+ justify-content: space-between;
72
+ margin-bottom: 8px;
73
+ }
74
+
75
+ .task-title {
76
+ font-size: 16px;
77
+ font-weight: 500;
78
+ color: #f3f4f6;
79
+ flex: 1;
80
+ margin-left: 8px;
81
+ text-decoration: none;
82
+ }
83
+
84
+ :host(.completed) .task-title {
85
+ text-decoration: line-through;
86
+ color: #9ca3af;
87
+ }
88
+
89
+ .task-checkbox {
90
+ width: 20px;
91
+ height: 20px;
92
+ cursor: pointer;
93
  }
94
 
95
+ .task-actions {
96
+ display: flex;
97
+ gap: 8px;
98
  }
99
 
100
+ .task-action {
101
  background: none;
102
  border: none;
103
+ color: #6b7280;
104
  cursor: pointer;
105
+ padding: 4px;
106
+ transition: color 0.2s ease;
107
+ }
108
+
109
+ .task-action:hover {
110
+ color: #f97316;
111
+ }
112
+
113
+ .task-info {
114
  display: flex;
115
  align-items: center;
116
+ gap: 16px;
117
+ margin-top: 8px;
118
+ }
119
+
120
+ .time-info {
121
+ font-size: 14px;
122
+ color: #9ca3af;
123
+ }
124
+
125
+ .progress-bar {
126
+ width: 100%;
127
+ height: 4px;
128
+ background: #374151;
129
+ border-radius: 2px;
130
+ overflow: hidden;
131
+ margin-top: 8px;
132
+ }
133
+
134
+ .progress-fill {
135
+ height: 100%;
136
+ background: linear-gradient(90deg, #f97316, #ea580c);
137
+ transition: width 0.3s ease;
138
+ }
139
+
140
+ .tags {
141
+ display: flex;
142
+ gap: 4px;
143
+ flex-wrap: wrap;
144
+ }
145
+
146
+ .tag-badge {
147
+ font-size: 12px;
148
+ padding: 2px 8px;
149
+ border-radius: 12px;
150
+ font-weight: 500;
151
+ }
152
+
153
+ .edit-form {
154
+ display: none;
155
+ margin-top: 12px;
156
+ padding-top: 12px;
157
+ border-top: 1px solid #374151;
158
+ }
159
+
160
+ .edit-form.active {
161
+ display: block;
162
+ }
163
+
164
+ .input-group {
165
+ display: flex;
166
+ gap: 8px;
167
+ margin-bottom: 8px;
168
+ }
169
+
170
+ .input-group input {
171
+ background: #374151;
172
+ border: 1px solid #4b5563;
173
+ border-radius: 4px;
174
+ padding: 6px 12px;
175
+ color: #f3f4f6;
176
+ font-size: 14px;
177
+ }
178
+
179
+ .input-group input:focus {
180
+ outline: none;
181
+ border-color: #f97316;
182
+ }
183
+
184
+ .edit-actions {
185
+ display: flex;
186
+ gap: 8px;
187
+ justify-content: flex-end;
188
+ }
189
+
190
+ .btn-small {
191
+ padding: 6px 12px;
192
+ border-radius: 4px;
193
+ border: none;
194
+ font-size: 14px;
195
+ cursor: pointer;
196
  transition: all 0.2s ease;
197
  }
198
 
199
+ .btn-save {
200
+ background: #059669;
201
+ color: white;
202
+ }
203
+
204
+ .btn-save:hover {
205
+ background: #047857;
206
+ }
207
+
208
+ .btn-cancel {
209
+ background: #374151;
210
+ color: #d1d5db;
211
+ }
212
+
213
+ .btn-cancel:hover {
214
+ background: #4b5563;
215
  }
216
  </style>
217
+ <div class="task-header">
218
+ <input type="checkbox" class="task-checkbox" ${this.completed ? 'checked' : ''}
219
+ @change="${this.handleToggleComplete}">
220
+ <span class="task-title">${title}</span>
221
+ <div class="task-actions">
222
+ <button class="focus-btn" @click="${this.handleFocus}" title="Pomodoro starten">
223
+ <i data-feather="clock"></i>
224
+ </button>
225
+ <button class="task-action" @click="${this.handleEdit}" title="Bearbeiten">
226
+ <i data-feather="edit-2"></i>
227
+ </button>
228
+ <button class="task-action" @click="${this.handleDelete}" title="Löschen">
229
+ <i data-feather="trash-2"></i>
230
+ </button>
231
+ </div>
232
+ </div>
233
 
234
+ <div class="task-info">
235
+ <span class="time-info">${formatTime(spentTime)} / ${formatTime(estimatedTime)}</span>
236
+ <div class="tags">${tagElements}</div>
237
+ </div>
238
+
239
+ <div class="progress-bar">
240
+ <div class="progress-fill" style="width: ${progress}%"></div>
241
+ </div>
242
+ <div class="edit-form" id="edit-form-${this.taskId}">
243
+ <div class="input-group">
244
+ <input type="text" value="${title}" id="edit-title-${this.taskId}" placeholder="Titel">
245
+ <input type="number" value="${Math.floor(estimatedTime / 60)}" id="edit-hours-${this.taskId}"
246
+ placeholder="h" min="0" max="24" style="width: 60px">
247
+ <input type="number" value="${estimatedTime % 60}" id="edit-minutes-${this.taskId}"
248
+ placeholder="m" min="0" max="59" step="15" style="width: 60px">
249
+ <button class="btn-small btn-add-time" @click="${this.handleAddTime}" title="Zeit hinzufügen">
250
+ <i data-feather="clock" style="width: 14px; height: 14px;"></i>
251
+ </button>
252
+ </div>
253
+ <div class="edit-actions">
254
+ <button class="btn-small btn-save" @click="${this.handleSave}">Speichern</button>
255
+ <button class="btn-small btn-cancel" @click="${this.handleCancel}">Abbrechen</button>
256
+ </div>
257
  </div>
258
  `;
259
+ if (this.completed) {
260
+ this.classList.add('completed');
261
+ } else {
262
+ this.classList.remove('completed');
263
+ }
264
+
265
+ // Replace feather icons
266
+ this.shadowRoot.querySelectorAll('i[data-feather]').forEach(icon => {
267
+ const iconName = icon.getAttribute('data-feather');
268
+ icon.outerHTML = feather.icons[iconName].toSvg({ width: 16, height: 16 });
269
+ });
270
  }
271
+
272
+ setupEventListeners() {
273
  this.shadowRoot.addEventListener('click', (e) => {
274
+ const action = e.target.closest('button')?.getAttribute('@click');
275
+ if (action) {
276
  e.stopPropagation();
277
+ this[action.replace('${this.', '').replace('}', '')](e);
278
+ }
279
+ });
280
+
281
+ this.shadowRoot.addEventListener('change', (e) => {
282
+ if (e.target.classList.contains('task-checkbox')) {
283
+ e.stopPropagation();
284
+ this.handleToggleComplete(e);
285
  }
286
  });
287
  }
288
 
289
+ handleToggleComplete(e) {
290
+ const completed = e.target.checked;
291
+ this.dispatchEvent(new CustomEvent('toggle-complete', {
292
+ detail: { taskId: this.getAttribute('task-id'), completed },
293
+ bubbles: true
294
+ }));
295
+
296
+ if (completed) {
297
+ this.classList.add('completed');
298
+ } else {
299
+ this.classList.remove('completed');
300
+ }
301
+ }
302
+ handleFocus(e) {
303
+ const taskId = this.getAttribute('task-id');
304
+ const task = state.tasks.find(t => t.id === taskId);
305
+ if (task) {
306
+ startFocusMode(task);
307
+ }
308
+ }
309
+ handleEdit(e) {
310
+ const editForm = this.shadowRoot.querySelector('.edit-form');
311
+ editForm.classList.add('active');
312
+ }
313
+
314
  handleDelete(e) {
315
+ if (confirm('Aufgabe wirklich löschen?')) {
316
+ this.dispatchEvent(new CustomEvent('delete-task', {
317
+ detail: { taskId: this.getAttribute('task-id') },
318
+ bubbles: true
319
+ }));
320
+ }
321
+ }
322
+
323
+ handleSave(e) {
324
+ const taskId = this.getAttribute('task-id');
325
+ const title = this.shadowRoot.getElementById(`edit-title-${taskId}`).value;
326
+ const hours = parseInt(this.shadowRoot.getElementById(`edit-hours-${taskId}`).value) || 0;
327
+ const minutes = parseInt(this.shadowRoot.getElementById(`edit-minutes-${taskId}`).value) || 0;
328
+ const estimatedTime = hours * 60 + minutes;
329
+
330
+ this.dispatchEvent(new CustomEvent('update-task', {
331
+ detail: { taskId, updates: { title, estimatedTime } },
332
+ bubbles: true
333
+ }));
334
+ }
335
+
336
+ handleCancel(e) {
337
+ const editForm = this.shadowRoot.querySelector('.edit-form');
338
+ editForm.classList.remove('active');
339
+ }
340
+
341
+ handleAddTime(e) {
342
+ const minutes = prompt('Wie viele Minuten willst du hinzufügen?', '15');
343
+ if (minutes && !isNaN(minutes)) {
344
+ this.dispatchEvent(new CustomEvent('add-time', {
345
+ detail: {
346
+ taskId: this.getAttribute('task-id'),
347
+ minutes: parseInt(minutes)
348
+ },
349
+ bubbles: true
350
  }));
351
  }
352
  }
353
  }
354
 
355
+ customElements.define('task-item', TaskItem);
components/timeline-sidebar.js CHANGED
@@ -1,273 +1,154 @@
 
 
 
 
 
 
1
 
2
- // Task Item Component
3
- class TaskItem extends HTMLElement {
4
- constructor() {
5
- super();
6
- this.attachShadow({ mode: 'open' });
7
- }
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- connectedCallback() {
10
- this.taskId = this.getAttribute('task-id');
11
- this.completed = this.getAttribute('completed') === 'true';
12
- this.loadTaskData();
13
- this.render();
14
- this.setupEventListeners();
15
- }
16
 
17
- static get observedAttributes() {
18
- return ['task-id', 'completed'];
19
- }
 
 
 
 
20
 
21
- attributeChangedCallback(name, oldValue, newValue) {
22
- if (name === 'task-id' && oldValue !== newValue) {
23
- this.taskId = newValue;
24
- this.loadTaskData();
25
- } else if (name === 'completed') {
26
- this.completed = newValue === 'true';
27
  }
28
- this.render();
29
- }
30
 
31
- loadTaskData() {
32
- if (!window.state || !window.state.tasks) return;
33
-
34
- this.task = window.state.tasks.find(t => t.id === this.taskId);
35
- this.tags = window.state.tags || [];
36
- }
37
 
38
- render() {
39
- if (!this.task) {
40
- this.shadowRoot.innerHTML = `<div class="loading" data-task-id="${this.taskId}">Lädt...</div>';
41
- return;
 
 
42
  }
43
- const { title, estimatedTime, spentTime, tags } = this.task;
44
- const progress = estimatedTime > 0 ? (spentTime / estimatedTime) * 100 : 0;
45
- const tagElements = (tags || []).map(tagId => {
46
- const tag = this.tags?.find(t => t.id === tagId);
47
- return tag ? `<span class="tag-badge" style="background-color: ${tag.color}20; color: ${tag.color}">${tag.name}</span>' : '';
48
- }).join('');
49
-
50
- this.shadowRoot.innerHTML = `
51
- <style>
52
- :host {
53
- display: block;
54
- background: #1f2937;
55
- border-radius: 8px;
56
- padding: 12px;
57
- margin-bottom: 8px;
58
- transition: all 0.3s ease;
59
- cursor: pointer;
60
- }
61
-
62
- :host(:hover) {
63
- transform: translateY(-2px);
64
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
65
- }
66
-
67
- :host(.completed) {
68
- opacity: 0.6;
69
- }
70
-
71
- .task-header {
72
- display: flex;
73
- align-items: center;
74
- justify-content: space-between;
75
- margin-bottom: 8px;
76
- }
77
-
78
- .task-title {
79
- font-size: 16px;
80
- font-weight: 500;
81
- color: #f3f4f6;
82
- flex: 1;
83
- margin-left: 8px;
84
- text-decoration: none;
85
- }
86
-
87
- :host(.completed) .task-title {
88
- text-decoration: line-through;
89
- color: #9ca3af;
90
- }
91
-
92
- .task-checkbox {
93
- width: 20px;
94
- height: 20px;
95
- cursor: pointer;
96
- }
97
-
98
- .task-actions {
99
- display: flex;
100
- gap: 8px;
101
- }
102
-
103
- .task-action {
104
- background: none;
105
- border: none;
106
- color: #6b7280;
107
- cursor: pointer;
108
- padding: 4px;
109
- transition: color 0.2s ease;
110
- }
111
-
112
- .task-action:hover {
113
- color: #f97316;
114
- }
115
-
116
- .task-info {
117
- display: flex;
118
- align-items: center;
119
- gap: 16px;
120
- margin-top: 8px;
121
- }
122
-
123
- .time-info {
124
- font-size: 14px;
125
- color: #9ca3af;
126
- }
127
-
128
- .progress-bar {
129
- width: 100%;
130
- height: 4px;
131
- background: #374151;
132
- border-radius: 2px;
133
- overflow: hidden;
134
- margin-top: 8px;
135
- }
136
-
137
- .progress-fill {
138
- height: 100%;
139
- background: linear-gradient(90deg, #f97316, #ea580c);
140
- transition: width 0.3s ease;
141
- }
142
-
143
- .tags {
144
- display: flex;
145
- gap: 4px;
146
- flex-wrap: wrap;
147
- }
148
-
149
- .tag-badge {
150
- font-size: 12px;
151
- padding: 2px 8px;
152
- border-radius: 12px;
153
- font-weight: 500;
154
- }
155
- </style>
156
- <div class="task-header">
157
- <input type="checkbox" class="task-checkbox" ${this.completed ? 'checked' : ''}>
158
- <span class="task-title">${title}</span>
159
- <div class="task-actions">
160
- <button class="task-action" title="Bearbeiten">
161
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M11 4H4a2 2 0 0 0-2 2v11a2 2 0 0 0 2 2h11a2 2 0 0 0 2-2v-7"></path><path d="M18.5 2.5a2.121 2.121 0 0 0-3-3L9 12"></path></svg>
162
- </button>
163
- <button class="task-action" title="Löschen">
164
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="3 6 5 6 21 6"></polyline><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path><line x1="10" y1="11" x2="10" y2="17"></line><line x1="14" y1="11" x2="14" y2="17"></line></svg>
165
- </button>
166
- </div>
167
- </div>
168
-
169
- <div class="task-info">
170
- <span class="time-info">${formatTime(spentTime)} / ${formatTime(estimatedTime)}</span>
171
- <div class="tags">${tagElements}</div>
172
- </div>
173
-
174
- <div class="progress-bar">
175
- <div class="progress-fill" style="width: ${progress}%"></div>
176
- </div>
177
- `;
178
-
179
- if (this.completed) {
180
- this.classList.add('completed');
181
- } else {
182
- this.classList.remove('completed');
183
  }
184
- }
185
- setupEventListeners() {
186
- this.shadowRoot.addEventListener('click', (e) => {
187
- const target = e.target.closest('button');
188
- if (target) {
189
- const action = target.getAttribute('title');
190
- if (action === 'Bearbeiten') {
191
- this.handleEdit(e);
192
- } else if (action === 'Löschen') {
193
- this.handleDelete(e);
194
- } else if (action === 'Pomodoro starten') {
195
- this.handleFocus(e);
196
- }
197
- }
198
- });
199
-
200
- this.shadowRoot.addEventListener('change', (e) => {
201
- if (e.target.classList.contains('task-checkbox')) {
202
- this.handleToggleComplete(e);
203
- }
204
- });
205
- }
206
 
207
- handleToggleComplete(e) {
208
- const completed = e.target.checked;
209
- this.dispatchEvent(new CustomEvent('toggle-complete', {
210
- detail: { taskId: this.getAttribute('task-id'), completed },
211
- bubbles: true
212
- }));
213
-
214
- if (completed) {
215
- this.classList.add('completed');
216
- } else {
217
- this.classList.remove('completed');
 
218
  }
219
- }
220
 
221
- handleFocus(e) {
222
- const taskId = this.getAttribute('task-id');
223
- const task = state.tasks.find(t => t.id === taskId);
224
- if (task) {
225
- startFocusMode(task);
 
226
  }
227
- }
228
 
229
- handleEdit(e) {
230
- const taskId = this.getAttribute('task-id');
231
- openEditTaskModal(taskId);
232
- }
 
233
 
234
- handleDelete(e) {
235
- if (confirm('Aufgabe wirklich löschen?')) {
236
- this.dispatchEvent(new CustomEvent('delete-task', {
237
- detail: { taskId: this.getAttribute('task-id') },
238
- bubbles: true
239
- }));
240
- }
 
 
241
 
242
- handleSave(e) {
243
- const taskId = this.getAttribute('task-id');
244
- const title = this.shadowRoot.getElementById(`edit-title-${taskId}`).value;
245
- const hours = parseInt(this.shadowRoot.getElementById(`edit-hours-${taskId}`).value) || 0;
246
- const minutes = parseInt(this.shadowRoot.getElementById(`edit-minutes-${taskId}`).value) || 0;
247
- const estimatedTime = hours * 60 + minutes;
248
-
249
- this.dispatchEvent(new CustomEvent('update-task', {
250
- detail: { taskId, updates: { title, estimatedTime } },
251
- bubbles: true
252
- }));
253
- }
254
 
255
- handleCancel(e) {
256
- const editForm = this.shadowRoot.querySelector('.edit-form');
257
- editForm.classList.remove('active');
258
- }
 
 
 
 
 
 
 
259
 
260
- handleAddTime(e) {
261
- const minutes = prompt('Wie viele Minuten willst du hinzufügen?', '15');
262
- if (minutes && !isNaN(minutes)) {
263
- this.dispatchEvent(new CustomEvent('add-time', {
264
- detail: {
265
- taskId: this.getAttribute('task-id'),
266
- minutes: parseInt(minutes)
267
- },
268
- bubbles: true
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  }));
270
- }
 
 
271
  }
272
 
273
- customElements.define('task-item', TaskItem);
 
1
+ class TimelineSidebar extends HTMLElement {
2
+ connectedCallback() {
3
+ this.attachShadow({ mode: 'open' });
4
+ this.render();
5
+ this.setupEventListeners();
6
+ }
7
 
8
+ render() {
9
+ this.shadowRoot.innerHTML = `
10
+ <style>
11
+ :host {
12
+ display: block;
13
+ position: fixed;
14
+ right: 0;
15
+ top: 0;
16
+ height: 100vh;
17
+ width: 300px;
18
+ background: #1f2937;
19
+ border-left: 1px solid #374151;
20
+ z-index: 30;
21
+ transform: translateX(100%);
22
+ transition: transform 0.3s ease;
23
+ overflow-y: auto;
24
+ }
25
 
26
+ :host(.open) {
27
+ transform: translateX(0);
28
+ }
 
 
 
 
29
 
30
+ .header {
31
+ padding: 1rem;
32
+ border-bottom: 1px solid #374151;
33
+ display: flex;
34
+ justify-content: space-between;
35
+ align-items: center;
36
+ }
37
 
38
+ .timeline-container {
39
+ padding: 1rem;
 
 
 
 
40
  }
 
 
41
 
42
+ .timeline-hour {
43
+ position: relative;
44
+ min-height: 60px;
45
+ border-bottom: 1px solid #374151;
46
+ padding-left: 50px;
47
+ }
48
 
49
+ .hour-label {
50
+ position: absolute;
51
+ left: 10px;
52
+ top: 5px;
53
+ font-size: 12px;
54
+ color: #9ca3af;
55
  }
56
+
57
+ .task-drop-area {
58
+ height: 100%;
59
+ min-height: 60px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
61
 
62
+ .toggle-btn {
63
+ position: fixed;
64
+ right: 300px;
65
+ top: 50%;
66
+ transform: translateY(-50%);
67
+ background: #1f2937;
68
+ border: 1px solid #374151;
69
+ border-right: none;
70
+ padding: 1rem 0.5rem;
71
+ border-radius: 5px 0 0 5px;
72
+ cursor: pointer;
73
+ z-index: 30;
74
  }
 
75
 
76
+ .task-block {
77
+ background: #374151;
78
+ border-left: 3px solid #f97316;
79
+ padding: 0.5rem;
80
+ margin: 0.5rem 0;
81
+ border-radius: 4px;
82
  }
83
+ </style>
84
 
85
+ <button class="toggle-btn">
86
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
87
+ <polyline points="15 18 9 12 15 6"></polyline>
88
+ </svg>
89
+ </button>
90
 
91
+ <div class="header">
92
+ <h3>Timeline</h3>
93
+ <button id="close-timeline">
94
+ <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
95
+ <line x1="18" y1="6" x2="6" y2="18"></line>
96
+ <line x1="6" y1="6" x2="18" y2="18"></line>
97
+ </svg>
98
+ </button>
99
+ </div>
100
 
101
+ <div class="timeline-container" id="timeline-hours">
102
+ ${Array.from({ length: 24 }, (_, i) => `
103
+ <div class="timeline-hour" data-hour="${i}">
104
+ <span class="hour-label">${i.toString().padStart(2, '0')}:00</span>
105
+ <div class="task-drop-area" data-hour="${i}"></div>
106
+ </div>
107
+ `).join('')}
108
+ </div>
109
+ `;
110
+ }
 
 
111
 
112
+ setupEventListeners() {
113
+ const toggleBtn = this.shadowRoot.querySelector('.toggle-btn');
114
+ toggleBtn.addEventListener('click', () => {
115
+ this.classList.toggle('open');
116
+ const icon = toggleBtn.querySelector('svg');
117
+ if (this.classList.contains('open')) {
118
+ icon.innerHTML = '<polyline points="15 18 9 12 15 6"></polyline>';
119
+ } else {
120
+ icon.innerHTML = '<polyline points="9 18 15 12 9 6"></polyline>';
121
+ }
122
+ });
123
 
124
+ // Setup drag and drop
125
+ this.shadowRoot.addEventListener('dragover', (e) => {
126
+ if (e.target.classList.contains('task-drop-area')) {
127
+ e.preventDefault();
128
+ e.target.style.backgroundColor = '#37415170';
129
+ }
130
+ });
131
+
132
+ this.shadowRoot.addEventListener('dragleave', (e) => {
133
+ if (e.target.classList.contains('task-drop-area')) {
134
+ e.target.style.backgroundColor = '';
135
+ }
136
+ });
137
+
138
+ this.shadowRoot.addEventListener('drop', (e) => {
139
+ if (e.target.classList.contains('task-drop-area')) {
140
+ e.preventDefault();
141
+ e.target.style.backgroundColor = '';
142
+
143
+ const taskId = e.dataTransfer.getData('text/plain');
144
+ const hour = parseInt(e.target.dataset.hour);
145
+
146
+ this.dispatchEvent(new CustomEvent('task-dropped', {
147
+ detail: { taskId, hour }
148
  }));
149
+ }
150
+ });
151
+ }
152
  }
153
 
154
+ customElements.define('timeline-sidebar', TimelineSidebar);
components/timeline-tasks.js CHANGED
@@ -1,8 +1,7 @@
1
- class TimelineSidebar extends HTMLElement {
2
  connectedCallback() {
3
  this.attachShadow({ mode: 'open' });
4
  this.render();
5
- this.setupEventListeners();
6
  }
7
 
8
  render() {
@@ -10,202 +9,74 @@ class TimelineSidebar extends HTMLElement {
10
  <style>
11
  :host {
12
  display: block;
13
- position: fixed;
14
- right: 0;
15
- top: 0;
16
- height: 100vh;
17
- width: 300px;
18
  background: #1f2937;
19
  border-left: 1px solid #374151;
20
- z-index: 30;
21
- transform: translateX(100%);
22
- transition: transform 0.3s ease;
 
 
23
  overflow-y: auto;
24
- }
25
-
26
- :host(.open) {
27
- transform: translateX(0);
28
- }
29
-
30
- .header {
31
  padding: 1rem;
32
- border-bottom: 1px solid #374151;
33
- display: flex;
34
- justify-content: space-between;
35
- align-items: center;
36
- background: #1f2937;
37
  }
38
-
39
- .timeline-container {
40
- padding: 1rem;
41
- }
42
-
43
- .timeline-hour {
44
- position: relative;
45
- min-height: 60px;
46
  border-bottom: 1px solid #374151;
47
- padding-left: 50px;
48
- }
49
-
50
- .hour-label {
51
- position: absolute;
52
- left: 10px;
53
- top: 5px;
54
- font-size: 12px;
55
- color: #9ca3af;
56
  }
57
-
58
- .task-drop-area {
59
- height: 100%;
60
- min-height: 60px;
61
- border-radius: 4px;
62
- transition: background-color 0.2s ease;
63
- }
64
-
65
- .task-drop-area:hover {
66
- background-color: #37415170;
67
  }
68
-
69
- .task-block {
70
  background: #374151;
71
- border-left: 3px solid #f97316;
72
- padding: 0.5rem;
73
- margin: 0.5rem 0;
74
- border-radius: 4px;
75
- font-size: 14px;
76
- cursor: pointer;
77
- margin-bottom: 4px;
78
  transition: all 0.2s ease;
79
  }
80
-
81
- .task-block:hover {
82
  background: #4b5563;
83
- transform: scale(1.02);
84
  }
85
-
86
- .toggle-btn {
87
- position: fixed;
88
- right: 300px;
89
- top: 50%;
90
- transform: translateY(-50%);
91
- background: #1f2937;
92
- border: 1px solid #374151;
93
- border-right: none;
94
- padding: 1rem 0.5rem;
95
- border-radius: 5px 0 0 5px;
96
- cursor: pointer;
97
- z-index: 30;
98
- color: white;
99
- }
100
-
101
- .toggle-btn:hover {
102
- background: #374151;
103
  }
104
-
105
- .current-time {
106
- background: #ef444420;
107
- border-left: 2px solid #ef4444;
108
  }
109
  </style>
110
-
111
- <button class="toggle-btn">
112
- <i data-feather="chevron-left"></i>
113
- </button>
114
-
115
- <div class="header">
116
- <h3>Timeline</h3>
117
- <button id="close-timeline">
118
- <i data-feather="x"></i>
119
- </button>
120
- </div>
121
-
122
- <div class="timeline-container" id="timeline-hours">
123
- ${Array.from({ length: 24 }, (_, i) => `
124
- <div class="timeline-hour" data-hour="${i}">
125
- <span class="hour-label">${i.toString().padStart(2, '0')}:00</span>
126
- <div class="task-drop-area" data-hour="${i}"></div>
127
- </div>
128
- `).join('')}
129
- </div>
130
  `;
131
  }
132
 
133
- setupEventListeners() {
134
- const toggleBtn = this.shadowRoot.querySelector('.toggle-btn');
135
- toggleBtn.addEventListener('click', () => {
136
- this.classList.toggle('open');
137
- const icon = toggleBtn.querySelector('i');
138
- if (this.classList.contains('open')) {
139
- } else {
140
- }
141
- });
142
-
143
- // Setup drag and drop
144
- this.shadowRoot.addEventListener('dragover', (e) => {
145
- if (e.target.classList.contains('task-drop-area')) {
146
- e.preventDefault();
147
- e.target.style.backgroundColor = '#37415170';
148
- }
149
- });
150
-
151
- this.shadowRoot.addEventListener('dragleave', (e) => {
152
- if (e.target.classList.contains('task-drop-area')) {
153
- e.target.style.backgroundColor = '';
154
- }
155
- });
156
-
157
- this.shadowRoot.addEventListener('drop', (e) => {
158
- if (e.target.classList.contains('task-drop-area')) {
159
- e.preventDefault();
160
- e.target.style.backgroundColor = '';
161
-
162
- const taskId = e.dataTransfer.getData('text/plain');
163
- const hour = parseInt(e.target.dataset.hour);
164
-
165
- this.dispatchEvent(new CustomEvent('task-dropped', {
166
- detail: { taskId, hour }
167
- }));
168
- }
169
- });
170
-
171
- // Initialize timeline
172
- setTimeout(() => {
173
- this.updateTimeline();
174
- }, 100);
175
- }
176
-
177
- updateTimeline() {
178
- const today = new Date().toDateString();
179
- const scheduledTasks = state.timelineTasks.filter(t =>
180
- new Date(t.date).toDateString() === today
181
- );
182
-
183
- const container = this.shadowRoot.querySelector('.timeline-container');
184
- container.querySelectorAll('.task-block').forEach(el => el.remove());
185
-
186
- scheduledTasks.forEach(timelineTask => {
187
- const task = state.tasks.find(t => t.id === timelineTask.taskId);
188
- if (!task || task.completed) return;
189
-
190
- const startTime = new Date(timelineTask.startTime);
191
- const hour = startTime.getHours();
192
- const minute = startTime.getMinutes();
193
-
194
- const hourEl = this.shadowRoot.querySelector(`.timeline-hour[data-hour="${hour}"]`);
195
- if (hourEl) {
196
- const taskEl = document.createElement('div');
197
- taskEl.className = 'task-block';
198
- taskEl.innerHTML = `
199
- <div class="task-title">${task.title}</div>
200
- <div class="task-time">${formatTime(task.estimatedTime)}</div>
201
- `;
202
- taskEl.draggable = true;
203
- taskEl.dataset.taskId = task.id;
204
-
205
- hourEl.appendChild(taskEl);
206
- }
207
- });
208
  }
209
  }
210
 
211
- customElements.define('timeline-sidebar', TimelineSidebar);
 
 
 
 
 
 
 
1
+ class TimelineTasks extends HTMLElement {
2
  connectedCallback() {
3
  this.attachShadow({ mode: 'open' });
4
  this.render();
 
5
  }
6
 
7
  render() {
 
9
  <style>
10
  :host {
11
  display: block;
 
 
 
 
 
12
  background: #1f2937;
13
  border-left: 1px solid #374151;
14
+ height: 100%;
15
+ width: 300px;
16
+ position: fixed;
17
+ right: 0;
18
+ top: 80px;
19
  overflow-y: auto;
 
 
 
 
 
 
 
20
  padding: 1rem;
 
 
 
 
 
21
  }
22
+
23
+ h3 {
24
+ margin-top: 0;
25
+ color: #f97316;
26
+ padding-bottom: 1rem;
 
 
 
27
  border-bottom: 1px solid #374151;
 
 
 
 
 
 
 
 
 
28
  }
29
+
30
+ .task-list {
31
+ display: flex;
32
+ flex-direction: column;
33
+ gap: 8px;
 
 
 
 
 
34
  }
35
+
36
+ .task-item {
37
  background: #374151;
38
+ border-radius: 6px;
39
+ padding: 8px;
40
+ cursor: grab;
 
 
 
 
41
  transition: all 0.2s ease;
42
  }
43
+
44
+ .task-item:hover {
45
  background: #4b5563;
46
+ transform: translateX(-4px);
47
  }
48
+
49
+ .task-title {
50
+ font-size: 14px;
51
+ margin-bottom: 4px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  }
53
+
54
+ .task-time {
55
+ font-size: 12px;
56
+ color: #9ca3af;
57
  }
58
  </style>
59
+
60
+ <h3>Timeline</h3>
61
+ <div class="task-list" id="timeline-task-list"></div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
62
  `;
63
  }
64
 
65
+ updateTasks(tasks) {
66
+ const container = this.shadowRoot.getElementById('timeline-task-list');
67
+ container.innerHTML = tasks.map(task => `
68
+ <div class="task-item" draggable="true" data-task-id="${task.id}">
69
+ <div class="task-title">${task.title}</div>
70
+ <div class="task-time">${formatTime(task.estimatedTime)}</div>
71
+ </div>
72
+ `).join('');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  }
74
  }
75
 
76
+ customElements.define('timeline-tasks', TimelineTasks);
77
+
78
+ function formatTime(minutes) {
79
+ const h = Math.floor(minutes / 60);
80
+ const m = minutes % 60;
81
+ return `${h}h ${m}m`;
82
+ }
index.html CHANGED
@@ -1,824 +1,237 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
 
2
- // TaskForge Pro - Main JavaScript
3
- // State management
4
- window.state = {
5
- tasks: [],
6
- tags: [],
7
- currentView: 'home',
8
- focusTask: null,
9
- pomodoro: {
10
- isRunning: false,
11
- isBreak: false,
12
- timeRemaining: 0,
13
- totalTime: 0,
14
- interval: null
15
- },
16
- timelineTasks: []
17
- };
18
- // Initialize
19
- document.addEventListener('DOMContentLoaded', function() {
20
- loadFromLocalStorage();
21
- initializeUI();
22
- setupEventListeners();
23
- render();
24
- });
25
- // Local Storage
26
- function loadFromLocalStorage() {
27
- const savedTasks = localStorage.getItem('tasks');
28
- const savedTags = localStorage.getItem('tags');
29
- const savedTimeline = localStorage.getItem('timeline');
30
-
31
- if (savedTasks) {
32
- state.tasks = JSON.parse(savedTasks);
33
- }
34
- if (savedTags) {
35
- state.tags = JSON.parse(savedTags);
36
- }
37
- if (savedTimeline) {
38
- state.timelineTasks = JSON.parse(savedTimeline);
39
- }
40
-
41
- // Load Pomodoro settings
42
- const workTime = localStorage.getItem('pomodoroWorkTime') || 25;
43
- const breakTime = localStorage.getItem('pomodoroBreakTime') || 5;
44
- document.getElementById('pomodoro-work').value = workTime;
45
- document.getElementById('pomodoro-break').value = breakTime;
46
- }
47
-
48
- function saveToLocalStorage() {
49
- localStorage.setItem('tasks', JSON.stringify(state.tasks));
50
- localStorage.setItem('tags', JSON.stringify(state.tags));
51
- localStorage.setItem('timeline', JSON.stringify(state.timelineTasks));
52
- }
53
-
54
- // UI Initialization
55
- function initializeUI() {
56
- // Current time display
57
- updateCurrentTime();
58
- setInterval(updateCurrentTime, 1000);
59
-
60
- // Initialize timeline
61
- initializeTimeline();
62
-
63
- // Scroll to current time in timeline
64
- setTimeout(scrollToCurrentTime, 100);
65
- // Check for default tags
66
- if (state.tags.length === 0) {
67
- state.tags = [
68
- { id: '1', name: 'Wichtig', color: '#ef4444' },
69
- { id: '2', name: 'Privat', color: '#10b981' },
70
- { id: '3', name: 'Arbeit', color: '#3b82f6' }
71
- ];
72
- saveToLocalStorage();
73
- }
74
- }
75
-
76
- function updateCurrentTime() {
77
- const now = new Date();
78
- const timeString = now.toLocaleTimeString('de-DE', {
79
- hour: '2-digit',
80
- minute: '2-digit'
81
- });
82
- document.getElementById('current-time').textContent = timeString;
83
- }
84
- function setupEventListeners() {
85
- // Navigation
86
- document.querySelectorAll('.nav-btn').forEach(btn => {
87
- btn.addEventListener('click', (e) => {
88
- const view = e.currentTarget.dataset.view;
89
- switchView(view);
90
- });
91
- });
92
-
93
- // FAB buttons
94
- document.getElementById('add-task-fab').addEventListener('click', openAddTaskModal);
95
- document.getElementById('focus-fab').addEventListener('click', enterFocusMode);
96
-
97
- // Add task modal
98
- document.getElementById('add-task-form').addEventListener('submit', handleAddTask);
99
- document.getElementById('cancel-task').addEventListener('click', closeAddTaskModal);
100
-
101
- // Settings
102
- document.getElementById('import-btn').addEventListener('click', handleImport);
103
- document.getElementById('export-btn').addEventListener('click', handleExport);
104
- document.getElementById('add-tag-btn').addEventListener('click', handleAddTag);
105
-
106
- document.getElementById('pomodoro-work').addEventListener('change', savePomodoroSettings);
107
- document.getElementById('pomodoro-break').addEventListener('change', savePomodoroSettings);
108
-
109
- // Focus mode
110
- document.getElementById('focus-pause').addEventListener('click', pausePomodoro);
111
- document.getElementById('focus-play').addEventListener('click', resumePomodoro);
112
- document.getElementById('focus-stop').addEventListener('click', stopPomodoro);
113
-
114
- // Event delegation for task item events
115
- document.addEventListener('delete-task', (e) => {
116
- deleteTask(e.detail.taskId);
117
- });
118
-
119
- document.addEventListener('toggle-complete', (e) => {
120
- toggleTaskComplete(e.detail.taskId, e.detail.completed);
121
- });
122
-
123
- document.addEventListener('update-task', (e) => {
124
- updateTask(e.detail.taskId, e.detail.updates);
125
- });
126
-
127
- document.addEventListener('add-time', (e) => {
128
- addTimeToTask(e.detail.taskId, e.detail.minutes);
129
- });
130
-
131
- // Timeline sidebar events
132
- const timelineSidebar = document.getElementById('timeline-sidebar');
133
- if (timelineSidebar) {
134
- timelineSidebar.addEventListener('task-dropped', (e) => {
135
- scheduleTask(e.detail.taskId, e.detail.hour, 0);
136
- });
137
- }
138
- }
139
- // View Management
140
- function switchView(view) {
141
- state.currentView = view;
142
-
143
- // Update nav buttons
144
- document.querySelectorAll('.nav-btn').forEach(btn => {
145
- btn.classList.toggle('active', btn.dataset.view === view);
146
- });
147
-
148
- // Hide all views first
149
- document.querySelectorAll('.view').forEach(v => {
150
- v.classList.remove('active');
151
- v.style.display = 'none';
152
- });
153
-
154
- // Show only the active view
155
- const activeView = document.getElementById(`${view}-view`);
156
- if (activeView) {
157
- activeView.style.display = 'block';
158
- activeView.classList.add('active');
159
- }
160
- }
161
- // Task Management
162
- function addTask(task) {
163
- const newTask = {
164
- id: Date.now().toString(),
165
- title: task.title,
166
- completed: false,
167
- estimatedTime: task.estimatedTime, // in minutes
168
- spentTime: 0, // in minutes
169
- tags: task.tags || [],
170
- createdAt: new Date().toISOString()
171
- };
172
-
173
- state.tasks.push(newTask);
174
- saveToLocalStorage();
175
- render();
176
-
177
- return newTask;
178
- }
179
-
180
- function updateTask(taskId, updates) {
181
- const task = state.tasks.find(t => t.id === taskId);
182
- if (task) {
183
- Object.assign(task, updates);
184
- saveToLocalStorage();
185
- render();
186
- }
187
- }
188
-
189
- function deleteTask(taskId) {
190
- state.tasks = state.tasks.filter(t => t.id !== taskId);
191
- state.timelineTasks = state.timelineTasks.filter(t => t.taskId !== taskId);
192
- saveToLocalStorage();
193
- render();
194
- }
195
- function toggleTaskComplete(taskId, completed) {
196
- const task = state.tasks.find(t => t.id === taskId);
197
- if (task) {
198
- task.completed = completed !== undefined ? completed : !task.completed;
199
- saveToLocalStorage();
200
- render();
201
- }
202
- }
203
- // Modal Functions
204
- function openAddTaskModal() {
205
- const modal = document.getElementById('add-task-modal');
206
- modal.classList.remove('hidden');
207
- renderModalTags();
208
- document.getElementById('task-title').focus();
209
- }
210
- function closeAddTaskModal() {
211
- const modal = document.getElementById('add-task-modal');
212
- modal.classList.add('hidden');
213
- document.getElementById('add-task-form').reset();
214
- }
215
-
216
- function handleAddTask(e) {
217
- e.preventDefault();
218
-
219
- const title = document.getElementById('task-title').value;
220
- const hours = parseInt(document.getElementById('task-hours').value) || 0;
221
- const minutes = parseInt(document.getElementById('task-minutes').value) || 0;
222
- const estimatedTime = hours * 60 + minutes;
223
-
224
- const selectedTags = Array.from(document.querySelectorAll('#modal-tags-list input:checked')).map(cb => cb.value);
225
-
226
- addTask({
227
- title,
228
- estimatedTime,
229
- tags: selectedTags
230
- });
231
-
232
- closeAddTaskModal();
233
- }
234
-
235
- function renderModalTags() {
236
- const container = document.getElementById('modal-tags-list');
237
- container.innerHTML = state.tags.map(tag => `
238
- <label class="flex items-center space-x-2 cursor-pointer">
239
- <input type="checkbox" value="${tag.id}" class="custom-checkbox">
240
- <span class="tag-badge" style="background-color: ${tag.color}20; color: ${tag.color}">
241
- ${tag.name}
242
- </span>
243
- </label>
244
- `).join('');
245
- }
246
-
247
- // Tag Management
248
- function addTag(tag) {
249
- state.tags.push({
250
- id: Date.now().toString(),
251
- name: tag.name,
252
- color: tag.color
253
- });
254
- saveToLocalStorage();
255
- render();
256
- }
257
-
258
- function deleteTag(tagId) {
259
- state.tags = state.tags.filter(t => t.id !== tagId);
260
- // Remove tag from all tasks
261
- state.tasks.forEach(task => {
262
- task.tags = task.tags.filter(t => t !== tagId);
263
- });
264
- saveToLocalStorage();
265
- render();
266
- }
267
-
268
- function handleAddTag() {
269
- const name = document.getElementById('new-tag-name').value;
270
- const color = document.getElementById('new-tag-color').value;
271
-
272
- if (name) {
273
- addTag({ name, color });
274
- document.getElementById('new-tag-name').value = '';
275
- }
276
- }
277
-
278
- // Import/Export
279
- function handleImport() {
280
- const markdown = document.getElementById('import-md').value;
281
- if (!markdown) return;
282
-
283
- const tasks = parseMarkdown(markdown);
284
- tasks.forEach(task => {
285
- // Check if task already exists
286
- const exists = state.tasks.some(t => t.title === task.title);
287
- if (!exists) {
288
- state.tasks.push({
289
- id: Date.now().toString() + Math.random(),
290
- title: task.title,
291
- completed: task.completed,
292
- estimatedTime: task.estimatedTime,
293
- spentTime: task.spentTime || 0,
294
- tags: [],
295
- createdAt: new Date().toISOString()
296
- });
297
- }
298
- });
299
-
300
- saveToLocalStorage();
301
- render();
302
- document.getElementById('import-md').value = '';
303
- }
304
-
305
- function handleExport() {
306
- const markdown = generateMarkdown();
307
- const blob = new Blob([markdown], { type: 'text/markdown' });
308
- const url = URL.createObjectURL(blob);
309
- const a = document.createElement('a');
310
- a.href = url;
311
- a.download = 'tasks.md';
312
- a.click();
313
- URL.revokeObjectURL(url);
314
- }
315
- function parseMarkdown(markdown) {
316
- const lines = markdown.split('\n');
317
- const tasks = [];
318
-
319
- const taskRegex = /^\s*-\s*\[([ x])\]\s+(.+?)(?:\s*\|\s*(.+?)(?:\s*\|\s*(.+?))?)?\s*$/;
320
-
321
- lines.forEach(line => {
322
- const match = line.match(taskRegex);
323
- if (match) {
324
- const completed = match[1] === 'x';
325
- const title = match[2].trim();
326
- const timePart = match[3] ? match[3].trim() : '0h';
327
- const tagsPart = match[4] ? match[4].trim() : '';
328
-
329
- // Parse time
330
- const timeMatch = timePart.match(/(\d+)(?:\s*h\s*)?(?:(?:\s*(\d+)\s*m\s*)?)?/);
331
- const hours = parseInt(timeMatch[1]) || 0;
332
- const minutes = parseInt(timeMatch[2]) || 0;
333
- const estimatedTime = hours * 60 + minutes;
334
-
335
- tasks.push({
336
- title,
337
- completed,
338
- estimatedTime
339
- });
340
- }
341
- });
342
- return tasks;
343
- }
344
-
345
- function generateMarkdown() {
346
- return state.tasks.map(task => {
347
- const status = task.completed ? 'x' : ' ';
348
- const hours = Math.floor(task.estimatedTime / 60);
349
- const minutes = task.estimatedTime % 60;
350
- const timeStr = `${hours}h${minutes > 0 ? ` ${minutes}m` : ''}`;
351
- return `- [${status}] ${task.title} | ${timeStr}`;
352
- }).join('\n');
353
- }
354
-
355
- // Rendering
356
- function render() {
357
- renderTasks();
358
- renderProgressBars();
359
- renderTags();
360
- renderTimeline();
361
- }
362
- function renderTasks() {
363
- const openContainer = document.getElementById('open-tasks');
364
- const completedContainer = document.getElementById('completed-tasks');
365
-
366
- const openTasks = state.tasks.filter(t => !t.completed);
367
- const completedTasks = state.tasks.filter(t => t.completed);
368
-
369
- // Render open tasks
370
- if (openTasks.length === 0) {
371
- openContainer.innerHTML = '<div class="empty-state"><i data-feather="inbox"></i><p>Keine offenen Aufgaben</p></div>';
372
- } else {
373
- openContainer.innerHTML = '';
374
- openTasks.forEach(task => {
375
- const taskElement = document.createElement('task-item');
376
- taskElement.setAttribute('task-id', task.id);
377
- taskElement.setAttribute('completed', 'false');
378
- openContainer.appendChild(taskElement);
379
- });
380
- }
381
-
382
- // Render completed tasks
383
- if (completedTasks.length === 0) {
384
- completedContainer.innerHTML = '<div class="empty-state"><i data-feather="check-circle"></i><p>Keine erledigten Aufgaben</p></div>';
385
- } else {
386
- completedContainer.innerHTML = '';
387
- completedTasks.forEach(task => {
388
- const taskElement = document.createElement('task-item');
389
- taskElement.setAttribute('task-id', task.id);
390
- taskElement.setAttribute('completed', 'true');
391
- completedContainer.appendChild(taskElement);
392
- });
393
- }
394
-
395
- document.getElementById('open-count').textContent = openTasks.length;
396
- document.getElementById('completed-count').textContent = completedTasks.length;
397
-
398
- // Setup drag and drop for task items
399
- setupTaskDragAndDrop();
400
-
401
- // Replace feather icons
402
- feather.replace();
403
- }
404
- function renderProgressBars() {
405
- // Overall progress
406
- const totalEstimated = state.tasks.reduce((sum, t) => sum + t.estimatedTime, 0) || 1;
407
- const totalSpent = state.tasks.reduce((sum, t) => sum + t.spentTime, 0);
408
- const totalProgress = (totalSpent / totalEstimated) * 100;
409
-
410
- document.getElementById('total-progress-bar').style.width = `${totalProgress}%`;
411
- document.getElementById('total-progress-text').textContent =
412
- `${Math.round(totalProgress)}% (${formatTime(totalSpent)} / ${formatTime(totalEstimated)})`;
413
-
414
- // Tag progress bars - only for tags with tasks
415
- const container = document.getElementById('tag-progress-container');
416
- container.innerHTML = state.tags
417
- .filter(tag => state.tasks.some(t => t.tags.includes(tag.id))) // Only tags with tasks
418
- .map(tag => {
419
- const tagTasks = state.tasks.filter(t => t.tags.includes(tag.id));
420
- const tagEstimated = tagTasks.reduce((sum, t) => sum + t.estimatedTime, 0) || 1;
421
- const tagSpent = tagTasks.reduce((sum, t) => sum + t.spentTime, 0);
422
- const tagProgress = (tagSpent / tagEstimated) * 100;
423
-
424
- return `
425
- <div class="bg-gray-800 rounded-lg p-4 shadow transition-all hover:scale-[1.02]">
426
- <div class="flex items-center justify-between mb-2">
427
- <span class="font-medium" style="color: ${tag.color}">${tag.name}</span>
428
- <span class="text-sm text-gray-400">
429
- ${Math.round(tagProgress)}% (${formatTime(tagSpent)} / ${formatTime(tagEstimated)})
430
- </span>
431
- </div>
432
- <div class="w-full bg-gray-700 rounded-full h-2 overflow-hidden">
433
- <div class="h-full rounded-full transition-all duration-500"
434
- style="width: ${tagProgress}%; background-color: ${tag.color}"></div>
435
  </div>
 
436
  </div>
437
- `;
438
- }).join('');
439
- }
440
- function renderTags() {
441
- const container = document.getElementById('tags-list');
442
- container.innerHTML = state.tags.map(tag => `
443
- <tag-item tag-id="${tag.id}" tag-name="${tag.name}" tag-color="${tag.color}"></tag-item>
444
- `).join('');
445
-
446
- // Render tag components
447
- document.querySelectorAll('tag-item').forEach(el => {
448
- const tagId = el.getAttribute('tag-id');
449
- el.addEventListener('delete', () => deleteTag(tagId));
450
- });
451
- }
452
- function highlightCurrentHour() {
453
- const now = new Date();
454
- const currentHour = now.getHours();
455
- const currentMinute = now.getMinutes();
456
-
457
- // Highlight current hour
458
- document.querySelectorAll('.timeline-hour').forEach(el => {
459
- el.classList.remove('bg-gray-750');
460
- });
461
-
462
- const hourElement = document.querySelector(`.timeline-hour[data-hour="${currentHour}"]`);
463
- if (hourElement) {
464
- hourElement.classList.add('bg-gray-750');
465
- }
466
-
467
- // Add current time indicator line
468
- document.getElementById('current-time-indicator')?.remove();
469
-
470
- const timelineContainer = document.getElementById('timeline-hours');
471
- if (timelineContainer) {
472
- const indicator = document.createElement('div');
473
- indicator.id = 'current-time-indicator';
474
- indicator.style.position = 'absolute';
475
- indicator.style.left = '0';
476
- indicator.style.right = '0';
477
- indicator.style.height = '2px';
478
- indicator.style.backgroundColor = '#ef4444';
479
- indicator.style.zIndex = '10';
480
-
481
- const hourElement = document.querySelector(`.timeline-hour[data-hour="${currentHour}"]`);
482
- if (hourElement) {
483
- const hourTop = hourElement.offsetTop;
484
- const hourHeight = hourElement.offsetHeight;
485
- const minutePosition = (currentMinute / 60) * hourHeight;
486
-
487
- indicator.style.top = `${hourTop + minutePosition}px`;
488
- timelineContainer.appendChild(indicator);
489
-
490
- // Add time label
491
- const timeLabel = document.createElement('div');
492
- timeLabel.textContent = now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
493
- timeLabel.style.position = 'absolute';
494
- timeLabel.style.left = '-64px';
495
- timeLabel.style.top = `${hourTop + minutePosition - 10}px`;
496
- timeLabel.style.backgroundColor = '#ef4444';
497
- timeLabel.style.color = 'white';
498
- timeLabel.style.padding = '2px 8px';
499
- timeLabel.style.borderRadius = '4px';
500
- timeLabel.style.fontSize = '12px';
501
- timeLabel.style.zIndex = '10';
502
-
503
- hourEl.appendChild(timeLabel);
504
- }
505
- }
506
- }
507
- function scrollToCurrentTime() {
508
- const now = new Date();
509
- const currentHour = now.getHours();
510
- const hourEl = document.querySelector(`[data-hour="${currentHour}"]`);
511
-
512
- if (hourEl) {
513
- hourEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
514
- }
515
- }
516
-
517
- // Focus Mode & Pomodoro
518
- function enterFocusMode() {
519
- // Open a task selection modal
520
- const openTasks = state.tasks.filter(t => !t.completed);
521
- if (openTasks.length === 0) {
522
- alert('Keine offenen Aufgaben für den Fokus-Modus!');
523
- return;
524
- }
525
 
526
- const modal = document.createElement('div');
527
- modal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4';
528
- modal.innerHTML = `
529
- <div class="bg-gray-800 rounded-xl p-6 max-w-md w-full max-h-[80vh] overflow-y-auto">
530
- <h3 class="text-xl font-semibold mb-4">Aufgabe für Fokus-Modus auswählen</h3>
531
- <div class="space-y-2">
532
- ${openTasks.map(task => `
533
- <div class="bg-gray-700 p-3 rounded-lg cursor-pointer hover:bg-gray-600 transition"
534
- onclick="startFocusModeWithId('${task.id}')">
535
- <div class="font-medium">${task.title}</div>
536
- <div class="text-sm text-gray-400">${formatTime(task.estimatedTime)}</div>
537
  </div>
538
- `).join('')}
 
539
  </div>
540
- <button onclick="this.closest('div[class*=\"fixed\"]').remove()"
541
- class="mt-4 px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition w-full">
542
- Abbrechen
543
- </button>
544
  </div>
545
- `;
546
- document.body.appendChild(modal);
547
- }
548
-
549
- function startFocusModeWithId(taskId) {
550
- const task = state.tasks.find(t => t.id === taskId);
551
- if (task) {
552
- document.querySelector('div[class*="fixed"]')?.remove();
553
- startFocusMode(task);
554
- }
555
- }
556
- function startFocusMode(task) {
557
- state.focusTask = task;
558
- document.getElementById('focus-task-title').textContent = task.title;
559
- document.getElementById('focus-mode').classList.remove('hidden');
560
-
561
- // Start pomodoro
562
- const workTime = parseInt(localStorage.getItem('pomodoroWorkTime')) || 25;
563
- startPomodoro(workTime * 60, false);
564
- }
565
- function startPomodoro(seconds, isBreak) {
566
- state.pomodoro.isRunning = true;
567
- state.pomodoro.isBreak = isBreak;
568
- state.pomodoro.timeRemaining = seconds;
569
- state.pomodoro.totalTime = seconds;
570
-
571
- clearInterval(state.pomodoro.interval);
572
-
573
- state.pomodoro.interval = setInterval(() => {
574
- if (state.pomodoro.timeRemaining > 0) {
575
- state.pomodoro.timeRemaining--;
576
- updatePomodoroDisplay();
577
- } else {
578
- handlePomodoroComplete();
579
- }
580
- }, 1000);
581
-
582
- updatePomodoroDisplay();
583
- }
584
-
585
- function updatePomodoroDisplay() {
586
- const minutes = Math.floor(state.pomodoro.timeRemaining / 60);
587
- const seconds = state.pomodoro.timeRemaining % 60;
588
- document.getElementById('pomodoro-timer').textContent =
589
- `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
590
-
591
- const statusEl = document.getElementById('pomodoro-status');
592
- statusEl.textContent = state.pomodoro.isBreak ? 'Pausenzeit läuft...' : 'Arbeitszeit läuft...';
593
- }
594
-
595
- function handlePomodoroComplete() {
596
- clearInterval(state.pomodoro.interval);
597
-
598
- if (!state.pomodoro.isBreak) {
599
- // Work session complete
600
- const elapsed = state.pomodoro.totalTime;
601
- state.focusTask.spentTime += elapsed;
602
- saveToLocalStorage();
603
-
604
- // Start break
605
- const breakTime = parseInt(localStorage.getItem('pomodoroBreakTime')) || 5;
606
- startPomodoro(breakTime * 60, true);
607
-
608
- // Notification
609
- if ('Notification' in window && Notification.permission === 'granted') {
610
- new Notification('Pause!', { body: 'Arbeitszeit abgeschlossen. Zeit für eine Pause!' });
611
- }
612
- } else {
613
- // Break complete
614
- if ('Notification' in window && Notification.permission === 'granted') {
615
- new Notification('Weiterarbeiten!', { body: 'Pausenzeit ist vorbei.' });
616
- }
617
- }
618
- }
619
-
620
- function pausePomodoro() {
621
- state.pomodoro.isRunning = false;
622
- clearInterval(state.pomodoro.interval);
623
- document.getElementById('focus-pause').classList.add('hidden');
624
- document.getElementById('focus-play').classList.remove('hidden');
625
- }
626
 
627
- function resumePomodoro() {
628
- startPomodoro(state.pomodoro.timeRemaining, state.pomodoro.isBreak);
629
- document.getElementById('focus-play').classList.add('hidden');
630
- document.getElementById('focus-pause').classList.remove('hidden');
631
- }
632
- function stopPomodoro() {
633
- clearInterval(state.pomodoro.interval);
634
-
635
- // Add elapsed time to task
636
- if (state.focusTask && !state.pomodoro.isBreak) {
637
- const elapsed = state.pomodoro.totalTime - state.pomodoro.timeRemaining;
638
- state.focusTask.spentTime += elapsed;
639
- saveToLocalStorage();
640
- }
641
-
642
- state.focusTask = null;
643
- state.pomodoro.isRunning = false;
644
- document.getElementById('focus-mode').classList.add('hidden');
645
- render();
646
- }
647
- function savePomodoroSettings() {
648
- localStorage.setItem('pomodoroWorkTime', document.getElementById('pomodoro-work').value);
649
- localStorage.setItem('pomodoroBreakTime', document.getElementById('pomodoro-break').value);
650
- }
651
- // Timeline Functions
652
- function initializeTimeline() {
653
- // Setup drag start for task items
654
- document.addEventListener('dragstart', (e) => {
655
- if (e.target.tagName === 'TASK-ITEM') {
656
- const taskId = e.target.getAttribute('task-id');
657
- e.dataTransfer.setData('text/plain', taskId);
658
- }
659
- });
660
- }
661
- function handleDragStart(e) {
662
- if (e.target.classList.contains('task-item')) {
663
- const taskId = e.target.getAttribute('task-id');
664
- e.dataTransfer.setData('text/plain', taskId);
665
- e.target.classList.add('dragging');
666
- }
667
- }
668
-
669
- function handleDragOver(e) {
670
- e.preventDefault();
671
-
672
- if (e.target.classList.contains('timeline-hour')) {
673
- e.target.classList.add('drag-over');
674
- }
675
- }
676
- function handleDragEnd(e) {
677
- e.target.classList.remove('dragging');
678
- document.querySelectorAll('.drag-over').forEach(el => {
679
- el.classList.remove('drag-over');
680
- });
681
- }
682
- function scheduleTask(taskId, hour, minute) {
683
- const taskDate = new Date();
684
- taskDate.setHours(0, 0, 0, 0); // Set to start of day for consistent date matching
685
-
686
- const startTime = new Date();
687
- startTime.setHours(hour, minute, 0, 0);
688
-
689
- // Remove existing schedule for this task today
690
- state.timelineTasks = state.timelineTasks.filter(t => {
691
- const scheduleDate = new Date(t.date);
692
- scheduleDate.setHours(0, 0, 0, 0);
693
- return !(scheduleDate.getTime() === taskDate.getTime() && t.taskId === taskId);
694
- });
695
-
696
- // Add new schedule
697
- state.timelineTasks.push({
698
- taskId,
699
- date: taskDate.toISOString(),
700
- startTime: startTime.toISOString()
701
- });
702
-
703
- saveToLocalStorage();
704
-
705
- // Update timeline sidebar
706
- const timelineSidebar = document.getElementById('timeline-sidebar');
707
- if (timelineSidebar && timelineSidebar.updateTimeline) {
708
- timelineSidebar.updateTimeline();
709
- }
710
- }
711
- // Utilities
712
- function formatTime(minutes) {
713
- const h = Math.floor(minutes / 60);
714
- const m = minutes % 60;
715
- return `${h}h${m > 0 ? ` ${m}m` : ''}`;
716
- }
717
-
718
- // Request notification permission
719
- if ('Notification' in window && Notification.permission === 'default') {
720
- Notification.requestPermission();
721
- }
722
 
723
- // New functions for task management
724
- function addTimeToTask(taskId, minutes) {
725
- const task = state.tasks.find(t => t.id === taskId);
726
- if (task) {
727
- task.spentTime += minutes;
728
- saveToLocalStorage();
729
- render();
730
- }
731
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
732
 
733
- function setupTaskDragAndDrop() {
734
- const openContainer = document.getElementById('open-tasks');
735
- const completedContainer = document.getElementById('completed-tasks');
736
-
737
- // Setup drag and drop for reordering
738
- [openContainer, completedContainer].forEach(container => {
739
- if (!container) return;
740
-
741
- container.addEventListener('dragover', (e) => {
742
- e.preventDefault();
743
- const afterElement = getDragAfterElement(container, e.clientY);
744
- const draggable = document.querySelector('.dragging');
745
- if (draggable) {
746
- if (afterElement == null) {
747
- container.appendChild(draggable);
748
- } else {
749
- container.insertBefore(draggable, afterElement);
750
- }
751
- });
752
- });
753
- }
754
 
755
- function getDragAfterElement(container, y) {
756
- const draggableElements = [...container.querySelectorAll('task-item:not(.dragging)'));
757
-
758
- return draggableElements.reduce((closest, child) => {
759
- const box = child.getBoundingClientRect();
760
- const offset = y - box.top - box.height / 2;
761
- if (offset < 0 && offset > closest.offset) {
762
- return { offset: offset, element: child };
763
- } else {
764
- return closest;
765
- }
766
- }, { offset: Number.NEGATIVE_INFINITY }).element;
767
- }
 
 
 
 
 
 
 
768
 
769
- // Edit task modal functionality
770
- function openEditTaskModal(taskId) {
771
- const task = state.tasks.find(t => t.id === taskId);
772
- if (!task) return;
773
-
774
- const modal = document.createElement('div');
775
- modal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4';
776
- modal.innerHTML = `
777
  <div class="bg-gray-800 rounded-xl p-6 max-w-md w-full">
778
- <h3 class="text-xl font-semibold mb-4">Aufgabe bearbeiten</h3>
779
- <form id="edit-task-form" class="space-y-4">
780
  <div>
781
  <label class="block text-sm font-medium mb-2">Titel</label>
782
- <input id="edit-task-title" type="text" required value="${task.title}"
783
  class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
784
  </div>
785
  <div>
786
  <label class="block text-sm font-medium mb-2">Geschätzte Zeit</label>
787
  <div class="flex space-x-2">
788
- <input id="edit-task-hours" type="number" placeholder="h" min="0" max="24" value="${Math.floor(task.estimatedTime / 60)}"
789
  class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
790
- <input id="edit-task-minutes" type="number" placeholder="m" min="0" max="59" step="15" value="${task.estimatedTime % 60}"
791
  class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
792
  </div>
793
  </div>
 
 
 
 
794
  <div class="flex justify-end space-x-2">
795
- <button type="button" onclick="this.closest('div[class*=\"fixed\"]').remove()"
796
- class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition">
797
- Abbrechen
798
- </button>
799
- <button type="submit" class="px-4 py-2 bg-orange-500 hover:bg-orange-600 rounded-lg transition">
800
- Speichern
801
- </button>
802
- </div>
803
- </form>
804
  </div>
805
- `;
806
-
807
- document.body.appendChild(modal);
808
-
809
- const form = modal.querySelector('#edit-task-form');
810
- form.addEventListener('submit', (e) => {
811
- e.preventDefault();
812
- handleEditTask(taskId);
813
- modal.remove();
814
- });
815
- }
816
 
817
- function handleEditTask(taskId) {
818
- const title = document.getElementById('edit-task-title').value;
819
- const hours = parseInt(document.getElementById('edit-task-hours').value) || 0;
820
- const minutes = parseInt(document.getElementById('edit-task-minutes').value) || 0;
821
- const estimatedTime = hours * 60 + minutes;
822
-
823
- updateTask(taskId, { title, estimatedTime });
824
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="de" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>TaskForge Pro</title>
7
+ <link rel="stylesheet" href="style.css">
8
+ <script src="https://cdn.tailwindcss.com"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
10
+ <script src="https://unpkg.com/feather-icons"></script>
11
+ </head>
12
+ <body class="bg-gray-900 text-gray-100 min-h-screen">
13
+ <!-- Header with Navigation -->
14
+ <header class="bg-gray-800 border-b border-gray-700 sticky top-0 z-40">
15
+ <div class="container mx-auto px-4 py-4">
16
+ <div class="flex items-center justify-between">
17
+ <h1 class="text-2xl font-bold text-orange-500">TaskForge Pro</h1>
18
+ <nav class="flex items-center space-x-4">
19
+ <button id="view-home" class="nav-btn active" data-view="home">
20
+ <i data-feather="home"></i> Übersicht
21
+ </button>
22
+ <button id="view-timeline" class="nav-btn" data-view="timeline">
23
+ <i data-feather="clock"></i> Timeline
24
+ </button>
25
+ <button id="view-settings" class="nav-btn" data-view="settings">
26
+ <i data-feather="settings"></i> Einstellungen
27
+ </button>
28
+ </nav>
29
+ </div>
30
+ </div>
31
+ </header>
32
+
33
+ <!-- Main Content -->
34
+ <main class="container mx-auto px-4 py-6">
35
+ <!-- Home View -->
36
+ <div id="home-view" class="view active">
37
+ <!-- Tag Progress Bars -->
38
+ <div id="tag-progress-container" class="mb-6 space-y-3"></div>
39
+
40
+ <!-- Main Progress Bar -->
41
+ <div class="bg-gray-800 rounded-xl p-6 mb-8 shadow-lg">
42
+ <div class="flex items-center justify-between mb-4">
43
+ <h2 class="text-xl font-semibold">Gesamtfortschritt</h2>
44
+ <span id="total-progress-text" class="text-sm text-gray-400">0% (0h / 0h)</span>
45
+ </div>
46
+ <div class="w-full bg-gray-700 rounded-full h-4 overflow-hidden">
47
+ <div id="total-progress-bar" class="bg-gradient-to-r from-orange-500 to-orange-600 h-full rounded-full transition-all duration-500" style="width: 0%"></div>
48
+ </div>
49
+ </div>
50
 
51
+ <!-- Task Lists -->
52
+ <div class="grid md:grid-cols-2 gap-6">
53
+ <!-- Open Tasks -->
54
+ <div class="bg-gray-800 rounded-xl shadow-lg">
55
+ <div class="p-4 border-b border-gray-700">
56
+ <h3 class="text-lg font-semibold flex items-center">
57
+ <i data-feather="circle" class="text-orange-500 mr-2"></i>
58
+ Offene Aufgaben (<span id="open-count">0</span>)
59
+ </h3>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  </div>
61
+ <div id="open-tasks" class="p-4 space-y-3 max-h-96 overflow-y-auto"></div>
62
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
+ <!-- Completed Tasks -->
65
+ <div class="bg-gray-800 rounded-xl shadow-lg">
66
+ <div class="p-4 border-b border-gray-700">
67
+ <h3 class="text-lg font-semibold flex items-center">
68
+ <i data-feather="check-circle" class="text-emerald-500 mr-2"></i>
69
+ Erledigte Aufgaben (<span id="completed-count">0</span>)
70
+ </h3>
 
 
 
 
71
  </div>
72
+ <div id="completed-tasks" class="p-4 space-y-3 max-h-96 overflow-y-auto"></div>
73
+ </div>
74
  </div>
 
 
 
 
75
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
76
 
77
+ <!-- Timeline View -->
78
+ <div id="timeline-view" class="view">
79
+ <div class="bg-gray-800 rounded-xl p-6 shadow-lg">
80
+ <div class="flex items-center justify-between mb-6">
81
+ <h2 class="text-xl font-semibold">Timeline - Heute</h2>
82
+ <div class="flex items-center space-x-4">
83
+ <button id="timeline-now" class="px-4 py-2 bg-orange-500 hover:bg-orange-600 rounded-lg transition">
84
+ Jetzt
85
+ </button>
86
+ <span id="current-time" class="text-lg font-mono"></span>
87
+ </div>
88
+ </div>
89
+ <div id="timeline-container" class="relative">
90
+ <div id="timeline-tasks" class="absolute left-0 top-0 w-64 space-y-2 p-4">
91
+ <p class="text-sm text-gray-400">Aufgaben hierher ziehen</p>
92
+ </div>
93
+ <div id="timeline-hours" class="ml-64 border-l border-gray-600"></div>
94
+ </div>
95
+ </div>
96
+ </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
97
 
98
+ <!-- Settings View -->
99
+ <div id="settings-view" class="view">
100
+ <div class="max-w-4xl mx-auto">
101
+ <div class="bg-gray-800 rounded-xl p-6 shadow-lg">
102
+ <h2 class="text-xl font-semibold mb-6">Einstellungen</h2>
103
+
104
+ <!-- Import/Export -->
105
+ <div class="mb-8">
106
+ <h3 class="text-lg font-medium mb-4">Import/Export</h3>
107
+ <div class="space-y-4">
108
+ <div>
109
+ <label class="block text-sm font-medium mb-2">Markdown importieren</label>
110
+ <textarea id="import-md" class="w-full h-64 bg-gray-700 rounded-lg p-4 text-gray-100"
111
+ placeholder="- [ ] Aufgabe | 2h | #tag1 #tag2&#10;- [x] Erledigte Aufgabe | 1h 30m | #tag1"></textarea>
112
+ <button id="import-btn" class="mt-2 px-4 py-2 bg-emerald-500 hover:bg-emerald-600 rounded-lg transition">
113
+ Importieren
114
+ </button>
115
+ </div>
116
+ <div>
117
+ <button id="export-btn" class="px-4 py-2 bg-orange-500 hover:bg-orange-600 rounded-lg transition">
118
+ Als Markdown exportieren
119
+ </button>
120
+ </div>
121
+ </div>
122
+ </div>
123
 
124
+ <!-- Tags -->
125
+ <div class="mb-8">
126
+ <h3 class="text-lg font-medium mb-4">Tags verwalten</h3>
127
+ <div class="flex items-center space-x-2 mb-4">
128
+ <input id="new-tag-name" type="text" placeholder="Tag-Name"
129
+ class="bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
130
+ <input id="new-tag-color" type="color" value="#f97316"
131
+ class="w-16 h-10 bg-gray-700 rounded cursor-pointer">
132
+ <button id="add-tag-btn" class="px-4 py-2 bg-emerald-500 hover:bg-emerald-600 rounded-lg transition">
133
+ <i data-feather="plus"></i>
134
+ </button>
135
+ </div>
136
+ <div id="tags-list" class="flex flex-wrap gap-2"></div>
137
+ </div>
 
 
 
 
 
 
 
138
 
139
+ <!-- Pomodoro Settings -->
140
+ <div>
141
+ <h3 class="text-lg font-medium mb-4">Pomodoro</h3>
142
+ <div class="grid grid-cols-2 gap-4">
143
+ <div>
144
+ <label class="block text-sm font-medium mb-2">Arbeitszeit (Min.)</label>
145
+ <input id="pomodoro-work" type="number" value="25" min="1" max="60"
146
+ class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
147
+ </div>
148
+ <div>
149
+ <label class="block text-sm font-medium mb-2">Pause (Min.)</label>
150
+ <input id="pomodoro-break" type="number" value="5" min="1" max="30"
151
+ class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
152
+ </div>
153
+ </div>
154
+ </div>
155
+ </div>
156
+ </div>
157
+ </div>
158
+ </main>
159
 
160
+ <!-- Add Task Modal -->
161
+ <div id="add-task-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center p-4">
 
 
 
 
 
 
162
  <div class="bg-gray-800 rounded-xl p-6 max-w-md w-full">
163
+ <h3 class="text-xl font-semibold mb-4">Aufgabe hinzufügen</h3>
164
+ <form id="add-task-form" class="space-y-4">
165
  <div>
166
  <label class="block text-sm font-medium mb-2">Titel</label>
167
+ <input id="task-title" type="text" required
168
  class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
169
  </div>
170
  <div>
171
  <label class="block text-sm font-medium mb-2">Geschätzte Zeit</label>
172
  <div class="flex space-x-2">
173
+ <input id="task-hours" type="number" placeholder="h" min="0" max="24" value="0"
174
  class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
175
+ <input id="task-minutes" type="number" placeholder="m" min="0" max="59" step="15" value="30"
176
  class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
177
  </div>
178
  </div>
179
+ <div>
180
+ <label class="block text-sm font-medium mb-2">Tags</label>
181
+ <div id="modal-tags-list" class="flex flex-wrap gap-2 mb-2"></div>
182
+ </div>
183
  <div class="flex justify-end space-x-2">
184
+ <button type="button" id="cancel-task" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition">
185
+ Abbrechen
186
+ </button>
187
+ <button type="submit" class="px-4 py-2 bg-orange-500 hover:bg-orange-600 rounded-lg transition">
188
+ Hinzufügen
189
+ </button>
 
 
 
190
  </div>
191
+ </form>
192
+ </div>
193
+ </div>
 
 
 
 
 
 
 
 
194
 
195
+ <!-- Focus Mode -->
196
+ <div id="focus-mode" class="fixed inset-0 bg-gray-900 z-50 hidden flex items-center justify-center">
197
+ <div class="text-center max-w-2xl mx-auto p-8">
198
+ <h2 id="focus-task-title" class="text-3xl font-bold mb-8"></h2>
199
+
200
+ <div id="pomodoro-timer" class="text-6xl font-mono font-bold text-orange-500 mb-8"></div>
201
+
202
+ <div class="flex items-center justify-center space-x-4 mb-8">
203
+ <button id="focus-pause" class="p-4 bg-gray-800 hover:bg-gray-700 rounded-full transition">
204
+ <i data-feather="pause" class="w-8 h-8"></i>
205
+ </button>
206
+ <button id="focus-play" class="p-4 bg-gray-800 hover:bg-gray-700 rounded-full transition hidden">
207
+ <i data-feather="play" class="w-8 h-8"></i>
208
+ </button>
209
+ <button id="focus-stop" class="p-4 bg-red-500 hover:bg-red-600 rounded-full transition">
210
+ <i data-feather="square" class="w-8 h-8"></i>
211
+ </button>
212
+ </div>
213
+
214
+ <div class="text-gray-400">
215
+ <p id="pomodoro-status">Arbeitszeit läuft...</p>
216
+ </div>
217
+ </div>
218
+ </div>
219
+
220
+ <!-- FAB Buttons -->
221
+ <div class="fixed bottom-6 right-6 flex flex-col space-y-3">
222
+ <button id="focus-fab" class="p-4 bg-orange-500 hover:bg-orange-600 rounded-full shadow-lg transition transform hover:scale-110">
223
+ <i data-feather="focus"></i>
224
+ </button>
225
+ <button id="add-task-fab" class="p-4 bg-emerald-500 hover:bg-emerald-600 rounded-full shadow-lg transition transform hover:scale-110">
226
+ <i data-feather="plus"></i>
227
+ </button>
228
+ </div>
229
+ <timeline-tasks id="timeline-sidebar"></timeline-tasks>
230
+ <script src="components/task-item.js"></script>
231
+ <script src="components/tag-item.js"></script>
232
+ <script src="components/timeline-tasks.js"></script>
233
+ <script src="script.js"></script>
234
+ <script>feather.replace();</script>
235
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
236
+ </body>
237
+ </html>
script.js CHANGED
@@ -1,241 +1,800 @@
1
- /* Custom styles for TaskForge Pro */
2
-
3
- /* Scrollbar styling */
4
- ::-webkit-scrollbar {
5
- width: 8px;
6
- height: 8px;
7
- }
8
-
9
- ::-webkit-scrollbar-track {
10
- @apply bg-gray-800;
11
- }
12
-
13
- ::-webkit-scrollbar-thumb {
14
- @apply bg-gray-600 rounded-full;
15
- }
16
-
17
- ::-webkit-scrollbar-thumb:hover {
18
- @apply bg-gray-500;
19
- }
20
- /* Task hover effects */
21
- .task-item {
22
- @apply relative overflow-hidden;
23
- transition: all 0.3s ease;
24
- border-left: 4px solid transparent;
25
- }
26
-
27
- .task-item.completed {
28
- animation: completeTask 0.6s ease forwards;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
- @keyframes completeTask {
32
- 0% { transform: scale(1); opacity: 1; }
33
- 50% { transform: scale(0.95); opacity: 0.8; }
34
- 100% { transform: scale(1); opacity: 1; border-left-color: #10b981; }
35
  }
36
 
37
- .task-item .focus-btn {
38
- @apply absolute right-12 top-1/2 transform -translate-y-1/2;
39
- @apply p-2 rounded-full bg-orange-500 bg-opacity-0 text-orange-500;
40
- @apply transition-all duration-300 ease-in-out;
41
- opacity: 0;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  }
43
 
44
- .task-item:hover .focus-btn {
45
- @apply bg-opacity-20;
46
- opacity: 1;
47
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- .task-item .focus-btn:hover {
50
- @apply bg-opacity-100 text-white;
51
- }
52
- .task-item:hover {
53
- @apply transform scale-105 shadow-lg;
54
- }
55
 
56
- /* Progress bar animation */
57
- .progress-bar-fill {
58
- transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
59
  }
60
 
61
- /* Focus mode animations */
62
- @keyframes pulse {
63
- 0%, 100% {
64
- opacity: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  }
66
- 50% {
67
- opacity: 0.5;
 
68
  }
69
  }
70
-
71
- .focus-pulse {
72
- animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
73
- }
74
-
75
- /* Button focus styles */
76
- .btn:focus {
77
- @apply outline-none ring-2 ring-orange-500 ring-offset-2 ring-offset-gray-900;
 
 
 
 
 
 
 
 
 
78
  }
79
 
80
- /* Tag badge styles */
81
- .tag-badge {
82
- @apply inline-flex items-center px-2 py-1 rounded-full text-xs font-medium;
 
 
 
 
83
  }
84
 
85
- /* Timeline styles */
86
- .timeline-hour {
87
- @apply relative border-b border-gray-700;
88
- min-height: 60px;
89
- position: relative;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  }
91
- .timeline-task-block {
92
- @apply absolute rounded-lg cursor-move transition-all;
93
- transition: all 0.2s ease;
 
 
 
94
  }
95
-
96
- .timeline-task-block:hover {
97
- @apply transform scale-105 shadow-lg;
 
98
  }
99
 
100
- .timeline-task-block.dragging {
101
- @apply opacity-50;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  }
103
 
104
- /* Modal backdrop blur */
105
- .modal-backdrop {
106
- backdrop-filter: blur(4px);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  }
108
-
109
- /* Custom input styles */
110
- input[type="text"], input[type="number"], textarea, select {
111
- @apply bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-gray-100;
112
- @apply focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent;
 
 
 
 
 
 
 
113
  }
114
-
115
- input[type="color"] {
116
- @apply cursor-pointer;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  }
118
-
119
- /* Navigation buttons */
120
- .nav-btn {
121
- @apply flex items-center space-x-2 px-4 py-2 rounded-lg transition;
122
- @apply text-gray-400 hover:text-white hover:bg-gray-700;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  }
124
-
125
- .nav-btn.active {
126
- @apply text-orange-500 bg-gray-700;
127
  }
128
-
129
- /* View transitions */
130
- .view {
131
- display: none;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  }
133
 
134
- .view.active {
135
- display: block;
136
- animation: fadeIn 0.3s ease-in-out;
137
- }
138
- @keyframes fadeIn {
139
- from {
140
- opacity: 0;
141
- transform: translateY(10px);
142
  }
143
- to {
144
- opacity: 1;
145
- transform: translateY(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  }
147
  }
148
-
149
- /* Drag and drop styles */
150
- .drag-over {
151
- @apply bg-gray-700 border-2 border-dashed border-orange-500;
 
 
 
 
152
  }
153
 
154
- /* Custom checkbox */
155
- .custom-checkbox {
156
- @apply w-5 h-5 rounded cursor-pointer appearance-none;
157
- @apply bg-gray-700 border border-gray-600;
158
- @apply checked:bg-emerald-500 checked:border-emerald-500;
 
 
 
 
 
 
 
 
 
 
 
 
 
159
  }
160
 
161
- .custom-checkbox:checked::after {
162
- content: '✓';
163
- @apply block text-white text-center leading-5;
 
 
 
 
 
164
  }
165
 
166
- /* Time display */
167
- .time-display {
168
- @apply font-mono text-lg tabular-nums;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
  }
170
 
171
- /* Empty state */
172
- .empty-state {
173
- @apply text-center py-12 text-gray-500;
 
 
174
  }
175
 
176
- .empty-state i {
177
- @apply w-16 h-16 mx-auto mb-4;
 
 
178
  }
179
 
180
- /* Responsive timeline */
181
- @media (max-width: 768px) {
182
- #timeline-tasks {
183
- width: 100%;
184
- position: static;
185
- }
186
 
187
- #timeline-hours {
188
- margin-left: 0;
 
 
 
189
  }
190
 
191
- .timeline-task-block {
192
- position: relative !important;
193
- width: 100% !important;
194
- left: 0 !important;
195
- top: auto !important;
196
- }
197
  }
198
 
199
- /* Loading animation */
200
- .loading {
201
- @apply animate-pulse;
202
  }
203
 
204
- .loading::after {
205
- content: '';
206
- @apply absolute inset-0 bg-gray-700 rounded-lg;
207
- animation: loading 1.5s ease-in-out infinite;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
  }
209
 
210
- @keyframes loading {
211
- 0% {
212
- transform: translateX(-100%);
213
- }
214
- 100% {
215
- transform: translateX(100%);
216
  }
217
  }
218
-
219
- /* Task priority indicator */
220
- .priority-high {
221
- @apply border-l-4 border-red-500;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
222
  }
223
-
224
- .priority-medium {
225
- @apply border-l-4 border-yellow-500;
 
 
226
  }
227
-
228
- .priority-low {
229
- @apply border-l-4 border-emerald-500;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  }
231
-
232
- /* Focus mode glass effect */
233
- #focus-mode {
234
- background: linear-gradient(135deg, rgba(17, 24, 39, 0.95) 0%, rgba(17, 24, 39, 0.98) 100%);
235
- backdrop-filter: blur(10px);
236
  }
237
 
238
- /* Smooth transitions for all interactive elements */
239
- * {
240
- transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
241
  }
 
1
+ // TaskForge Pro - Main JavaScript
2
+ // State management
3
+ window.state = {
4
+ tasks: [],
5
+ tags: [],
6
+ currentView: 'home',
7
+ focusTask: null,
8
+ pomodoro: {
9
+ isRunning: false,
10
+ isBreak: false,
11
+ timeRemaining: 0,
12
+ totalTime: 0,
13
+ interval: null
14
+ },
15
+ timelineTasks: []
16
+ };
17
+
18
+ // Initialize
19
+ document.addEventListener('DOMContentLoaded', () => {
20
+ loadFromLocalStorage();
21
+ initializeUI();
22
+ setupEventListeners();
23
+ render();
24
+ });
25
+
26
+ // Local Storage
27
+ function loadFromLocalStorage() {
28
+ const savedTasks = localStorage.getItem('tasks');
29
+ const savedTags = localStorage.getItem('tags');
30
+ const savedTimeline = localStorage.getItem('timeline');
31
+
32
+ if (savedTasks) {
33
+ state.tasks = JSON.parse(savedTasks);
34
+ }
35
+ if (savedTags) {
36
+ state.tags = JSON.parse(savedTags);
37
+ }
38
+ if (savedTimeline) {
39
+ state.timelineTasks = JSON.parse(savedTimeline);
40
+ }
41
+
42
+ // Load Pomodoro settings
43
+ const workTime = localStorage.getItem('pomodoroWorkTime') || 25;
44
+ const breakTime = localStorage.getItem('pomodoroBreakTime') || 5;
45
+ document.getElementById('pomodoro-work').value = workTime;
46
+ document.getElementById('pomodoro-break').value = breakTime;
47
  }
48
 
49
+ function saveToLocalStorage() {
50
+ localStorage.setItem('tasks', JSON.stringify(state.tasks));
51
+ localStorage.setItem('tags', JSON.stringify(state.tags));
52
+ localStorage.setItem('timeline', JSON.stringify(state.timelineTasks));
53
  }
54
 
55
+ // UI Initialization
56
+ function initializeUI() {
57
+ // Current time display
58
+ updateCurrentTime();
59
+ setInterval(updateCurrentTime, 1000);
60
+
61
+ // Initialize timeline
62
+ initializeTimeline();
63
+
64
+ // Scroll to current time in timeline
65
+ setTimeout(scrollToCurrentTime, 100);
66
+ // Check for default tags
67
+ if (state.tags.length === 0) {
68
+ state.tags = [
69
+ { id: '1', name: 'Wichtig', color: '#ef4444' },
70
+ { id: '2', name: 'Privat', color: '#10b981' },
71
+ { id: '3', name: 'Arbeit', color: '#3b82f6' }
72
+ ];
73
+ saveToLocalStorage();
74
+ }
75
  }
76
 
77
+ function updateCurrentTime() {
78
+ const now = new Date();
79
+ const timeString = now.toLocaleTimeString('de-DE', {
80
+ hour: '2-digit',
81
+ minute: '2-digit'
82
+ });
83
+ document.getElementById('current-time').textContent = timeString;
84
+ }
85
+
86
+ function setupEventListeners() {
87
+ // Navigation
88
+ document.querySelectorAll('.nav-btn').forEach(btn => {
89
+ btn.addEventListener('click', (e) => {
90
+ const view = e.currentTarget.dataset.view;
91
+ switchView(view);
92
+ });
93
+ });
94
+
95
+ // FAB buttons
96
+ document.getElementById('add-task-fab').addEventListener('click', openAddTaskModal);
97
+ document.getElementById('focus-fab').addEventListener('click', enterFocusMode);
98
+
99
+ // Add task modal
100
+ document.getElementById('add-task-form').addEventListener('submit', handleAddTask);
101
+ document.getElementById('cancel-task').addEventListener('click', closeAddTaskModal);
102
+
103
+ // Settings
104
+ document.getElementById('import-btn').addEventListener('click', handleImport);
105
+ document.getElementById('export-btn').addEventListener('click', handleExport);
106
+ document.getElementById('add-tag-btn').addEventListener('click', handleAddTag);
107
+
108
+ document.getElementById('pomodoro-work').addEventListener('change', savePomodoroSettings);
109
+ document.getElementById('pomodoro-break').addEventListener('change', savePomodoroSettings);
110
 
111
+ // Focus mode
112
+ document.getElementById('focus-pause').addEventListener('click', pausePomodoro);
113
+ document.getElementById('focus-play').addEventListener('click', resumePomodoro);
114
+ document.getElementById('focus-stop').addEventListener('click', stopPomodoro);
 
 
115
 
116
+ // Timeline
117
+ document.getElementById('timeline-now').addEventListener('click', scrollToCurrentTime);
 
118
  }
119
 
120
+ // View Management
121
+ function switchView(view) {
122
+ state.currentView = view;
123
+
124
+ // Update nav buttons
125
+ document.querySelectorAll('.nav-btn').forEach(btn => {
126
+ btn.classList.toggle('active', btn.dataset.view === view);
127
+ });
128
+
129
+ // Hide all views first
130
+ document.querySelectorAll('.view').forEach(v => {
131
+ v.classList.remove('active');
132
+ v.style.display = 'none';
133
+ });
134
+
135
+ // Show only the active view
136
+ const activeView = document.getElementById(`${view}-view`);
137
+ if (activeView) {
138
+ activeView.style.display = 'block';
139
+ activeView.classList.add('active');
140
  }
141
+
142
+ if (view === 'timeline') {
143
+ renderTimeline();
144
  }
145
  }
146
+ // Task Management
147
+ function addTask(task) {
148
+ const newTask = {
149
+ id: Date.now().toString(),
150
+ title: task.title,
151
+ completed: false,
152
+ estimatedTime: task.estimatedTime, // in minutes
153
+ spentTime: 0, // in minutes
154
+ tags: task.tags || [],
155
+ createdAt: new Date().toISOString()
156
+ };
157
+
158
+ state.tasks.push(newTask);
159
+ saveToLocalStorage();
160
+ render();
161
+
162
+ return newTask;
163
  }
164
 
165
+ function updateTask(taskId, updates) {
166
+ const task = state.tasks.find(t => t.id === taskId);
167
+ if (task) {
168
+ Object.assign(task, updates);
169
+ saveToLocalStorage();
170
+ render();
171
+ }
172
  }
173
 
174
+ function deleteTask(taskId) {
175
+ state.tasks = state.tasks.filter(t => t.id !== taskId);
176
+ state.timelineTasks = state.timelineTasks.filter(t => t.taskId !== taskId);
177
+ saveToLocalStorage();
178
+ render();
179
+ }
180
+ function toggleTaskComplete(taskId) {
181
+ const task = state.tasks.find(t => t.id === taskId);
182
+ if (task) {
183
+ task.completed = !task.completed;
184
+ saveToLocalStorage();
185
+
186
+ // Animate task completion
187
+ const taskElement = document.querySelector(`task-item[task-id="${taskId}"]`);
188
+ if (taskElement) {
189
+ taskElement.classList.add('completed');
190
+ setTimeout(() => {
191
+ render();
192
+ }, 600); // Match animation duration
193
+ } else {
194
+ render();
195
+ }
196
+ }
197
  }
198
+ // Modal Functions
199
+ function openAddTaskModal() {
200
+ const modal = document.getElementById('add-task-modal');
201
+ modal.classList.remove('hidden');
202
+ renderModalTags();
203
+ document.getElementById('task-title').focus();
204
  }
205
+ function closeAddTaskModal() {
206
+ const modal = document.getElementById('add-task-modal');
207
+ modal.classList.add('hidden');
208
+ document.getElementById('add-task-form').reset();
209
  }
210
 
211
+ function handleAddTask(e) {
212
+ e.preventDefault();
213
+
214
+ const title = document.getElementById('task-title').value;
215
+ const hours = parseInt(document.getElementById('task-hours').value) || 0;
216
+ const minutes = parseInt(document.getElementById('task-minutes').value) || 0;
217
+ const estimatedTime = hours * 60 + minutes;
218
+
219
+ const selectedTags = Array.from(document.querySelectorAll('#modal-tags-list input:checked')).map(cb => cb.value);
220
+
221
+ addTask({
222
+ title,
223
+ estimatedTime,
224
+ tags: selectedTags
225
+ });
226
+
227
+ closeAddTaskModal();
228
+ }
229
+
230
+ function renderModalTags() {
231
+ const container = document.getElementById('modal-tags-list');
232
+ container.innerHTML = state.tags.map(tag => `
233
+ <label class="flex items-center space-x-2 cursor-pointer">
234
+ <input type="checkbox" value="${tag.id}" class="custom-checkbox">
235
+ <span class="tag-badge" style="background-color: ${tag.color}20; color: ${tag.color}">
236
+ ${tag.name}
237
+ </span>
238
+ </label>
239
+ `).join('');
240
+ }
241
+
242
+ // Tag Management
243
+ function addTag(tag) {
244
+ state.tags.push({
245
+ id: Date.now().toString(),
246
+ name: tag.name,
247
+ color: tag.color
248
+ });
249
+ saveToLocalStorage();
250
+ render();
251
+ }
252
+
253
+ function deleteTag(tagId) {
254
+ state.tags = state.tags.filter(t => t.id !== tagId);
255
+ // Remove tag from all tasks
256
+ state.tasks.forEach(task => {
257
+ task.tags = task.tags.filter(t => t !== tagId);
258
+ });
259
+ saveToLocalStorage();
260
+ render();
261
+ }
262
+
263
+ function handleAddTag() {
264
+ const name = document.getElementById('new-tag-name').value;
265
+ const color = document.getElementById('new-tag-color').value;
266
+
267
+ if (name) {
268
+ addTag({ name, color });
269
+ document.getElementById('new-tag-name').value = '';
270
+ }
271
  }
272
 
273
+ // Import/Export
274
+ function handleImport() {
275
+ const markdown = document.getElementById('import-md').value;
276
+ if (!markdown) return;
277
+
278
+ const tasks = parseMarkdown(markdown);
279
+ tasks.forEach(task => {
280
+ // Check if task already exists
281
+ const exists = state.tasks.some(t => t.title === task.title);
282
+ if (!exists) {
283
+ state.tasks.push({
284
+ id: Date.now().toString() + Math.random(),
285
+ title: task.title,
286
+ completed: task.completed,
287
+ estimatedTime: task.estimatedTime,
288
+ spentTime: task.spentTime || 0,
289
+ tags: [],
290
+ createdAt: new Date().toISOString()
291
+ });
292
+ }
293
+ });
294
+
295
+ saveToLocalStorage();
296
+ render();
297
+ document.getElementById('import-md').value = '';
298
+ }
299
+
300
+ function handleExport() {
301
+ const markdown = generateMarkdown();
302
+ const blob = new Blob([markdown], { type: 'text/markdown' });
303
+ const url = URL.createObjectURL(blob);
304
+ const a = document.createElement('a');
305
+ a.href = url;
306
+ a.download = 'tasks.md';
307
+ a.click();
308
+ URL.revokeObjectURL(url);
309
+ }
310
+ function parseMarkdown(markdown) {
311
+ const lines = markdown.split('\n');
312
+ const tasks = [];
313
+
314
+ const taskRegex = /^\s*-\s*\[([ x])\]\s+(.+?)(?:\s*\|\s*(.+?)(?:\s*\|\s*(.+?))?)?\s*$/;
315
+
316
+ lines.forEach(line => {
317
+ const match = line.match(taskRegex);
318
+ if (match) {
319
+ const completed = match[1] === 'x';
320
+ const title = match[2].trim();
321
+ const timePart = match[3] ? match[3].trim() : '0h';
322
+ const tagsPart = match[4] ? match[4].trim() : '';
323
+
324
+ // Parse time
325
+ const timeMatch = timePart.match(/(\d+)(?:\s*h\s*)?(?:(?:\s*(\d+)\s*m\s*)?)?/);
326
+ const hours = parseInt(timeMatch[1]) || 0;
327
+ const minutes = parseInt(timeMatch[2]) || 0;
328
+ const estimatedTime = hours * 60 + minutes;
329
+
330
+ tasks.push({
331
+ title,
332
+ completed,
333
+ estimatedTime
334
+ });
335
+ }
336
+ });
337
+ return tasks;
338
+ }
339
+
340
+ function generateMarkdown() {
341
+ return state.tasks.map(task => {
342
+ const status = task.completed ? 'x' : ' ';
343
+ const hours = Math.floor(task.estimatedTime / 60);
344
+ const minutes = task.estimatedTime % 60;
345
+ const timeStr = `${hours}h${minutes > 0 ? ` ${minutes}m` : ''}`;
346
+ return `- [${status}] ${task.title} | ${timeStr}`;
347
+ }).join('\n');
348
+ }
349
+
350
+ // Rendering
351
+ function render() {
352
+ renderTasks();
353
+ renderProgressBars();
354
+ renderTags();
355
+ renderTimeline();
356
+ }
357
+ function renderTasks() {
358
+ const openContainer = document.getElementById('open-tasks');
359
+ const completedContainer = document.getElementById('completed-tasks');
360
+
361
+ const openTasks = state.tasks.filter(t => !t.completed);
362
+ const completedTasks = state.tasks.filter(t => t.completed);
363
+
364
+ // Render open tasks
365
+ if (openTasks.length === 0) {
366
+ openContainer.innerHTML = '<div class="empty-state"><i data-feather="inbox"></i><p>Keine offenen Aufgaben</p></div>';
367
+ } else {
368
+ openContainer.innerHTML = '';
369
+ openTasks.forEach(task => {
370
+ const taskElement = document.createElement('task-item');
371
+ taskElement.setAttribute('task-id', task.id);
372
+ taskElement.setAttribute('completed', 'false');
373
+ openContainer.appendChild(taskElement);
374
+ });
375
  }
376
+
377
+ // Render completed tasks
378
+ if (completedTasks.length === 0) {
379
+ completedContainer.innerHTML = '<div class="empty-state"><i data-feather="check-circle"></i><p>Keine erledigten Aufgaben</p></div>';
380
+ } else {
381
+ completedContainer.innerHTML = '';
382
+ completedTasks.forEach(task => {
383
+ const taskElement = document.createElement('task-item');
384
+ taskElement.setAttribute('task-id', task.id);
385
+ taskElement.setAttribute('completed', 'true');
386
+ completedContainer.appendChild(taskElement);
387
+ });
388
  }
389
+
390
+ document.getElementById('open-count').textContent = openTasks.length;
391
+ document.getElementById('completed-count').textContent = completedTasks.length;
392
+
393
+ // Replace feather icons
394
+ feather.replace();
395
+ }
396
+ function renderProgressBars() {
397
+ // Overall progress
398
+ const totalEstimated = state.tasks.reduce((sum, t) => sum + t.estimatedTime, 0) || 1;
399
+ const totalSpent = state.tasks.reduce((sum, t) => sum + t.spentTime, 0);
400
+ const totalProgress = (totalSpent / totalEstimated) * 100;
401
+
402
+ document.getElementById('total-progress-bar').style.width = `${totalProgress}%`;
403
+ document.getElementById('total-progress-text').textContent =
404
+ `${Math.round(totalProgress)}% (${formatTime(totalSpent)} / ${formatTime(totalEstimated)})`;
405
+
406
+ // Tag progress bars - only for tags with tasks
407
+ const container = document.getElementById('tag-progress-container');
408
+ container.innerHTML = state.tags
409
+ .filter(tag => state.tasks.some(t => t.tags.includes(tag.id))) // Only tags with tasks
410
+ .map(tag => {
411
+ const tagTasks = state.tasks.filter(t => t.tags.includes(tag.id));
412
+ const tagEstimated = tagTasks.reduce((sum, t) => sum + t.estimatedTime, 0) || 1;
413
+ const tagSpent = tagTasks.reduce((sum, t) => sum + t.spentTime, 0);
414
+ const tagProgress = (tagSpent / tagEstimated) * 100;
415
+
416
+ return `
417
+ <div class="bg-gray-800 rounded-lg p-4 shadow transition-all hover:scale-[1.02]">
418
+ <div class="flex items-center justify-between mb-2">
419
+ <span class="font-medium" style="color: ${tag.color}">${tag.name}</span>
420
+ <span class="text-sm text-gray-400">
421
+ ${Math.round(tagProgress)}% (${formatTime(tagSpent)} / ${formatTime(tagEstimated)})
422
+ </span>
423
+ </div>
424
+ <div class="w-full bg-gray-700 rounded-full h-2 overflow-hidden">
425
+ <div class="h-full rounded-full transition-all duration-500"
426
+ style="width: ${tagProgress}%; background-color: ${tag.color}"></div>
427
+ </div>
428
+ </div>
429
+ `;
430
+ }).join('');
431
+ }
432
+ function renderTags() {
433
+ const container = document.getElementById('tags-list');
434
+ container.innerHTML = state.tags.map(tag => `
435
+ <tag-item tag-id="${tag.id}" tag-name="${tag.name}" tag-color="${tag.color}"></tag-item>
436
+ `).join('');
437
+
438
+ // Render tag components
439
+ document.querySelectorAll('tag-item').forEach(el => {
440
+ const tagId = el.getAttribute('tag-id');
441
+ el.addEventListener('delete', () => deleteTag(tagId));
442
+ });
443
+ }
444
+ function renderTimeline() {
445
+ if (state.currentView !== 'timeline') return;
446
+
447
+ const container = document.getElementById('timeline-hours');
448
+ container.innerHTML = '';
449
+
450
+ // Generate hours
451
+ for (let h = 6; h <= 22; h++) {
452
+ const hourDiv = document.createElement('div');
453
+ hourDiv.className = 'timeline-hour relative border-b border-gray-700';
454
+ hourDiv.dataset.hour = h;
455
+ hourDiv.style.minHeight = '60px';
456
+
457
+ const timeLabel = document.createElement('span');
458
+ timeLabel.className = 'absolute -ml-20 mt-2 text-sm text-gray-400 w-16 text-right';
459
+ timeLabel.textContent = `${h.toString().padStart(2, '0')}:00`;
460
+
461
+ hourDiv.appendChild(timeLabel);
462
+ container.appendChild(hourDiv);
463
+ }
464
+
465
+ // Highlight current hour
466
+ highlightCurrentHour();
467
+
468
+ // Render scheduled tasks
469
+ renderScheduledTasks();
470
  }
471
+ function highlightCurrentHour() {
472
+ const now = new Date();
473
+ const currentHour = now.getHours();
474
+ const currentMinute = now.getMinutes();
475
+
476
+ // Highlight current hour
477
+ document.querySelectorAll('.timeline-hour').forEach(el => {
478
+ el.classList.remove('bg-gray-750');
479
+ });
480
+
481
+ const hourElement = document.querySelector(`.timeline-hour[data-hour="${currentHour}"]`);
482
+ if (hourElement) {
483
+ hourElement.classList.add('bg-gray-750');
484
+ }
485
+
486
+ // Add current time indicator line
487
+ document.getElementById('current-time-indicator')?.remove();
488
+
489
+ const timelineContainer = document.getElementById('timeline-hours');
490
+ if (timelineContainer) {
491
+ const indicator = document.createElement('div');
492
+ indicator.id = 'current-time-indicator';
493
+ indicator.style.position = 'absolute';
494
+ indicator.style.left = '0';
495
+ indicator.style.right = '0';
496
+ indicator.style.height = '2px';
497
+ indicator.style.backgroundColor = '#ef4444';
498
+ indicator.style.zIndex = '10';
499
+
500
+ const hourElement = document.querySelector(`.timeline-hour[data-hour="${currentHour}"]`);
501
+ if (hourElement) {
502
+ const hourTop = hourElement.offsetTop;
503
+ const hourHeight = hourElement.offsetHeight;
504
+ const minutePosition = (currentMinute / 60) * hourHeight;
505
+
506
+ indicator.style.top = `${hourTop + minutePosition}px`;
507
+ timelineContainer.appendChild(indicator);
508
+
509
+ // Add time label
510
+ const timeLabel = document.createElement('div');
511
+ timeLabel.textContent = now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
512
+ timeLabel.style.position = 'absolute';
513
+ timeLabel.style.left = '-64px';
514
+ timeLabel.style.top = `${hourTop + minutePosition - 10}px`;
515
+ timeLabel.style.backgroundColor = '#ef4444';
516
+ timeLabel.style.color = 'white';
517
+ timeLabel.style.padding = '2px 8px';
518
+ timeLabel.style.borderRadius = '4px';
519
+ timeLabel.style.fontSize = '12px';
520
+ timeLabel.style.zIndex = '10';
521
+
522
+ hourEl.appendChild(timeLabel);
523
  }
524
+ }
 
 
525
  }
526
+ function renderScheduledTasks() {
527
+ const today = new Date().toDateString();
528
+ const scheduledTasks = state.timelineTasks.filter(t =>
529
+ new Date(t.date).toDateString() === today
530
+ );
531
+
532
+ scheduledTasks.forEach(task => {
533
+ const taskData = state.tasks.find(t => t.id === task.taskId);
534
+ if (!taskData) return;
535
+
536
+ const startHour = new Date(task.startTime).getHours();
537
+ const startMinute = new Date(task.startTime).getMinutes();
538
+ const duration = taskData.estimatedTime;
539
+
540
+ const hourEl = document.querySelector(`[data-hour="${startHour}"]`);
541
+ if (hourEl) {
542
+ const taskEl = document.createElement('div');
543
+ taskEl.className = 'timeline-task-block';
544
+ taskEl.style.left = `${(startMinute / 60) * 100}%`;
545
+ taskEl.style.width = `${(duration / 60) * 100}%`;
546
+ taskEl.style.backgroundColor = task.tags[0] ?
547
+ state.tags.find(t => t.id === task.tags[0])?.color + '20' : '#f9731620';
548
+ taskEl.textContent = taskData.title;
549
+ taskEl.draggable = true;
550
+
551
+ hourEl.appendChild(taskEl);
552
+ }
553
+ });
554
+ }
555
+
556
+ function scrollToCurrentTime() {
557
+ const now = new Date();
558
+ const currentHour = now.getHours();
559
+ const hourEl = document.querySelector(`[data-hour="${currentHour}"]`);
560
+
561
+ if (hourEl) {
562
+ hourEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
563
+ }
564
  }
565
 
566
+ // Focus Mode & Pomodoro
567
+ function enterFocusMode() {
568
+ // Open a task selection modal
569
+ const openTasks = state.tasks.filter(t => !t.completed);
570
+ if (openTasks.length === 0) {
571
+ alert('Keine offenen Aufgaben für den Fokus-Modus!');
572
+ return;
 
573
  }
574
+
575
+ const modal = document.createElement('div');
576
+ modal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4';
577
+ modal.innerHTML = `
578
+ <div class="bg-gray-800 rounded-xl p-6 max-w-md w-full max-h-[80vh] overflow-y-auto">
579
+ <h3 class="text-xl font-semibold mb-4">Aufgabe für Fokus-Modus auswählen</h3>
580
+ <div class="space-y-2">
581
+ ${openTasks.map(task => `
582
+ <div class="bg-gray-700 p-3 rounded-lg cursor-pointer hover:bg-gray-600 transition"
583
+ onclick="startFocusModeWithId('${task.id}')">
584
+ <div class="font-medium">${task.title}</div>
585
+ <div class="text-sm text-gray-400">${formatTime(task.estimatedTime)}</div>
586
+ </div>
587
+ `).join('')}
588
+ </div>
589
+ <button onclick="this.closest('div[class*=\"fixed\"]').remove()"
590
+ class="mt-4 px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition w-full">
591
+ Abbrechen
592
+ </button>
593
+ </div>
594
+ `;
595
+ document.body.appendChild(modal);
596
+ }
597
+
598
+ function startFocusModeWithId(taskId) {
599
+ const task = state.tasks.find(t => t.id === taskId);
600
+ if (task) {
601
+ document.querySelector('div[class*="fixed"]')?.remove();
602
+ startFocusMode(task);
603
  }
604
  }
605
+ function startFocusMode(task) {
606
+ state.focusTask = task;
607
+ document.getElementById('focus-task-title').textContent = task.title;
608
+ document.getElementById('focus-mode').classList.remove('hidden');
609
+
610
+ // Start pomodoro
611
+ const workTime = parseInt(localStorage.getItem('pomodoroWorkTime')) || 25;
612
+ startPomodoro(workTime * 60, false);
613
  }
614
 
615
+ function startPomodoro(seconds, isBreak) {
616
+ state.pomodoro.isRunning = true;
617
+ state.pomodoro.isBreak = isBreak;
618
+ state.pomodoro.timeRemaining = seconds;
619
+ state.pomodoro.totalTime = seconds;
620
+
621
+ clearInterval(state.pomodoro.interval);
622
+
623
+ state.pomodoro.interval = setInterval(() => {
624
+ if (state.pomodoro.timeRemaining > 0) {
625
+ state.pomodoro.timeRemaining--;
626
+ updatePomodoroDisplay();
627
+ } else {
628
+ handlePomodoroComplete();
629
+ }
630
+ }, 1000);
631
+
632
+ updatePomodoroDisplay();
633
  }
634
 
635
+ function updatePomodoroDisplay() {
636
+ const minutes = Math.floor(state.pomodoro.timeRemaining / 60);
637
+ const seconds = state.pomodoro.timeRemaining % 60;
638
+ document.getElementById('pomodoro-timer').textContent =
639
+ `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
640
+
641
+ const statusEl = document.getElementById('pomodoro-status');
642
+ statusEl.textContent = state.pomodoro.isBreak ? 'Pausenzeit läuft...' : 'Arbeitszeit läuft...';
643
  }
644
 
645
+ function handlePomodoroComplete() {
646
+ clearInterval(state.pomodoro.interval);
647
+
648
+ if (!state.pomodoro.isBreak) {
649
+ // Work session complete
650
+ const elapsed = state.pomodoro.totalTime;
651
+ state.focusTask.spentTime += elapsed;
652
+ saveToLocalStorage();
653
+
654
+ // Start break
655
+ const breakTime = parseInt(localStorage.getItem('pomodoroBreakTime')) || 5;
656
+ startPomodoro(breakTime * 60, true);
657
+
658
+ // Notification
659
+ if ('Notification' in window && Notification.permission === 'granted') {
660
+ new Notification('Pause!', { body: 'Arbeitszeit abgeschlossen. Zeit für eine Pause!' });
661
+ }
662
+ } else {
663
+ // Break complete
664
+ if ('Notification' in window && Notification.permission === 'granted') {
665
+ new Notification('Weiterarbeiten!', { body: 'Pausenzeit ist vorbei.' });
666
+ }
667
+ }
668
  }
669
 
670
+ function pausePomodoro() {
671
+ state.pomodoro.isRunning = false;
672
+ clearInterval(state.pomodoro.interval);
673
+ document.getElementById('focus-pause').classList.add('hidden');
674
+ document.getElementById('focus-play').classList.remove('hidden');
675
  }
676
 
677
+ function resumePomodoro() {
678
+ startPomodoro(state.pomodoro.timeRemaining, state.pomodoro.isBreak);
679
+ document.getElementById('focus-play').classList.add('hidden');
680
+ document.getElementById('focus-pause').classList.remove('hidden');
681
  }
682
 
683
+ function stopPomodoro() {
684
+ clearInterval(state.pomodoro.interval);
 
 
 
 
685
 
686
+ // Add elapsed time to task
687
+ if (state.focusTask && !state.pomodoro.isBreak) {
688
+ const elapsed = state.pomodoro.totalTime - state.pomodoro.timeRemaining;
689
+ state.focusTask.spentTime += elapsed;
690
+ saveToLocalStorage();
691
  }
692
 
693
+ state.focusTask = null;
694
+ state.pomodoro.isRunning = false;
695
+ document.getElementById('focus-mode').classList.add('hidden');
696
+ render();
 
 
697
  }
698
 
699
+ function savePomodoroSettings() {
700
+ localStorage.setItem('pomodoroWorkTime', document.getElementById('pomodoro-work').value);
701
+ localStorage.setItem('pomodoroBreakTime', document.getElementById('pomodoro-break').value);
702
  }
703
 
704
+ // Timeline Functions
705
+ function initializeTimeline() {
706
+ // Initialize timeline sidebar
707
+ const timelineSidebar = document.createElement('timeline-sidebar');
708
+ document.body.appendChild(timelineSidebar);
709
+
710
+ // Listen for task drops
711
+ timelineSidebar.addEventListener('task-dropped', (e) => {
712
+ const { taskId, hour } = e.detail;
713
+ scheduleTask(taskId, hour, 0); // Schedule at top of hour
714
+ });
715
+
716
+ // Setup drag start for task items
717
+ document.addEventListener('dragstart', (e) => {
718
+ if (e.target.tagName === 'TASK-ITEM') {
719
+ const taskId = e.target.getAttribute('task-id');
720
+ e.dataTransfer.setData('text/plain', taskId);
721
+ }
722
+ });
723
+ }
724
+ function handleDragStart(e) {
725
+ if (e.target.classList.contains('task-item')) {
726
+ const taskId = e.target.getAttribute('task-id');
727
+ e.dataTransfer.setData('text/plain', taskId);
728
+ e.target.classList.add('dragging');
729
+ }
730
  }
731
 
732
+ function handleDragOver(e) {
733
+ e.preventDefault();
734
+
735
+ if (e.target.classList.contains('timeline-hour')) {
736
+ e.target.classList.add('drag-over');
 
737
  }
738
  }
739
+ function handleDrop(e) {
740
+ e.preventDefault();
741
+
742
+ // Remove drag-over class from all elements
743
+ document.querySelectorAll('.drag-over').forEach(el => {
744
+ el.classList.remove('drag-over');
745
+ });
746
+
747
+ const taskId = e.dataTransfer.getData('text/plain');
748
+ const hourEl = e.target.closest('.timeline-hour');
749
+
750
+ if (hourEl && taskId) {
751
+ const hour = parseInt(hourEl.dataset.hour);
752
+ const rect = hourEl.getBoundingClientRect();
753
+ const x = e.clientX - rect.left;
754
+ const hourWidth = rect.width;
755
+ const minute = Math.floor((x / hourWidth) * 60);
756
+
757
+ scheduleTask(taskId, hour, minute);
758
+ }
759
  }
760
+ function handleDragEnd(e) {
761
+ e.target.classList.remove('dragging');
762
+ document.querySelectorAll('.drag-over').forEach(el => {
763
+ el.classList.remove('drag-over');
764
+ });
765
  }
766
+ function scheduleTask(taskId, hour, minute) {
767
+ const taskDate = new Date();
768
+ taskDate.setHours(0, 0, 0, 0); // Set to start of day for consistent date matching
769
+
770
+ const startTime = new Date();
771
+ startTime.setHours(hour, minute, 0, 0);
772
+
773
+ // Remove existing schedule for this task today
774
+ state.timelineTasks = state.timelineTasks.filter(t => {
775
+ const scheduleDate = new Date(t.date);
776
+ scheduleDate.setHours(0, 0, 0, 0);
777
+ return !(scheduleDate.getTime() === taskDate.getTime() && t.taskId === taskId);
778
+ });
779
+
780
+ // Add new schedule
781
+ state.timelineTasks.push({
782
+ taskId,
783
+ date: taskDate.toISOString(),
784
+ startTime: startTime.toISOString()
785
+ });
786
+
787
+ saveToLocalStorage();
788
+ renderTimeline();
789
  }
790
+ // Utilities
791
+ function formatTime(minutes) {
792
+ const h = Math.floor(minutes / 60);
793
+ const m = minutes % 60;
794
+ return `${h}h${m > 0 ? ` ${m}m` : ''}`;
795
  }
796
 
797
+ // Request notification permission
798
+ if ('Notification' in window && Notification.permission === 'default') {
799
+ Notification.requestPermission();
800
  }
style.css CHANGED
@@ -1,824 +1,241 @@
 
1
 
2
- // TaskForge Pro - Main JavaScript
3
- // State management
4
- window.state = {
5
- tasks: [],
6
- tags: [],
7
- currentView: 'home',
8
- focusTask: null,
9
- pomodoro: {
10
- isRunning: false,
11
- isBreak: false,
12
- timeRemaining: 0,
13
- totalTime: 0,
14
- interval: null
15
- },
16
- timelineTasks: []
17
- };
18
- // Initialize
19
- document.addEventListener('DOMContentLoaded', function() {
20
- loadFromLocalStorage();
21
- initializeUI();
22
- setupEventListeners();
23
- render();
24
- });
25
- // Local Storage
26
- function loadFromLocalStorage() {
27
- const savedTasks = localStorage.getItem('tasks');
28
- const savedTags = localStorage.getItem('tags');
29
- const savedTimeline = localStorage.getItem('timeline');
30
-
31
- if (savedTasks) {
32
- state.tasks = JSON.parse(savedTasks);
33
- }
34
- if (savedTags) {
35
- state.tags = JSON.parse(savedTags);
36
- }
37
- if (savedTimeline) {
38
- state.timelineTasks = JSON.parse(savedTimeline);
39
- }
40
-
41
- // Load Pomodoro settings
42
- const workTime = localStorage.getItem('pomodoroWorkTime') || 25;
43
- const breakTime = localStorage.getItem('pomodoroBreakTime') || 5;
44
- document.getElementById('pomodoro-work').value = workTime;
45
- document.getElementById('pomodoro-break').value = breakTime;
46
  }
47
 
48
- function saveToLocalStorage() {
49
- localStorage.setItem('tasks', JSON.stringify(state.tasks));
50
- localStorage.setItem('tags', JSON.stringify(state.tags));
51
- localStorage.setItem('timeline', JSON.stringify(state.timelineTasks));
52
  }
53
 
54
- // UI Initialization
55
- function initializeUI() {
56
- // Current time display
57
- updateCurrentTime();
58
- setInterval(updateCurrentTime, 1000);
59
-
60
- // Initialize timeline
61
- initializeTimeline();
62
-
63
- // Scroll to current time in timeline
64
- setTimeout(scrollToCurrentTime, 100);
65
- // Check for default tags
66
- if (state.tags.length === 0) {
67
- state.tags = [
68
- { id: '1', name: 'Wichtig', color: '#ef4444' },
69
- { id: '2', name: 'Privat', color: '#10b981' },
70
- { id: '3', name: 'Arbeit', color: '#3b82f6' }
71
- ];
72
- saveToLocalStorage();
73
- }
74
  }
75
 
76
- function updateCurrentTime() {
77
- const now = new Date();
78
- const timeString = now.toLocaleTimeString('de-DE', {
79
- hour: '2-digit',
80
- minute: '2-digit'
81
- });
82
- document.getElementById('current-time').textContent = timeString;
83
- }
84
- function setupEventListeners() {
85
- // Navigation
86
- document.querySelectorAll('.nav-btn').forEach(btn => {
87
- btn.addEventListener('click', (e) => {
88
- const view = e.currentTarget.dataset.view;
89
- switchView(view);
90
- });
91
- });
92
-
93
- // FAB buttons
94
- document.getElementById('add-task-fab').addEventListener('click', openAddTaskModal);
95
- document.getElementById('focus-fab').addEventListener('click', enterFocusMode);
96
-
97
- // Add task modal
98
- document.getElementById('add-task-form').addEventListener('submit', handleAddTask);
99
- document.getElementById('cancel-task').addEventListener('click', closeAddTaskModal);
100
-
101
- // Settings
102
- document.getElementById('import-btn').addEventListener('click', handleImport);
103
- document.getElementById('export-btn').addEventListener('click', handleExport);
104
- document.getElementById('add-tag-btn').addEventListener('click', handleAddTag);
105
-
106
- document.getElementById('pomodoro-work').addEventListener('change', savePomodoroSettings);
107
- document.getElementById('pomodoro-break').addEventListener('change', savePomodoroSettings);
108
-
109
- // Focus mode
110
- document.getElementById('focus-pause').addEventListener('click', pausePomodoro);
111
- document.getElementById('focus-play').addEventListener('click', resumePomodoro);
112
- document.getElementById('focus-stop').addEventListener('click', stopPomodoro);
113
-
114
- // Event delegation for task item events
115
- document.addEventListener('delete-task', (e) => {
116
- deleteTask(e.detail.taskId);
117
- });
118
-
119
- document.addEventListener('toggle-complete', (e) => {
120
- toggleTaskComplete(e.detail.taskId, e.detail.completed);
121
- });
122
-
123
- document.addEventListener('update-task', (e) => {
124
- updateTask(e.detail.taskId, e.detail.updates);
125
- });
126
-
127
- document.addEventListener('add-time', (e) => {
128
- addTimeToTask(e.detail.taskId, e.detail.minutes);
129
- });
130
-
131
- // Timeline sidebar events
132
- const timelineSidebar = document.getElementById('timeline-sidebar');
133
- if (timelineSidebar) {
134
- timelineSidebar.addEventListener('task-dropped', (e) => {
135
- scheduleTask(e.detail.taskId, e.detail.hour, 0);
136
- });
137
- }
138
  }
139
- // View Management
140
- function switchView(view) {
141
- state.currentView = view;
142
-
143
- // Update nav buttons
144
- document.querySelectorAll('.nav-btn').forEach(btn => {
145
- btn.classList.toggle('active', btn.dataset.view === view);
146
- });
147
-
148
- // Hide all views first
149
- document.querySelectorAll('.view').forEach(v => {
150
- v.classList.remove('active');
151
- v.style.display = 'none';
152
- });
153
-
154
- // Show only the active view
155
- const activeView = document.getElementById(`${view}-view`);
156
- if (activeView) {
157
- activeView.style.display = 'block';
158
- activeView.classList.add('active');
159
- }
160
  }
161
- // Task Management
162
- function addTask(task) {
163
- const newTask = {
164
- id: Date.now().toString(),
165
- title: task.title,
166
- completed: false,
167
- estimatedTime: task.estimatedTime, // in minutes
168
- spentTime: 0, // in minutes
169
- tags: task.tags || [],
170
- createdAt: new Date().toISOString()
171
- };
172
-
173
- state.tasks.push(newTask);
174
- saveToLocalStorage();
175
- render();
176
-
177
- return newTask;
178
  }
179
 
180
- function updateTask(taskId, updates) {
181
- const task = state.tasks.find(t => t.id === taskId);
182
- if (task) {
183
- Object.assign(task, updates);
184
- saveToLocalStorage();
185
- render();
186
- }
187
  }
188
 
189
- function deleteTask(taskId) {
190
- state.tasks = state.tasks.filter(t => t.id !== taskId);
191
- state.timelineTasks = state.timelineTasks.filter(t => t.taskId !== taskId);
192
- saveToLocalStorage();
193
- render();
194
  }
195
- function toggleTaskComplete(taskId, completed) {
196
- const task = state.tasks.find(t => t.id === taskId);
197
- if (task) {
198
- task.completed = completed !== undefined ? completed : !task.completed;
199
- saveToLocalStorage();
200
- render();
201
- }
202
  }
203
- // Modal Functions
204
- function openAddTaskModal() {
205
- const modal = document.getElementById('add-task-modal');
206
- modal.classList.remove('hidden');
207
- renderModalTags();
208
- document.getElementById('task-title').focus();
209
  }
210
- function closeAddTaskModal() {
211
- const modal = document.getElementById('add-task-modal');
212
- modal.classList.add('hidden');
213
- document.getElementById('add-task-form').reset();
214
  }
215
 
216
- function handleAddTask(e) {
217
- e.preventDefault();
218
-
219
- const title = document.getElementById('task-title').value;
220
- const hours = parseInt(document.getElementById('task-hours').value) || 0;
221
- const minutes = parseInt(document.getElementById('task-minutes').value) || 0;
222
- const estimatedTime = hours * 60 + minutes;
223
-
224
- const selectedTags = Array.from(document.querySelectorAll('#modal-tags-list input:checked')).map(cb => cb.value);
225
-
226
- addTask({
227
- title,
228
- estimatedTime,
229
- tags: selectedTags
230
- });
231
-
232
- closeAddTaskModal();
233
- }
234
-
235
- function renderModalTags() {
236
- const container = document.getElementById('modal-tags-list');
237
- container.innerHTML = state.tags.map(tag => `
238
- <label class="flex items-center space-x-2 cursor-pointer">
239
- <input type="checkbox" value="${tag.id}" class="custom-checkbox">
240
- <span class="tag-badge" style="background-color: ${tag.color}20; color: ${tag.color}">
241
- ${tag.name}
242
- </span>
243
- </label>
244
- `).join('');
245
- }
246
-
247
- // Tag Management
248
- function addTag(tag) {
249
- state.tags.push({
250
- id: Date.now().toString(),
251
- name: tag.name,
252
- color: tag.color
253
- });
254
- saveToLocalStorage();
255
- render();
256
- }
257
-
258
- function deleteTag(tagId) {
259
- state.tags = state.tags.filter(t => t.id !== tagId);
260
- // Remove tag from all tasks
261
- state.tasks.forEach(task => {
262
- task.tags = task.tags.filter(t => t !== tagId);
263
- });
264
- saveToLocalStorage();
265
- render();
266
- }
267
-
268
- function handleAddTag() {
269
- const name = document.getElementById('new-tag-name').value;
270
- const color = document.getElementById('new-tag-color').value;
271
-
272
- if (name) {
273
- addTag({ name, color });
274
- document.getElementById('new-tag-name').value = '';
275
- }
276
  }
277
 
278
- // Import/Export
279
- function handleImport() {
280
- const markdown = document.getElementById('import-md').value;
281
- if (!markdown) return;
282
-
283
- const tasks = parseMarkdown(markdown);
284
- tasks.forEach(task => {
285
- // Check if task already exists
286
- const exists = state.tasks.some(t => t.title === task.title);
287
- if (!exists) {
288
- state.tasks.push({
289
- id: Date.now().toString() + Math.random(),
290
- title: task.title,
291
- completed: task.completed,
292
- estimatedTime: task.estimatedTime,
293
- spentTime: task.spentTime || 0,
294
- tags: [],
295
- createdAt: new Date().toISOString()
296
- });
297
- }
298
- });
299
-
300
- saveToLocalStorage();
301
- render();
302
- document.getElementById('import-md').value = '';
303
- }
304
-
305
- function handleExport() {
306
- const markdown = generateMarkdown();
307
- const blob = new Blob([markdown], { type: 'text/markdown' });
308
- const url = URL.createObjectURL(blob);
309
- const a = document.createElement('a');
310
- a.href = url;
311
- a.download = 'tasks.md';
312
- a.click();
313
- URL.revokeObjectURL(url);
314
- }
315
- function parseMarkdown(markdown) {
316
- const lines = markdown.split('\n');
317
- const tasks = [];
318
-
319
- const taskRegex = /^\s*-\s*\[([ x])\]\s+(.+?)(?:\s*\|\s*(.+?)(?:\s*\|\s*(.+?))?)?\s*$/;
320
-
321
- lines.forEach(line => {
322
- const match = line.match(taskRegex);
323
- if (match) {
324
- const completed = match[1] === 'x';
325
- const title = match[2].trim();
326
- const timePart = match[3] ? match[3].trim() : '0h';
327
- const tagsPart = match[4] ? match[4].trim() : '';
328
-
329
- // Parse time
330
- const timeMatch = timePart.match(/(\d+)(?:\s*h\s*)?(?:(?:\s*(\d+)\s*m\s*)?)?/);
331
- const hours = parseInt(timeMatch[1]) || 0;
332
- const minutes = parseInt(timeMatch[2]) || 0;
333
- const estimatedTime = hours * 60 + minutes;
334
-
335
- tasks.push({
336
- title,
337
- completed,
338
- estimatedTime
339
- });
340
- }
341
- });
342
- return tasks;
343
- }
344
-
345
- function generateMarkdown() {
346
- return state.tasks.map(task => {
347
- const status = task.completed ? 'x' : ' ';
348
- const hours = Math.floor(task.estimatedTime / 60);
349
- const minutes = task.estimatedTime % 60;
350
- const timeStr = `${hours}h${minutes > 0 ? ` ${minutes}m` : ''}`;
351
- return `- [${status}] ${task.title} | ${timeStr}`;
352
- }).join('\n');
353
- }
354
-
355
- // Rendering
356
- function render() {
357
- renderTasks();
358
- renderProgressBars();
359
- renderTags();
360
- renderTimeline();
361
- }
362
- function renderTasks() {
363
- const openContainer = document.getElementById('open-tasks');
364
- const completedContainer = document.getElementById('completed-tasks');
365
-
366
- const openTasks = state.tasks.filter(t => !t.completed);
367
- const completedTasks = state.tasks.filter(t => t.completed);
368
-
369
- // Render open tasks
370
- if (openTasks.length === 0) {
371
- openContainer.innerHTML = '<div class="empty-state"><i data-feather="inbox"></i><p>Keine offenen Aufgaben</p></div>';
372
- } else {
373
- openContainer.innerHTML = '';
374
- openTasks.forEach(task => {
375
- const taskElement = document.createElement('task-item');
376
- taskElement.setAttribute('task-id', task.id);
377
- taskElement.setAttribute('completed', 'false');
378
- openContainer.appendChild(taskElement);
379
- });
380
  }
381
-
382
- // Render completed tasks
383
- if (completedTasks.length === 0) {
384
- completedContainer.innerHTML = '<div class="empty-state"><i data-feather="check-circle"></i><p>Keine erledigten Aufgaben</p></div>';
385
- } else {
386
- completedContainer.innerHTML = '';
387
- completedTasks.forEach(task => {
388
- const taskElement = document.createElement('task-item');
389
- taskElement.setAttribute('task-id', task.id);
390
- taskElement.setAttribute('completed', 'true');
391
- completedContainer.appendChild(taskElement);
392
- });
393
- }
394
-
395
- document.getElementById('open-count').textContent = openTasks.length;
396
- document.getElementById('completed-count').textContent = completedTasks.length;
397
-
398
- // Setup drag and drop for task items
399
- setupTaskDragAndDrop();
400
-
401
- // Replace feather icons
402
- feather.replace();
403
- }
404
- function renderProgressBars() {
405
- // Overall progress
406
- const totalEstimated = state.tasks.reduce((sum, t) => sum + t.estimatedTime, 0) || 1;
407
- const totalSpent = state.tasks.reduce((sum, t) => sum + t.spentTime, 0);
408
- const totalProgress = (totalSpent / totalEstimated) * 100;
409
-
410
- document.getElementById('total-progress-bar').style.width = `${totalProgress}%`;
411
- document.getElementById('total-progress-text').textContent =
412
- `${Math.round(totalProgress)}% (${formatTime(totalSpent)} / ${formatTime(totalEstimated)})`;
413
-
414
- // Tag progress bars - only for tags with tasks
415
- const container = document.getElementById('tag-progress-container');
416
- container.innerHTML = state.tags
417
- .filter(tag => state.tasks.some(t => t.tags.includes(tag.id))) // Only tags with tasks
418
- .map(tag => {
419
- const tagTasks = state.tasks.filter(t => t.tags.includes(tag.id));
420
- const tagEstimated = tagTasks.reduce((sum, t) => sum + t.estimatedTime, 0) || 1;
421
- const tagSpent = tagTasks.reduce((sum, t) => sum + t.spentTime, 0);
422
- const tagProgress = (tagSpent / tagEstimated) * 100;
423
-
424
- return `
425
- <div class="bg-gray-800 rounded-lg p-4 shadow transition-all hover:scale-[1.02]">
426
- <div class="flex items-center justify-between mb-2">
427
- <span class="font-medium" style="color: ${tag.color}">${tag.name}</span>
428
- <span class="text-sm text-gray-400">
429
- ${Math.round(tagProgress)}% (${formatTime(tagSpent)} / ${formatTime(tagEstimated)})
430
- </span>
431
- </div>
432
- <div class="w-full bg-gray-700 rounded-full h-2 overflow-hidden">
433
- <div class="h-full rounded-full transition-all duration-500"
434
- style="width: ${tagProgress}%; background-color: ${tag.color}"></div>
435
- </div>
436
- </div>
437
- `;
438
- }).join('');
439
- }
440
- function renderTags() {
441
- const container = document.getElementById('tags-list');
442
- container.innerHTML = state.tags.map(tag => `
443
- <tag-item tag-id="${tag.id}" tag-name="${tag.name}" tag-color="${tag.color}"></tag-item>
444
- `).join('');
445
-
446
- // Render tag components
447
- document.querySelectorAll('tag-item').forEach(el => {
448
- const tagId = el.getAttribute('tag-id');
449
- el.addEventListener('delete', () => deleteTag(tagId));
450
- });
451
- }
452
- function highlightCurrentHour() {
453
- const now = new Date();
454
- const currentHour = now.getHours();
455
- const currentMinute = now.getMinutes();
456
-
457
- // Highlight current hour
458
- document.querySelectorAll('.timeline-hour').forEach(el => {
459
- el.classList.remove('bg-gray-750');
460
- });
461
-
462
- const hourElement = document.querySelector(`.timeline-hour[data-hour="${currentHour}"]`);
463
- if (hourElement) {
464
- hourElement.classList.add('bg-gray-750');
465
  }
466
-
467
- // Add current time indicator line
468
- document.getElementById('current-time-indicator')?.remove();
469
-
470
- const timelineContainer = document.getElementById('timeline-hours');
471
- if (timelineContainer) {
472
- const indicator = document.createElement('div');
473
- indicator.id = 'current-time-indicator';
474
- indicator.style.position = 'absolute';
475
- indicator.style.left = '0';
476
- indicator.style.right = '0';
477
- indicator.style.height = '2px';
478
- indicator.style.backgroundColor = '#ef4444';
479
- indicator.style.zIndex = '10';
480
-
481
- const hourElement = document.querySelector(`.timeline-hour[data-hour="${currentHour}"]`);
482
- if (hourElement) {
483
- const hourTop = hourElement.offsetTop;
484
- const hourHeight = hourElement.offsetHeight;
485
- const minutePosition = (currentMinute / 60) * hourHeight;
486
-
487
- indicator.style.top = `${hourTop + minutePosition}px`;
488
- timelineContainer.appendChild(indicator);
489
-
490
- // Add time label
491
- const timeLabel = document.createElement('div');
492
- timeLabel.textContent = now.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' });
493
- timeLabel.style.position = 'absolute';
494
- timeLabel.style.left = '-64px';
495
- timeLabel.style.top = `${hourTop + minutePosition - 10}px`;
496
- timeLabel.style.backgroundColor = '#ef4444';
497
- timeLabel.style.color = 'white';
498
- timeLabel.style.padding = '2px 8px';
499
- timeLabel.style.borderRadius = '4px';
500
- timeLabel.style.fontSize = '12px';
501
- timeLabel.style.zIndex = '10';
502
-
503
- hourEl.appendChild(timeLabel);
504
  }
505
- }
 
 
506
  }
507
- function scrollToCurrentTime() {
508
- const now = new Date();
509
- const currentHour = now.getHours();
510
- const hourEl = document.querySelector(`[data-hour="${currentHour}"]`);
511
-
512
- if (hourEl) {
513
- hourEl.scrollIntoView({ behavior: 'smooth', block: 'center' });
514
- }
515
  }
516
 
517
- // Focus Mode & Pomodoro
518
- function enterFocusMode() {
519
- // Open a task selection modal
520
- const openTasks = state.tasks.filter(t => !t.completed);
521
- if (openTasks.length === 0) {
522
- alert('Keine offenen Aufgaben für den Fokus-Modus!');
523
- return;
524
- }
525
 
526
- const modal = document.createElement('div');
527
- modal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4';
528
- modal.innerHTML = `
529
- <div class="bg-gray-800 rounded-xl p-6 max-w-md w-full max-h-[80vh] overflow-y-auto">
530
- <h3 class="text-xl font-semibold mb-4">Aufgabe für Fokus-Modus auswählen</h3>
531
- <div class="space-y-2">
532
- ${openTasks.map(task => `
533
- <div class="bg-gray-700 p-3 rounded-lg cursor-pointer hover:bg-gray-600 transition"
534
- onclick="startFocusModeWithId('${task.id}')">
535
- <div class="font-medium">${task.title}</div>
536
- <div class="text-sm text-gray-400">${formatTime(task.estimatedTime)}</div>
537
- </div>
538
- `).join('')}
539
- </div>
540
- <button onclick="this.closest('div[class*=\"fixed\"]').remove()"
541
- class="mt-4 px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition w-full">
542
- Abbrechen
543
- </button>
544
- </div>
545
- `;
546
- document.body.appendChild(modal);
547
- }
548
-
549
- function startFocusModeWithId(taskId) {
550
- const task = state.tasks.find(t => t.id === taskId);
551
- if (task) {
552
- document.querySelector('div[class*="fixed"]')?.remove();
553
- startFocusMode(task);
554
- }
555
  }
556
- function startFocusMode(task) {
557
- state.focusTask = task;
558
- document.getElementById('focus-task-title').textContent = task.title;
559
- document.getElementById('focus-mode').classList.remove('hidden');
560
-
561
- // Start pomodoro
562
- const workTime = parseInt(localStorage.getItem('pomodoroWorkTime')) || 25;
563
- startPomodoro(workTime * 60, false);
564
- }
565
- function startPomodoro(seconds, isBreak) {
566
- state.pomodoro.isRunning = true;
567
- state.pomodoro.isBreak = isBreak;
568
- state.pomodoro.timeRemaining = seconds;
569
- state.pomodoro.totalTime = seconds;
570
-
571
- clearInterval(state.pomodoro.interval);
572
-
573
- state.pomodoro.interval = setInterval(() => {
574
- if (state.pomodoro.timeRemaining > 0) {
575
- state.pomodoro.timeRemaining--;
576
- updatePomodoroDisplay();
577
- } else {
578
- handlePomodoroComplete();
579
- }
580
- }, 1000);
581
-
582
- updatePomodoroDisplay();
583
  }
584
 
585
- function updatePomodoroDisplay() {
586
- const minutes = Math.floor(state.pomodoro.timeRemaining / 60);
587
- const seconds = state.pomodoro.timeRemaining % 60;
588
- document.getElementById('pomodoro-timer').textContent =
589
- `${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
590
-
591
- const statusEl = document.getElementById('pomodoro-status');
592
- statusEl.textContent = state.pomodoro.isBreak ? 'Pausenzeit läuft...' : 'Arbeitszeit läuft...';
593
  }
594
 
595
- function handlePomodoroComplete() {
596
- clearInterval(state.pomodoro.interval);
597
-
598
- if (!state.pomodoro.isBreak) {
599
- // Work session complete
600
- const elapsed = state.pomodoro.totalTime;
601
- state.focusTask.spentTime += elapsed;
602
- saveToLocalStorage();
603
-
604
- // Start break
605
- const breakTime = parseInt(localStorage.getItem('pomodoroBreakTime')) || 5;
606
- startPomodoro(breakTime * 60, true);
607
-
608
- // Notification
609
- if ('Notification' in window && Notification.permission === 'granted') {
610
- new Notification('Pause!', { body: 'Arbeitszeit abgeschlossen. Zeit für eine Pause!' });
611
- }
612
- } else {
613
- // Break complete
614
- if ('Notification' in window && Notification.permission === 'granted') {
615
- new Notification('Weiterarbeiten!', { body: 'Pausenzeit ist vorbei.' });
616
- }
617
- }
618
  }
619
 
620
- function pausePomodoro() {
621
- state.pomodoro.isRunning = false;
622
- clearInterval(state.pomodoro.interval);
623
- document.getElementById('focus-pause').classList.add('hidden');
624
- document.getElementById('focus-play').classList.remove('hidden');
625
  }
626
 
627
- function resumePomodoro() {
628
- startPomodoro(state.pomodoro.timeRemaining, state.pomodoro.isBreak);
629
- document.getElementById('focus-play').classList.add('hidden');
630
- document.getElementById('focus-pause').classList.remove('hidden');
631
  }
632
- function stopPomodoro() {
633
- clearInterval(state.pomodoro.interval);
634
-
635
- // Add elapsed time to task
636
- if (state.focusTask && !state.pomodoro.isBreak) {
637
- const elapsed = state.pomodoro.totalTime - state.pomodoro.timeRemaining;
638
- state.focusTask.spentTime += elapsed;
639
- saveToLocalStorage();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
640
  }
641
-
642
- state.focusTask = null;
643
- state.pomodoro.isRunning = false;
644
- document.getElementById('focus-mode').classList.add('hidden');
645
- render();
646
- }
647
- function savePomodoroSettings() {
648
- localStorage.setItem('pomodoroWorkTime', document.getElementById('pomodoro-work').value);
649
- localStorage.setItem('pomodoroBreakTime', document.getElementById('pomodoro-break').value);
650
- }
651
- // Timeline Functions
652
- function initializeTimeline() {
653
- // Setup drag start for task items
654
- document.addEventListener('dragstart', (e) => {
655
- if (e.target.tagName === 'TASK-ITEM') {
656
- const taskId = e.target.getAttribute('task-id');
657
- e.dataTransfer.setData('text/plain', taskId);
658
- }
659
- });
660
- }
661
- function handleDragStart(e) {
662
- if (e.target.classList.contains('task-item')) {
663
- const taskId = e.target.getAttribute('task-id');
664
- e.dataTransfer.setData('text/plain', taskId);
665
- e.target.classList.add('dragging');
666
  }
667
  }
668
 
669
- function handleDragOver(e) {
670
- e.preventDefault();
671
-
672
- if (e.target.classList.contains('timeline-hour')) {
673
- e.target.classList.add('drag-over');
674
- }
675
  }
676
- function handleDragEnd(e) {
677
- e.target.classList.remove('dragging');
678
- document.querySelectorAll('.drag-over').forEach(el => {
679
- el.classList.remove('drag-over');
680
- });
 
681
  }
682
- function scheduleTask(taskId, hour, minute) {
683
- const taskDate = new Date();
684
- taskDate.setHours(0, 0, 0, 0); // Set to start of day for consistent date matching
685
-
686
- const startTime = new Date();
687
- startTime.setHours(hour, minute, 0, 0);
688
-
689
- // Remove existing schedule for this task today
690
- state.timelineTasks = state.timelineTasks.filter(t => {
691
- const scheduleDate = new Date(t.date);
692
- scheduleDate.setHours(0, 0, 0, 0);
693
- return !(scheduleDate.getTime() === taskDate.getTime() && t.taskId === taskId);
694
- });
695
-
696
- // Add new schedule
697
- state.timelineTasks.push({
698
- taskId,
699
- date: taskDate.toISOString(),
700
- startTime: startTime.toISOString()
701
- });
 
 
 
 
 
 
702
 
703
- saveToLocalStorage();
 
 
704
 
705
- // Update timeline sidebar
706
- const timelineSidebar = document.getElementById('timeline-sidebar');
707
- if (timelineSidebar && timelineSidebar.updateTimeline) {
708
- timelineSidebar.updateTimeline();
 
709
  }
710
  }
711
- // Utilities
712
- function formatTime(minutes) {
713
- const h = Math.floor(minutes / 60);
714
- const m = minutes % 60;
715
- return `${h}h${m > 0 ? ` ${m}m` : ''}`;
716
  }
717
 
718
- // Request notification permission
719
- if ('Notification' in window && Notification.permission === 'default') {
720
- Notification.requestPermission();
 
721
  }
722
 
723
- // New functions for task management
724
- function addTimeToTask(taskId, minutes) {
725
- const task = state.tasks.find(t => t.id === taskId);
726
- if (task) {
727
- task.spentTime += minutes;
728
- saveToLocalStorage();
729
- render();
730
  }
731
  }
732
 
733
- function setupTaskDragAndDrop() {
734
- const openContainer = document.getElementById('open-tasks');
735
- const completedContainer = document.getElementById('completed-tasks');
736
-
737
- // Setup drag and drop for reordering
738
- [openContainer, completedContainer].forEach(container => {
739
- if (!container) return;
740
-
741
- container.addEventListener('dragover', (e) => {
742
- e.preventDefault();
743
- const afterElement = getDragAfterElement(container, e.clientY);
744
- const draggable = document.querySelector('.dragging');
745
- if (draggable) {
746
- if (afterElement == null) {
747
- container.appendChild(draggable);
748
- } else {
749
- container.insertBefore(draggable, afterElement);
750
- }
751
- });
752
- });
753
- }
754
-
755
- function getDragAfterElement(container, y) {
756
- const draggableElements = [...container.querySelectorAll('task-item:not(.dragging)'));
757
-
758
- return draggableElements.reduce((closest, child) => {
759
- const box = child.getBoundingClientRect();
760
- const offset = y - box.top - box.height / 2;
761
- if (offset < 0 && offset > closest.offset) {
762
- return { offset: offset, element: child };
763
- } else {
764
- return closest;
765
- }
766
- }, { offset: Number.NEGATIVE_INFINITY }).element;
767
- }
768
-
769
- // Edit task modal functionality
770
- function openEditTaskModal(taskId) {
771
- const task = state.tasks.find(t => t.id === taskId);
772
- if (!task) return;
773
-
774
- const modal = document.createElement('div');
775
- modal.className = 'fixed inset-0 bg-black bg-opacity-50 z-50 flex items-center justify-center p-4';
776
- modal.innerHTML = `
777
- <div class="bg-gray-800 rounded-xl p-6 max-w-md w-full">
778
- <h3 class="text-xl font-semibold mb-4">Aufgabe bearbeiten</h3>
779
- <form id="edit-task-form" class="space-y-4">
780
- <div>
781
- <label class="block text-sm font-medium mb-2">Titel</label>
782
- <input id="edit-task-title" type="text" required value="${task.title}"
783
- class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
784
- </div>
785
- <div>
786
- <label class="block text-sm font-medium mb-2">Geschätzte Zeit</label>
787
- <div class="flex space-x-2">
788
- <input id="edit-task-hours" type="number" placeholder="h" min="0" max="24" value="${Math.floor(task.estimatedTime / 60)}"
789
- class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
790
- <input id="edit-task-minutes" type="number" placeholder="m" min="0" max="59" step="15" value="${task.estimatedTime % 60}"
791
- class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
792
- </div>
793
- </div>
794
- <div class="flex justify-end space-x-2">
795
- <button type="button" onclick="this.closest('div[class*=\"fixed\"]').remove()"
796
- class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition">
797
- Abbrechen
798
- </button>
799
- <button type="submit" class="px-4 py-2 bg-orange-500 hover:bg-orange-600 rounded-lg transition">
800
- Speichern
801
- </button>
802
- </div>
803
- </form>
804
- </div>
805
- `;
806
-
807
- document.body.appendChild(modal);
808
-
809
- const form = modal.querySelector('#edit-task-form');
810
- form.addEventListener('submit', (e) => {
811
- e.preventDefault();
812
- handleEditTask(taskId);
813
- modal.remove();
814
- });
815
- }
816
-
817
- function handleEditTask(taskId) {
818
- const title = document.getElementById('edit-task-title').value;
819
- const hours = parseInt(document.getElementById('edit-task-hours').value) || 0;
820
- const minutes = parseInt(document.getElementById('edit-task-minutes').value) || 0;
821
- const estimatedTime = hours * 60 + minutes;
822
-
823
- updateTask(taskId, { title, estimatedTime });
824
  }
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Custom styles for TaskForge Pro */
2
 
3
+ /* Scrollbar styling */
4
+ ::-webkit-scrollbar {
5
+ width: 8px;
6
+ height: 8px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
  }
8
 
9
+ ::-webkit-scrollbar-track {
10
+ @apply bg-gray-800;
 
 
11
  }
12
 
13
+ ::-webkit-scrollbar-thumb {
14
+ @apply bg-gray-600 rounded-full;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  }
16
 
17
+ ::-webkit-scrollbar-thumb:hover {
18
+ @apply bg-gray-500;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  }
20
+ /* Task hover effects */
21
+ .task-item {
22
+ @apply relative overflow-hidden;
23
+ transition: all 0.3s ease;
24
+ border-left: 4px solid transparent;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
  }
26
+
27
+ .task-item.completed {
28
+ animation: completeTask 0.6s ease forwards;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
29
  }
30
 
31
+ @keyframes completeTask {
32
+ 0% { transform: scale(1); opacity: 1; }
33
+ 50% { transform: scale(0.95); opacity: 0.8; }
34
+ 100% { transform: scale(1); opacity: 1; border-left-color: #10b981; }
 
 
 
35
  }
36
 
37
+ .task-item .focus-btn {
38
+ @apply absolute right-12 top-1/2 transform -translate-y-1/2;
39
+ @apply p-2 rounded-full bg-orange-500 bg-opacity-0 text-orange-500;
40
+ @apply transition-all duration-300 ease-in-out;
41
+ opacity: 0;
42
  }
43
+
44
+ .task-item:hover .focus-btn {
45
+ @apply bg-opacity-20;
46
+ opacity: 1;
 
 
 
47
  }
48
+
49
+ .task-item .focus-btn:hover {
50
+ @apply bg-opacity-100 text-white;
 
 
 
51
  }
52
+ .task-item:hover {
53
+ @apply transform scale-105 shadow-lg;
 
 
54
  }
55
 
56
+ /* Progress bar animation */
57
+ .progress-bar-fill {
58
+ transition: width 0.5s cubic-bezier(0.4, 0, 0.2, 1);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  }
60
 
61
+ /* Focus mode animations */
62
+ @keyframes pulse {
63
+ 0%, 100% {
64
+ opacity: 1;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  }
66
+ 50% {
67
+ opacity: 0.5;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
68
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  }
70
+
71
+ .focus-pulse {
72
+ animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
73
  }
74
+
75
+ /* Button focus styles */
76
+ .btn:focus {
77
+ @apply outline-none ring-2 ring-orange-500 ring-offset-2 ring-offset-gray-900;
 
 
 
 
78
  }
79
 
80
+ /* Tag badge styles */
81
+ .tag-badge {
82
+ @apply inline-flex items-center px-2 py-1 rounded-full text-xs font-medium;
83
+ }
 
 
 
 
84
 
85
+ /* Timeline styles */
86
+ .timeline-hour {
87
+ @apply relative border-b border-gray-700;
88
+ min-height: 60px;
89
+ position: relative;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
90
  }
91
+ .timeline-task-block {
92
+ @apply absolute rounded-lg cursor-move transition-all;
93
+ transition: all 0.2s ease;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  }
95
 
96
+ .timeline-task-block:hover {
97
+ @apply transform scale-105 shadow-lg;
 
 
 
 
 
 
98
  }
99
 
100
+ .timeline-task-block.dragging {
101
+ @apply opacity-50;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
102
  }
103
 
104
+ /* Modal backdrop blur */
105
+ .modal-backdrop {
106
+ backdrop-filter: blur(4px);
 
 
107
  }
108
 
109
+ /* Custom input styles */
110
+ input[type="text"], input[type="number"], textarea, select {
111
+ @apply bg-gray-700 border border-gray-600 rounded-lg px-4 py-2 text-gray-100;
112
+ @apply focus:outline-none focus:ring-2 focus:ring-orange-500 focus:border-transparent;
113
  }
114
+
115
+ input[type="color"] {
116
+ @apply cursor-pointer;
117
+ }
118
+
119
+ /* Navigation buttons */
120
+ .nav-btn {
121
+ @apply flex items-center space-x-2 px-4 py-2 rounded-lg transition;
122
+ @apply text-gray-400 hover:text-white hover:bg-gray-700;
123
+ }
124
+
125
+ .nav-btn.active {
126
+ @apply text-orange-500 bg-gray-700;
127
+ }
128
+
129
+ /* View transitions */
130
+ .view {
131
+ display: none;
132
+ }
133
+
134
+ .view.active {
135
+ display: block;
136
+ animation: fadeIn 0.3s ease-in-out;
137
+ }
138
+ @keyframes fadeIn {
139
+ from {
140
+ opacity: 0;
141
+ transform: translateY(10px);
142
  }
143
+ to {
144
+ opacity: 1;
145
+ transform: translateY(0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  }
147
  }
148
 
149
+ /* Drag and drop styles */
150
+ .drag-over {
151
+ @apply bg-gray-700 border-2 border-dashed border-orange-500;
 
 
 
152
  }
153
+
154
+ /* Custom checkbox */
155
+ .custom-checkbox {
156
+ @apply w-5 h-5 rounded cursor-pointer appearance-none;
157
+ @apply bg-gray-700 border border-gray-600;
158
+ @apply checked:bg-emerald-500 checked:border-emerald-500;
159
  }
160
+
161
+ .custom-checkbox:checked::after {
162
+ content: '✓';
163
+ @apply block text-white text-center leading-5;
164
+ }
165
+
166
+ /* Time display */
167
+ .time-display {
168
+ @apply font-mono text-lg tabular-nums;
169
+ }
170
+
171
+ /* Empty state */
172
+ .empty-state {
173
+ @apply text-center py-12 text-gray-500;
174
+ }
175
+
176
+ .empty-state i {
177
+ @apply w-16 h-16 mx-auto mb-4;
178
+ }
179
+
180
+ /* Responsive timeline */
181
+ @media (max-width: 768px) {
182
+ #timeline-tasks {
183
+ width: 100%;
184
+ position: static;
185
+ }
186
 
187
+ #timeline-hours {
188
+ margin-left: 0;
189
+ }
190
 
191
+ .timeline-task-block {
192
+ position: relative !important;
193
+ width: 100% !important;
194
+ left: 0 !important;
195
+ top: auto !important;
196
  }
197
  }
198
+
199
+ /* Loading animation */
200
+ .loading {
201
+ @apply animate-pulse;
 
202
  }
203
 
204
+ .loading::after {
205
+ content: '';
206
+ @apply absolute inset-0 bg-gray-700 rounded-lg;
207
+ animation: loading 1.5s ease-in-out infinite;
208
  }
209
 
210
+ @keyframes loading {
211
+ 0% {
212
+ transform: translateX(-100%);
213
+ }
214
+ 100% {
215
+ transform: translateX(100%);
 
216
  }
217
  }
218
 
219
+ /* Task priority indicator */
220
+ .priority-high {
221
+ @apply border-l-4 border-red-500;
222
+ }
223
+
224
+ .priority-medium {
225
+ @apply border-l-4 border-yellow-500;
226
+ }
227
+
228
+ .priority-low {
229
+ @apply border-l-4 border-emerald-500;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  }
231
+
232
+ /* Focus mode glass effect */
233
+ #focus-mode {
234
+ background: linear-gradient(135deg, rgba(17, 24, 39, 0.95) 0%, rgba(17, 24, 39, 0.98) 100%);
235
+ backdrop-filter: blur(10px);
236
+ }
237
+
238
+ /* Smooth transitions for all interactive elements */
239
+ * {
240
+ transition: color 0.2s ease, background-color 0.2s ease, border-color 0.2s ease;
241
+ }