mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 21:01:08 +02:00
feat(todo): redesign task detail modal + add inline subtasks
- Redesign TaskEditModal as a multi-column page-like layout:
- Title (large, borderless textarea with autoGrow)
- Content grid: description (left) | subtasks + links (right)
- Props strip: all metadata as horizontal flex-wrap cells (status,
priority, due date, recurrence, tags, assignee, story points, etc.)
- No sidebar, no scrollable property list
- Remove redundant `notes` field from TaskMetadata (keep only `description`)
- Remove all legacy migration code from useTaskForm composable
- Add inline subtasks display to TaskItem on the homepage:
- Shown indented under parent task when task has subtasks and is incomplete
- Each subtask is directly checkable (toggleSubtask via onSave callback)
- Vertical connecting line via CSS pseudo-element
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d8a2b37126
commit
b37df6facf
5 changed files with 637 additions and 433 deletions
File diff suppressed because it is too large
Load diff
|
|
@ -97,7 +97,6 @@
|
|||
form.selectedLabelIds,
|
||||
form.subtasks,
|
||||
form.recurrenceRule,
|
||||
form.notes,
|
||||
form.storyPoints,
|
||||
form.effectiveDuration,
|
||||
form.funRating,
|
||||
|
|
@ -208,6 +207,20 @@
|
|||
form.subtasks = newSubtasks;
|
||||
}
|
||||
|
||||
function toggleSubtask(subtaskId: string) {
|
||||
if (!onSave) return;
|
||||
const updated = (task.subtasks ?? []).map((s) =>
|
||||
s.id === subtaskId
|
||||
? {
|
||||
...s,
|
||||
isCompleted: !s.isCompleted,
|
||||
completedAt: !s.isCompleted ? new Date().toISOString() : null,
|
||||
}
|
||||
: s
|
||||
);
|
||||
onSave({ subtasks: updated });
|
||||
}
|
||||
|
||||
const priorityColors = PRIORITY_COLORS;
|
||||
|
||||
// Format due date
|
||||
|
|
@ -224,7 +237,7 @@
|
|||
});
|
||||
|
||||
// Subtasks progress
|
||||
let subtaskProgress = $derived(() => getSubtaskProgress(task.subtasks));
|
||||
let subtaskProgress = $derived(() => getSubtaskProgress(task.subtasks ?? undefined));
|
||||
|
||||
// Long press to expand (mobile)
|
||||
let longPressTimer: ReturnType<typeof setTimeout> | null = null;
|
||||
|
|
@ -315,25 +328,16 @@
|
|||
{task.title}
|
||||
</span>
|
||||
|
||||
<!-- Labels and subtasks below title -->
|
||||
{#if subtaskProgress() || (task.labels && task.labels.length > 0)}
|
||||
<!-- Labels below title -->
|
||||
{#if task.labels && task.labels.length > 0}
|
||||
<div class="task-meta">
|
||||
{#if subtaskProgress()}
|
||||
<span class="meta-item">
|
||||
<CheckSquare size={20} class="meta-icon" />
|
||||
{subtaskProgress()}
|
||||
{#each task.labels.slice(0, 2) as label}
|
||||
<span class="label-tag" style="--label-color: {label.color}">
|
||||
{label.name}
|
||||
</span>
|
||||
{/if}
|
||||
|
||||
{#if task.labels && task.labels.length > 0}
|
||||
{#each task.labels.slice(0, 2) as label}
|
||||
<span class="label-tag" style="--label-color: {label.color}">
|
||||
{label.name}
|
||||
</span>
|
||||
{/each}
|
||||
{#if task.labels.length > 2}
|
||||
<span class="meta-item">+{task.labels.length - 2}</span>
|
||||
{/if}
|
||||
{/each}
|
||||
{#if task.labels.length > 2}
|
||||
<span class="meta-item">+{task.labels.length - 2}</span>
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
|
@ -399,6 +403,24 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Inline subtasks -->
|
||||
{#if task.subtasks && task.subtasks.length > 0 && !task.isCompleted}
|
||||
<div class="subtasks-inline">
|
||||
{#each task.subtasks as subtask (subtask.id)}
|
||||
<button
|
||||
class="subtask-row"
|
||||
class:done={subtask.isCompleted}
|
||||
onclick={() => toggleSubtask(subtask.id)}
|
||||
>
|
||||
<span class="subtask-check" class:checked={subtask.isCompleted}>
|
||||
{#if subtask.isCompleted}<Check size={10} />{/if}
|
||||
</span>
|
||||
<span class="subtask-title">{subtask.title}</span>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- Expanded inline edit form -->
|
||||
{#if isExpanded}
|
||||
<div class="expanded-form">
|
||||
|
|
@ -533,18 +555,6 @@
|
|||
/>
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="form-section">
|
||||
<label class="form-label" for="task-notes-{task.id}">Notizen</label>
|
||||
<textarea
|
||||
id="task-notes-{task.id}"
|
||||
class="form-textarea"
|
||||
bind:value={form.notes}
|
||||
placeholder="Zusätzliche Notizen..."
|
||||
rows="2"
|
||||
></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Story Points & Duration & Fun Rating row -->
|
||||
<div class="form-row-3">
|
||||
<div class="form-section">
|
||||
|
|
@ -1191,4 +1201,86 @@
|
|||
background: #ef4444;
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* ── Inline subtasks ────────────────────────────── */
|
||||
.subtasks-inline {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* align with task title: padding-left + gap + checkbox + gap */
|
||||
padding: 0.125rem 1.5rem 0.375rem calc(1.5rem + 1.25rem + 1.25rem);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.subtasks-inline::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: calc(1.5rem + 0.625rem + 0.625rem);
|
||||
top: 0;
|
||||
bottom: 0.375rem;
|
||||
width: 1px;
|
||||
background: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
:global(.dark) .subtasks-inline::before {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
.subtask-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.1875rem 0;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
text-align: left;
|
||||
width: 100%;
|
||||
border-radius: 0.25rem;
|
||||
transition: background 0.1s;
|
||||
}
|
||||
|
||||
.subtask-row:hover {
|
||||
background: rgba(0, 0, 0, 0.03);
|
||||
}
|
||||
|
||||
:global(.dark) .subtask-row:hover {
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.subtask-check {
|
||||
width: 0.875rem;
|
||||
height: 0.875rem;
|
||||
border-radius: 50%;
|
||||
border: 1.5px solid rgba(0, 0, 0, 0.2);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
transition: all 0.15s;
|
||||
color: white;
|
||||
}
|
||||
|
||||
:global(.dark) .subtask-check {
|
||||
border-color: rgba(255, 255, 255, 0.25);
|
||||
}
|
||||
|
||||
.subtask-check.checked {
|
||||
background: #8b5cf6;
|
||||
border-color: #8b5cf6;
|
||||
}
|
||||
|
||||
.subtask-title {
|
||||
font-size: 0.8125rem;
|
||||
color: #374151;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
:global(.dark) .subtask-title {
|
||||
color: #d1d5db;
|
||||
}
|
||||
|
||||
.subtask-row.done .subtask-title {
|
||||
text-decoration: line-through;
|
||||
color: #9ca3af;
|
||||
}
|
||||
</style>
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@
|
|||
});
|
||||
|
||||
// Subtasks progress
|
||||
let subtaskProgress = $derived(() => getSubtaskProgress(task.subtasks));
|
||||
let subtaskProgress = $derived(() => getSubtaskProgress(task.subtasks ?? undefined));
|
||||
|
||||
// Click to open modal
|
||||
function handleCardClick(e: MouseEvent) {
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ export function useTaskForm() {
|
|||
let selectedLabelIds = $state<string[]>([]);
|
||||
let subtasks = $state<Subtask[]>([]);
|
||||
let recurrenceRule = $state('');
|
||||
let notes = $state('');
|
||||
let storyPoints = $state<number | null>(null);
|
||||
let effectiveDuration = $state<EffectiveDuration | null>(null);
|
||||
let funRating = $state<number | null>(null);
|
||||
|
|
@ -53,7 +52,6 @@ export function useTaskForm() {
|
|||
selectedLabelIds = task.labels?.map((l) => l.id) || [];
|
||||
subtasks = task.subtasks ? [...task.subtasks] : [];
|
||||
recurrenceRule = task.recurrenceRule || '';
|
||||
notes = task.metadata?.notes || '';
|
||||
storyPoints = task.metadata?.storyPoints ?? null;
|
||||
effectiveDuration = task.metadata?.effectiveDuration ?? null;
|
||||
funRating = task.metadata?.funRating ?? null;
|
||||
|
|
@ -99,7 +97,6 @@ export function useTaskForm() {
|
|||
recurrenceRule: recurrenceRule || null,
|
||||
metadata: {
|
||||
...task.metadata,
|
||||
notes: notes.trim() || undefined,
|
||||
storyPoints: storyPoints ?? undefined,
|
||||
effectiveDuration: effectiveDuration ?? undefined,
|
||||
funRating: funRating ?? undefined,
|
||||
|
|
@ -172,12 +169,6 @@ export function useTaskForm() {
|
|||
set recurrenceRule(v: string) {
|
||||
recurrenceRule = v;
|
||||
},
|
||||
get notes() {
|
||||
return notes;
|
||||
},
|
||||
set notes(v: string) {
|
||||
notes = v;
|
||||
},
|
||||
get storyPoints() {
|
||||
return storyPoints;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ export interface EffectiveDuration {
|
|||
}
|
||||
|
||||
export interface TaskMetadata {
|
||||
notes?: string;
|
||||
attachments?: string[];
|
||||
linkedCalendarEventId?: string | null;
|
||||
// Agile/Productivity metadata
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue