Add checklist templates with import into devices
- checklist_templates and template_items tables for reusable checklists - /checklists page: create/edit/delete templates with ordered items - "Import" button on device detail imports a template as a new checklist with all items copied (unchecked) - Sidebar nav item for Checklists between Locations and Gallery Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,8 @@ import {
|
||||
deviceLog,
|
||||
deviceChecklists,
|
||||
checklistItems,
|
||||
checklistTemplates,
|
||||
templateItems,
|
||||
locations
|
||||
} from '$lib/server/db/schema.js';
|
||||
import { eq, desc, sql } from 'drizzle-orm';
|
||||
@@ -128,6 +130,12 @@ export const load: PageServerLoad = async ({ params }) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Available templates for import
|
||||
const templates = await db
|
||||
.select({ id: checklistTemplates.id, title: checklistTemplates.title })
|
||||
.from(checklistTemplates)
|
||||
.orderBy(checklistTemplates.title);
|
||||
|
||||
return {
|
||||
device,
|
||||
computerDetails: compDetails,
|
||||
@@ -139,7 +147,8 @@ export const load: PageServerLoad = async ({ params }) => {
|
||||
checklists: checklists.map((c) => ({
|
||||
...c,
|
||||
items: itemsByChecklist[c.id] ?? []
|
||||
}))
|
||||
})),
|
||||
templates
|
||||
};
|
||||
};
|
||||
|
||||
@@ -314,5 +323,47 @@ export const actions: Actions = {
|
||||
const itemId = formData.get('itemId') as string;
|
||||
await db.delete(checklistItems).where(eq(checklistItems.id, itemId));
|
||||
return { itemDeleted: true };
|
||||
},
|
||||
|
||||
importChecklist: async ({ request, params }) => {
|
||||
const formData = await request.formData();
|
||||
const templateId = formData.get('templateId') as string;
|
||||
if (!templateId) return fail(400, { error: 'Select a template' });
|
||||
|
||||
// Get template
|
||||
const [template] = await db
|
||||
.select()
|
||||
.from(checklistTemplates)
|
||||
.where(eq(checklistTemplates.id, templateId));
|
||||
if (!template) return fail(400, { error: 'Template not found' });
|
||||
|
||||
// Get template items
|
||||
const items = await db
|
||||
.select()
|
||||
.from(templateItems)
|
||||
.where(eq(templateItems.templateId, templateId))
|
||||
.orderBy(templateItems.sortOrder);
|
||||
|
||||
// Create device checklist
|
||||
const [checklist] = await db
|
||||
.insert(deviceChecklists)
|
||||
.values({
|
||||
deviceId: params.id,
|
||||
title: template.title
|
||||
})
|
||||
.returning({ id: deviceChecklists.id });
|
||||
|
||||
// Copy items
|
||||
if (items.length > 0 && checklist) {
|
||||
await db.insert(checklistItems).values(
|
||||
items.map((item) => ({
|
||||
checklistId: checklist.id,
|
||||
text: item.text,
|
||||
sortOrder: item.sortOrder
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
return { checklistImported: true };
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
let showDeleteConfirm = $state(false);
|
||||
let showLogForm = $state(false);
|
||||
let showNewChecklist = $state(false);
|
||||
let showImportChecklist = $state(false);
|
||||
let newItemText: Record<string, string> = $state({});
|
||||
</script>
|
||||
|
||||
@@ -334,12 +335,33 @@
|
||||
<div class="rounded-lg border border-gray-200 bg-white p-5 dark:border-gray-700 dark:bg-gray-800">
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h2 class="text-sm font-semibold uppercase tracking-wider text-gray-400 dark:text-gray-500">Checklists</h2>
|
||||
<button onclick={() => (showNewChecklist = !showNewChecklist)}
|
||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">
|
||||
{showNewChecklist ? 'Cancel' : 'New Checklist'}
|
||||
</button>
|
||||
<div class="flex gap-2">
|
||||
{#if data.templates.length > 0}
|
||||
<button onclick={() => { showImportChecklist = !showImportChecklist; showNewChecklist = false; }}
|
||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">
|
||||
{showImportChecklist ? 'Cancel' : 'Import'}
|
||||
</button>
|
||||
{/if}
|
||||
<button onclick={() => { showNewChecklist = !showNewChecklist; showImportChecklist = false; }}
|
||||
class="text-sm text-blue-600 hover:text-blue-700 dark:text-blue-400">
|
||||
{showNewChecklist ? 'Cancel' : 'New'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#if showImportChecklist}
|
||||
<form method="POST" action="?/importChecklist" use:enhance class="mb-4 flex gap-2">
|
||||
<select name="templateId" required
|
||||
class="flex-1 rounded-md border border-gray-300 px-3 py-1.5 text-sm dark:border-gray-600 dark:bg-gray-700 dark:text-white">
|
||||
<option value="">Select template...</option>
|
||||
{#each data.templates as t}
|
||||
<option value={t.id}>{t.title}</option>
|
||||
{/each}
|
||||
</select>
|
||||
<button type="submit" class="rounded-md bg-blue-600 px-3 py-1.5 text-sm font-medium text-white hover:bg-blue-700">Import</button>
|
||||
</form>
|
||||
{/if}
|
||||
|
||||
{#if showNewChecklist}
|
||||
<form method="POST" action="?/createChecklist" use:enhance class="mb-4 flex gap-2">
|
||||
<input type="text" name="title" required placeholder="Checklist name..."
|
||||
|
||||
Reference in New Issue
Block a user