deploy: add single-barcode printing to Code Lab
This commit is contained in:
parent
13b3de661b
commit
30bb21dcc4
@ -145,3 +145,101 @@
|
||||
font-size: 5.8px;
|
||||
}
|
||||
}
|
||||
|
||||
.code-card-heading-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 9px;
|
||||
}
|
||||
|
||||
.code-print-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 5px;
|
||||
min-height: 30px;
|
||||
padding: 0 9px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 5px;
|
||||
color: var(--ink);
|
||||
background: #fff;
|
||||
font-size: 9px;
|
||||
font-weight: 900;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.code-print-button:hover {
|
||||
border-color: #c9a72d;
|
||||
background: #fff8d7;
|
||||
}
|
||||
|
||||
.barcode-print-sheet {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media print {
|
||||
@page {
|
||||
margin: 12mm;
|
||||
}
|
||||
|
||||
body * {
|
||||
visibility: hidden !important;
|
||||
}
|
||||
|
||||
.barcode-print-sheet,
|
||||
.barcode-print-sheet * {
|
||||
visibility: visible !important;
|
||||
}
|
||||
|
||||
.barcode-print-sheet {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
display: grid !important;
|
||||
place-items: center;
|
||||
padding: 0;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.barcode-print-label {
|
||||
display: grid;
|
||||
width: min(170mm, 100%);
|
||||
place-items: center;
|
||||
gap: 8mm;
|
||||
color: #000;
|
||||
background: #fff;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.barcode-print-label strong {
|
||||
font: 700 14pt/1.2 Arial, Helvetica, sans-serif;
|
||||
}
|
||||
|
||||
.barcode-print-label canvas {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.barcode-print-label code {
|
||||
max-width: 100%;
|
||||
overflow-wrap: anywhere;
|
||||
color: #000;
|
||||
font: 11pt/1.3 "Courier New", monospace;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 760px) {
|
||||
.code-card-heading {
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.code-card-heading-actions {
|
||||
align-items: flex-end;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.code-print-button {
|
||||
min-height: 29px;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import bwipjs from 'bwip-js'
|
||||
import { RefreshCw } from 'lucide-react'
|
||||
import { Printer, RefreshCw } from 'lucide-react'
|
||||
import { useEffect, useRef, useState } from 'react'
|
||||
|
||||
import { appendGtinCheckDigit, randomDigits } from '../../lib/barcodes'
|
||||
@ -65,23 +65,27 @@ function makeEanFixtures(): EanLabelFixture[] {
|
||||
}))
|
||||
}
|
||||
|
||||
function CodeCard({ fixture }: { fixture: CodeFixture }) {
|
||||
function drawBarcode(canvas: HTMLCanvasElement, fixture: CodeFixture, print = false) {
|
||||
bwipjs.toCanvas(canvas, {
|
||||
bcid: fixture.bcid,
|
||||
text: fixture.value,
|
||||
scale: print ? 5 : 3,
|
||||
height: fixture.bcid === 'qrcode' ? (print ? 34 : 24) : (print ? 20 : 13),
|
||||
includetext: fixture.bcid !== 'qrcode',
|
||||
textxalign: 'center',
|
||||
backgroundcolor: 'FFFFFF',
|
||||
paddingwidth: print ? 12 : 8,
|
||||
paddingheight: print ? 12 : 8,
|
||||
})
|
||||
}
|
||||
|
||||
function CodeCard({ fixture, onPrint }: { fixture: CodeFixture; onPrint: (fixture: CodeFixture) => void }) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current) return
|
||||
try {
|
||||
bwipjs.toCanvas(canvasRef.current, {
|
||||
bcid: fixture.bcid,
|
||||
text: fixture.value,
|
||||
scale: 3,
|
||||
height: fixture.bcid === 'qrcode' ? 24 : 13,
|
||||
includetext: fixture.bcid !== 'qrcode',
|
||||
textxalign: 'center',
|
||||
backgroundcolor: 'FFFFFF',
|
||||
paddingwidth: 8,
|
||||
paddingheight: 8,
|
||||
})
|
||||
drawBarcode(canvasRef.current, fixture)
|
||||
} catch {
|
||||
// A failed fixture remains labeled so generator errors are visible during testing.
|
||||
}
|
||||
@ -89,13 +93,44 @@ function CodeCard({ fixture }: { fixture: CodeFixture }) {
|
||||
|
||||
return (
|
||||
<article className="code-card">
|
||||
<div className="code-card-heading"><strong>{fixture.name}</strong><span>VALID SAMPLE</span></div>
|
||||
<div className="code-card-heading">
|
||||
<strong>{fixture.name}</strong>
|
||||
<div className="code-card-heading-actions">
|
||||
<span>VALID SAMPLE</span>
|
||||
<button className="code-print-button" type="button" onClick={() => onPrint(fixture)} aria-label={`Print one ${fixture.name} barcode`}>
|
||||
<Printer size={14} /> Print one
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="barcode-canvas"><canvas ref={canvasRef} /></div>
|
||||
<code>{fixture.value}</code>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
|
||||
function PrintableBarcode({ fixture }: { fixture: CodeFixture }) {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current) return
|
||||
try {
|
||||
drawBarcode(canvasRef.current, fixture, true)
|
||||
} catch {
|
||||
// Keep the value visible if rendering fails so the print is diagnosable.
|
||||
}
|
||||
}, [fixture])
|
||||
|
||||
return (
|
||||
<div className="barcode-print-sheet" aria-hidden="true">
|
||||
<div className="barcode-print-label">
|
||||
<strong>{fixture.name}</strong>
|
||||
<canvas ref={canvasRef} />
|
||||
<code>{fixture.value}</code>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function EanLabelCard({ fixture }: { fixture: EanLabelFixture }) {
|
||||
return (
|
||||
<article className="ean-lab-carton">
|
||||
@ -126,12 +161,35 @@ export function CodeLab() {
|
||||
const [tab, setTab] = useState<LabTab>('barcodes')
|
||||
const [fixtures, setFixtures] = useState(makeFixtures)
|
||||
const [eanFixtures, setEanFixtures] = useState(makeEanFixtures)
|
||||
const [printFixture, setPrintFixture] = useState<CodeFixture | null>(null)
|
||||
|
||||
const regenerate = () => {
|
||||
if (tab === 'ean') setEanFixtures(makeEanFixtures())
|
||||
else setFixtures(makeFixtures())
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (!printFixture) return
|
||||
|
||||
let cancelled = false
|
||||
let frameTwo = 0
|
||||
const clearPrintFixture = () => setPrintFixture(null)
|
||||
window.addEventListener('afterprint', clearPrintFixture, { once: true })
|
||||
|
||||
const frameOne = window.requestAnimationFrame(() => {
|
||||
frameTwo = window.requestAnimationFrame(() => {
|
||||
if (!cancelled) window.print()
|
||||
})
|
||||
})
|
||||
|
||||
return () => {
|
||||
cancelled = true
|
||||
window.cancelAnimationFrame(frameOne)
|
||||
if (frameTwo) window.cancelAnimationFrame(frameTwo)
|
||||
window.removeEventListener('afterprint', clearPrintFixture)
|
||||
}
|
||||
}, [printFixture])
|
||||
|
||||
return (
|
||||
<section className="workspace-view code-lab" aria-labelledby="lab-title">
|
||||
<header className="view-heading">
|
||||
@ -153,10 +211,11 @@ export function CodeLab() {
|
||||
</div>
|
||||
|
||||
{tab === 'barcodes' ? (
|
||||
<div className="code-grid">{fixtures.map((fixture) => <CodeCard fixture={fixture} key={fixture.id} />)}</div>
|
||||
<div className="code-grid">{fixtures.map((fixture) => <CodeCard fixture={fixture} key={fixture.id} onPrint={setPrintFixture} />)}</div>
|
||||
) : (
|
||||
<div className="ean-lab-grid">{eanFixtures.map((fixture) => <EanLabelCard fixture={fixture} key={fixture.id} />)}</div>
|
||||
)}
|
||||
{printFixture && <PrintableBarcode fixture={printFixture} />}
|
||||
</section>
|
||||
)
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user