feat(tasks): support subtasks with inline creation, checklist and title progress bar
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 54s

This commit is contained in:
sandrodach committed 2026-10-25 01:43:14 +02:00
1 parent 537b916533
commit 0ec1a8b7f7
7 files changed
+388 -58

No files matched your search

@@ -523,6 +523,22 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
setSlashSearch('');
};
const getDirectChildTodos = (blocks: DocumentBlock[], parentIndex: number): DocumentBlock[] => {
const parent = blocks[parentIndex];
if (!parent) return [];
const parentLevel = parent.level || 0;
const childTodos: DocumentBlock[] = [];
for (let i = parentIndex + 1; i < blocks.length; i++) {
const cur = blocks[i];
const curLevel = cur.level || 0;
if (curLevel <= parentLevel) break;
if (cur.type === 'todo') {
childTodos.push(cur);
}
}
return childTodos;
};
// Drag & Drop Handlers on the Bullet Dot
const handleDragStart = (e: React.DragEvent, blockId: string) => {
e.dataTransfer.setData('text/plain', blockId);
@@ -1467,26 +1483,68 @@ export const WorkspaceView: React.FC<WorkspaceViewProps> = ({
<div className="flex-1 min-w-0 pt-0.5 relative">
{/* TYPE: TODO */}
{block.type === 'todo' && (
<div className="flex items-center gap-2">
<input
type="checkbox"
checked={!!block.checked}
onChange={(e) =>
updateBlock(block.id, { checked: e.target.checked })
}
className="w-4 h-4 checkbox-square cursor-pointer flex-shrink-0"
/>
<input
ref={(el) => (blockInputRefs.current[block.id] = el)}
type="text"
value={block.content}
onChange={(e) => handleContentChange(block, e.target.value)}
onKeyDown={(e) => handleKeyDown(e, block, originalIndex)}
placeholder="Aufgabe tippen… (/ für Befehle)"
className={`w-full bg-transparent text-sm focus:outline-none placeholder:text-faint ${
block.checked ? 'line-through text-faint' : 'text-main'
}`}
/>
<div className="space-y-1">
<div className="flex items-center gap-2 group/todo">
<input
type="checkbox"
checked={!!block.checked}
onChange={(e) =>
updateBlock(block.id, { checked: e.target.checked })
}
className="w-4 h-4 checkbox-square cursor-pointer flex-shrink-0"
/>
<input
ref={(el) => (blockInputRefs.current[block.id] = el)}
type="text"
value={block.content}
onChange={(e) => handleContentChange(block, e.target.value)}
onKeyDown={(e) => handleKeyDown(e, block, originalIndex)}
placeholder="Aufgabe tippen… (/ für Befehle)"
className={`w-full bg-transparent text-sm focus:outline-none placeholder:text-faint ${
block.checked ? 'line-through text-faint' : 'text-main'
}`}
/>
<button
type="button"
onClick={() => insertNewBulletAfter(originalIndex, (block.level || 0) + 1, 'todo')}
className="opacity-0 group-hover/todo:opacity-100 p-0.5 text-muted hover:text-accent rounded transition-opacity cursor-pointer flex-shrink-0"
title="Unteraufgabe erstellen"
>
<Plus className="w-3.5 h-3.5" />
</button>
</div>
{/* Progress bar for child todos if any exist */}
{(() => {
const childTodos = getDirectChildTodos(selectedDoc?.blocks || [], originalIndex);
if (childTodos.length === 0) return null;
const doneChild = childTodos.filter((c) => c.checked).length;
const totalChild = childTodos.length;
const pct = Math.round((doneChild / totalChild) * 100);
return (
<div
className="space-y-0.5 ml-6 pt-0.5 select-none"
title={`${doneChild} von ${totalChild} Unteraufgaben erledigt (${pct}%)`}
>
<div className="flex items-center justify-between text-[10px] text-muted">
<span>
{doneChild}/{totalChild} erledigt
</span>
<span className={pct === 100 ? 'text-status-ok font-bold' : ''}>
{pct}%
</span>
</div>
<div className="w-full h-1 bg-border rounded-full overflow-hidden">
<div
className={`h-full transition-all duration-300 ${
pct === 100 ? 'bg-status-ok' : 'bg-accent'
}`}
style={{ width: `${pct}%` }}
/>
</div>
</div>
);
})()}
</div>
)}