diff --git a/src/lib/components/layout/Sidebar.svelte b/src/lib/components/layout/Sidebar.svelte
index 0120c7b..11e850d 100644
--- a/src/lib/components/layout/Sidebar.svelte
+++ b/src/lib/components/layout/Sidebar.svelte
@@ -48,6 +48,11 @@
label: 'Checklists',
icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2m-6 9l2 2 4-4'
},
+ {
+ href: '/batch-print',
+ label: 'Batch Print',
+ icon: 'M17 17h2a2 2 0 002-2v-4a2 2 0 00-2-2H5a2 2 0 00-2 2v4a2 2 0 002 2h2m2 4h6a2 2 0 002-2v-4a2 2 0 00-2-2H9a2 2 0 00-2 2v4a2 2 0 002 2zm8-12V5a2 2 0 00-2-2H9a2 2 0 00-2 2v4h10z'
+ },
{
href: '/gallery',
label: 'Gallery',
diff --git a/src/routes/(app)/batch-print/+page.server.ts b/src/routes/(app)/batch-print/+page.server.ts
new file mode 100644
index 0000000..4710a36
--- /dev/null
+++ b/src/routes/(app)/batch-print/+page.server.ts
@@ -0,0 +1,21 @@
+import type { PageServerLoad } from './$types';
+import { db } from '$lib/server/db/index.js';
+import { devices } from '$lib/server/db/schema.js';
+import { eq } from 'drizzle-orm';
+
+export const load: PageServerLoad = async () => {
+ const deviceList = await db
+ .select({
+ id: devices.id,
+ title: devices.title,
+ brand: devices.brand,
+ model: devices.model,
+ serialNumber: devices.serialNumber,
+ category: devices.category
+ })
+ .from(devices)
+ .where(eq(devices.disabled, false))
+ .orderBy(devices.title);
+
+ return { devices: deviceList };
+};
diff --git a/src/routes/(app)/batch-print/+page.svelte b/src/routes/(app)/batch-print/+page.svelte
new file mode 100644
index 0000000..cd4d2d0
--- /dev/null
+++ b/src/routes/(app)/batch-print/+page.svelte
@@ -0,0 +1,111 @@
+
+
+
+ Batch Print - My Collection
+
+
+
+
+
Batch Print Labels
+
+
+
+
+
+
+
+ {filtered.length} device{filtered.length !== 1 ? 's' : ''}
+
+
+
+
+
diff --git a/src/routes/(print)/print/batch/+page.server.ts b/src/routes/(print)/print/batch/+page.server.ts
new file mode 100644
index 0000000..e953869
--- /dev/null
+++ b/src/routes/(print)/print/batch/+page.server.ts
@@ -0,0 +1,37 @@
+import type { PageServerLoad } from './$types';
+import { db } from '$lib/server/db/index.js';
+import { devices } from '$lib/server/db/schema.js';
+import { sql } from 'drizzle-orm';
+import { error } from '@sveltejs/kit';
+import { generateQrSvg } from '$lib/server/qr.js';
+
+export const load: PageServerLoad = async ({ url }) => {
+ const idsParam = url.searchParams.get('ids');
+ if (!idsParam) error(400, 'No devices selected');
+
+ const ids = idsParam.split(',').filter(Boolean);
+ if (ids.length === 0) error(400, 'No devices selected');
+
+ const deviceList = await db
+ .select({
+ id: devices.id,
+ title: devices.title,
+ brand: devices.brand,
+ model: devices.model,
+ serialNumber: devices.serialNumber,
+ category: devices.category
+ })
+ .from(devices)
+ .where(sql`${devices.id} IN ${ids}`);
+
+ // Generate QR codes for each
+ const labels = await Promise.all(
+ deviceList.map(async (device) => {
+ const shortId = device.id.slice(0, 8).toUpperCase();
+ const qrSvg = await generateQrSvg(shortId);
+ return { ...device, qrSvg, shortId };
+ })
+ );
+
+ return { labels };
+};
diff --git a/src/routes/(print)/print/batch/+page.svelte b/src/routes/(print)/print/batch/+page.svelte
new file mode 100644
index 0000000..52a91b8
--- /dev/null
+++ b/src/routes/(print)/print/batch/+page.svelte
@@ -0,0 +1,80 @@
+
+
+
+ Batch Print - {data.labels.length} Labels
+
+
+
+
+
← Back
+
+
+
+ Setup: Brother QL-820NWB, paper "29mm x 62mm" (DK-11209), margins minimum.
+
+
+
+{#each data.labels as label}
+
+
+
+ {@html label.qrSvg}
+
+
+
+ {label.title}
+
+ {#if label.brand || label.model}
+
+ {[label.brand, label.model].filter(Boolean).join(' ')}
+
+ {/if}
+ {#if label.serialNumber}
+
+ S/N: {label.serialNumber}
+
+ {/if}
+
+ {label.shortId}
+
+
+
+
+{/each}
+
+