Fadikkop commited on
Commit
7124801
·
verified ·
1 Parent(s): 3d9c9a5

Alles, was interaktiv ist, wird nicht geladen. Implementiere gutes logging und versuche, die Integration des js sicherzustellen. Hast du irgendeinen Fehler gemacht?

Browse files

```sh
cdn.tailwindcss.com should not be used in production. To use Tailwind CSS in production, install it as a PostCSS plugin or use the Tailwind CLI: https://tailwindcss.com/docs/installation cdn.tailwindcss.com:64:1711
Uncaught SyntaxError: unexpected token: keyword 'class' task-item.js:47:33
Uncaught SyntaxError: expected expression, got ')' script.js:751:10
feather: 'focus' is not a valid icon feather-icons:12:5197
Content script loaded, initializing... content.js:1719:13
Error parsing watch history: SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data
loadWatchHistory moz-extension://fbbd56d3-98cc-4e3b-a6f1-5229548dd187/content.js:794
content.js:848:17
Initial watch history loaded. content.js:936:13
Could not extract video UUID from URL content.js:1684:21
SyntaxError: JSON.parse: unexpected character at line 1 column 2 of the JSON data content.js:574:73
[PeerTube Companion] Retrieved item "getUrlService-fadikkop-taskforge-pro.static.hf.space" from cache redirection-content-script.f3b7260b.js:1:21789
Source-Map-Fehler: Error: request failed with status 404
Stack in the worker:networkRequest@resource://devtools/client/shared/source-map-loader/utils/network-request.js:43:9

Ressourcen-Adresse: https://unpkg.com/feather-icons
Source-Map-Adresse: feather.min.js.map 2
```

components/feather-icons-fix.js ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Feather Icons Fix - Ensure icons are properly loaded
2
+ class FeatherIconsFix extends HTMLElement {
3
+ connectedCallback() {
4
+ console.log('TaskForge Pro: Feather icons fix component loaded');
5
+
6
+ // Wait for DOM to be fully ready
7
+ setTimeout(() => {
8
+ if (typeof feather !== 'undefined') {
9
+ console.log('TaskForge Pro: Feather icons library found, replacing icons...');
10
+ feather.replace();
11
+ } else {
12
+ console.warn('TaskForge Pro: Feather icons library not found');
13
+ }
14
+ }, 100);
15
+ }
16
+ }
17
+
18
+ customElements.define('feather-icons-fix', FeatherIconsFix);
components/tag-item.js CHANGED
@@ -1,101 +1,3 @@
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
 
 
 
 
 
 
 
2
 
 
 
 
 
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
components/task-item.js CHANGED
@@ -1,273 +1,101 @@
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
 
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);
components/timeline-sidebar.js CHANGED
@@ -1,211 +1,273 @@
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
- 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
 
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);
components/timeline-tasks.js CHANGED
@@ -1,3 +1,211 @@
 
 
 
 
 
 
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
 
 
 
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ 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);
index.html CHANGED
@@ -1,209 +1,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://unpkg.com/feather-icons"></script>
10
- </head>
11
- <body class="bg-gray-900 text-gray-100 min-h-screen">
12
- <!-- Header with Navigation -->
13
- <header class="bg-gray-800 border-b border-gray-700 sticky top-0 z-40">
14
- <div class="container mx-auto px-4 py-4">
15
- <div class="flex items-center justify-between">
16
- <h1 class="text-2xl font-bold text-orange-500">TaskForge Pro</h1>
17
- <nav class="flex items-center space-x-4">
18
- <button id="view-home" class="nav-btn active" data-view="home">
19
- <i data-feather="home"></i> Übersicht
20
- </button>
21
- <button id="view-settings" class="nav-btn" data-view="settings">
22
- <i data-feather="settings"></i> Einstellungen
23
- </button>
24
- </nav>
25
- </div>
26
- </div>
27
- </header>
28
- <!-- Main Content -->
29
- <main class="container mx-auto px-4 py-6">
30
- <!-- Home View -->
31
- <div id="home-view" class="view active">
32
- <!-- Tag Progress Bars -->
33
- <div id="tag-progress-container" class="mb-6 space-y-3"></div>
34
-
35
- <!-- Main Progress Bar -->
36
- <div class="bg-gray-800 rounded-xl p-6 mb-8 shadow-lg">
37
- <div class="flex items-center justify-between mb-4">
38
- <h2 class="text-xl font-semibold">Gesamtfortschritt</h2>
39
- <span id="total-progress-text" class="text-sm text-gray-400">0% (0h / 0h)</span>
40
- </div>
41
- <div class="w-full bg-gray-700 rounded-full h-4 overflow-hidden">
42
- <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>
43
- </div>
44
- </div>
45
 
46
- <!-- Task Lists -->
47
- <div class="flex gap-6">
48
- <!-- Open Tasks -->
49
- <div class="flex-1 bg-gray-800 rounded-xl shadow-lg">
50
- <div class="p-4 border-b border-gray-700">
51
- <h3 class="text-lg font-semibold flex items-center">
52
- <i data-feather="circle" class="text-orange-500 mr-2"></i>
53
- Offene Aufgaben (<span id="open-count">0</span>)
54
- </h3>
55
- </div>
56
- <div id="open-tasks" class="p-4 space-y-3 max-h-96 overflow-y-auto"></div>
57
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
58
 
59
- <!-- Completed Tasks -->
60
- <div class="flex-1 bg-gray-800 rounded-xl shadow-lg">
61
- <div class="p-4 border-b border-gray-700">
62
- <h3 class="text-lg font-semibold flex items-center">
63
- <i data-feather="check-circle" class="text-emerald-500 mr-2"></i>
64
- Erledigte Aufgaben (<span id="completed-count">0</span>)
65
- </h3>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
66
  </div>
67
- <div id="completed-tasks" class="p-4 space-y-3 max-h-96 overflow-y-auto"></div>
68
- </div>
69
- </div>
70
- </div>
71
- <!-- Settings View -->
72
- <div id="settings-view" class="view">
73
- <div class="max-w-4xl mx-auto">
74
- <div class="bg-gray-800 rounded-xl p-6 shadow-lg">
75
- <h2 class="text-xl font-semibold mb-6">Einstellungen</h2>
76
-
77
- <!-- Import/Export -->
78
- <div class="mb-8">
79
- <h3 class="text-lg font-medium mb-4">Import/Export</h3>
80
- <div class="space-y-4">
81
- <div>
82
- <label class="block text-sm font-medium mb-2">Markdown importieren</label>
83
- <textarea id="import-md" class="w-full h-64 bg-gray-700 rounded-lg p-4 text-gray-100"
84
- placeholder="- [ ] Aufgabe | 2h | #tag1 #tag2&#10;- [x] Erledigte Aufgabe | 1h 30m | #tag1"></textarea>
85
- <button id="import-btn" class="mt-2 px-4 py-2 bg-emerald-500 hover:bg-emerald-600 rounded-lg transition">
86
- Importieren
87
- </button>
88
- </div>
89
- <div>
90
- <button id="export-btn" class="px-4 py-2 bg-orange-500 hover:bg-orange-600 rounded-lg transition">
91
- Als Markdown exportieren
92
- </button>
93
- </div>
94
- </div>
95
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
96
 
97
- <!-- Tags -->
98
- <div class="mb-8">
99
- <h3 class="text-lg font-medium mb-4">Tags verwalten</h3>
100
- <div class="flex items-center space-x-2 mb-4">
101
- <input id="new-tag-name" type="text" placeholder="Tag-Name"
102
- class="bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
103
- <input id="new-tag-color" type="color" value="#f97316"
104
- class="w-16 h-10 bg-gray-700 rounded cursor-pointer">
105
- <button id="add-tag-btn" class="px-4 py-2 bg-emerald-500 hover:bg-emerald-600 rounded-lg transition">
106
- <i data-feather="plus"></i>
107
- </button>
108
- </div>
109
- <div id="tags-list" class="flex flex-wrap gap-2"></div>
110
- </div>
111
 
112
- <!-- Pomodoro Settings -->
113
- <div>
114
- <h3 class="text-lg font-medium mb-4">Pomodoro</h3>
115
- <div class="grid grid-cols-2 gap-4">
116
- <div>
117
- <label class="block text-sm font-medium mb-2">Arbeitszeit (Min.)</label>
118
- <input id="pomodoro-work" type="number" value="25" min="1" max="60"
119
- class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
120
- </div>
121
- <div>
122
- <label class="block text-sm font-medium mb-2">Pause (Min.)</label>
123
- <input id="pomodoro-break" type="number" value="5" min="1" max="30"
124
- class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
125
- </div>
126
- </div>
127
  </div>
