fix: match pallet print sheet to production card layout
Some checks failed
CI / verify (push) Has been cancelled
CI / print-rust (push) Has been cancelled

This commit is contained in:
Admin 2026-09-25 21:46:38 +00:00
parent b000b08dc6
commit fed1bd4c56
2 changed files with 404 additions and 141 deletions

View File

@ -268,70 +268,220 @@
.pallet-production-sheet { .pallet-production-sheet {
box-sizing: border-box; box-sizing: border-box;
width: 198mm; width: 198mm;
min-height: 277mm; height: 277mm;
margin: 0 auto; margin: 0 auto;
padding: 5mm; padding: 4mm;
border: 0; color: #111;
background: #fff;
font-family: Arial, Helvetica, sans-serif;
break-inside: avoid-page; break-inside: avoid-page;
page-break-inside: avoid; page-break-inside: avoid;
} }
.pallet-top-strip, .photo-sheet-layout {
.pallet-bottom-strip {
display: grid; display: grid;
grid-template-columns: repeat(6, 1fr); grid-template-columns: 64mm minmax(0, 1fr);
grid-template-rows: 43mm 48mm 103mm 64mm;
gap: 3mm 4mm;
}
.photo-top-left {
grid-column: 1;
grid-row: 1 / span 2;
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 3mm;
min-height: 0;
}
.photo-mini-ticket {
display: grid;
grid-template-rows: 39mm minmax(0, 1fr);
gap: 2mm; gap: 2mm;
}
.pallet-ticket {
min-width: 0; min-width: 0;
padding: 1.5mm 1mm;
border: .25mm solid #bbb;
text-align: center;
} }
.pallet-ticket canvas { .photo-mini-size,
display: block; .photo-main-size,
width: 100%; .photo-large-size {
height: 11mm; position: relative;
object-fit: contain;
}
.pallet-ticket strong {
display: block;
margin-top: .5mm;
font: 700 8pt/1 Arial, Helvetica, sans-serif;
}
.pallet-sheet-main {
display: grid;
grid-template-columns: 33mm minmax(0, 1fr) 36mm;
gap: 4mm;
align-items: stretch;
margin: 5mm 0;
}
.pallet-size-panel {
display: grid; display: grid;
place-items: center; place-items: center;
border: .4mm solid #222; border: .35mm solid #333;
font: 700 48pt/1 Arial, Helvetica, sans-serif; overflow: hidden;
} }
.pallet-card { .photo-mini-size strong {
border: .4mm solid #222; font: 400 30pt/1 Arial, Helvetica, sans-serif;
padding: 3mm; transform: rotate(90deg);
}
.photo-mini-size small,
.photo-main-size small,
.photo-large-size small {
position: absolute;
right: 3mm;
bottom: 2mm;
font: 700 9pt/1 Arial, Helvetica, sans-serif;
}
.photo-mini-code,
.photo-tear-ticket {
display: grid;
grid-template-columns: minmax(0, 1fr) 12mm;
gap: 1mm;
align-items: stretch;
min-height: 0;
}
.photo-rotated-barcode {
position: relative;
min-width: 0;
overflow: hidden;
}
.photo-rotated-barcode canvas {
position: absolute;
left: 50%;
top: 50%;
width: 49mm;
height: 16mm;
max-width: none;
transform: translate(-50%, -50%) rotate(90deg);
transform-origin: center;
}
.photo-yellow-id {
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
background: #f2d51a;
color: #111;
font: 700 10pt/1 Arial, Helvetica, sans-serif;
white-space: nowrap;
writing-mode: vertical-rl;
transform: rotate(180deg);
}
.photo-sheet-header {
position: relative;
grid-column: 2;
grid-row: 1;
min-width: 0;
}
.photo-header-id {
position: relative;
z-index: 1;
display: flex;
align-items: baseline;
gap: 5mm;
height: 25mm;
padding: 1mm 3mm;
font: 400 28pt/1 Arial, Helvetica, sans-serif;
}
.photo-header-id strong {
position: relative;
z-index: 2;
font-size: 33pt;
font-weight: 400;
letter-spacing: .4mm;
}
.photo-header-id i {
position: absolute;
z-index: -1;
top: 0;
right: 0;
width: 54mm;
height: 16mm;
background: #f2d51a;
}
.photo-brand-mark {
position: absolute;
right: 6mm;
bottom: 0;
display: grid;
grid-template-columns: auto auto;
grid-template-rows: auto auto;
align-items: end;
column-gap: 1mm;
color: #333;
transform: scale(.96);
transform-origin: right bottom;
}
.photo-brand-script {
grid-row: 1 / span 2;
align-self: start;
margin-top: -1mm;
font: italic 12pt/1 Georgia, serif;
}
.photo-brand-mark strong {
font: 400 17pt/1 Georgia, serif;
}
.photo-brand-mark small {
justify-self: end;
font: 700 4.8pt/1.1 Arial, Helvetica, sans-serif;
letter-spacing: .9mm;
}
.photo-main-size {
grid-column: 2;
grid-row: 2;
}
.photo-main-size strong {
font: 400 74pt/.9 Arial, Helvetica, sans-serif;
}
.photo-main-size small {
font-size: 15pt;
right: 4mm;
bottom: 3mm;
}
.photo-large-size {
grid-column: 1;
grid-row: 3;
}
.photo-large-size strong {
font: 400 56pt/.9 Arial, Helvetica, sans-serif;
transform: rotate(90deg);
}
.photo-large-size small {
left: 4mm;
right: auto;
font-size: 12pt;
}
.photo-pallet-card {
grid-column: 2;
grid-row: 3;
min-width: 0;
border: 0;
padding: 0;
} }
.pallet-card-title { .pallet-card-title {
margin-bottom: 2mm; width: 52%;
font: 700 14pt/1.1 Arial, Helvetica, sans-serif; margin: 0 0 2mm;
font: 700 12pt/1.1 Arial, Helvetica, sans-serif;
text-align: center;
} }
.pallet-card-columns { .pallet-card-columns {
display: grid; display: grid;
grid-template-columns: 1fr 1.05fr; grid-template-columns: 1fr 1.03fr;
gap: 3mm; gap: 2mm;
height: calc(100% - 8mm);
} }
.pallet-card-left, .pallet-card-left,
@ -339,115 +489,179 @@
min-width: 0; min-width: 0;
} }
.pallet-spec-table, .pallet-spec-table {
.pallet-run-table {
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
table-layout: fixed; table-layout: fixed;
} }
.pallet-spec-table th, .pallet-spec-table th,
.pallet-spec-table td, .pallet-spec-table td {
.pallet-run-table th, height: 7mm;
.pallet-run-table td { border: .28mm solid #333;
border: .25mm solid #333; padding: .6mm .4mm;
padding: 1mm .8mm;
text-align: center; text-align: center;
font: 700 7.5pt/1.1 Arial, Helvetica, sans-serif; font: 700 7.2pt/1 Arial, Helvetica, sans-serif;
} }
.pallet-spec-table th, .pallet-spec-table th {
.pallet-run-table th { font-size: 6.3pt;
font-size: 6.5pt; font-weight: 400;
} }
.pallet-card-left p { .pallet-card-left p {
margin: 2mm 0; margin: 1.8mm 0;
font: 9pt/1.2 Arial, Helvetica, sans-serif; font: 8.4pt/1.15 Arial, Helvetica, sans-serif;
}
.pallet-card-left p b {
display: inline-block;
min-width: 17mm;
font-weight: 400;
} }
.pallet-time { .pallet-time {
margin-top: 4mm !important; margin-top: 5mm !important;
} }
.pallet-export-copy { .pallet-export-copy {
margin: 13mm 2mm 7mm; margin: 20mm 2mm 6mm;
text-align: center; text-align: center;
font: 8pt/1.25 Arial, Helvetica, sans-serif; font: 7.4pt/1.25 Arial, Helvetica, sans-serif;
}
.pallet-destination-line {
width: 92%;
margin: 0 auto 4mm;
border-bottom: .25mm dotted #555;
} }
.pallet-signature { .pallet-signature {
display: flex; display: flex;
align-items: end; align-items: end;
gap: 2mm; gap: 2mm;
margin-top: 5mm; margin: 0 1mm;
font: 8pt/1 Arial, Helvetica, sans-serif; font: 7.5pt/1 Arial, Helvetica, sans-serif;
} }
.pallet-signature span { .pallet-signature span {
flex: 1; flex: 1;
border-bottom: .25mm dotted #444; border-bottom: .25mm dotted #555;
}
.pallet-card-right {
position: relative;
margin-top: 8mm;
border: .35mm solid #333;
font-family: "Courier New", monospace;
} }
.pallet-main-barcode { .pallet-main-barcode {
position: absolute;
top: -18mm;
left: 50%;
display: block; display: block;
width: 72%; width: 54mm;
height: 15mm; height: 17mm;
margin: 0 auto 2mm;
object-fit: contain; object-fit: contain;
transform: translateX(-50%);
} }
.pallet-id-row, .pallet-id-row {
.pallet-total-row {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
gap: 3mm; gap: 3mm;
padding: 1mm 0; padding: 1.2mm 1.5mm 1mm;
font: 10pt/1.2 "Courier New", monospace; font: 700 10.5pt/1.1 "Courier New", monospace;
} }
.pallet-id-row strong { .pallet-id-row strong {
font-size: 12pt; font-size: 11.5pt;
} }
.pallet-run-table { .pallet-run-grid {
margin-top: 2mm; display: grid;
grid-template-columns: 1fr 2.2fr .72fr .62fr;
align-items: center;
padding: 0 1.2mm;
column-gap: 1mm;
}
.pallet-run-head {
height: 7mm;
border-top: .2mm solid #777;
border-bottom: .2mm solid #777;
font: 7.3pt/1 "Courier New", monospace;
}
.pallet-run-values {
height: 7mm;
font: 7pt/1 "Courier New", monospace;
}
.pallet-run-head span:nth-child(3),
.pallet-run-head span:nth-child(4),
.pallet-run-values span:nth-child(3),
.pallet-run-values span:nth-child(4) {
text-align: right;
} }
.pallet-total-row { .pallet-total-row {
margin-top: 44mm; position: absolute;
border-top: .25mm solid #333; right: 0;
font-size: 11pt; bottom: 0;
} left: 0;
.pallet-hero-ticket {
display: grid;
grid-template-rows: 1fr auto;
align-items: center;
border: .4mm solid #222;
padding: 2mm;
}
.pallet-hero-ticket canvas {
display: block;
width: 100%;
height: 34mm;
object-fit: contain;
}
.pallet-hero-ticket div {
display: flex; display: flex;
justify-content: center; justify-content: space-between;
gap: 2mm; padding: 1.2mm 1.5mm .8mm;
align-items: baseline; border-top: .28mm solid #333;
padding: 2mm 0; font: 700 10.5pt/1 "Courier New", monospace;
background: #f3d923;
font: 700 13pt/1 Arial, Helvetica, sans-serif;
} }
.pallet-hero-ticket strong { .photo-large-ticket {
font-size: 20pt; grid-column: 1;
grid-row: 4;
display: grid;
grid-template-columns: 18mm minmax(0, 1fr);
gap: 4mm;
min-height: 0;
} }
.photo-yellow-id-large {
font-size: 16pt;
letter-spacing: .2mm;
}
.photo-rotated-barcode-large canvas {
width: 57mm;
height: 24mm;
}
.photo-bottom-tickets {
grid-column: 2;
grid-row: 4;
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 1.5mm;
align-items: end;
min-height: 0;
padding-top: 17mm;
}
.photo-tear-ticket {
grid-template-columns: minmax(0, 1fr) 7mm;
height: 43mm;
}
.photo-tear-ticket .photo-rotated-barcode canvas {
width: 36mm;
height: 11mm;
}
.photo-tear-ticket .photo-yellow-id {
font-size: 6.5pt;
}
} }
@media (max-width: 760px) { @media (max-width: 760px) {

View File

@ -215,66 +215,115 @@ function PrintablePalletSheet({ fixture }: { fixture: PalletSheetFixture }) {
}) })
}, [fixture]) }, [fixture])
const tickets = Array.from({ length: 6 }, (_, index) => ( const miniTicket = (key: string) => (
<div className="pallet-ticket" key={index}> <div className="photo-mini-ticket" key={key}>
<canvas data-pallet-barcode data-compact="true" /> <div className="photo-mini-size">
<strong>JA {fixture.pallet}</strong> <strong>{fixture.size}</strong>
<small>{fixture.size}</small>
</div>
<div className="photo-mini-code">
<div className="photo-rotated-barcode">
<canvas data-pallet-barcode data-compact="true" />
</div>
<div className="photo-yellow-id">JA {fixture.pallet}</div>
</div>
</div> </div>
)) )
const tearTicket = (key: string) => (
<div className="photo-tear-ticket" key={key}>
<div className="photo-rotated-barcode">
<canvas data-pallet-barcode data-compact="true" />
</div>
<div className="photo-yellow-id">JA {fixture.pallet}</div>
</div>
)
return ( return (
<div className="pallet-print-sheet" aria-hidden="true" ref={sheetRef}> <div className="pallet-print-sheet" aria-hidden="true" ref={sheetRef}>
<div className="pallet-production-sheet"> <div className="pallet-production-sheet photo-sheet-layout">
<div className="pallet-top-strip">{tickets}</div> <section className="photo-top-left">
{miniTicket('mini-a')}
{miniTicket('mini-b')}
</section>
<div className="pallet-sheet-main"> <header className="photo-sheet-header">
<div className="pallet-size-panel">20</div> <div className="photo-header-id">
<span>JA</span>
<strong>{fixture.pallet}</strong>
<i />
</div>
<div className="photo-brand-mark">
<span className="photo-brand-script">just</span>
<strong>Avocados</strong>
<small>LIMITED</small>
</div>
</header>
<div className="pallet-card"> <section className="photo-main-size">
<div className="pallet-card-title">Avocados - Hass</div> <strong>{fixture.size}</strong>
<div className="pallet-card-columns"> <small>{fixture.size}</small>
<div className="pallet-card-left"> </section>
<table className="pallet-spec-table">
<thead><tr><th>ORIGIN</th><th>VAR</th><th>BRAND</th><th>CLASS</th></tr></thead> <section className="photo-large-size">
<tbody> <strong>{fixture.size}</strong>
<tr><td>{fixture.origin}</td><td>{fixture.variety}</td><td>{fixture.brand}</td><td>{fixture.className}.</td></tr> <small>{fixture.size}</small>
<tr><td></td><th>BASE</th><th>PACK</th><td></td></tr> </section>
<tr><td></td><td>{fixture.base}</td><td>{fixture.pack}</td><td></td></tr>
</tbody> <section className="pallet-card photo-pallet-card">
</table> <div className="pallet-card-title">Avocados - Hass</div>
<p><b>Pick Date:</b> {fixture.pickDate}</p> <div className="pallet-card-columns">
<p><b>Pack Date:</b> {fixture.packDate}</p> <div className="pallet-card-left">
<p className="pallet-time"><b>Time:</b> {fixture.printedTime}</p> <table className="pallet-spec-table">
<div className="pallet-export-copy"> <thead><tr><th>ORIGIN</th><th>VAR</th><th>BRAND</th><th>CLASS</th></tr></thead>
This product meets export quality specifications and has been inspected and passed for export to <tbody>
</div> <tr><td>{fixture.origin}</td><td>{fixture.variety}</td><td>{fixture.brand}</td><td>{fixture.className}.</td></tr>
<div className="pallet-signature">Signature: <span></span></div> <tr><td></td><th>BASE</th><th>PACK</th><td></td></tr>
<tr><td></td><td>{fixture.base}</td><td>{fixture.pack}</td><td></td></tr>
</tbody>
</table>
<p><b>Pick Date:</b> {fixture.pickDate}</p>
<p><b>Pack Date:</b> {fixture.packDate}</p>
<p className="pallet-time"><b>Time:</b> {fixture.printedTime}</p>
<div className="pallet-export-copy">
This product meets export quality<br />
specifications and has been inspected<br />
and passed for export to
</div> </div>
<div className="pallet-destination-line" />
<div className="pallet-signature">Signature: <span /></div>
</div>
<div className="pallet-card-right"> <div className="pallet-card-right">
<canvas className="pallet-main-barcode" data-pallet-barcode /> <canvas className="pallet-main-barcode" data-pallet-barcode />
<div className="pallet-id-row"><b>Pallet:</b><strong>{fixture.pallet}</strong></div> <div className="pallet-id-row"><b>Pallet:</b><strong>{fixture.pallet}</strong></div>
<table className="pallet-run-table"> <div className="pallet-run-grid pallet-run-head">
<thead><tr><th>Grower</th><th>Packrun</th><th>Size</th><th>Qty</th></tr></thead> <span>Grower</span><span>Packrun</span><span>Size</span><span>Qty</span>
<tbody><tr><td>{fixture.grower}</td><td>{fixture.packrun}</td><td>{fixture.size}</td><td>{fixture.qty}</td></tr></tbody>
</table>
<div className="pallet-total-row"><b>Total</b><strong>{fixture.total}</strong></div>
</div> </div>
<div className="pallet-run-grid pallet-run-values">
<span>{fixture.grower}</span><span>{fixture.packrun}</span><span>{fixture.size}</span><span>{fixture.qty}</span>
</div>
<div className="pallet-total-row"><b>Total</b><strong>{fixture.total}</strong></div>
</div> </div>
</div> </div>
</section>
<div className="pallet-hero-ticket"> <section className="photo-large-ticket">
<div className="photo-yellow-id photo-yellow-id-large">JA {fixture.pallet}</div>
<div className="photo-rotated-barcode photo-rotated-barcode-large">
<canvas data-pallet-barcode /> <canvas data-pallet-barcode />
<div><span>JA</span><strong>{fixture.pallet}</strong></div>
</div> </div>
</div> </section>
<div className="pallet-bottom-strip">{tickets}</div> <section className="photo-bottom-tickets">
{Array.from({ length: 6 }, (_, index) => tearTicket(`tear-${index}`))}
</section>
</div> </div>
</div> </div>
) )
} }
function EanLabelCard({ fixture }: { fixture: EanLabelFixture }) { function EanLabelCard({ fixture }: { fixture: EanLabelFixture }) {
return ( return (
<article className="ean-lab-carton"> <article className="ean-lab-carton">