Add checklist editing: rename, edit items, reorder with arrows
Deploy to LXC / deploy (push) Successful in 20s

Templates page (/checklists):
- Edit template name/description inline (pencil icon)
- Edit item text and unit inline (pencil icon on hover)
- Move items up/down with arrow buttons
- Reorder swaps sort order values in the database

Device checklists:
- Rename checklist title inline (pencil icon)
- Edit item text and unit inline (pencil icon on hover)
- Move items up/down with arrow buttons
- All item types (checkbox and input) support editing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-08 09:47:51 +07:00
parent 1dcc69482e
commit 68abb3e734
4 changed files with 341 additions and 93 deletions
@@ -89,6 +89,61 @@ export const actions: Actions = {
return { itemAdded: true }; return { itemAdded: true };
}, },
renameTemplate: async ({ request }) => {
const formData = await request.formData();
const templateId = formData.get('templateId') as string;
const title = (formData.get('title') as string)?.trim();
const description = (formData.get('description') as string)?.trim();
if (!title) return fail(400, { error: 'Title is required' });
await db
.update(checklistTemplates)
.set({ title, description: description || null, updatedAt: new Date() })
.where(eq(checklistTemplates.id, templateId));
return { renamed: true };
},
editItem: async ({ request }) => {
const formData = await request.formData();
const itemId = formData.get('itemId') as string;
const text = (formData.get('text') as string)?.trim();
const unit = (formData.get('unit') as string)?.trim();
if (!text) return fail(400, { error: 'Item text is required' });
await db
.update(templateItems)
.set({ text, unit: unit || null })
.where(eq(templateItems.id, itemId));
return { itemEdited: true };
},
moveItem: async ({ request }) => {
const formData = await request.formData();
const itemId = formData.get('itemId') as string;
const direction = formData.get('direction') as string; // 'up' or 'down'
const templateId = formData.get('templateId') as string;
const items = await db
.select({ id: templateItems.id, sortOrder: templateItems.sortOrder })
.from(templateItems)
.where(eq(templateItems.templateId, templateId))
.orderBy(templateItems.sortOrder);
const idx = items.findIndex((i) => i.id === itemId);
if (idx < 0) return fail(400, { error: 'Item not found' });
const swapIdx = direction === 'up' ? idx - 1 : idx + 1;
if (swapIdx < 0 || swapIdx >= items.length) return { moved: true };
// Swap sort orders
const a = items[idx];
const b = items[swapIdx];
await db.update(templateItems).set({ sortOrder: b.sortOrder }).where(eq(templateItems.id, a.id));
await db.update(templateItems).set({ sortOrder: a.sortOrder }).where(eq(templateItems.id, b.id));
return { moved: true };
},
deleteItem: async ({ request }) => { deleteItem: async ({ request }) => {
const formData = await request.formData(); const formData = await request.formData();
const itemId = formData.get('itemId') as string; const itemId = formData.get('itemId') as string;
+88 -2
View File
@@ -3,6 +3,8 @@
let { data, form } = $props(); let { data, form } = $props();
let showNewTemplate = $state(false); let showNewTemplate = $state(false);
let editingTemplateId = $state<string | null>(null);
let editingItemId = $state<string | null>(null);
function resetForm(form: HTMLFormElement) { function resetForm(form: HTMLFormElement) {
const input = form.querySelector('input[name="text"]') as HTMLInputElement; const input = form.querySelector('input[name="text"]') as HTMLInputElement;
const unit = form.querySelector('input[name="unit"]') as HTMLInputElement; const unit = form.querySelector('input[name="unit"]') as HTMLInputElement;
@@ -59,6 +61,29 @@
<div class="space-y-4"> <div class="space-y-4">
{#each data.templates as template} {#each data.templates as template}
<div class="rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800"> <div class="rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
<!-- Template header -->
{#if editingTemplateId === template.id}
<form method="POST" action="?/renameTemplate" use:enhance={() => {
return async ({ update, result }) => {
await update();
if (result.type === 'success') editingTemplateId = null;
};
}} class="mb-3 flex flex-wrap items-end gap-2">
<input type="hidden" name="templateId" value={template.id} />
<div class="flex-1">
<label for="title-{template.id}" class="mb-1 block text-xs text-gray-500 dark:text-gray-400">Name</label>
<input type="text" id="title-{template.id}" name="title" value={template.title} required
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" />
</div>
<div class="flex-1">
<label for="desc-{template.id}" class="mb-1 block text-xs text-gray-500 dark:text-gray-400">Description</label>
<input type="text" id="desc-{template.id}" name="description" value={template.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" />
</div>
<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={() => (editingTemplateId = 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>
</form>
{:else}
<div class="mb-3 flex items-start justify-between"> <div class="mb-3 flex items-start justify-between">
<div> <div>
<h2 class="font-medium text-gray-900 dark:text-white">{template.title}</h2> <h2 class="font-medium text-gray-900 dark:text-white">{template.title}</h2>
@@ -70,6 +95,11 @@
<span class="rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-600 dark:bg-gray-700 dark:text-gray-400"> <span class="rounded-full bg-gray-100 px-2 py-0.5 text-xs text-gray-600 dark:bg-gray-700 dark:text-gray-400">
{template.items.length} item{template.items.length !== 1 ? 's' : ''} {template.items.length} item{template.items.length !== 1 ? 's' : ''}
</span> </span>
<button type="button" onclick={() => (editingTemplateId = template.id)} class="text-gray-400 hover:text-blue-600 dark:text-gray-500 dark:hover:text-blue-400" title="Edit template">
<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="?/deleteTemplate" use:enhance> <form method="POST" action="?/deleteTemplate" use:enhance>
<input type="hidden" name="templateId" value={template.id} /> <input type="hidden" name="templateId" value={template.id} />
<button type="submit" class="text-gray-400 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-400" title="Delete template"> <button type="submit" class="text-gray-400 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-400" title="Delete template">
@@ -80,18 +110,73 @@
</form> </form>
</div> </div>
</div> </div>
{/if}
<!-- Items --> <!-- Items -->
<div class="space-y-1"> <div class="space-y-1">
{#each template.items as item} {#each template.items as item, idx}
{#if editingItemId === item.id}
<!-- Inline edit item -->
<form method="POST" action="?/editItem" use:enhance={() => {
return async ({ update, result }) => {
await update();
if (result.type === 'success') editingItemId = null;
};
}} class="flex items-center gap-2 rounded-md bg-gray-50 p-2 dark:bg-gray-700/30">
<input type="hidden" name="itemId" value={item.id} />
<input type="text" name="text" value={item.text} required
class="flex-1 rounded-md border border-gray-300 px-2 py-1 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" />
{#if item.itemType === 'input'}
<input type="text" name="unit" value={item.unit ?? ''} placeholder="Unit"
class="w-16 rounded-md border border-gray-300 px-2 py-1 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
{/if}
<button type="submit" class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">Save</button>
<button type="button" onclick={() => (editingItemId = null)} class="text-sm text-gray-500 hover:text-gray-700 dark:text-gray-400">Cancel</button>
</form>
{:else}
<div class="group flex items-center gap-2 rounded-md py-1"> <div class="group flex items-center gap-2 rounded-md py-1">
<span class="text-sm text-gray-400 dark:text-gray-500">{item.sortOrder + 1}.</span> <!-- Move buttons -->
<div class="flex flex-col">
{#if idx > 0}
<form method="POST" action="?/moveItem" use:enhance>
<input type="hidden" name="itemId" value={item.id} />
<input type="hidden" name="templateId" value={template.id} />
<input type="hidden" name="direction" value="up" />
<button type="submit" class="text-gray-300 hover:text-gray-600 dark:text-gray-600 dark:hover:text-gray-400" title="Move up">
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
</svg>
</button>
</form>
{:else}
<div class="h-3 w-3"></div>
{/if}
{#if idx < template.items.length - 1}
<form method="POST" action="?/moveItem" use:enhance>
<input type="hidden" name="itemId" value={item.id} />
<input type="hidden" name="templateId" value={template.id} />
<input type="hidden" name="direction" value="down" />
<button type="submit" class="text-gray-300 hover:text-gray-600 dark:text-gray-600 dark:hover:text-gray-400" title="Move down">
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
</svg>
</button>
</form>
{:else}
<div class="h-3 w-3"></div>
{/if}
</div>
<span class="flex-1 text-sm text-gray-700 dark:text-gray-300">{item.text}</span> <span class="flex-1 text-sm text-gray-700 dark:text-gray-300">{item.text}</span>
{#if item.itemType === 'input'} {#if item.itemType === 'input'}
<span class="rounded bg-purple-100 px-1.5 py-0.5 text-xs text-purple-700 dark:bg-purple-900/40 dark:text-purple-300"> <span class="rounded bg-purple-100 px-1.5 py-0.5 text-xs text-purple-700 dark:bg-purple-900/40 dark:text-purple-300">
input{#if item.unit}: {item.unit}{/if} input{#if item.unit}: {item.unit}{/if}
</span> </span>
{/if} {/if}
<button type="button" onclick={() => (editingItemId = item.id)} class="hidden text-gray-400 hover:text-blue-600 group-hover:block dark:text-gray-500 dark:hover:text-blue-400" 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="?/deleteItem" use:enhance> <form method="POST" action="?/deleteItem" use:enhance>
<input type="hidden" name="itemId" value={item.id} /> <input type="hidden" name="itemId" value={item.id} />
<button type="submit" class="hidden text-gray-400 hover:text-red-500 group-hover:block dark:text-gray-500 dark:hover:text-red-400" title="Remove"> <button type="submit" class="hidden text-gray-400 hover:text-red-500 group-hover:block dark:text-gray-500 dark:hover:text-red-400" title="Remove">
@@ -101,6 +186,7 @@
</button> </button>
</form> </form>
</div> </div>
{/if}
{/each} {/each}
</div> </div>
@@ -351,6 +351,59 @@ export const actions: Actions = {
return { valueSaved: true }; return { valueSaved: true };
}, },
editChecklistItem: async ({ request }) => {
const formData = await request.formData();
const itemId = formData.get('itemId') as string;
const text = (formData.get('text') as string)?.trim();
const unit = (formData.get('unit') as string)?.trim();
if (!text) return fail(400, { error: 'Item text is required' });
await db
.update(checklistItems)
.set({ text, unit: unit || null })
.where(eq(checklistItems.id, itemId));
return { itemEdited: true };
},
moveChecklistItem: async ({ request }) => {
const formData = await request.formData();
const itemId = formData.get('itemId') as string;
const direction = formData.get('direction') as string;
const checklistId = formData.get('checklistId') as string;
const items = await db
.select({ id: checklistItems.id, sortOrder: checklistItems.sortOrder })
.from(checklistItems)
.where(eq(checklistItems.checklistId, checklistId))
.orderBy(checklistItems.sortOrder);
const idx = items.findIndex((i) => i.id === itemId);
if (idx < 0) return fail(400, { error: 'Item not found' });
const swapIdx = direction === 'up' ? idx - 1 : idx + 1;
if (swapIdx < 0 || swapIdx >= items.length) return { moved: true };
const a = items[idx];
const b = items[swapIdx];
await db.update(checklistItems).set({ sortOrder: b.sortOrder }).where(eq(checklistItems.id, a.id));
await db.update(checklistItems).set({ sortOrder: a.sortOrder }).where(eq(checklistItems.id, b.id));
return { moved: true };
},
renameChecklist: async ({ request }) => {
const formData = await request.formData();
const checklistId = formData.get('checklistId') as string;
const title = (formData.get('title') as string)?.trim();
if (!title) return fail(400, { error: 'Title is required' });
await db
.update(deviceChecklists)
.set({ title })
.where(eq(deviceChecklists.id, checklistId));
return { checklistRenamed: true };
},
deleteChecklistItem: async ({ request }) => { deleteChecklistItem: async ({ request }) => {
const formData = await request.formData(); const formData = await request.formData();
const itemId = formData.get('itemId') as string; const itemId = formData.get('itemId') as string;
+85 -31
View File
@@ -11,6 +11,8 @@
let showLogForm = $state(false); let showLogForm = $state(false);
let showNewChecklist = $state(false); let showNewChecklist = $state(false);
let showImportChecklist = $state(false); let showImportChecklist = $state(false);
let editingChecklistId = $state<string | null>(null);
let editingChecklistItemId = $state<string | null>(null);
function resetInput(formEl: HTMLFormElement) { function resetInput(formEl: HTMLFormElement) {
const input = formEl.querySelector('input[name="text"]') as HTMLInputElement; const input = formEl.querySelector('input[name="text"]') as HTMLInputElement;
const unit = formEl.querySelector('input[name="unit"]') as HTMLInputElement; const unit = formEl.querySelector('input[name="unit"]') as HTMLInputElement;
@@ -390,11 +392,33 @@
{#each data.checklists as checklist} {#each data.checklists as checklist}
<div class="rounded-md border border-gray-100 p-3 dark:border-gray-700"> <div class="rounded-md border border-gray-100 p-3 dark:border-gray-700">
<div class="mb-2 flex items-center justify-between"> <div class="mb-2 flex items-center justify-between">
{#if editingChecklistId === checklist.id}
<form method="POST" action="?/renameChecklist" use:enhance={() => {
return async ({ update, result }) => {
await update();
if (result.type === 'success') editingChecklistId = null;
};
}} class="flex flex-1 items-center gap-2">
<input type="hidden" name="checklistId" value={checklist.id} />
<input type="text" name="title" value={checklist.title} required
class="flex-1 rounded-md border border-gray-300 px-2 py-1 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" />
<button type="submit" class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">Save</button>
<button type="button" onclick={() => (editingChecklistId = null)} class="text-sm text-gray-500 dark:text-gray-400">Cancel</button>
</form>
{:else}
<h3 class="text-sm font-medium text-gray-900 dark:text-white">{checklist.title}</h3> <h3 class="text-sm font-medium text-gray-900 dark:text-white">{checklist.title}</h3>
{/if}
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<span class="text-xs text-gray-400 dark:text-gray-500"> <span class="text-xs text-gray-400 dark:text-gray-500">
{checklist.items.filter((i: any) => i.itemType === 'input' ? !!i.value : i.checked).length}/{checklist.items.length} {checklist.items.filter((i: any) => i.itemType === 'input' ? !!i.value : i.checked).length}/{checklist.items.length}
</span> </span>
{#if editingChecklistId !== checklist.id}
<button type="button" onclick={() => (editingChecklistId = checklist.id)} class="text-gray-400 hover:text-blue-600 dark:text-gray-500 dark:hover:text-blue-400" title="Rename">
<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>
{/if}
<form method="POST" action="?/deleteChecklist" use:enhance> <form method="POST" action="?/deleteChecklist" use:enhance>
<input type="hidden" name="checklistId" value={checklist.id} /> <input type="hidden" name="checklistId" value={checklist.id} />
<button type="submit" class="text-gray-400 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-400" title="Delete checklist"> <button type="submit" class="text-gray-400 hover:text-red-500 dark:text-gray-500 dark:hover:text-red-400" title="Delete checklist">
@@ -417,65 +441,95 @@
<!-- Items --> <!-- Items -->
<div class="space-y-1.5"> <div class="space-y-1.5">
{#each checklist.items as item} {#each checklist.items as item, idx}
<div class="group grid grid-cols-[auto_1fr_auto] items-center gap-2"> {#if editingChecklistItemId === item.id}
<!-- Inline edit -->
<form method="POST" action="?/editChecklistItem" use:enhance={() => {
return async ({ update, result }) => {
await update();
if (result.type === 'success') editingChecklistItemId = null;
};
}} class="flex items-center gap-2 rounded-md bg-gray-50 p-2 dark:bg-gray-700/30">
<input type="hidden" name="itemId" value={item.id} />
<input type="text" name="text" value={item.text} required
class="flex-1 rounded-md border border-gray-300 px-2 py-1 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" />
{#if item.itemType === 'input'}
<input type="text" name="unit" value={item.unit ?? ''} placeholder="Unit"
class="w-16 rounded-md border border-gray-300 px-2 py-1 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white" />
{/if}
<button type="submit" class="text-sm text-blue-600 dark:text-blue-400">Save</button>
<button type="button" onclick={() => (editingChecklistItemId = null)} class="text-sm text-gray-500 dark:text-gray-400">Cancel</button>
</form>
{:else}
<div class="group grid grid-cols-[auto_auto_1fr_auto] items-center gap-2">
<!-- Move arrows -->
<div class="flex flex-col">
{#if idx > 0}
<form method="POST" action="?/moveChecklistItem" use:enhance>
<input type="hidden" name="itemId" value={item.id} />
<input type="hidden" name="checklistId" value={checklist.id} />
<input type="hidden" name="direction" value="up" />
<button type="submit" class="text-gray-300 hover:text-gray-600 dark:text-gray-600 dark:hover:text-gray-400" title="Move up">
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" /></svg>
</button>
</form>
{:else}<div class="h-3 w-3"></div>{/if}
{#if idx < checklist.items.length - 1}
<form method="POST" action="?/moveChecklistItem" use:enhance>
<input type="hidden" name="itemId" value={item.id} />
<input type="hidden" name="checklistId" value={checklist.id} />
<input type="hidden" name="direction" value="down" />
<button type="submit" class="text-gray-300 hover:text-gray-600 dark:text-gray-600 dark:hover:text-gray-400" title="Move down">
<svg class="h-3 w-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" /></svg>
</button>
</form>
{:else}<div class="h-3 w-3"></div>{/if}
</div>
{#if item.itemType === 'input'} {#if item.itemType === 'input'}
<!-- Input: filled = completed -->
{@const filled = !!item.value} {@const filled = !!item.value}
<span class="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded border <span class="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded border
{filled {filled ? 'border-green-500 bg-green-500 text-white dark:border-green-400 dark:bg-green-500' : 'border-gray-200 dark:border-gray-600'}
? 'border-green-500 bg-green-500 text-white dark:border-green-400 dark:bg-green-500'
: 'border-gray-200 dark:border-gray-600'}
"> ">
{#if filled} {#if filled}<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>{/if}
<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>
{/if}
</span> </span>
<form method="POST" action="?/saveChecklistValue" use:enhance class="flex flex-1 items-center gap-2"> <form method="POST" action="?/saveChecklistValue" use:enhance class="flex flex-1 items-center gap-2">
<input type="hidden" name="itemId" value={item.id} /> <input type="hidden" name="itemId" value={item.id} />
<span class="truncate text-sm text-gray-700 dark:text-gray-300" title={item.text}>{item.text}</span> <span class="truncate text-sm text-gray-700 dark:text-gray-300" title={item.text}>{item.text}</span>
<div class="ml-auto flex items-center gap-2"> <div class="ml-auto flex items-center gap-2">
<input type="text" name="value" value={item.value ?? ''} <input type="text" name="value" value={item.value ?? ''} placeholder="—"
placeholder="—"
class="w-24 rounded border border-gray-200 px-2 py-0.5 text-sm text-right font-mono 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" /> class="w-24 rounded border border-gray-200 px-2 py-0.5 text-sm text-right font-mono 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" />
{#if item.unit} {#if item.unit}<span class="w-10 flex-shrink-0 text-xs text-gray-400 dark:text-gray-500">{item.unit}</span>{/if}
<span class="w-10 flex-shrink-0 text-xs text-gray-400 dark:text-gray-500">{item.unit}</span>
{/if}
<button type="submit" class="rounded px-1.5 py-0.5 text-xs text-blue-600 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-900/20">Save</button> <button type="submit" class="rounded px-1.5 py-0.5 text-xs text-blue-600 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-900/20">Save</button>
</div> </div>
</form> </form>
{:else} {:else}
<!-- Checkbox -->
<form method="POST" action="?/toggleChecklistItem" use:enhance class="flex items-center"> <form method="POST" action="?/toggleChecklistItem" use:enhance class="flex items-center">
<input type="hidden" name="itemId" value={item.id} /> <input type="hidden" name="itemId" value={item.id} />
<input type="hidden" name="checked" value={String(item.checked)} /> <input type="hidden" name="checked" value={String(item.checked)} />
<button type="submit" class="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded border <button type="submit" class="flex h-5 w-5 flex-shrink-0 items-center justify-center rounded border
{item.checked {item.checked ? 'border-green-500 bg-green-500 text-white dark:border-green-400 dark:bg-green-500' : 'border-gray-300 hover:border-blue-400 dark:border-gray-600 dark:hover:border-blue-500'}
? 'border-green-500 bg-green-500 text-white dark:border-green-400 dark:bg-green-500'
: 'border-gray-300 hover:border-blue-400 dark:border-gray-600 dark:hover:border-blue-500'}
"> ">
{#if item.checked} {#if item.checked}<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>{/if}
<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>
{/if}
</button> </button>
</form> </form>
<span class="text-sm {item.checked ? 'text-gray-400 line-through dark:text-gray-500' : 'text-gray-700 dark:text-gray-300'}"> <span class="text-sm {item.checked ? 'text-gray-400 line-through dark:text-gray-500' : 'text-gray-700 dark:text-gray-300'}">{item.text}</span>
{item.text}
</span>
{/if} {/if}
<!-- Edit + Delete -->
<div class="flex items-center gap-1">
<button type="button" onclick={() => (editingChecklistItemId = item.id)} class="hidden text-gray-400 hover:text-blue-600 group-hover:block dark:text-gray-500 dark:hover:text-blue-400" 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="?/deleteChecklistItem" use:enhance> <form method="POST" action="?/deleteChecklistItem" use:enhance>
<input type="hidden" name="itemId" value={item.id} /> <input type="hidden" name="itemId" value={item.id} />
<button type="submit" class="hidden text-gray-400 hover:text-red-500 group-hover:block dark:text-gray-500 dark:hover:text-red-400" title="Remove"> <button type="submit" class="hidden text-gray-400 hover:text-red-500 group-hover:block dark:text-gray-500 dark:hover:text-red-400" title="Remove">
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"> <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>
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
</svg>
</button> </button>
</form> </form>
</div> </div>
</div>
{/if}
{/each} {/each}
</div> </div>