mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-15 12:01:10 +02:00
♻️ refactor(todo): replace edit modal with inline task editing
Redesign TaskItem to expand inline for editing instead of opening a separate modal. Improves UX by keeping user context and reducing visual interruption. Removes modal-related code from pages. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
09b8d7b384
commit
cb3c1ffb93
7 changed files with 848 additions and 263 deletions
|
|
@ -68,7 +68,7 @@
|
|||
{#if visible}
|
||||
<!-- svelte-ignore a11y_click_events_have_key_events a11y_no_static_element_interactions -->
|
||||
<div
|
||||
class="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||
class="fixed inset-0 z-[9995] flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
||||
onclick={handleBackdropClick}
|
||||
>
|
||||
<div
|
||||
|
|
|
|||
|
|
@ -385,7 +385,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 50;
|
||||
z-index: 9995;
|
||||
padding: 4rem 2rem;
|
||||
}
|
||||
|
||||
|
|
@ -409,19 +409,18 @@
|
|||
border: 1px solid rgba(255, 255, 255, 0.15);
|
||||
}
|
||||
|
||||
/* Mobile: Full screen from bottom, above QuickAdd bar */
|
||||
/* Mobile: Full screen from bottom, modal covers all UI elements */
|
||||
@media (max-width: 640px) {
|
||||
.modal-backdrop {
|
||||
align-items: flex-end;
|
||||
padding: 0;
|
||||
/* QuickAdd is at bottom: 70px + ~60px height = 130px, plus PillNav */
|
||||
padding-bottom: calc(env(safe-area-inset-bottom, 0px) + 140px);
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
max-width: 100%;
|
||||
max-height: calc(100vh - 160px); /* Account for QuickAdd + PillNav */
|
||||
max-height: calc(100vh - 60px - env(safe-area-inset-top, 0px));
|
||||
border-radius: 1.5rem 1.5rem 0 0;
|
||||
margin-bottom: env(safe-area-inset-bottom, 0px);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -1,6 +1,6 @@
|
|||
<script lang="ts">
|
||||
import { dndzone, SHADOW_PLACEHOLDER_ITEM_ID } from 'svelte-dnd-action';
|
||||
import type { Task } from '@todo/shared';
|
||||
import type { Task, UpdateTaskInput } from '@todo/shared';
|
||||
import TaskItem from './TaskItem.svelte';
|
||||
import { tasksStore } from '$lib/stores/tasks.svelte';
|
||||
|
||||
|
|
@ -22,6 +22,43 @@
|
|||
onTaskDrop,
|
||||
}: Props = $props();
|
||||
|
||||
// Track which task is expanded for inline editing
|
||||
let expandedTaskId = $state<string | null>(null);
|
||||
|
||||
function handleExpandTask(taskId: string) {
|
||||
// Toggle - if same task clicked, collapse it
|
||||
if (expandedTaskId === taskId) {
|
||||
expandedTaskId = null;
|
||||
} else {
|
||||
expandedTaskId = taskId;
|
||||
}
|
||||
}
|
||||
|
||||
function handleCollapseTask() {
|
||||
expandedTaskId = null;
|
||||
}
|
||||
|
||||
async function handleSaveTask(taskId: string, data: UpdateTaskInput) {
|
||||
try {
|
||||
// Update task
|
||||
const updateData = {
|
||||
...data,
|
||||
metadata: data.metadata as { [key: string]: unknown } | null | undefined,
|
||||
};
|
||||
await tasksStore.updateTask(taskId, updateData);
|
||||
|
||||
// Update labels if provided
|
||||
if (data.labelIds !== undefined) {
|
||||
await tasksStore.updateLabels(taskId, data.labelIds);
|
||||
}
|
||||
|
||||
// Collapse after save
|
||||
expandedTaskId = null;
|
||||
} catch (error) {
|
||||
console.error('Failed to save task:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Local mutable state for dnd-zone
|
||||
let items = $state<Task[]>([]);
|
||||
|
||||
|
|
@ -111,9 +148,12 @@
|
|||
{task}
|
||||
{showCompleted}
|
||||
animateComplete={animatingTaskId === task.id}
|
||||
isExpanded={expandedTaskId === task.id}
|
||||
onToggleComplete={() => handleToggleComplete(task)}
|
||||
onDelete={() => handleDelete(task.id)}
|
||||
onEdit={onEditTask ? () => onEditTask(task) : undefined}
|
||||
onExpand={() => handleExpandTask(task.id)}
|
||||
onCollapse={handleCollapseTask}
|
||||
onSave={(data) => handleSaveTask(task.id, data)}
|
||||
/>
|
||||
{/each}
|
||||
{#if items.length === 0}
|
||||
|
|
@ -129,9 +169,12 @@
|
|||
{task}
|
||||
{showCompleted}
|
||||
animateComplete={animatingTaskId === task.id}
|
||||
isExpanded={expandedTaskId === task.id}
|
||||
onToggleComplete={() => handleToggleComplete(task)}
|
||||
onDelete={() => handleDelete(task.id)}
|
||||
onEdit={onEditTask ? () => onEditTask(task) : undefined}
|
||||
onExpand={() => handleExpandTask(task.id)}
|
||||
onCollapse={handleCollapseTask}
|
||||
onSave={(data) => handleSaveTask(task.id, data)}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
|
@ -140,7 +183,7 @@
|
|||
<style>
|
||||
.task-list {
|
||||
min-height: 60px;
|
||||
padding: 0.25rem;
|
||||
padding: 0;
|
||||
border-radius: 0.5rem;
|
||||
transition: background-color 0.15s ease;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -523,7 +523,6 @@
|
|||
.main-content {
|
||||
transition: all 300ms ease;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
/* Space for QuickInputBar at bottom */
|
||||
padding-bottom: calc(80px + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
|
@ -554,8 +553,6 @@
|
|||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
padding: 1rem;
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.content-wrapper.full-width {
|
||||
|
|
|
|||
|
|
@ -2,17 +2,15 @@
|
|||
import { onMount } from 'svelte';
|
||||
import { format, addDays, subDays, startOfDay } from 'date-fns';
|
||||
import { de } from 'date-fns/locale';
|
||||
import { ListChecks, Sparkle, ArrowDown } from '@manacore/shared-icons';
|
||||
import { Sparkle, ArrowDown } from '@manacore/shared-icons';
|
||||
import { tasksStore } from '$lib/stores/tasks.svelte';
|
||||
import { viewStore } from '$lib/stores/view.svelte';
|
||||
import TaskList from '$lib/components/TaskList.svelte';
|
||||
import CollapsibleSection from '$lib/components/CollapsibleSection.svelte';
|
||||
import TaskEditModal from '$lib/components/TaskEditModal.svelte';
|
||||
import { TaskListSkeleton } from '$lib/components/skeletons';
|
||||
import type { Task, UpdateTaskInput } from '@todo/shared';
|
||||
import type { Task } from '@todo/shared';
|
||||
|
||||
let isLoading = $state(true);
|
||||
let editingTask = $state<Task | null>(null);
|
||||
|
||||
onMount(async () => {
|
||||
viewStore.setToday();
|
||||
|
|
@ -104,46 +102,6 @@
|
|||
window.dispatchEvent(new CustomEvent('quick-input-set', { detail: { text } }));
|
||||
}
|
||||
|
||||
// Modal handlers
|
||||
function openEditModal(task: Task) {
|
||||
editingTask = task;
|
||||
}
|
||||
|
||||
function closeEditModal() {
|
||||
editingTask = null;
|
||||
}
|
||||
|
||||
async function handleSaveTask(data: UpdateTaskInput) {
|
||||
if (!editingTask) return;
|
||||
|
||||
try {
|
||||
// Update task - cast metadata to be compatible with store type
|
||||
const updateData = {
|
||||
...data,
|
||||
metadata: data.metadata as { [key: string]: unknown } | null | undefined,
|
||||
};
|
||||
await tasksStore.updateTask(editingTask.id, updateData);
|
||||
|
||||
// Update labels if provided
|
||||
if (data.labelIds !== undefined) {
|
||||
await tasksStore.updateLabels(editingTask.id, data.labelIds);
|
||||
}
|
||||
|
||||
closeEditModal();
|
||||
} catch (error) {
|
||||
console.error('Failed to save task:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async function handleDeleteTask(taskId: string) {
|
||||
try {
|
||||
await tasksStore.deleteTask(taskId);
|
||||
closeEditModal();
|
||||
} catch (error) {
|
||||
console.error('Failed to delete task:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Drag and drop handler - uses optimistic updates for smooth UX
|
||||
function handleTaskDrop(taskId: string, targetDate: Date | 'completed' | 'overdue') {
|
||||
const task = tasksStore.tasks.find((t) => t.id === taskId);
|
||||
|
|
@ -236,7 +194,6 @@
|
|||
enableDragDrop
|
||||
dropTargetDate="overdue"
|
||||
onTaskDrop={handleTaskDrop}
|
||||
onEditTask={openEditModal}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
{/if}
|
||||
|
|
@ -255,7 +212,6 @@
|
|||
enableDragDrop
|
||||
dropTargetDate={startOfDay(new Date())}
|
||||
onTaskDrop={handleTaskDrop}
|
||||
onEditTask={openEditModal}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
{/if}
|
||||
|
|
@ -274,7 +230,6 @@
|
|||
enableDragDrop
|
||||
dropTargetDate={tomorrowDate}
|
||||
onTaskDrop={handleTaskDrop}
|
||||
onEditTask={openEditModal}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
{/if}
|
||||
|
|
@ -299,7 +254,6 @@
|
|||
enableDragDrop
|
||||
dropTargetDate={group.date}
|
||||
onTaskDrop={handleTaskDrop}
|
||||
onEditTask={openEditModal}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
|
|
@ -322,7 +276,6 @@
|
|||
dropTargetDate="completed"
|
||||
onTaskDrop={handleTaskDrop}
|
||||
showCompleted
|
||||
onEditTask={openEditModal}
|
||||
/>
|
||||
</CollapsibleSection>
|
||||
{/if}
|
||||
|
|
@ -340,17 +293,6 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Task Edit Modal -->
|
||||
{#if editingTask}
|
||||
<TaskEditModal
|
||||
task={editingTask}
|
||||
open={true}
|
||||
onClose={closeEditModal}
|
||||
onSave={handleSaveTask}
|
||||
onDelete={handleDeleteTask}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.unified-view {
|
||||
padding-bottom: 100px;
|
||||
|
|
|
|||
|
|
@ -7,13 +7,9 @@
|
|||
import { tasksStore } from '$lib/stores/tasks.svelte';
|
||||
import { labelsStore } from '$lib/stores/labels.svelte';
|
||||
import TaskList from '$lib/components/TaskList.svelte';
|
||||
import TaskEditModal from '$lib/components/TaskEditModal.svelte';
|
||||
import { TaskListSkeleton } from '$lib/components/skeletons';
|
||||
import type { Task, Label } from '@todo/shared';
|
||||
|
||||
let isLoading = $state(true);
|
||||
let editingTask = $state<Task | null>(null);
|
||||
let showEditModal = $state(false);
|
||||
|
||||
// Get tag ID from URL
|
||||
const tagId = $derived($page.params.id ?? '');
|
||||
|
|
@ -46,45 +42,6 @@
|
|||
|
||||
isLoading = false;
|
||||
});
|
||||
|
||||
// Modal handlers
|
||||
function openEditModal(task: Task) {
|
||||
editingTask = task;
|
||||
showEditModal = true;
|
||||
}
|
||||
|
||||
function closeEditModal() {
|
||||
showEditModal = false;
|
||||
editingTask = null;
|
||||
}
|
||||
|
||||
function handleSaveTask(data: Partial<Task>) {
|
||||
if (!editingTask) return;
|
||||
|
||||
// Extract only the fields that updateTask accepts
|
||||
const updateData = {
|
||||
title: data.title,
|
||||
description: data.description ?? undefined,
|
||||
projectId: data.projectId,
|
||||
dueDate: typeof data.dueDate === 'string' ? data.dueDate : data.dueDate?.toISOString(),
|
||||
priority: data.priority,
|
||||
status: data.status,
|
||||
subtasks: data.subtasks ?? undefined,
|
||||
recurrenceRule: data.recurrenceRule,
|
||||
};
|
||||
|
||||
tasksStore.updateTask(editingTask.id, updateData).catch((error) => {
|
||||
console.error('Failed to update task:', error);
|
||||
});
|
||||
closeEditModal();
|
||||
}
|
||||
|
||||
function handleDeleteTask(taskId: string) {
|
||||
tasksStore.deleteTask(taskId).catch((error) => {
|
||||
console.error('Failed to delete task:', error);
|
||||
});
|
||||
closeEditModal();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
|
|
@ -141,7 +98,7 @@
|
|||
<h2 class="section-title">
|
||||
Offen ({incompleteTasks.length})
|
||||
</h2>
|
||||
<TaskList tasks={incompleteTasks} onEditTask={openEditModal} />
|
||||
<TaskList tasks={incompleteTasks} />
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
|
|
@ -151,7 +108,7 @@
|
|||
<h2 class="section-title completed">
|
||||
Erledigt ({completedTasks.length})
|
||||
</h2>
|
||||
<TaskList tasks={completedTasks} showCompleted={true} onEditTask={openEditModal} />
|
||||
<TaskList tasks={completedTasks} showCompleted={true} />
|
||||
</section>
|
||||
{/if}
|
||||
|
||||
|
|
@ -162,17 +119,6 @@
|
|||
{/if}
|
||||
</div>
|
||||
|
||||
<!-- Task Edit Modal -->
|
||||
{#if editingTask}
|
||||
<TaskEditModal
|
||||
task={editingTask}
|
||||
open={showEditModal}
|
||||
onClose={closeEditModal}
|
||||
onSave={handleSaveTask}
|
||||
onDelete={handleDeleteTask}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.page-container {
|
||||
max-width: 640px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue