Print a single label at a chosen ULINE sheet cell
All checks were successful
CI / backend (push) Successful in 1m24s
CI / naming (push) Successful in 1s
CI / frontend (push) Successful in 7s

The single-label page gains an Output toggle (standalone vs place on a
ULINE 6-up sheet) with a 2x3 cell picker, so one label can be printed
into the correct physical position on a partially-used sheet - the
single-label equivalent of the batch page start-cell offset. Batch page
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-07-12 13:09:55 -04:00
parent cced6fe96e
commit 68b6949f1c
2 changed files with 165 additions and 3 deletions

View File

@@ -33,13 +33,36 @@
<p v-if="encodes === 'location' && !asset.locationcode" class="control-note">
This tool has no location; the label falls back to the asset page.
</p>
<div class="control-row">
<label>Output
<select v-model="outputMode">
<option value="single">Single label (standalone)</option>
<option value="uline-sheet">Place on ULINE 6-up sheet</option>
</select>
</label>
</div>
<!-- Cell picker: pick which cell of a partially-used ULINE sheet gets -->
<!-- this one label. 2x3 grid mirrors the physical cell positions. -->
<div v-if="outputMode === 'uline-sheet'" class="cell-picker">
<span class="cell-picker-label">Sheet cell</span>
<div class="cell-grid">
<button
v-for="cellNum in 6"
:key="cellNum"
type="button"
class="cell-btn"
:class="{ active: cellNum === sheetCell }"
@click="sheetCell = cellNum"
>{{ cellNum }}</button>
</div>
</div>
<button class="print-btn" @click="print">Print</button>
</template>
</div>
</div>
<!-- Printable area -->
<div v-if="asset" class="label-sheet">
<!-- Printable area: standalone single label (unchanged default). -->
<div v-if="asset && outputMode === 'single'" class="label-sheet">
<div class="asset-label" :class="style">
<template v-if="style === 'card'">
<div class="label-title">{{ cardTitle }}</div>
@@ -61,6 +84,44 @@
</div>
</div>
</div>
<!-- One label placed at the chosen cell of a ULINE 6-up sheet, other five -->
<!-- blank, so it lands on the right spot of a partially-used sheet. Cell -->
<!-- layout/CSS replicated from AssetLabelBatch (left untouched) to keep -->
<!-- batch's verified rendering isolated. Only one branch renders at a time -->
<!-- so the shared barcodeEl ref resolves to whichever label is live. -->
<div v-else-if="asset" class="sheets-container">
<div class="print-sheet">
<div class="sheet-label no-print">ULINE 6-up sheet - cell {{ sheetCell }}</div>
<div
v-for="cellNum in 6"
:key="cellNum"
class="label"
:class="[`pos-${cellNum}`, cellNum === sheetCell ? 'active' : 'inactive']"
>
<div v-if="cellNum === sheetCell" class="asset-label in-cell" :class="style">
<template v-if="style === 'card'">
<div class="label-title">{{ cardTitle }}</div>
<img v-if="imageUrl" class="label-image" :src="imageUrl" :alt="cardTitle" />
<div class="label-fields">
<div v-for="field in identityFields" :key="field.label" class="label-field">
<span class="field-label">{{ field.label }}</span>
<span class="field-value">{{ field.value }}</span>
</div>
</div>
</template>
<div class="code-area">
<template v-if="codeText">
<img v-if="codetype === 'qr' && qrImage" class="code-qr" :src="qrImage" alt="QR" />
<svg v-show="codetype === 'barcode'" ref="barcodeEl" class="code-barcode"></svg>
<div class="code-caption">{{ caption }}</div>
</template>
<div v-else class="code-missing">No {{ encodeLabel }} recorded for this asset.</div>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
@@ -90,6 +151,10 @@ const codeText = ref('')
const style = ref('card')
const codetype = ref('qr')
const encodes = ref('assetpage')
// Output target: 'single' standalone label, or 'uline-sheet' placing this one
// label at sheetCell (1-6) of a ULINE 6-up sheet.
const outputMode = ref('single')
const sheetCell = ref(1)
const hasLocation = computed(() => hasLocationType(assettype))
@@ -168,7 +233,9 @@ onMounted(async () => {
}
})
watch([style, codetype, encodes], renderCode)
// outputMode/sheetCell re-mount the live label element, so redraw the barcode
// into the newly rendered svg (the qr path is just an <img> src, unaffected).
watch([style, codetype, encodes, outputMode, sheetCell], renderCode)
function print() {
window.print()
@@ -176,7 +243,11 @@ function print() {
</script>
<style scoped>
/* Standalone label is a small stock; the ULINE sheet is a letter page. Named */
/* pages let each render its own size (only one branch renders at a time). */
@page { size: 2.13in 3.38in; margin: 0; }
@page uline { size: letter; margin: 0; }
.print-sheet { page: uline; }
.no-print { padding: 20px; }
.controls {
@@ -193,6 +264,33 @@ function print() {
.control-row select { padding: 6px; font-size: 0.875rem; }
.control-note { color: var(--warning); font-size: 0.8125rem; margin: 0 0 12px; }
/* Cell picker: 2x3 grid mirroring the physical ULINE cell positions. */
.cell-picker { margin-bottom: 12px; }
.cell-picker-label { display: block; font-size: 0.875rem; margin-bottom: 4px; }
.cell-grid {
display: grid;
grid-template-columns: repeat(2, 3rem);
grid-auto-rows: 3rem;
gap: 6px;
}
.cell-btn {
display: flex;
align-items: center;
justify-content: center;
font-size: 1rem;
cursor: pointer;
background: var(--bg);
color: var(--text);
border: 1px solid var(--border);
border-radius: 4px;
}
.cell-btn:hover { border-color: var(--primary); }
.cell-btn.active {
background: var(--primary);
color: white;
border-color: var(--primary);
}
.print-btn {
padding: 10px 30px;
font-size: 16px;
@@ -264,10 +362,61 @@ function print() {
padding: 0.3in 0.1in;
}
/* ULINE 6-up sheet (replicated from AssetLabelBatch; batch left untouched). */
.sheets-container { display: flex; justify-content: center; padding: 20px 0; }
.print-sheet {
width: 8.5in;
height: 11in;
background: white;
position: relative;
border: 1px solid #ccc;
}
.sheet-label { position: absolute; top: -25px; left: 0; font-size: 12px; color: #666; }
.label {
width: 3in;
height: 3in;
position: absolute;
display: flex;
align-items: center;
justify-content: center;
padding: 0.1in;
box-sizing: border-box;
}
.label.inactive { border: 1px dashed #ccc; }
.label.active { border: 2px solid var(--primary); }
/* ULINE S-5627 / 6-up 3in cell positions on a letter sheet (match batch). */
.pos-1 { top: 0.875in; left: 1.1875in; }
.pos-2 { top: 0.875in; left: 4.3125in; }
.pos-3 { top: 4in; left: 1.1875in; }
.pos-4 { top: 4in; left: 4.3125in; }
.pos-5 { top: 7.125in; left: 1.1875in; }
.pos-6 { top: 7.125in; left: 4.3125in; }
/* The label shrunk to fit a 3in cell (standalone stock is taller than 3in). */
.label .asset-label.in-cell {
width: auto;
min-height: 0;
border: none;
padding: 0;
justify-content: center;
}
.label .asset-label.in-cell .label-title { font-size: 10pt; margin-bottom: 0.04in; }
.label .asset-label.in-cell .label-image { max-width: 1.2in; max-height: 0.7in; margin-bottom: 0.04in; }
.label .asset-label.in-cell .label-fields { margin-bottom: 0.04in; }
.label .asset-label.in-cell .code-area { margin-top: 0.05in; }
.label .asset-label.in-cell .code-qr { width: 1.1in; height: 1.1in; }
.label .asset-label.in-cell .code-barcode { width: 2.2in; height: 0.7in; }
.label .asset-label.in-cell .code-caption { font-size: 10pt; }
@media print {
.no-print { display: none !important; }
.label-sheet { padding: 0; }
.asset-label { border: none; }
.sheets-container { padding: 0; }
.print-sheet { border: none; }
.label { border: none !important; }
.label.inactive { visibility: hidden; }
body, .asset-label, .code-qr, .code-barcode {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;