|
|
|
@@ -0,0 +1,350 @@
|
|
|
|
|
<script lang="ts">
|
|
|
|
|
import { enhance } from '$app/forms';
|
|
|
|
|
import { goto } from '$app/navigation';
|
|
|
|
|
import { page } from '$app/stores';
|
|
|
|
|
import { TODO_STATUSES, TODO_STATUS_LABELS, TODO_PRIORITIES } from '$lib/constants.js';
|
|
|
|
|
import { formatDate } from '$lib/utils/date.js';
|
|
|
|
|
|
|
|
|
|
let { data } = $props();
|
|
|
|
|
|
|
|
|
|
let showNewForm = $state(false);
|
|
|
|
|
let editingId = $state<string | null>(null);
|
|
|
|
|
const view = $derived(data.view);
|
|
|
|
|
|
|
|
|
|
function setView(v: string) {
|
|
|
|
|
const url = new URL($page.url);
|
|
|
|
|
url.searchParams.set('view', v);
|
|
|
|
|
goto(url.toString());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function priorityStyle(p: number) {
|
|
|
|
|
const map: Record<number, string> = {
|
|
|
|
|
0: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-300',
|
|
|
|
|
1: 'bg-orange-100 text-orange-700 dark:bg-orange-900/40 dark:text-orange-300',
|
|
|
|
|
2: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300',
|
|
|
|
|
3: 'bg-gray-100 text-gray-600 dark:bg-gray-700 dark:text-gray-400'
|
|
|
|
|
};
|
|
|
|
|
return map[p] ?? map[2];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function priorityLabel(p: number) {
|
|
|
|
|
return TODO_PRIORITIES.find((x) => x.value === p)?.label ?? 'Medium';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function statusStyle(s: string) {
|
|
|
|
|
const map: Record<string, string> = {
|
|
|
|
|
todo: 'border-gray-300 dark:border-gray-600',
|
|
|
|
|
in_progress: 'border-blue-400 dark:border-blue-500',
|
|
|
|
|
done: 'border-green-400 dark:border-green-500'
|
|
|
|
|
};
|
|
|
|
|
return map[s] ?? map['todo'];
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const todosByStatus = $derived(
|
|
|
|
|
TODO_STATUSES.reduce(
|
|
|
|
|
(acc, s) => {
|
|
|
|
|
acc[s] = data.todos.filter((t) => t.status === s);
|
|
|
|
|
return acc;
|
|
|
|
|
},
|
|
|
|
|
{} as Record<string, typeof data.todos>
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:head>
|
|
|
|
|
<title>Todos - B4L Repair</title>
|
|
|
|
|
</svelte:head>
|
|
|
|
|
|
|
|
|
|
<div class="mx-auto max-w-6xl">
|
|
|
|
|
<div class="mb-6 flex flex-wrap items-center justify-between gap-3">
|
|
|
|
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">Todos</h1>
|
|
|
|
|
<div class="flex items-center gap-3">
|
|
|
|
|
<!-- View toggle -->
|
|
|
|
|
<div class="flex rounded-md border border-gray-300 dark:border-gray-600">
|
|
|
|
|
<button onclick={() => setView('list')}
|
|
|
|
|
class="px-3 py-1.5 text-sm {view === 'list' ? 'bg-blue-600 text-white' : 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700'} rounded-l-md">
|
|
|
|
|
List
|
|
|
|
|
</button>
|
|
|
|
|
<button onclick={() => setView('kanban')}
|
|
|
|
|
class="px-3 py-1.5 text-sm {view === 'kanban' ? 'bg-blue-600 text-white' : 'text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700'} rounded-r-md">
|
|
|
|
|
Board
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<button onclick={() => { showNewForm = !showNewForm; editingId = null; }}
|
|
|
|
|
class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">
|
|
|
|
|
{showNewForm ? 'Cancel' : 'New Todo'}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- New todo form -->
|
|
|
|
|
{#if showNewForm}
|
|
|
|
|
<div class="mb-6 rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
|
|
|
|
|
<form method="POST" action="?/create" use:enhance={() => {
|
|
|
|
|
return async ({ update, result }) => {
|
|
|
|
|
await update();
|
|
|
|
|
if (result.type === 'success') showNewForm = false;
|
|
|
|
|
};
|
|
|
|
|
}} class="space-y-3">
|
|
|
|
|
<div class="grid gap-3 sm:grid-cols-2">
|
|
|
|
|
<div>
|
|
|
|
|
<label for="title" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Title *</label>
|
|
|
|
|
<input type="text" id="title" name="title" required
|
|
|
|
|
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
|
|
|
|
placeholder="What needs to be done?" />
|
|
|
|
|
</div>
|
|
|
|
|
<div class="grid grid-cols-3 gap-2">
|
|
|
|
|
<div>
|
|
|
|
|
<label for="priority" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Priority</label>
|
|
|
|
|
<select id="priority" name="priority"
|
|
|
|
|
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
|
|
|
|
{#each TODO_PRIORITIES as p}
|
|
|
|
|
<option value={p.value} selected={p.value === 2}>{p.label}</option>
|
|
|
|
|
{/each}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label for="status" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Status</label>
|
|
|
|
|
<select id="status" name="status"
|
|
|
|
|
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
|
|
|
|
{#each TODO_STATUSES as s}
|
|
|
|
|
<option value={s}>{TODO_STATUS_LABELS[s]}</option>
|
|
|
|
|
{/each}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label for="dueDate" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Due</label>
|
|
|
|
|
<input type="date" id="dueDate" name="dueDate"
|
|
|
|
|
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="grid gap-3 sm:grid-cols-2">
|
|
|
|
|
<div>
|
|
|
|
|
<label for="description" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Description</label>
|
|
|
|
|
<textarea id="description" name="description" rows="2"
|
|
|
|
|
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
|
|
|
|
|
placeholder="Details..."></textarea>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label for="deviceId" class="mb-1 block text-sm font-medium text-gray-700 dark:text-gray-300">Linked Device</label>
|
|
|
|
|
<select id="deviceId" name="deviceId"
|
|
|
|
|
class="w-full rounded-md border border-gray-300 px-3 py-2 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
|
|
|
|
<option value="">None</option>
|
|
|
|
|
{#each data.devices as d}
|
|
|
|
|
<option value={d.id}>{d.title}</option>
|
|
|
|
|
{/each}
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<button type="submit" class="rounded-md bg-blue-600 px-4 py-2 text-sm font-medium text-white hover:bg-blue-700">Create</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<!-- List View -->
|
|
|
|
|
{#if view === 'list'}
|
|
|
|
|
{#if data.todos.length === 0}
|
|
|
|
|
<div class="rounded-lg border border-gray-200 bg-white p-12 text-center dark:border-gray-700 dark:bg-gray-800">
|
|
|
|
|
<p class="text-gray-500 dark:text-gray-400">No todos yet.</p>
|
|
|
|
|
</div>
|
|
|
|
|
{:else}
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
{#each data.todos as todo}
|
|
|
|
|
<div class="rounded-lg border-l-4 border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800 {statusStyle(todo.status)}">
|
|
|
|
|
{#if editingId === todo.id}
|
|
|
|
|
<!-- Inline edit form -->
|
|
|
|
|
<form method="POST" action="?/update" use:enhance={() => {
|
|
|
|
|
return async ({ update, result }) => {
|
|
|
|
|
await update();
|
|
|
|
|
if (result.type === 'success') editingId = null;
|
|
|
|
|
};
|
|
|
|
|
}} class="space-y-2">
|
|
|
|
|
<input type="hidden" name="id" value={todo.id} />
|
|
|
|
|
<div class="grid gap-2 sm:grid-cols-2">
|
|
|
|
|
<input type="text" name="title" value={todo.title} required
|
|
|
|
|
class="rounded-md border border-gray-300 px-3 py-1.5 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
|
|
|
|
|
<div class="grid grid-cols-3 gap-2">
|
|
|
|
|
<select name="priority"
|
|
|
|
|
class="rounded-md border border-gray-300 px-2 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
|
|
|
|
{#each TODO_PRIORITIES as p}
|
|
|
|
|
<option value={p.value} selected={p.value === todo.priority}>{p.label}</option>
|
|
|
|
|
{/each}
|
|
|
|
|
</select>
|
|
|
|
|
<select name="status"
|
|
|
|
|
class="rounded-md border border-gray-300 px-2 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
|
|
|
|
{#each TODO_STATUSES as s}
|
|
|
|
|
<option value={s} selected={s === todo.status}>{TODO_STATUS_LABELS[s]}</option>
|
|
|
|
|
{/each}
|
|
|
|
|
</select>
|
|
|
|
|
<input type="date" name="dueDate" value={todo.dueDate ? formatDate(todo.dueDate) : ''}
|
|
|
|
|
class="rounded-md border border-gray-300 px-2 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<textarea name="description" rows="2" placeholder="Description..."
|
|
|
|
|
class="w-full rounded-md border border-gray-300 px-3 py-1.5 text-sm focus:border-blue-500 focus:ring-1 focus:ring-blue-500 focus:outline-none dark:border-gray-600 dark:bg-gray-700 dark:text-white">{todo.description ?? ''}</textarea>
|
|
|
|
|
<input type="hidden" name="deviceId" value={todo.deviceId ?? ''} />
|
|
|
|
|
<div class="flex gap-2">
|
|
|
|
|
<button type="submit" class="rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700">Save</button>
|
|
|
|
|
<button type="button" onclick={() => (editingId = null)} class="rounded-md px-3 py-1.5 text-sm text-gray-600 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700">Cancel</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
{:else}
|
|
|
|
|
<div class="flex items-start gap-3">
|
|
|
|
|
<!-- Quick status toggle -->
|
|
|
|
|
<form method="POST" action="?/moveStatus" use:enhance class="flex-shrink-0 pt-0.5">
|
|
|
|
|
<input type="hidden" name="id" value={todo.id} />
|
|
|
|
|
<input type="hidden" name="status" value={todo.status === 'done' ? 'todo' : todo.status === 'todo' ? 'in_progress' : 'done'} />
|
|
|
|
|
<button type="submit" class="flex h-5 w-5 items-center justify-center rounded-full border-2
|
|
|
|
|
{todo.status === 'done' ? 'border-green-500 bg-green-500 text-white' : ''}
|
|
|
|
|
{todo.status === 'in_progress' ? 'border-blue-500 bg-blue-100 dark:bg-blue-900/40' : ''}
|
|
|
|
|
{todo.status === 'todo' ? 'border-gray-300 dark:border-gray-600' : ''}
|
|
|
|
|
" title="Click to advance status">
|
|
|
|
|
{#if todo.status === 'done'}
|
|
|
|
|
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7" />
|
|
|
|
|
</svg>
|
|
|
|
|
{:else if todo.status === 'in_progress'}
|
|
|
|
|
<div class="h-2 w-2 rounded-full bg-blue-500"></div>
|
|
|
|
|
{/if}
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<div class="flex-1 min-w-0">
|
|
|
|
|
<div class="flex items-center gap-2">
|
|
|
|
|
<span class="text-sm font-medium {todo.status === 'done' ? 'text-gray-400 line-through dark:text-gray-500' : 'text-gray-900 dark:text-white'}">
|
|
|
|
|
{todo.title}
|
|
|
|
|
</span>
|
|
|
|
|
<span class="rounded-full px-1.5 py-0.5 text-xs font-medium {priorityStyle(todo.priority)}">
|
|
|
|
|
{priorityLabel(todo.priority)}
|
|
|
|
|
</span>
|
|
|
|
|
{#if todo.status !== 'todo'}
|
|
|
|
|
<span class="rounded-full px-1.5 py-0.5 text-xs
|
|
|
|
|
{todo.status === 'in_progress' ? 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-300' : 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-300'}
|
|
|
|
|
">
|
|
|
|
|
{TODO_STATUS_LABELS[todo.status]}
|
|
|
|
|
</span>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
{#if todo.description}
|
|
|
|
|
<p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400">{todo.description}</p>
|
|
|
|
|
{/if}
|
|
|
|
|
<div class="mt-1 flex items-center gap-3 text-xs text-gray-400 dark:text-gray-500">
|
|
|
|
|
{#if todo.deviceTitle}
|
|
|
|
|
<a href="/devices/{todo.deviceId}" class="hover:text-blue-600 dark:hover:text-blue-400">{todo.deviceTitle}</a>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if todo.dueDate}
|
|
|
|
|
<span>Due {formatDate(todo.dueDate)}</span>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex items-center gap-1">
|
|
|
|
|
<button onclick={() => (editingId = todo.id)}
|
|
|
|
|
class="rounded p-1 text-gray-400 hover:text-blue-600 dark:text-gray-500 dark:hover:text-blue-400" title="Edit">
|
|
|
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
<form method="POST" action="?/delete" use:enhance>
|
|
|
|
|
<input type="hidden" name="id" value={todo.id} />
|
|
|
|
|
<button type="submit" class="rounded p-1 text-gray-400 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-400" title="Delete">
|
|
|
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<!-- Kanban View -->
|
|
|
|
|
{:else}
|
|
|
|
|
<div class="grid gap-4 lg:grid-cols-3">
|
|
|
|
|
{#each TODO_STATUSES as status}
|
|
|
|
|
<div class="rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800/50">
|
|
|
|
|
<div class="mb-3 flex items-center justify-between">
|
|
|
|
|
<h2 class="text-sm font-semibold uppercase tracking-wider
|
|
|
|
|
{status === 'todo' ? 'text-gray-500 dark:text-gray-400' : ''}
|
|
|
|
|
{status === 'in_progress' ? 'text-blue-600 dark:text-blue-400' : ''}
|
|
|
|
|
{status === 'done' ? 'text-green-600 dark:text-green-400' : ''}
|
|
|
|
|
">
|
|
|
|
|
{TODO_STATUS_LABELS[status]}
|
|
|
|
|
</h2>
|
|
|
|
|
<span class="rounded-full bg-gray-200 px-2 py-0.5 text-xs font-medium text-gray-600 dark:bg-gray-700 dark:text-gray-400">
|
|
|
|
|
{todosByStatus[status]?.length ?? 0}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="space-y-2">
|
|
|
|
|
{#each todosByStatus[status] ?? [] as todo}
|
|
|
|
|
<div class="rounded-md border border-gray-200 bg-white p-3 shadow-sm dark:border-gray-600 dark:bg-gray-800">
|
|
|
|
|
<div class="mb-1 flex items-start justify-between gap-2">
|
|
|
|
|
<span class="text-sm font-medium text-gray-900 dark:text-white">{todo.title}</span>
|
|
|
|
|
<span class="flex-shrink-0 rounded-full px-1.5 py-0.5 text-xs font-medium {priorityStyle(todo.priority)}">
|
|
|
|
|
{priorityLabel(todo.priority)}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
{#if todo.description}
|
|
|
|
|
<p class="mb-2 text-xs text-gray-500 dark:text-gray-400">{todo.description}</p>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if todo.deviceTitle}
|
|
|
|
|
<a href="/devices/{todo.deviceId}" class="mb-2 block text-xs text-blue-600 hover:text-blue-700 dark:text-blue-400">{todo.deviceTitle}</a>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if todo.dueDate}
|
|
|
|
|
<p class="mb-2 text-xs text-gray-400 dark:text-gray-500">Due {formatDate(todo.dueDate)}</p>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<!-- Move buttons -->
|
|
|
|
|
<div class="flex gap-1">
|
|
|
|
|
{#if status !== 'todo'}
|
|
|
|
|
<form method="POST" action="?/moveStatus" use:enhance>
|
|
|
|
|
<input type="hidden" name="id" value={todo.id} />
|
|
|
|
|
<input type="hidden" name="status" value={status === 'done' ? 'in_progress' : 'todo'} />
|
|
|
|
|
<button type="submit" class="rounded px-2 py-0.5 text-xs text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700" title="Move left">
|
|
|
|
|
← {status === 'done' ? 'In Progress' : 'To Do'}
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
{/if}
|
|
|
|
|
{#if status !== 'done'}
|
|
|
|
|
<form method="POST" action="?/moveStatus" use:enhance>
|
|
|
|
|
<input type="hidden" name="id" value={todo.id} />
|
|
|
|
|
<input type="hidden" name="status" value={status === 'todo' ? 'in_progress' : 'done'} />
|
|
|
|
|
<button type="submit" class="rounded px-2 py-0.5 text-xs text-gray-500 hover:bg-gray-100 dark:text-gray-400 dark:hover:bg-gray-700" title="Move right">
|
|
|
|
|
{status === 'todo' ? 'In Progress' : 'Done'} →
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
{/if}
|
|
|
|
|
<div class="ml-auto flex gap-1">
|
|
|
|
|
<button onclick={() => { editingId = todo.id; showNewForm = false; }}
|
|
|
|
|
class="rounded p-0.5 text-gray-400 hover:text-blue-600 dark:text-gray-500" title="Edit">
|
|
|
|
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
<form method="POST" action="?/delete" use:enhance>
|
|
|
|
|
<input type="hidden" name="id" value={todo.id} />
|
|
|
|
|
<button type="submit" class="rounded p-0.5 text-gray-400 hover:text-red-500 dark:text-gray-500" title="Delete">
|
|
|
|
|
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
|
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
|
|
|
|
</svg>
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
|
|
|
|
|
{#if (todosByStatus[status]?.length ?? 0) === 0}
|
|
|
|
|
<p class="py-4 text-center text-xs text-gray-400 dark:text-gray-500">No items</p>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{/each}
|
|
|
|
|
</div>
|
|
|
|
|
{/if}
|
|
|
|
|
</div>
|