Add collapsible checklists on device detail, auto-collapse completed
Deploy to LXC / deploy (push) Successful in 19s

- Click chevron or checklist title to expand/collapse
- Completed checklists (100%) auto-collapse on page load
- Completed title shown in green with checkmark icon
- Progress bar and count always visible when collapsed
- Items and add-item form hidden when collapsed

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 16:37:39 +07:00
parent 36f4d4b8d5
commit 99371648be
+51 -13
View File
@@ -15,6 +15,26 @@
let showImportChecklist = $state(false);
let editingChecklistId = $state<string | null>(null);
let editingChecklistItemId = $state<string | null>(null);
let collapsedChecklists = $state<Set<string>>(new Set());
// Auto-collapse completed checklists on load
$effect(() => {
const newCollapsed = new Set<string>();
for (const cl of data.checklists) {
if (cl.items.length > 0) {
const allDone = cl.items.every((i: any) => i.itemType === 'input' ? !!i.value : i.checked);
if (allDone) newCollapsed.add(cl.id);
}
}
collapsedChecklists = newCollapsed;
});
function toggleChecklist(id: string) {
const next = new Set(collapsedChecklists);
if (next.has(id)) next.delete(id);
else next.add(id);
collapsedChecklists = next;
}
function resetInput(formEl: HTMLFormElement) {
const input = formEl.querySelector('input[name="text"]') as HTMLInputElement;
const unit = formEl.querySelector('input[name="unit"]') as HTMLInputElement;
@@ -383,8 +403,30 @@
{:else}
<div class="space-y-4">
{#each data.checklists as checklist}
{@const clCompleted = checklist.items.filter((i: any) => i.itemType === 'input' ? !!i.value : i.checked).length}
{@const clTotal = checklist.items.length}
{@const clPct = clTotal > 0 ? Math.round((clCompleted / clTotal) * 100) : 0}
{@const isCollapsed = collapsedChecklists.has(checklist.id)}
<div class="rounded-md border border-gray-100 p-3 dark:border-gray-700">
<div class="mb-2 flex items-center justify-between">
<!-- Header (always visible, clickable to toggle) -->
<div class="flex items-center justify-between">
<button type="button" onclick={() => toggleChecklist(checklist.id)}
class="flex flex-1 items-center gap-2 text-left">
<svg class="h-4 w-4 text-gray-400 transition-transform {isCollapsed ? '' : 'rotate-90'}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
</svg>
{#if editingChecklistId === checklist.id}
<!-- stop propagation handled by form click -->
{:else}
<h3 class="text-sm font-medium {clPct === 100 ? 'text-green-600 dark:text-green-400' : 'text-gray-900 dark:text-white'}">{checklist.title}</h3>
{#if clPct === 100}
<svg class="h-4 w-4 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7" />
</svg>
{/if}
{/if}
</button>
{#if editingChecklistId === checklist.id}
<form method="POST" action="?/renameChecklist" use:enhance={() => {
return async ({ update, result }) => {
@@ -398,13 +440,9 @@
<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>
{/if}
<div class="flex items-center gap-2">
<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}
</span>
<span class="text-xs text-gray-400 dark:text-gray-500">{clCompleted}/{clTotal}</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">
@@ -423,17 +461,16 @@
</div>
</div>
<!-- Progress bar -->
{#if checklist.items.length > 0}
{@const completed = checklist.items.filter((i: any) => i.itemType === 'input' ? !!i.value : i.checked).length}
{@const pct = Math.round((completed / checklist.items.length) * 100)}
<div class="mb-2 h-1.5 w-full rounded-full bg-gray-100 dark:bg-gray-700">
<div class="h-1.5 rounded-full transition-all {pct === 100 ? 'bg-green-500' : 'bg-blue-500'}" style="width: {pct}%"></div>
<!-- Progress bar (always visible) -->
{#if clTotal > 0}
<div class="mt-2 h-1.5 w-full rounded-full bg-gray-100 dark:bg-gray-700">
<div class="h-1.5 rounded-full transition-all {clPct === 100 ? 'bg-green-500' : 'bg-blue-500'}" style="width: {clPct}%"></div>
</div>
{/if}
{#if !isCollapsed}
<!-- Items -->
<div class="space-y-1.5">
<div class="mt-3 space-y-1.5">
{#each checklist.items as item, idx}
{#if editingChecklistItemId === item.id}
<!-- Inline edit -->
@@ -555,6 +592,7 @@
</svg>
</button>
</form>
{/if}
</div>
{/each}
</div>