Fadikkop commited on
Commit
4e9dea2
·
verified ·
1 Parent(s): 4549c0d

Überarbeite die gesamte Logik. Die Timeline-Seite brauche ich nicht mehr, ihr Inhalt, soll in der Sidebar angezeigt werden. Die breite der Übersicht soll sich an die Timeline-sidebar anpassen, aktuell überdeckt die sidebar die Übersicht teilweise. Wenn ich Aufgaben abhake, sollen die nicht nur anders aussehen, sondern gar nicht mehr in "Offene Aufgaben" angezeigt werden, sondern stattdessen in "Erledigte Aufgaben". Der Löschen Knopf funktioniert nicht. Der Bearbeiten-Knopf soll ein Bearbeiten-Menü wie das Erstellen-Menü öffnen, aktuell tut er nichts. ich möchte Aufgaben mit drag and drop in ihrer Reihenfolge verschieben können und genauso auch in die Timeline. Wenn ich eine Aufgabe in die Timeline ziehe, soll sie in Offene Aufgaben weiterhin angezeigt werden, aber an auf die Zeit geplant werden, wo ich sie hingezogen habe und in der Timeline mit der geschätzten Zeit angezeigt werden. Auch der Pomodoro Starten Knopf funktioniert nicht

Browse files
components/task-item.js CHANGED
@@ -4,6 +4,7 @@ class TaskItem extends HTMLElement {
4
  super();
5
  this.attachShadow({ mode: 'open' });
6
  }
 
7
  connectedCallback() {
8
  this.taskId = this.getAttribute('task-id');
9
  this.completed = this.getAttribute('completed') === 'true';
@@ -35,16 +36,17 @@ class TaskItem extends HTMLElement {
35
 
36
  render() {
37
  if (!this.task) {
38
- this.shadowRoot.innerHTML = `<div class="loading" data-task-id="${this.taskId}">Lädt...</div>`;
39
  return;
40
  }
41
  const { title, estimatedTime, spentTime, tags } = this.task;
42
  const progress = estimatedTime > 0 ? (spentTime / estimatedTime) * 100 : 0;
43
  const tagElements = (tags || []).map(tagId => {
44
  const tag = this.tags?.find(t => t.id === tagId);
45
- return tag ? `<span class="tag-badge" style="background-color: ${tag.color}20; color: ${tag.color}">${tag.name}</span>` : '';
46
  }).join('');
47
- this.shadowRoot.innerHTML = `
 
48
  <style>
49
  :host {
50
  display: block;
@@ -215,17 +217,16 @@ this.shadowRoot.innerHTML = `
215
  }
216
  </style>
217
  <div class="task-header">
218
- <input type="checkbox" class="task-checkbox" ${this.completed ? 'checked' : ''}
219
- @change="${this.handleToggleComplete}">
220
  <span class="task-title">${title}</span>
221
  <div class="task-actions">
222
- <button class="focus-btn" @click="${this.handleFocus}" title="Pomodoro starten">
223
  <i data-feather="clock"></i>
224
  </button>
225
- <button class="task-action" @click="${this.handleEdit}" title="Bearbeiten">
226
  <i data-feather="edit-2"></i>
227
  </button>
228
- <button class="task-action" @click="${this.handleDelete}" title="Löschen">
229
  <i data-feather="trash-2"></i>
230
  </button>
231
  </div>
@@ -246,18 +247,19 @@ this.shadowRoot.innerHTML = `
246
  placeholder="h" min="0" max="24" style="width: 60px">
247
  <input type="number" value="${estimatedTime % 60}" id="edit-minutes-${this.taskId}"
248
  placeholder="m" min="0" max="59" step="15" style="width: 60px">
249
- <button class="btn-small btn-add-time" @click="${this.handleAddTime}" title="Zeit hinzufügen">
250
  <i data-feather="clock" style="width: 14px; height: 14px;"></i>
251
  </button>
252
  </div>
253
  <div class="edit-actions">
254
- <button class="btn-small btn-save" @click="${this.handleSave}">Speichern</button>
255
- <button class="btn-small btn-cancel" @click="${this.handleCancel}">Abbrechen</button>
256
  </div>
257
  </div>
258
  `;
 
259
  if (this.completed) {
260
- this.classList.add('completed');
261
  } else {
262
  this.classList.remove('completed');
263
  }
@@ -271,16 +273,21 @@ this.classList.add('completed');
271
 
272
  setupEventListeners() {
273
  this.shadowRoot.addEventListener('click', (e) => {
274
- const action = e.target.closest('button')?.getAttribute('@click');
275
- if (action) {
276
- e.stopPropagation();
277
- this[action.replace('${this.', '').replace('}', '')](e);
 
 
 
 
 
 
278
  }
279
  });
280
 
281
  this.shadowRoot.addEventListener('change', (e) => {
282
  if (e.target.classList.contains('task-checkbox')) {
283
- e.stopPropagation();
284
  this.handleToggleComplete(e);
285
  }
286
  });
@@ -299,6 +306,7 @@ this.classList.add('completed');
299
  this.classList.remove('completed');
300
  }
301
  }
 
302
  handleFocus(e) {
303
  const taskId = this.getAttribute('task-id');
304
  const task = state.tasks.find(t => t.id === taskId);
@@ -306,18 +314,18 @@ this.classList.add('completed');
306
  startFocusMode(task);
307
  }
308
  }
