Fadikkop commited on
Commit
eac69cf
·
verified ·
1 Parent(s): 906718e

Die drei Seiten werden irgendwie unterteinander angezeigt und nicht richtig ausgeblendet. In der Timeline soll die aktuelle Zeit ersichtlich sein. Stelle sicher, dass Drag-and-Drop der Aufgaben in die Timeline funktioniert. Überlege, was noch zu verbessern ist und verbessere das.

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