128
- </div>
129
  </div>
 
 
 
 
130
  </div>
131
- </main>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
- <!-- Add Task Modal -->
134
- <div id="add-task-modal" class="fixed inset-0 bg-black bg-opacity-50 hidden z-50 flex items-center justify-center p-4">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  <div class="bg-gray-800 rounded-xl p-6 max-w-md w-full">
136
- <h3 class="text-xl font-semibold mb-4">Aufgabe hinzufügen</h3>
137
- <form id="add-task-form" class="space-y-4">
138
  <div>
139
  <label class="block text-sm font-medium mb-2">Titel</label>
140
- <input id="task-title" type="text" required
141
  class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
142
  </div>
143
  <div>
144
  <label class="block text-sm font-medium mb-2">Geschätzte Zeit</label>
145
  <div class="flex space-x-2">
146
- <input id="task-hours" type="number" placeholder="h" min="0" max="24" value="0"
147
  class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
148
- <input id="task-minutes" type="number" placeholder="m" min="0" max="59" step="15" value="30"
149
  class="w-full bg-gray-700 rounded-lg px-4 py-2 text-gray-100">
150
  </div>
151
  </div>
152
- <div>
153
- <label class="block text-sm font-medium mb-2">Tags</label>
154
- <div id="modal-tags-list" class="flex flex-wrap gap-2 mb-2"></div>
155
- </div>
156
  <div class="flex justify-end space-x-2">
157
- <button type="button" id="cancel-task" class="px-4 py-2 bg-gray-700 hover:bg-gray-600 rounded-lg transition">
158
- Abbrechen
159
- </button>
160
- <button type="submit" class="px-4 py-2 bg-orange-500 hover:bg-orange-600 rounded-lg transition">
161
- Hinzufügen
162
- </button>
 
 
 
163
  </div>
164
- </form>
165
- </div>
166
- </div>
 
 
 
 
 
 
 
 
167
 
168
- <!-- Focus Mode -->
169
- <div id="focus-mode" class="fixed inset-0 bg-gray-900 z-50 hidden flex items-center justify-center">
170
- <div class="text-center max-w-2xl mx-auto p-8">
171
- <h2 id="focus-task-title" class="text-3xl font-bold mb-8"></h2>
172
-
173
- <div id="pomodoro-timer" class="text-6xl font-mono font-bold text-orange-500 mb-8"></div>
174
-
175
- <div class="flex items-center justify-center space-x-4 mb-8">
176
- <button id="focus-pause" class="p-4 bg-gray-800 hover:bg-gray-700 rounded-full transition">
177
- <i data-feather="pause" class="w-8 h-8"></i>
178
- </button>
179
- <button id="focus-play" class="p-4 bg-gray-800 hover:bg-gray-700 rounded-full transition hidden">
180
- <i data-feather="play" class="w-8 h-8"></i>
181
- </button>
182
- <button id="focus-stop" class="p-4 bg-red-500 hover:bg-red-600 rounded-full transition">
183
- <i data-feather="square" class="w-8 h-8"></i>
184
- </button>
185
- </div>
186
-
187
- <div class="text-gray-400">
188
- <p id="pomodoro-status">Arbeitszeit läuft...</p>
189
- </div>
190
- </div>
191
- </div>
192
- <!-- FAB Buttons -->
193
- <div class="fixed bottom-6 right-6 flex flex-col space-y-3">
194
- <button id="focus-fab" class="p-4 bg-orange-500 hover:bg-orange-600 rounded-full shadow-lg transition transform hover:scale-110">
195
- <i data-feather="focus"></i>
196
- </button>
197
- <button id="add-task-fab" class="p-4 bg-emerald-500 hover:bg-emerald-600 rounded-full shadow-lg transition transform hover:scale-110">
198
- <i data-feather="plus"></i>
199
- </button>
200
- </div>
201
- <timeline-sidebar id="timeline-sidebar"></timeline-sidebar>
202
- <script src="components/task-item.js"></script>
203
- <script src="components/tag-item.js"></script>
204
- <script src="components/timeline-tasks.js"></script>
205
- <script src="script.js"></script>
206
- <script>feather.replace();</script>
207
- <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
208
- </body>
209
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
script.js 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
+ }
style.css CHANGED
@@ -1,241 +1,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
- }
 
 
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
  }