309
- handleEdit(e) {
310
- const editForm = this.shadowRoot.querySelector('.edit-form');
311
- editForm.classList.add('active');
 
312
  }
313
 
314
  handleDelete(e) {
315
  if (confirm('Aufgabe wirklich löschen?')) {
316
  this.dispatchEvent(new CustomEvent('delete-task', {
317
- detail: { taskId: this.getAttribute('task-id') },
318
- bubbles: true
319
- }));
320
- }
321
  }
322
 
323
  handleSave(e) {
@@ -342,13 +350,12 @@ handleEdit(e) {
342
  const minutes = prompt('Wie viele Minuten willst du hinzufügen?', '15');
343
  if (minutes && !isNaN(minutes)) {
344
  this.dispatchEvent(new CustomEvent('add-time', {
345
- detail: {
346
- taskId: this.getAttribute('task-id'),
347
- minutes: parseInt(minutes)
348
- },
349
- bubbles: true
350
- }));
351
- }
352
  }
353
  }
354
 
 
4
  super();
5
  this.attachShadow({ mode: 'open' });
6
  }
7
+
8
  connectedCallback() {
9
  this.taskId = this.getAttribute('task-id');
10
  this.completed = this.getAttribute('completed') === 'true';
 
36
 
37
  render() {
38
  if (!this.task) {
39
+ this.shadowRoot.innerHTML = `<div class="loading" data-task-id="${this.taskId}">Lädt...</div>';
40
  return;
41
  }
42
  const { title, estimatedTime, spentTime, tags } = this.task;
43
  const progress = estimatedTime > 0 ? (spentTime / estimatedTime) * 100 : 0;
44
  const tagElements = (tags || []).map(tagId => {
45
  const tag = this.tags?.find(t => t.id === tagId);
46
+ return tag ? `<span class="tag-badge" style="background-color: ${tag.color}20; color: ${tag.color}">${tag.name}</span>' : '';
47
  }).join('');
48
+
49
+ this.shadowRoot.innerHTML = `
50
  <style>
51
  :host {
52
  display: block;
 
217
  }
218
  </style>
219
  <div class="task-header">
220
+ <input type="checkbox" class="task-checkbox" ${this.completed ? 'checked' : ''}>
 
221
  <span class="task-title">${title}</span>
222
  <div class="task-actions">
223
+ <button class="focus-btn" title="Pomodoro starten">
224
  <i data-feather="clock"></i>
225
  </button>
226
+ <button class="task-action" title="Bearbeiten">
227
  <i data-feather="edit-2"></i>
228
  </button>
229
+ <button class="task-action" title="Löschen">
230
  <i data-feather="trash-2"></i>
231
  </button>
232
  </div>
 
247
  placeholder="h" min="0" max="24" style="width: 60px">
248
  <input type="number" value="${estimatedTime % 60}" id="edit-minutes-${this.taskId}"
249
  placeholder="m" min="0" max="59" step="15" style="width: 60px">
250
+ <button class="btn-small btn-add-time" title="Zeit hinzufügen">
251
  <i data-feather="clock" style="width: 14px; height: 14px;"></i>
252
  </button>
253
  </div>
254
  <div class="edit-actions">
255
+ <button class="btn-small btn-save">Speichern</button>
256
+ <button class="btn-small btn-cancel">Abbrechen</button>
257
  </div>
258
  </div>
259
  `;
260
+
261
  if (this.completed) {
262
+ this.classList.add('completed');
263
  } else {
264
  this.classList.remove('completed');
265
  }
 
273
 
274
  setupEventListeners() {
275
  this.shadowRoot.addEventListener('click', (e) => {
276
+ const target = e.target.closest('button');
277
+ if (target) {
278
+ const action = target.getAttribute('title');
279
+ if (action === 'Bearbeiten') {
280
+ this.handleEdit(e);
281
+ } else if (action === 'Löschen') {
282
+ this.handleDelete(e);
283
+ } else if (action === 'Pomodoro starten') {
284
+ this.handleFocus(e);
285
+ }
286
  }
287
  });
288
 
289
  this.shadowRoot.addEventListener('change', (e) => {
290
  if (e.target.classList.contains('task-checkbox')) {
 
291
  this.handleToggleComplete(e);
292
  }
293
  });
 
306
  this.classList.remove('completed');
307
  }
308
  }
