2026-04-29 23:05:27 +12:00
|
|
|
<script lang="ts">
|
|
|
|
|
import type { MixCalculatorSession } from '$lib/types';
|
|
|
|
|
|
|
|
|
|
let { session }: { session: MixCalculatorSession } = $props();
|
|
|
|
|
|
|
|
|
|
function formatDate(value: string) {
|
|
|
|
|
return new Intl.DateTimeFormat('en-NZ', {
|
|
|
|
|
dateStyle: 'medium',
|
|
|
|
|
timeStyle: undefined
|
|
|
|
|
}).format(new Date(value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatTimestamp(value: string) {
|
|
|
|
|
return new Intl.DateTimeFormat('en-NZ', {
|
|
|
|
|
dateStyle: 'medium',
|
|
|
|
|
timeStyle: 'short'
|
|
|
|
|
}).format(new Date(value));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatNumber(value: number, digits = 2) {
|
|
|
|
|
return value.toFixed(digits);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const printableTitle = $derived(
|
|
|
|
|
`MixCalculator_${session.client_name}_${session.product_name}_${session.mix_date}_${session.session_number}`.replace(/[^\w.-]+/g, '_')
|
|
|
|
|
);
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<svelte:head>
|
|
|
|
|
<title>{printableTitle}</title>
|
|
|
|
|
</svelte:head>
|
|
|
|
|
|
|
|
|
|
<section class="print-page">
|
|
|
|
|
<div class="print-toolbar">
|
|
|
|
|
<a class="secondary-button" href={`/mix-calculator/${session.id}`}>Back to session</a>
|
|
|
|
|
<button class="primary-button" type="button" onclick={() => window.print()}>Print / Save PDF</button>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<article class="sheet">
|
|
|
|
|
<header class="sheet-header">
|
|
|
|
|
<div>
|
|
|
|
|
<p class="eyebrow">Mix Calculator</p>
|
|
|
|
|
<h1>{session.session_number}</h1>
|
|
|
|
|
<p>Generated from the saved session snapshot without re-reading live product or recipe data.</p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="sheet-meta">
|
|
|
|
|
<div>
|
|
|
|
|
<span>Generated</span>
|
|
|
|
|
<strong>{formatTimestamp(new Date().toISOString())}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span>Status</span>
|
|
|
|
|
<strong>{session.status}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
<section class="summary-grid">
|
|
|
|
|
<div>
|
|
|
|
|
<span>Date</span>
|
|
|
|
|
<strong>{formatDate(session.mix_date)}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span>Client</span>
|
|
|
|
|
<strong>{session.client_name}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span>Product</span>
|
|
|
|
|
<strong>{session.product_name}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span>Mix source</span>
|
|
|
|
|
<strong>{session.mix_name}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span>Batch size</span>
|
|
|
|
|
<strong>{formatNumber(session.batch_size_kg, 2)}kg</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span>Total bags</span>
|
|
|
|
|
<strong>{formatNumber(session.total_bags, 2)}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span>Total kilograms</span>
|
|
|
|
|
<strong>{formatNumber(session.total_kg, 2)}kg</strong>
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<span>Prepared by</span>
|
|
|
|
|
<strong>{session.prepared_by_name}</strong>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
{#if session.notes}
|
|
|
|
|
<section class="notes-card">
|
|
|
|
|
<h2>Session notes</h2>
|
|
|
|
|
<p>{session.notes}</p>
|
|
|
|
|
</section>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
{#if session.warnings.length}
|
|
|
|
|
<section class="warning-card">
|
|
|
|
|
<h2>Warnings</h2>
|
|
|
|
|
{#each session.warnings as warning}
|
|
|
|
|
<p>{warning}</p>
|
|
|
|
|
{/each}
|
|
|
|
|
</section>
|
|
|
|
|
{/if}
|
|
|
|
|
|
|
|
|
|
<section class="table-card">
|
|
|
|
|
<div class="table-header">
|
|
|
|
|
<h2>Required raw materials</h2>
|
|
|
|
|
<span>{session.product_unit_of_measure} · {formatNumber(session.product_unit_size_kg, 2)}kg per unit</span>
|
|
|
|
|
</div>
|
|
|
|
|
<table>
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>Raw material</th>
|
|
|
|
|
<th>Mix %</th>
|
|
|
|
|
<th>Required kg</th>
|
|
|
|
|
<th>Unit</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
{#each session.lines as line}
|
|
|
|
|
<tr>
|
|
|
|
|
<td>{line.raw_material_name}</td>
|
|
|
|
|
<td>{formatNumber(line.mix_percentage, 2)}%</td>
|
|
|
|
|
<td>{formatNumber(line.required_kg, 2)}kg</td>
|
|
|
|
|
<td>{line.unit}</td>
|
|
|
|
|
</tr>
|
|
|
|
|
{/each}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</section>
|
|
|
|
|
</article>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
<style>
|
|
|
|
|
h1,
|
|
|
|
|
h2,
|
|
|
|
|
p {
|
|
|
|
|
margin: 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.print-page {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.print-toolbar {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
gap: 0.75rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.primary-button,
|
|
|
|
|
.secondary-button {
|
|
|
|
|
display: inline-flex;
|
|
|
|
|
align-items: center;
|
|
|
|
|
justify-content: center;
|
|
|
|
|
padding: 0.78rem 0.96rem;
|
|
|
|
|
border-radius: 0.9rem;
|
|
|
|
|
border: 1px solid var(--line-strong);
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
cursor: pointer;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.primary-button {
|
|
|
|
|
border: none;
|
2026-05-08 09:06:14 +12:00
|
|
|
background: var(--color-brand);
|
2026-04-29 23:05:27 +12:00
|
|
|
color: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.secondary-button {
|
|
|
|
|
background: #fff;
|
|
|
|
|
color: #304038;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sheet {
|
|
|
|
|
width: min(960px, 100%);
|
|
|
|
|
margin: 0 auto;
|
|
|
|
|
padding: 2rem;
|
|
|
|
|
border: 1px solid var(--line);
|
|
|
|
|
border-radius: 1.5rem;
|
|
|
|
|
background: #fff;
|
|
|
|
|
box-shadow: var(--shadow);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.eyebrow {
|
|
|
|
|
color: #7d8d84;
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
font-weight: 600;
|
|
|
|
|
letter-spacing: 0.08em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sheet-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 1.5rem;
|
|
|
|
|
padding-bottom: 1.25rem;
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sheet-header h1 {
|
|
|
|
|
margin: 0.3rem 0 0.45rem;
|
|
|
|
|
font-size: clamp(2rem, 4vw, 2.6rem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sheet-header p:last-child,
|
|
|
|
|
.sheet-meta span,
|
|
|
|
|
.summary-grid span,
|
|
|
|
|
.table-header span {
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sheet-meta {
|
|
|
|
|
min-width: 14rem;
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 0.9rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sheet-meta div,
|
|
|
|
|
.summary-grid div {
|
|
|
|
|
display: grid;
|
|
|
|
|
gap: 0.16rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.summary-grid {
|
|
|
|
|
display: grid;
|
|
|
|
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
padding: 1.35rem 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notes-card,
|
|
|
|
|
.warning-card,
|
|
|
|
|
.table-card {
|
|
|
|
|
margin-top: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notes-card,
|
|
|
|
|
.warning-card {
|
|
|
|
|
padding: 1rem;
|
|
|
|
|
border-radius: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.notes-card {
|
|
|
|
|
background: var(--panel-soft);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.warning-card {
|
|
|
|
|
background: #fff6e6;
|
|
|
|
|
color: #8b5b1e;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.warning-card h2,
|
|
|
|
|
.notes-card h2,
|
|
|
|
|
.table-header h2 {
|
|
|
|
|
margin-bottom: 0.45rem;
|
|
|
|
|
font-size: 1rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.table-header {
|
|
|
|
|
display: flex;
|
|
|
|
|
align-items: baseline;
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
gap: 1rem;
|
|
|
|
|
margin-bottom: 0.75rem;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
table {
|
|
|
|
|
width: 100%;
|
|
|
|
|
border-collapse: collapse;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
th,
|
|
|
|
|
td {
|
|
|
|
|
padding: 0.88rem 0.75rem;
|
|
|
|
|
text-align: left;
|
|
|
|
|
border-bottom: 1px solid var(--line);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
th {
|
|
|
|
|
color: var(--muted);
|
|
|
|
|
font-size: 0.78rem;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
letter-spacing: 0.06em;
|
|
|
|
|
text-transform: uppercase;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 900px) {
|
|
|
|
|
.sheet-header,
|
|
|
|
|
.table-header {
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.summary-grid {
|
|
|
|
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media print {
|
|
|
|
|
:global(body) {
|
|
|
|
|
background: #fff;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.print-toolbar {
|
|
|
|
|
display: none;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.sheet {
|
|
|
|
|
width: 100%;
|
|
|
|
|
margin: 0;
|
|
|
|
|
padding: 0;
|
|
|
|
|
border: none;
|
|
|
|
|
border-radius: 0;
|
|
|
|
|
box-shadow: none;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|