Spaces:
Running
Running
Ü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 +38 -31
- components/timeline-sidebar.js +72 -15
- components/timeline-tasks.js +0 -81
- index.html +29 -56
- script.js +146 -122
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 |
-
|
|
|
|
| 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"
|
| 223 |
<i data-feather="clock"></i>
|
| 224 |
</button>
|
| 225 |
-
<button class="task-action"
|
| 226 |
<i data-feather="edit-2"></i>
|
| 227 |
</button>
|
| 228 |
-
<button class="task-action"
|
| 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"
|
| 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"
|
| 255 |
-
<button class="btn-small btn-cancel"
|
| 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
|
| 275 |
-
if (
|
| 276 |
-
|
| 277 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 310 |
-
|
| 311 |
-
|
|
|
|
| 312 |
}
|
| 313 |
|
| 314 |
handleDelete(e) {
|
| 315 |
if (confirm('Aufgabe wirklich löschen?')) {
|
| 316 |
this.dispatchEvent(new CustomEvent('delete-task', {
|
| 317 |
-
|
| 318 |
-
|
| 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 |
-
|
| 346 |
-
|
| 347 |
-
|
| 348 |
-
|
| 349 |
-
|
| 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 |
-
.
|
| 77 |
background: #374151;
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
|
|
|
| 82 |
}
|
| 83 |
</style>
|
| 84 |
|
| 85 |
<button class="toggle-btn">
|
| 86 |
-
<
|
| 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 |
-
<
|
| 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('
|
| 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 |
-
|
| 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 |
-
|
| 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 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
</div>
|
| 49 |
</div>
|
|
|
|
| 50 |
|
| 51 |
<!-- Task Lists -->
|
| 52 |
-
<div class="
|
| 53 |
<!-- Open Tasks -->
|
| 54 |
-
<div class="bg-gray-800 rounded-xl shadow-lg">
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 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 |
-
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 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 |
-
//
|
| 117 |
-
document.
|
| 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 |
-
|
| 719 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
}
|