309
+
310
  handleFocus(e) {
311
  const taskId = this.getAttribute('task-id');
312
  const task = state.tasks.find(t => t.id === taskId);
 
314
  startFocusMode(task);
315
  }
316
  }
317
+
318
+ handleEdit(e) {
319
+ const taskId = this.getAttribute('task-id');
320
+ openEditTaskModal(taskId);
321
  }
322
 
323
  handleDelete(e) {
324
  if (confirm('Aufgabe wirklich löschen?')) {
325
  this.dispatchEvent(new CustomEvent('delete-task', {
326
+ detail: { taskId: this.getAttribute('task-id') },
327
+ bubbles: true
328
+ }));
 
329
  }
330
 
331
  handleSave(e) {
 
350
  const minutes = prompt('Wie viele Minuten willst du hinzufügen?', '15');
351
  if (minutes && !isNaN(minutes)) {
352
  this.dispatchEvent(new CustomEvent('add-time', {
353
+ detail: {
354
+ taskId: this.getAttribute('task-id'),
355
+ minutes: parseInt(minutes)
356
+ },
357
+ bubbles: true
358
+ }));
 
359
  }
360
  }
361
 
components/timeline-sidebar.js CHANGED
@@ -33,6 +33,7 @@ class TimelineSidebar extends HTMLElement {
33
  display: flex;
34
  justify-content: space-between;
35
  align-items: center;
 
36
  }
37
 
38
  .timeline-container {
@@ -57,6 +58,29 @@ class TimelineSidebar extends HTMLElement {
57
  .task-drop-area {
58
  height: 100%;
59
  min-height: 60px;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  }
61
 
62
  .toggle-btn {
@@ -71,30 +95,27 @@ class TimelineSidebar extends HTMLElement {
71
  border-radius: 5px 0 0 5px;
72
  cursor: pointer;
73
  z-index: 30;
 
74
  }
75
 
76
- .task-block {
77
  background: #374151;
78
- border-left: 3px solid #f97316;
79
- padding: 0.5rem;
80
- margin: 0.5rem 0;
81
- border-radius: 4px;
 
82
  }
83
  </style>
84
 
85
  <button class="toggle-btn">
86
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
87
- <polyline points="15 18 9 12 15 6"></polyline>
88
- </svg>
89
  </button>
90
 
91
  <div class="header">
92
  <h3>Timeline</h3>
93
  <button id="close-timeline">
94
- <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
95
- <line x1="18" y1="6" x2="6" y2="18"></line>
96
- <line x1="6" y1="6" x2="18" y2="18"></line>
97
- </svg>
98
  </button>
99
  </div>
100
 
@@ -113,11 +134,9 @@ class TimelineSidebar extends HTMLElement {
113
  const toggleBtn = this.shadowRoot.querySelector('.toggle-btn');
114
  toggleBtn.addEventListener('click', () => {
115
  this.classList.toggle('open');
116
- const icon = toggleBtn.querySelector('svg');
117
  if (this.classList.contains('open')) {
118
- icon.innerHTML = '<polyline points="15 18 9 12 15 6"></polyline>';
119
  } else {
120
- icon.innerHTML = '<polyline points="9 18 15 12 9 6"></polyline>';
121
  }
122
  });
123
 
@@ -148,6 +167,44 @@ class TimelineSidebar extends HTMLElement {
148
  }));
149
  }
150
  });
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
  }
152
  }
153
 
 
33
  display: flex;
34
  justify-content: space-between;
35
  align-items: center;
36
+ background: #1f2937;
37
  }
38
 
