Spaces:
Running
Running
Okay, die Aufgaben werden jetzt angezeigt. Ich möchte einen Knopf an jeder Aufgabe, um den Pomodoro Modus mit ihr zu starten. Wenn ich Aufgaben abhake, wird die Aufgabe nicht nach erledigt verschoben. Die Progress Bars für die Tags sollen nur für Tags angezeigt werden, die auch Aufgaben haben. Das Abhaken von Aufgaben soll visuell noch cooler aussehen, bisschen gamification. Und die Timeline soll eine schmale Seitenleiste rechts in der Übersicht sein. Implementiere all das!
Browse files- components/task-item.js +3 -3
- components/timeline-tasks.js +82 -0
- index.html +2 -1
- script.js +34 -25
- style.css +26 -1
components/task-item.js
CHANGED
|
@@ -219,10 +219,10 @@ this.shadowRoot.innerHTML = `
|
|
| 219 |
@change="${this.handleToggleComplete}">
|
| 220 |
<span class="task-title">${title}</span>
|
| 221 |
<div class="task-actions">
|
| 222 |
-
<button class="
|
| 223 |
-
<i data-feather="
|
| 224 |
</button>
|
| 225 |
-
|
| 226 |
<i data-feather="edit-2"></i>
|
| 227 |
</button>
|
| 228 |
<button class="task-action" @click="${this.handleDelete}" title="Löschen">
|
|
|
|
| 219 |
@change="${this.handleToggleComplete}">
|
| 220 |
<span class="task-title">${title}</span>
|
| 221 |
<div class="task-actions">
|
| 222 |
+
<button class="focus-btn" @click="${this.handleFocus}" title="Pomodoro starten">
|
| 223 |
+
<i data-feather="clock"></i>
|
| 224 |
</button>
|
| 225 |
+
<button class="task-action" @click="${this.handleEdit}" title="Bearbeiten">
|
| 226 |
<i data-feather="edit-2"></i>
|
| 227 |
</button>
|
| 228 |
<button class="task-action" @click="${this.handleDelete}" title="Löschen">
|
components/timeline-tasks.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
+
}
|
index.html
CHANGED
|
@@ -226,9 +226,10 @@
|
|
| 226 |
<i data-feather="plus"></i>
|
| 227 |
</button>
|
| 228 |
</div>
|
|
|
|
| 229 |
<script src="components/task-item.js"></script>
|
| 230 |
<script src="components/tag-item.js"></script>
|
| 231 |
-
<script src="components/timeline-
|
| 232 |
<script src="script.js"></script>
|
| 233 |
<script>feather.replace();</script>
|
| 234 |
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
|
|
|
| 226 |
<i data-feather="plus"></i>
|
| 227 |
</button>
|
| 228 |
</div>
|
| 229 |
+
<timeline-tasks id="timeline-sidebar"></timeline-tasks>
|
| 230 |
<script src="components/task-item.js"></script>
|
| 231 |
<script src="components/tag-item.js"></script>
|
| 232 |
+
<script src="components/timeline-tasks.js"></script>
|
| 233 |
<script src="script.js"></script>
|
| 234 |
<script>feather.replace();</script>
|
| 235 |
<script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
|
script.js
CHANGED
|
@@ -177,16 +177,24 @@ function deleteTask(taskId) {
|
|
| 177 |
saveToLocalStorage();
|
| 178 |
render();
|
| 179 |
}
|
| 180 |
-
|
| 181 |
function toggleTaskComplete(taskId) {
|
| 182 |
const task = state.tasks.find(t => t.id === taskId);
|
| 183 |
if (task) {
|
| 184 |
task.completed = !task.completed;
|
| 185 |
saveToLocalStorage();
|
| 186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
}
|
| 188 |
}
|
| 189 |
-
|
| 190 |
// Modal Functions
|
| 191 |
function openAddTaskModal() {
|
| 192 |
const modal = document.getElementById('add-task-modal');
|
|
@@ -395,31 +403,32 @@ function renderProgressBars() {
|
|
| 395 |
document.getElementById('total-progress-text').textContent =
|
| 396 |
`${Math.round(totalProgress)}% (${formatTime(totalSpent)} / ${formatTime(totalEstimated)})`;
|
| 397 |
|
| 398 |
-
// Tag progress bars
|
| 399 |
const container = document.getElementById('tag-progress-container');
|
| 400 |
-
container.innerHTML = state.tags
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
<
|
| 411 |
-
${
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
|
|
|
|
|
|
|
|
|
| 417 |
</div>
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
}).join('');
|
| 421 |
}
|
| 422 |
-
|
| 423 |
function renderTags() {
|
| 424 |
const container = document.getElementById('tags-list');
|
| 425 |
container.innerHTML = state.tags.map(tag => `
|
|
|
|
| 177 |
saveToLocalStorage();
|
| 178 |
render();
|
| 179 |
}
|
|
|
|
| 180 |
function toggleTaskComplete(taskId) {
|
| 181 |
const task = state.tasks.find(t => t.id === taskId);
|
| 182 |
if (task) {
|
| 183 |
task.completed = !task.completed;
|
| 184 |
saveToLocalStorage();
|
| 185 |
+
|
| 186 |
+
// Animate task completion
|
| 187 |
+
const taskElement = document.querySelector(`task-item[task-id="${taskId}"]`);
|
| 188 |
+
if (taskElement) {
|
| 189 |
+
taskElement.classList.add('completed');
|
| 190 |
+
setTimeout(() => {
|
| 191 |
+
render();
|
| 192 |
+
}, 600); // Match animation duration
|
| 193 |
+
} else {
|
| 194 |
+
render();
|
| 195 |
+
}
|
| 196 |
}
|
| 197 |
}
|
|
|
|
| 198 |
// Modal Functions
|
| 199 |
function openAddTaskModal() {
|
| 200 |
const modal = document.getElementById('add-task-modal');
|
|
|
|
| 403 |
document.getElementById('total-progress-text').textContent =
|
| 404 |
`${Math.round(totalProgress)}% (${formatTime(totalSpent)} / ${formatTime(totalEstimated)})`;
|
| 405 |
|
| 406 |
+
// Tag progress bars - only for tags with tasks
|
| 407 |
const container = document.getElementById('tag-progress-container');
|
| 408 |
+
container.innerHTML = state.tags
|
| 409 |
+
.filter(tag => state.tasks.some(t => t.tags.includes(tag.id))) // Only tags with tasks
|
| 410 |
+
.map(tag => {
|
| 411 |
+
const tagTasks = state.tasks.filter(t => t.tags.includes(tag.id));
|
| 412 |
+
const tagEstimated = tagTasks.reduce((sum, t) => sum + t.estimatedTime, 0) || 1;
|
| 413 |
+
const tagSpent = tagTasks.reduce((sum, t) => sum + t.spentTime, 0);
|
| 414 |
+
const tagProgress = (tagSpent / tagEstimated) * 100;
|
| 415 |
+
|
| 416 |
+
return `
|
| 417 |
+
<div class="bg-gray-800 rounded-lg p-4 shadow transition-all hover:scale-[1.02]">
|
| 418 |
+
<div class="flex items-center justify-between mb-2">
|
| 419 |
+
<span class="font-medium" style="color: ${tag.color}">${tag.name}</span>
|
| 420 |
+
<span class="text-sm text-gray-400">
|
| 421 |
+
${Math.round(tagProgress)}% (${formatTime(tagSpent)} / ${formatTime(tagEstimated)})
|
| 422 |
+
</span>
|
| 423 |
+
</div>
|
| 424 |
+
<div class="w-full bg-gray-700 rounded-full h-2 overflow-hidden">
|
| 425 |
+
<div class="h-full rounded-full transition-all duration-500"
|
| 426 |
+
style="width: ${tagProgress}%; background-color: ${tag.color}"></div>
|
| 427 |
+
</div>
|
| 428 |
</div>
|
| 429 |
+
`;
|
| 430 |
+
}).join('');
|
|
|
|
| 431 |
}
|
|
|
|
| 432 |
function renderTags() {
|
| 433 |
const container = document.getElementById('tags-list');
|
| 434 |
container.innerHTML = state.tags.map(tag => `
|
style.css
CHANGED
|
@@ -17,13 +17,38 @@
|
|
| 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 |
}
|
|
|
|
| 17 |
::-webkit-scrollbar-thumb:hover {
|
| 18 |
@apply bg-gray-500;
|
| 19 |
}
|
|
|
|
| 20 |
/* Task hover effects */
|
| 21 |
.task-item {
|
| 22 |
@apply relative overflow-hidden;
|
| 23 |
transition: all 0.3s ease;
|
| 24 |
+
border-left: 4px solid transparent;
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
.task-item.completed {
|
| 28 |
+
animation: completeTask 0.6s ease forwards;
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
@keyframes completeTask {
|
| 32 |
+
0% { transform: scale(1); opacity: 1; }
|
| 33 |
+
50% { transform: scale(0.95); opacity: 0.8; }
|
| 34 |
+
100% { transform: scale(1); opacity: 1; border-left-color: #10b981; }
|
| 35 |
}
|
| 36 |
|
| 37 |
+
.task-item .focus-btn {
|
| 38 |
+
@apply absolute right-12 top-1/2 transform -translate-y-1/2;
|
| 39 |
+
@apply p-2 rounded-full bg-orange-500 bg-opacity-0 text-orange-500;
|
| 40 |
+
@apply transition-all duration-300 ease-in-out;
|
| 41 |
+
opacity: 0;
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
.task-item:hover .focus-btn {
|
| 45 |
+
@apply bg-opacity-20;
|
| 46 |
+
opacity: 1;
|
| 47 |
+
}
|
| 48 |
+
|
| 49 |
+
.task-item .focus-btn:hover {
|
| 50 |
+
@apply bg-opacity-100 text-white;
|
| 51 |
+
}
|
| 52 |
.task-item:hover {
|
| 53 |
@apply transform scale-105 shadow-lg;
|
| 54 |
}
|