From 45d93738d734fdf0af59bf5006e72429a22205c2 Mon Sep 17 00:00:00 2001 From: grabowski Date: Tue, 7 Apr 2026 11:26:53 +0700 Subject: [PATCH] Add DK-22210 label printing with barcode and QR code - Print page formatted for Brother DK-22210 (29mm continuous tape) - Each label has: title, brand/model, serial, QR code, Code 128 barcode - CSS @page sized to 29mm width with minimal margins - Print button opens popup that auto-triggers print dialog - Copies selector to print multiple labels at once - Barcode encodes short ID (first 8 chars) scannable by the lookup endpoint - Available on both device and component detail pages Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 10 ++ package.json | 1 + src/lib/server/barcode.ts | 17 +++ src/routes/(app)/components/[id]/+page.svelte | 6 +- .../components/[id]/print/+page.server.ts | 31 +++++ .../(app)/components/[id]/print/+page.svelte | 105 +++++++++++++++++ src/routes/(app)/devices/[id]/+page.svelte | 6 +- .../(app)/devices/[id]/print/+page.server.ts | 31 +++++ .../(app)/devices/[id]/print/+page.svelte | 109 ++++++++++++++++++ 9 files changed, 314 insertions(+), 2 deletions(-) create mode 100644 src/lib/server/barcode.ts create mode 100644 src/routes/(app)/components/[id]/print/+page.server.ts create mode 100644 src/routes/(app)/components/[id]/print/+page.svelte create mode 100644 src/routes/(app)/devices/[id]/print/+page.server.ts create mode 100644 src/routes/(app)/devices/[id]/print/+page.svelte diff --git a/package-lock.json b/package-lock.json index eac8ac5..0696615 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", + "bwip-js": "^4.9.0", "date-fns": "^4.1.0", "dotenv": "^17.4.1", "drizzle-orm": "^0.38.4", @@ -2649,6 +2650,15 @@ "dev": true, "license": "MIT" }, + "node_modules/bwip-js": { + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/bwip-js/-/bwip-js-4.9.0.tgz", + "integrity": "sha512-U3aWIxR/Px4m3GPd0opQ5GQJq/G8Cj0cr5z5hrcvy/SAApPnfkLqBqjRuB3GiEAasEQup3m7k/MDM/uiS9te8Q==", + "license": "MIT", + "bin": { + "bwip-js": "bin/bwip-js.js" + } + }, "node_modules/camelcase": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", diff --git a/package.json b/package.json index f9b08ff..04fea00 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@node-rs/argon2": "^2.0.2", "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", + "bwip-js": "^4.9.0", "date-fns": "^4.1.0", "dotenv": "^17.4.1", "drizzle-orm": "^0.38.4", diff --git a/src/lib/server/barcode.ts b/src/lib/server/barcode.ts new file mode 100644 index 0000000..8e2b275 --- /dev/null +++ b/src/lib/server/barcode.ts @@ -0,0 +1,17 @@ +// @ts-expect-error bwip-js types not resolved by bundler moduleResolution +import bwipjs from 'bwip-js'; + +export async function generateBarcodeSvg(text: string): Promise { + const buf = await bwipjs.toBuffer({ + bcid: 'code128', + text, + scale: 3, + height: 8, + includetext: true, + textsize: 8, + textxalign: 'center' + }); + + // Return as base64 data URL for embedding in HTML + return `data:image/png;base64,${buf.toString('base64')}`; +} diff --git a/src/routes/(app)/components/[id]/+page.svelte b/src/routes/(app)/components/[id]/+page.svelte index 9154c79..beb95f7 100644 --- a/src/routes/(app)/components/[id]/+page.svelte +++ b/src/routes/(app)/components/[id]/+page.svelte @@ -35,8 +35,12 @@ - QR Label + Label + {#if c.currentDeviceId} diff --git a/src/routes/(app)/components/[id]/print/+page.server.ts b/src/routes/(app)/components/[id]/print/+page.server.ts new file mode 100644 index 0000000..7104f7d --- /dev/null +++ b/src/routes/(app)/components/[id]/print/+page.server.ts @@ -0,0 +1,31 @@ +import type { PageServerLoad } from './$types'; +import { db } from '$lib/server/db/index.js'; +import { components } from '$lib/server/db/schema.js'; +import { eq } from 'drizzle-orm'; +import { error } from '@sveltejs/kit'; +import { generateQrSvg } from '$lib/server/qr.js'; +import { generateBarcodeSvg } from '$lib/server/barcode.js'; +import { env } from '$env/dynamic/private'; + +export const load: PageServerLoad = async ({ params }) => { + const [component] = await db + .select({ + id: components.id, + title: components.title, + componentType: components.componentType, + brand: components.brand, + partNumber: components.partNumber, + serialNumber: components.serialNumber + }) + .from(components) + .where(eq(components.id, params.id)); + + if (!component) error(404, 'Component not found'); + + const url = `${env.BASE_URL ?? 'http://localhost:5173'}/components/${params.id}`; + const qrSvg = await generateQrSvg(url); + const shortId = component.id.slice(0, 8).toUpperCase(); + const barcodeDataUrl = await generateBarcodeSvg(shortId); + + return { component, qrSvg, barcodeDataUrl, shortId }; +}; diff --git a/src/routes/(app)/components/[id]/print/+page.svelte b/src/routes/(app)/components/[id]/print/+page.svelte new file mode 100644 index 0000000..397ce32 --- /dev/null +++ b/src/routes/(app)/components/[id]/print/+page.svelte @@ -0,0 +1,105 @@ + + + + Print Label - {data.component.title} + + + +
+
+ ← Back +
+ + +
+
+ +
+ Printer setup: Select your Brother printer, paper size "DK-22210 (29mm)" or "29mm" roll. Set margins to minimum. +
+ +

