Add text input type to checklist items for measurements

Checklist items can now be either 'checkbox' (tick/untick) or 'input'
(text value with optional unit). Useful for recording measurements
like RPM, wow & flutter, torque, voltage, dB levels, etc.

- itemType and unit fields on template_items and checklist_items
- Template page shows type selector and unit field when adding items
- Device checklist renders input items as labeled text fields with unit
- Save button persists measured values
- Import copies item types and units from templates

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-07 10:57:19 +07:00
parent cceba73785
commit 97f97f5571
5 changed files with 117 additions and 35 deletions
+20 -1
View File
@@ -115,7 +115,7 @@ export const load: PageServerLoad = async ({ params }) => {
const checklistIds = checklists.map((c) => c.id);
let itemsByChecklist: Record<string, typeof allItems> = {};
let allItems: Array<{ id: string; checklistId: string; text: string; checked: boolean; sortOrder: number; createdAt: Date }> = [];
let allItems: Array<{ id: string; checklistId: string; text: string; itemType: string; unit: string | null; checked: boolean; value: string | null; sortOrder: number; createdAt: Date }> = [];
if (checklistIds.length > 0) {
allItems = await db
@@ -284,6 +284,8 @@ export const actions: Actions = {
const formData = await request.formData();
const checklistId = formData.get('checklistId') as string;
const text = (formData.get('text') as string)?.trim();
const itemType = (formData.get('itemType') as string) || 'checkbox';
const unit = (formData.get('unit') as string)?.trim();
if (!text) return fail(400, { error: 'Item text is required' });
// Get next sort order
@@ -299,6 +301,8 @@ export const actions: Actions = {
await db.insert(checklistItems).values({
checklistId,
text,
itemType,
unit: unit || null,
sortOrder: nextOrder
});
@@ -318,6 +322,19 @@ export const actions: Actions = {
return { itemToggled: true };
},
saveChecklistValue: async ({ request }) => {
const formData = await request.formData();
const itemId = formData.get('itemId') as string;
const value = formData.get('value') as string;
await db
.update(checklistItems)
.set({ value: value || null })
.where(eq(checklistItems.id, itemId));
return { valueSaved: true };
},
deleteChecklistItem: async ({ request }) => {
const formData = await request.formData();
const itemId = formData.get('itemId') as string;
@@ -359,6 +376,8 @@ export const actions: Actions = {
items.map((item) => ({
checklistId: checklist.id,
text: item.text,
itemType: item.itemType,
unit: item.unit,
sortOrder: item.sortOrder
}))
);
+63 -28
View File
@@ -13,7 +13,9 @@
let showImportChecklist = $state(false);
function resetInput(formEl: HTMLFormElement) {
const input = formEl.querySelector('input[name="text"]') as HTMLInputElement;
const unit = formEl.querySelector('input[name="unit"]') as HTMLInputElement;
if (input) input.value = '';
if (unit) unit.value = '';
}
</script>
@@ -405,36 +407,62 @@
{/if}
<!-- Items -->
<div class="space-y-1">
<div class="space-y-1.5">
{#each checklist.items as item}
<div class="group flex items-center gap-2">
<form method="POST" action="?/toggleChecklistItem" use:enhance class="flex items-center">
<input type="hidden" name="itemId" value={item.id} />
<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
{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'}
">
{#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 item.itemType === 'input'}
<!-- Input type item -->
<div class="group flex items-center gap-2">
<span class="flex-shrink-0 text-sm text-gray-700 dark:text-gray-300">{item.text}</span>
<form method="POST" action="?/saveChecklistValue" use:enhance class="flex flex-1 items-center gap-1">
<input type="hidden" name="itemId" value={item.id} />
<input type="text" name="value" value={item.value ?? ''}
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" />
{#if item.unit}
<span class="text-xs text-gray-400 dark:text-gray-500">{item.unit}</span>
{/if}
</button>
</form>
<span class="flex-1 text-sm {item.checked ? 'text-gray-400 line-through dark:text-gray-500' : 'text-gray-700 dark:text-gray-300'}">
{item.text}
</span>
<form method="POST" action="?/deleteChecklistItem" use:enhance>
<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">
<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>
<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>
</form>
<form method="POST" action="?/deleteChecklistItem" use:enhance>
<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">
<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>
{:else}
<!-- Checkbox type item -->
<div class="group flex items-center gap-2">
<form method="POST" action="?/toggleChecklistItem" use:enhance class="flex items-center">
<input type="hidden" name="itemId" value={item.id} />
<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
{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'}
">
{#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}
</button>
</form>
<span class="flex-1 text-sm {item.checked ? 'text-gray-400 line-through dark:text-gray-500' : 'text-gray-700 dark:text-gray-300'}">
{item.text}
</span>
<form method="POST" action="?/deleteChecklistItem" use:enhance>
<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">
<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>
{/if}
{/each}
</div>
@@ -454,6 +482,13 @@
<input type="text" name="text" required
placeholder="Add item..."
class="flex-1 rounded-md border border-gray-200 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 dark:placeholder-gray-400" />
<select name="itemType"
class="rounded-md border border-gray-200 px-1 py-1 text-xs dark:border-gray-600 dark:bg-gray-700 dark:text-white">
<option value="checkbox">Check</option>
<option value="input">Input</option>
</select>
<input type="text" name="unit" placeholder="Unit"
class="w-16 rounded-md border border-gray-200 px-2 py-1 text-xs 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" />
<button type="submit" class="rounded-md px-2 py-1 text-sm text-blue-600 hover:bg-blue-50 dark:text-blue-400 dark:hover:bg-blue-900/20">
<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="M12 4v16m8-8H4" />