39
  .timeline-container {
 
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 {
 
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
 
 
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
 
 
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
 
components/timeline-tasks.js CHANGED
@@ -1,82 +1 @@
1
- class TimelineTasks extends HTMLElement {
2
- connectedCallback() {
3
- this.attachShadow({ mode: 'open' });
4
- this.render();
5
- }
6
 
7
- render() {
8
- this.shadowRoot.innerHTML = `
9
- <style>
10
- :host {
11
- display: block;
12
- background: #1f2937;
13
- border-left: 1px solid #374151;
14
- height: 100%;
15
- width: 300px;
16
- position: fixed;
17
- right: 0;
18
- top: 80px;
19
- overflow-y: auto;
20
- padding: 1rem;
21
- }
22
-
23
- h3 {
24
- margin-top: 0;
25
- color: #f97316;
26
- padding-bottom: 1rem;
27
- border-bottom: 1px solid #374151;
28
- }
29
-
30
- .task-list {
31
- display: flex;
32
- flex-direction: column;
33
- gap: 8px;
34
- }
35
-
36
- .task-item {
37
- background: #374151;
38
- border-radius: 6px;
39
- padding: 8px;
40
- cursor: grab;
41
- transition: all 0.2s ease;
42
- }
43
-
44
- .task-item:hover {
45
- background: #4b5563;
46
- transform: translateX(-4px);
47
- }
48
-
49
- .task-title {
50
- font-size: 14px;
51
- margin-bottom: 4px;
52
- }
53
-
54
- .task-time {
55
- font-size: 12px;
56
- color: #9ca3af;
57
- }
58
- </style>
59
-
60
- <h3>Timeline</h3>
61
- <div class="task-list" id="timeline-task-list"></div>
62
- `;
63
- }
64
-
65
- updateTasks(tasks) {
66
- const container = this.shadowRoot.getElementById('timeline-task-list');
67
- container.innerHTML = tasks.map(task => `
68
- <div class="task-item" draggable="true" data-task-id="${task.id}">
69
- <div class="task-title">${task.title}</div>
70
- <div class="task-time">${formatTime(task.estimatedTime)}</div>
71
- </div>
72
- `).join('');
73
- }
74
- }
75
-
76
- customElements.define('timeline-tasks', TimelineTasks);
77
-
78
- function formatTime(minutes) {
79
- const h = Math.floor(minutes / 60);
80
- const m = minutes % 60;
81
- return `${h}h ${m}m`;
82
- }
 
 
 
 
 
 
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
index.html CHANGED
@@ -18,51 +18,47 @@
18
  <nav class="flex items-center space-x-4">
19
  <button id="view-home" class="nav-btn active" data-view="home">
20
  <i data-feather="home"></i> Übersicht
21
- </button>
22
- <button id="view-timeline" class="nav-btn" data-view="timeline">
23
- <i data-feather="clock"></i> Timeline
24
- </button>
25
  <button id="view-settings" class="nav-btn" data-view="settings">
26
  <i data-feather="settings"></i> Einstellungen
27
- </button>
28
  </nav>
29
  </div>
30
  </div>
31
  </header>
32
-
33
- <!-- Main Content -->
34
  <main class="container mx-auto px-4 py-6">
35
  <!-- Home View -->
36
  <div id="home-view" class="view active">
37
- <!-- Tag Progress Bars -->
38
  <div id="tag-progress-container" class="mb-6 space-y-3"></div>
39
 
40
  <!-- Main Progress Bar -->
41
  <div class="bg-gray-800 rounded-xl p-6 mb-8 shadow-lg">
42
  <div class="flex items-center justify-between mb-4">
43
- <h2 class="text-xl font-semibold">Gesamtfortschritt</h2>
44
- <span id="total-progress-text" class="text-sm text-gray-400">0% (0h / 0h)</span>
45
- </div>
46
- <div class="w-full bg-gray-700 rounded-full h-4 overflow-hidden">
47
- <div id="total-progress-bar" class="bg-gradient-to-r from-orange-500 to-orange-600 h-full rounded-full transition-all duration-500" style="width: 0%"></div>
48
- </div>
49
  </div>
 
50
 
51
  <!-- Task Lists -->
52
- <div class="grid md:grid-cols-2 gap-6">
53
  <!-- Open Tasks -->
54
- <div class="bg-gray-800 rounded-xl shadow-lg">
55
- <div class="p-4 border-b border-gray-700">
56
- <h3 class="text-lg font-semibold flex items-center">
57
- <i data-feather="circle" class="text-orange-500 mr-2"></i>
58
- Offene Aufgaben (<span id="open-count">0</span>)
59
- </h3>
60
- </div>
61
- <div id="open-tasks" class="p-4 space-y-3 max-h-96 overflow-y-auto"></div>
62
  </div>
63
 
64
  <!-- Completed Tasks -->
65
- <div class="bg-gray-800 rounded-xl shadow-lg">
66
  <div class="p-4 border-b border-gray-700">
67
  <h3 class="text-lg font-semibold flex items-center">
68
  <i data-feather="check-circle" class="text-emerald-500 mr-2"></i>
@@ -73,29 +69,7 @@
73
  </div>
74
  </div>
75
  </div>
76
-
77
- <!-- Timeline View -->
78
- <div id="timeline-view" class="view">
79
- <div class="bg-gray-800 rounded-xl p-6 shadow-lg">
80
- <div class="flex items-center justify-between mb-6">
81
- <h2 class="text-xl font-semibold">Timeline - Heute</h2>
82
- <div class="flex items-center space-x-4">
83
- <button id="timeline-now" class="px-4 py-2 bg-orange-500 hover:bg-orange-600 rounded-lg transition">
84
- Jetzt
85
- </button>
86
- <span id="current-time" class="text-lg font-mono"></span>
87
- </div>
88
- </div>
89
- <div id="timeline-container" class="relative">
90
- <div id="timeline-tasks" class="absolute left-0 top-0 w-64 space-y-2 p-4">
91
- <p class="text-sm text-gray-400">Aufgaben hierher ziehen</p>
92
- </div>
93
- <div id="timeline-hours" class="ml-64 border-l border-gray-600"></div>
94
- </div>
95
- </div>
96
- </div>
97
-
98
- <!-- Settings View -->
99
  <div id="settings-view" class="view">
100
  <div class="max-w-4xl mx-auto">
101
  <div class="bg-gray-800 rounded-xl p-6 shadow-lg">
@@ -216,18 +190,17 @@
216
  </div>
217
  </div>
218
  </div>
219
-
220
  <!-- FAB Buttons -->
221
  <div class="fixed bottom-6 right-6 flex flex-col space-y-3">
222
  <button id="focus-fab" class="p-4 bg-orange-500 hover:bg-orange-600 rounded-full shadow-lg transition transform hover:scale-110">
223
- <i data-feather="focus"></i>
224
- </button>
225
- <button id="add-task-fab" class="p-4 bg-emerald-500 hover:bg-emerald-600 rounded-full shadow-lg transition transform hover:scale-110">
226
- <i data-feather="plus"></i>
227
- </button>
228
- </div>
229
- <timeline-tasks id="timeline-sidebar"></timeline-tasks>
230
- <script src="components/task-item.js"></script>
231
  <script src="components/tag-item.js"></script>
232
  <script src="components/timeline-tasks.js"></script>
233
  <script src="script.js"></script>
 
18
  <nav class="flex items-center space-x-4">
19
  <button id="view-home" class="nav-btn active" data-view="home">
20
  <i data-feather="home"></i> Übersicht
21
+ </button>
 
 
 
22
  <button id="view-settings" class="nav-btn" data-view="settings">
23
  <i data-feather="settings"></i> Einstellungen
24
+ </button>
25
  </nav>
26
  </div>
27
  </div>
28
  </header>
29
+ <!-- Main Content -->
 
30
  <main class="container mx-auto px-4 py-6">
31
  <!-- Home View -->
32
  <div id="home-view" class="view active">
33
+ <!-- Tag Progress Bars -->
34
  <div id="tag-progress-container" class="mb-6 space-y-3"></div>
35
 
36
  <!-- Main Progress Bar -->
37
  <div class="bg-gray-800 rounded-xl p-6 mb-8 shadow-lg">
38
  <div class="flex items-center justify-between mb-4">
39
+ <h2 class="text-xl font-semibold">Gesamtfortschritt</h2>
40
+ <span id="total-progress-text" class="text-sm text-gray-400">0% (0h / 0h)</span>
41
+ </div>
42
+ <div class="w-full bg-gray-700 rounded-full h-4 overflow-hidden">
43
+ <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>
 
44
  </div>
45
+ </div>
46
 
47
  <!-- Task Lists -->
48
+ <div class="flex gap-6">
49
  <!-- Open Tasks -->
50
+ <div class="flex-1 bg-gray-800 rounded-xl shadow-lg">
51
+ <div class="p-4 border-b border-gray-700">
52
+ <h3 class="text-lg font-semibold flex items-center">
53
+ <i data-feather="circle" class="text-orange-500 mr-2"></i>
54
+ Offene Aufgaben (<span id="open-count">0</span>)
55
+ </h3>
56
+ </div>
57
+ <div id="open-tasks" class="p-4 space-y-3 max-h-96 overflow-y-auto"></div>
58
  </div>
59
 
60
  <!-- Completed Tasks -->
61
+ <div class="flex-1 bg-gray-800 rounded-xl shadow-lg">
62
  <div class="p-4 border-b border-gray-700">
63
  <h3 class="text-lg font-semibold flex items-center">
64
  <i data-feather="check-circle" class="text-emerald-500 mr-2"></i>
 
69
  </div>
70
  </div>
71
  </div>
72
+ <!-- Settings View -->
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  <div id="settings-view" class="view">
74
  <div class="max-w-4xl mx-auto">
75
  <div class="bg-gray-800 rounded-xl p-6 shadow-lg">
 
190
  </div>
191
  </div>
192
  </div>
 
193
  <!-- FAB Buttons -->
194
  <div class="fixed bottom-6 right-6 flex flex-col space-y-3">
195
  <button id="focus-fab" class="p-4 bg-orange-500 hover:bg-orange-600 rounded-full shadow-lg transition transform hover:scale-110">
196
+ <i data-feather="focus"></i>
197
+ </button>
198
+ <button id="add-task-fab" class="p-4 bg-emerald-500 hover:bg-emerald-600 rounded-full shadow-lg transition transform hover:scale-110">
199
+ <i data-feather="plus"></i>
200
+ </button>
201
+ </div>
202
+ <timeline-sidebar id="timeline-sidebar"></timeline-sidebar>
203
+ <script src="components/task-item.js"></script>
204
  <script src="components/tag-item.js"></script>
205
  <script src="components/timeline-tasks.js"></script>
206
  <script src="script.js"></script>
script.js CHANGED
@@ -1,7 +1,8 @@
 
1
  // TaskForge Pro - Main JavaScript
2
  // State management
3
  window.state = {
4
- tasks: [],
5
  tags: [],
6
  currentView: 'home',
7
  focusTask: null,
@@ -14,7 +15,6 @@ tasks: [],
14
  },
15
  timelineTasks: []
16
  };
17
-
18
  // Initialize
19
  document.addEventListener('DOMContentLoaded', () => {
20
  loadFromLocalStorage();
@@ -22,7 +22,6 @@ document.addEventListener('DOMContentLoaded', () => {
22
  setupEventListeners();
23
  render();
24
  });
25
-
26
  // Local Storage
27
  function loadFromLocalStorage() {
28
  const savedTasks = localStorage.getItem('tasks');
@@ -82,7 +81,6 @@ function updateCurrentTime() {
82
  });
83
  document.getElementById('current-time').textContent = timeString;
84
  }
85
-
86
  function setupEventListeners() {
87
  // Navigation
88
  document.querySelectorAll('.nav-btn').forEach(btn => {
@@ -113,10 +111,31 @@ function setupEventListeners() {
113
  document.getElementById('focus-play').addEventListener('click', resumePomodoro);
114
  document.getElementById('focus-stop').addEventListener('click', stopPomodoro);
115
 
116
- // Timeline
117
- document.getElementById('timeline-now').addEventListener('click', scrollToCurrentTime);
118
- }
 
119
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  // View Management
121
  function switchView(view) {
122
  state.currentView = view;
@@ -138,10 +157,6 @@ function switchView(view) {
138
  activeView.style.display = 'block';
139
  activeView.classList.add('active');
140
  }
141
-
142
- if (view === 'timeline') {
143
- renderTimeline();
144
- }
145
  }
146
  // Task Management
147
  function addTask(task) {
@@ -177,22 +192,12 @@ function deleteTask(taskId) {
177
  saveToLocalStorage();
178
  render();
179
  }
180
- function toggleTaskComplete(taskId) {
181
  const task = state.tasks.find(t => t.id === taskId);
182
  if (task) {
183
- task.completed = !task.completed;
184
  saveToLocalStorage();
185
-
186
- // Animate task completion
187
- const taskElement = document.querySelector(`task-item[task-id="${taskId}"]`);
188
- if (taskElement) {
189
- taskElement.classList.add('completed');
190
- setTimeout(() => {
191
- render();
192
- }, 600); // Match animation duration
193
- } else {
194
- render();
195
- }
196
  }
197
  }
198
  // Modal Functions
@@ -372,7 +377,7 @@ function renderTasks() {
372
  taskElement.setAttribute('completed', 'false');
373
  openContainer.appendChild(taskElement);
374
  });
375
- }
376
 
377
  // Render completed tasks
378
  if (completedTasks.length === 0) {
@@ -385,11 +390,14 @@ function renderTasks() {
385
  taskElement.setAttribute('completed', 'true');
386
  completedContainer.appendChild(taskElement);
387
  });
388
- }
389
 
390
  document.getElementById('open-count').textContent = openTasks.length;
391
  document.getElementById('completed-count').textContent = completedTasks.length;
392
 
 
 
 
393
  // Replace feather icons
394
  feather.replace();
395
  }
@@ -441,33 +449,6 @@ function renderTags() {
441
  el.addEventListener('delete', () => deleteTag(tagId));
442
  });
443
  }