Preview ({copies} label{copies > 1 ? 's' : ''}):

+
+ +{#each Array(copies) as _, i} + +{/each} diff --git a/src/routes/(app)/devices/[id]/+page.svelte b/src/routes/(app)/devices/[id]/+page.svelte index 85d33fe..c6eb46f 100644 --- a/src/routes/(app)/devices/[id]/+page.svelte +++ b/src/routes/(app)/devices/[id]/+page.svelte @@ -60,8 +60,12 @@ - QR Label + Label + Install Component diff --git a/src/routes/(app)/devices/[id]/print/+page.server.ts b/src/routes/(app)/devices/[id]/print/+page.server.ts new file mode 100644 index 0000000..d00efa2 --- /dev/null +++ b/src/routes/(app)/devices/[id]/print/+page.server.ts @@ -0,0 +1,31 @@ +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'; +import { error } from '@sveltejs/kit'; +import { generateQrSvg } from '$lib/server/qr.js'; +import { generateBarcodeSvg } from '$lib/server/barcode.js'; +import { env } from '$env/dynamic/private'; + +export const load: PageServerLoad = async ({ params }) => { + const [device] = 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.id, params.id)); + + if (!device) error(404, 'Device not found'); + + const url = `${env.BASE_URL ?? 'http://localhost:5173'}/devices/${params.id}`; + const qrSvg = await generateQrSvg(url); + const shortId = device.id.slice(0, 8).toUpperCase(); + const barcodeDataUrl = await generateBarcodeSvg(shortId); + + return { device, qrSvg, barcodeDataUrl, shortId }; +}; diff --git a/src/routes/(app)/devices/[id]/print/+page.svelte b/src/routes/(app)/devices/[id]/print/+page.svelte new file mode 100644 index 0000000..043dac0 --- /dev/null +++ b/src/routes/(app)/devices/[id]/print/+page.svelte @@ -0,0 +1,109 @@ + + + + Print Label - {data.device.title} + + + + +
+
+ ← Back +
+ + +
+
+ +
+ Printer setup: Select your Brother printer, paper size "DK-22210 (29mm)" or "29mm" roll. Set margins to minimum. +
+ +

Preview ({copies} label{copies > 1 ? 's' : ''}):

+
+ + +{#each Array(copies) as _, i} + +{/each}