444
- function renderTimeline() {
445
- if (state.currentView !== 'timeline') return;
446
-
447
- const container = document.getElementById('timeline-hours');
448
- container.innerHTML = '';
449
-
450
- // Generate hours
451
- for (let h = 6; h <= 22; h++) {
452
- const hourDiv = document.createElement('div');
453
- hourDiv.className = 'timeline-hour relative border-b border-gray-700';
454
- hourDiv.dataset.hour = h;
455
- hourDiv.style.minHeight = '60px';
456
-
457
- const timeLabel = document.createElement('span');
458
- timeLabel.className = 'absolute -ml-20 mt-2 text-sm text-gray-400 w-16 text-right';
459
- timeLabel.textContent = `${h.toString().padStart(2, '0')}:00`;
460
-
461
- hourDiv.appendChild(timeLabel);
462
- container.appendChild(hourDiv);
463
- }
464
-
465
- // Highlight current hour
466
- highlightCurrentHour();
467
-
468
- // Render scheduled tasks
469
- renderScheduledTasks();
470
- }
471
  function highlightCurrentHour() {
472
  const now = new Date();
473
  const currentHour = now.getHours();
@@ -523,36 +504,6 @@ function highlightCurrentHour() {
523
  }
524
  }
525
  }
526
- function renderScheduledTasks() {
527
- const today = new Date().toDateString();
528
- const scheduledTasks = state.timelineTasks.filter(t =>
529
- new Date(t.date).toDateString() === today
530
- );
531
-
532
- scheduledTasks.forEach(task => {
533
- const taskData = state.tasks.find(t => t.id === task.taskId);
534
- if (!taskData) return;
535
-
536
- const startHour = new Date(task.startTime).getHours();
537
- const startMinute = new Date(task.startTime).getMinutes();
538
- const duration = taskData.estimatedTime;
539
-
540
- const hourEl = document.querySelector(`[data-hour="${startHour}"]`);
541
- if (hourEl) {
542
- const taskEl = document.createElement('div');
543
- taskEl.className = 'timeline-task-block';
544
- taskEl.style.left = `${(startMinute / 60) * 100}%`;
545
- taskEl.style.width = `${(duration / 60) * 100}%`;
546
- taskEl.style.backgroundColor = task.tags[0] ?
547
- state.tags.find(t => t.id === task.tags[0])?.color + '20' : '#f9731620';
548
- taskEl.textContent = taskData.title;
549
- taskEl.draggable = true;
550
-
551
- hourEl.appendChild(taskEl);
552
- }
553
- });
554
- }
555
-
556
  function scrollToCurrentTime() {
557
  const now = new Date();
558
  const currentHour = now.getHours();
@@ -611,7 +562,6 @@ function startFocusMode(task) {
611
  const workTime = parseInt(localStorage.getItem('pomodoroWorkTime')) || 25;
612
  startPomodoro(workTime * 60, false);
613
  }
614
-
615
  function startPomodoro(seconds, isBreak) {
616
  state.pomodoro.isRunning = true;
617
  state.pomodoro.isBreak = isBreak;
@@ -679,7 +629,6 @@ function resumePomodoro() {
679
  document.getElementById('focus-play').classList.add('hidden');
680
  document.getElementById('focus-pause').classList.remove('hidden');
681
  }
682
-
683
  function stopPomodoro() {
684
  clearInterval(state.pomodoro.interval);
685
 
@@ -695,28 +644,16 @@ function stopPomodoro() {
695
  document.getElementById('focus-mode').classList.add('hidden');
696
  render();
697
  }
698
-
699
  function savePomodoroSettings() {
700
  localStorage.setItem('pomodoroWorkTime', document.getElementById('pomodoro-work').value);
701
  localStorage.setItem('pomodoroBreakTime', document.getElementById('pomodoro-break').value);
702
  }
703
-
704
  // Timeline Functions
705
  function initializeTimeline() {
706
- // Initialize timeline sidebar
707
- const timelineSidebar = document.createElement('timeline-sidebar');
708
- document.body.appendChild(timelineSidebar);
709
-
710
- // Listen for task drops
711
- timelineSidebar.addEventListener('task-dropped', (e) => {
712
- const { taskId, hour } = e.detail;
713
- scheduleTask(taskId, hour, 0); // Schedule at top of hour
714
- });
715
-
716
  // Setup drag start for task items
717
  document.addEventListener('dragstart', (e) => {
718
- if (e.target.tagName === 'TASK-ITEM') {
719
- const taskId = e.target.getAttribute('task-id');
720
  e.dataTransfer.setData('text/plain', taskId);
721
  }
722
  });
@@ -736,27 +673,6 @@ function handleDragOver(e) {
736
  e.target.classList.add('drag-over');
737
  }
738
  }
739
- function handleDrop(e) {
740
- e.preventDefault();
741
-
742
- // Remove drag-over class from all elements
743
- document.querySelectorAll('.drag-over').forEach(el => {
744
- el.classList.remove('drag-over');
745
- });
746
-
747
- const taskId = e.dataTransfer.getData('text/plain');
748
- const hourEl = e.target.closest('.timeline-hour');
749
-
750
- if (hourEl && taskId) {
751
- const hour = parseInt(hourEl.dataset.hour);
752
- const rect = hourEl.getBoundingClientRect();
753
- const x = e.clientX - rect.left;
754
- const hourWidth = rect.width;
755
- const minute = Math.floor((x / hourWidth) * 60);
756
-
757
- scheduleTask(taskId, hour, minute);
758
- }
759
- }
760
  function handleDragEnd(e) {
761
  e.target.classList.remove('dragging');
762
  document.querySelectorAll('.drag-over').forEach(el => {
@@ -785,7 +701,12 @@ function scheduleTask(taskId, hour, minute) {
785
  });
786
 
787
  saveToLocalStorage();
788
- renderTimeline();
 
 
 
 
 
789
  }
790
  // Utilities
791
  function formatTime(minutes) {
@@ -797,4 +718,107 @@ function formatTime(minutes) {
797
  // Request notification permission
798
  if ('Notification' in window && Notification.permission === 'default') {
799
  Notification.requestPermission();
800
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
  // TaskForge Pro - Main JavaScript
3
  // State management
4
  window.state = {
5
+ tasks: [],
6
  tags: [],
7
  currentView: 'home',
8
  focusTask: null,
 
15
  },
16
  timelineTasks: []
17
  };
 
18
  // Initialize
19
  document.addEventListener('DOMContentLoaded', () => {
20
  loadFromLocalStorage();
 
22
  setupEventListeners();
23
  render();
24
  });
 
25
  // Local Storage
26
  function loadFromLocalStorage() {
27
  const savedTasks = localStorage.getItem('tasks');
 
81
  });
82
  document.getElementById('current-time').textContent = timeString;
83
  }
 
84
  function setupEventListeners() {
85
  // Navigation
86
  document.querySelectorAll('.nav-btn').forEach(btn => {
 
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;
 
157
  activeView.style.display = 'block';
158
  activeView.classList.add('active');
159
  }
 
 
 
 
160
  }
161
  // Task Management
162
  function addTask(task) {
 
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
 
377
  taskElement.setAttribute('completed', 'false');
378
  openContainer.appendChild(taskElement);
379
  });
380
+ }
381
 
382
  // Render completed tasks
383
  if (completedTasks.length === 0) {
 
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
  }
 
449
  el.addEventListener('delete', () => deleteTag(tagId));
450
  });
451
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
452
  function highlightCurrentHour() {
453
  const now = new Date();
454
  const currentHour = now.getHours();
 
504
  }
505
  }
506
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
  function scrollToCurrentTime() {
508
  const now = new Date();
509
  const currentHour = now.getHours();
 
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;
 
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
 
 
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
  });
 
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 => {
 
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) {
 
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
+ }