Fix report pagination and rebuild saved plan navigation
Test and deploy / test-and-deploy (push) Successful in 1m43s
Test and deploy / test-and-deploy (push) Successful in 1m43s
This commit is contained in:
1 parent
824a964f9d
commit
5f3a015ea2
20 files changed
+1480
-809
No files matched your search
+7
-7
@@ -75,7 +75,7 @@ export const REPORT_CSS = `
|
||||
.roast-report h1{font-size:25px;line-height:1.12;margin:0 0 8px}.roast-report h2{display:block;border:0;padding:0;font-size:13px;text-transform:uppercase;letter-spacing:.08em;margin:18px 0 8px}.roast-report p{margin:7px 0}.roast-report .subtitle{color:#626262;border-bottom:3px solid #161616;padding-bottom:14px;margin-bottom:16px}
|
||||
.roast-report .cards{display:grid;grid-template-columns:repeat(3,1fr);gap:10px}.roast-report .card{border:1px solid #d7d7d7;border-radius:9px;padding:11px}.roast-report .label{font-size:10px;color:#626262;text-transform:uppercase}.roast-report .value{font-size:20px;font-weight:700}.roast-report .callout{background:#f5f5f2;border-left:4px solid #7a4c2a;padding:10px 12px;margin:12px 0}
|
||||
.roast-report table{width:100%;border-collapse:collapse;font-size:11px}.roast-report th{text-align:left;border-bottom:2px solid #161616;font-size:10px}.roast-report td{border-bottom:1px solid #d7d7d7}.roast-report th,.roast-report td{padding:7px 5px;vertical-align:top}.roast-report svg{display:block;width:100%;height:auto}.roast-report .chart{border:1px solid #d7d7d7;border-radius:10px;padding:6px;break-inside:avoid}.roast-report .notes{white-space:pre-wrap;min-height:45px;border-bottom:1px solid #bbb}.roast-report .footer{margin-top:16px;padding-top:8px;border-top:1px solid #ddd;font-size:10px;color:#626262}.roast-report a{color:#6c482b;overflow-wrap:anywhere}.roast-report .cards,.roast-report tr{break-inside:avoid}
|
||||
@page{size:Letter;margin:.35in}@media print{.roast-report{background:white;padding:0;font-size:10px}.roast-report .sheet{width:auto;max-width:none;margin:0;padding:.16in .2in;box-shadow:none}.roast-report .sheet+.sheet{break-before:page}.roast-report h2{margin-top:12px}.roast-report th,.roast-report td{padding:5px}.roast-report .chart{max-width:100%}.roast-report a{color:inherit;text-decoration:none}.roast-report p{margin:5px 0}.roast-report .notes{min-height:32px}.roast-report .card{padding:8px}.roast-report .value{font-size:18px}.roast-report table{font-size:10px}.roast-report .footer{margin-top:10px}.roast-report h1{font-size:23px}}
|
||||
@page{size:Letter;margin:.35in}@media print{.roast-report{background:white;padding:0;font-size:10px}.roast-report .sheet{width:auto;max-width:none;margin:0;padding:.16in .2in;box-shadow:none}.roast-report .sheet+.sheet{break-before:auto;padding-top:18px}.roast-report h2{margin-top:12px}.roast-report th,.roast-report td{padding:5px}.roast-report .chart{max-width:100%;max-height:3.1in}.roast-report .chart svg{height:auto;max-height:2.95in}.roast-report h1,.roast-report h2{break-after:avoid}.roast-report .record-intro{break-inside:avoid}.roast-report .footer{break-before:avoid}.roast-report p{orphans:3;widows:3}.roast-report a{color:inherit;text-decoration:none}.roast-report p{margin:5px 0}.roast-report .notes{min-height:40px}.roast-report .record-notes{display:grid;grid-template-columns:1fr 1fr;gap:18px;break-inside:avoid}.roast-report .card{padding:8px}.roast-report .value{font-size:18px}.roast-report table{font-size:10px}.roast-report .footer{margin-top:10px}.roast-report h1{font-size:23px}.roast-report .subtitle{padding-bottom:8px;margin-bottom:10px}.roast-report .callout{padding:8px 10px;margin:8px 0}}
|
||||
`;
|
||||
export function renderReportBody(plan, name = "") {
|
||||
const l = computeLedger(plan),
|
||||
@@ -122,11 +122,11 @@ export function renderReportBody(plan, name = "") {
|
||||
<div class="callout">Use this as a starting hypothesis. Light, fruity coffee depends on the green coffee, roast progression and cup results. Machine selection never changes this plan. * Blank temperatures use an illustrative reference curve, not measured or calibrated targets.</div>
|
||||
${warnings.length ? `<div class="callout">${warnings.map((w) => `<p>${e(w)}</p>`).join("")}</div>` : ""}
|
||||
<h2>Milestone plan</h2><table><thead><tr><th>Milestone</th><th>Time</th><th>BT (°F)</th><th>What to look for</th></tr></thead><tbody>${rows}</tbody></table>
|
||||
<h2>Roast curve</h2><div class="chart">${renderReportCurve(p, l)}</div><p>The smooth line interpolates milestones; it is not a predicted sensor trace. Aim for controlled progression after recovery. Phase averages cannot rule out a brief crash or flick.</p><div class="footer">Roast plan · Grams & Fahrenheit · Page 1</div></section>
|
||||
<section class="sheet"><h1>Roast schedule & record</h1><div class="subtitle">${e(name || fields["0.1"] || "Roast plan")} · Use at the roaster</div>
|
||||
<h2>Preheat</h2><div class="cards">${card("Setpoint (°F)", number(plan.preheat?.tempC) === null ? "________" : f(number(plan.preheat.tempC)))}${card("Preheat time (min)", plan.preheat?.minutes || "________")}${card("Drum / fan (%)", (plan.preheat?.drumPct || "____") + " / " + (plan.preheat?.fanPct || "____"))}</div><p>Use your manufacturer’s guidance and a repeatable preheat routine. The charge BT plotted on page 1 is not a preheat setpoint. Record preheat separately.</p>
|
||||
<h2>Roast curve</h2><div class="chart">${renderReportCurve(p, l)}</div><p>The smooth line interpolates milestones; it is not a predicted sensor trace. Aim for controlled progression after recovery. Phase averages cannot rule out a brief crash or flick.</p><div class="footer">Roast plan · Grams & Fahrenheit</div></section>
|
||||
<section class="sheet"><div class="record-intro"><h1>Roast schedule & record</h1><div class="subtitle">${e(name || fields["0.1"] || "Roast plan")} · Use at the roaster</div>
|
||||
<h2>Preheat</h2><div class="cards">${card("Setpoint (°F)", number(plan.preheat?.tempC) === null ? "________" : f(number(plan.preheat.tempC)))}${card("Preheat time (min)", plan.preheat?.minutes || "________")}${card("Drum / fan (%)", (plan.preheat?.drumPct || "____") + " / " + (plan.preheat?.fanPct || "____"))}</div><p>Follow a repeatable preheat routine. Record setpoint separately from the charge BT shown on the curve.</p></div>
|
||||
<h2>Actuator schedule</h2><table><thead><tr><th>When</th><th>Heat</th><th>Fan</th><th>BT (°F)</th><th>Purpose</th></tr></thead><tbody>${sr}</tbody></table>
|
||||
<div class="callout">Watch actual RoR, color and aroma. Plan heat changes ahead of the machine’s response; avoid abrupt late cuts. Record one change to evaluate in the next cup.</div>
|
||||
<div class="callout">Watch RoR, color and aroma. Anticipate the machine’s response and avoid abrupt late cuts. Test one change in the next batch.</div>
|
||||
<h2>At-the-roaster record</h2><table><thead><tr><th>Milestone</th><th>Plan time / BT</th><th>Actual time</th><th>Actual BT (°F)</th><th>Notes</th></tr></thead><tbody>${p
|
||||
.map((v) => {
|
||||
const a = plan.planActual?.[v.key] ?? {};
|
||||
@@ -134,8 +134,8 @@ export function renderReportBody(plan, name = "") {
|
||||
})
|
||||
.join("")}</tbody></table>
|
||||
<h2>After the roast</h2><div class="cards">${card("Roasted out", after.out ? after.out + " g" : "________ g")}${card("Weight loss", after.weightLossPct ? after.weightLossPct + "%" : "________ %")}${card("Rest before tasting", after.restedDays ? after.restedDays + " days" : "________ days")}</div>
|
||||
<h2>Cup / brew notes</h2><div class="notes">${e([after.cupNotes, after.cupNotesLine2].filter(Boolean).join("\n"))}</div><h2>One change for the next batch</h2><div class="notes">${e(after.oneChange)}</div>
|
||||
<h2>Sources & interpretation</h2><p><a href="https://library.sweetmarias.com/first-crack-faq-what-is-first-crack-what-is-second-crack/">Sweet Maria’s: First Crack FAQ</a> — audible event and temperature context.</p><p><a href="https://www.scottrao.com/blog/2020/3/29/coffee-roasting-fundamentals">Scott Rao: Coffee Roasting Fundamentals</a> — probe readings do not transfer directly; time and DTR alone do not establish quality.</p><p>Timing begins with the app’s cultivar worksheet. The 1:15 light-development suggestion floor and illustrated curve are app planning heuristics, not universal roasting rules. Your explicit development override takes precedence.</p><div class="footer">Roast record · Grams & Fahrenheit · Page 2</div></section></div>`;
|
||||
<div class="record-notes"><div><h2>Cup / brew notes</h2><div class="notes">${e([after.cupNotes, after.cupNotesLine2].filter(Boolean).join("\n"))}</div></div><div><h2>One change for the next batch</h2><div class="notes">${e(after.oneChange)}</div></div></div>
|
||||
<h2>Sources & interpretation</h2><p><a href="https://library.sweetmarias.com/first-crack-faq-what-is-first-crack-what-is-second-crack/">Sweet Maria’s: First Crack FAQ</a> — audible event and temperature context.</p><p><a href="https://www.scottrao.com/blog/2020/3/29/coffee-roasting-fundamentals">Scott Rao: Coffee Roasting Fundamentals</a> — probe readings do not transfer directly; time and DTR alone do not establish quality.</p><p>Cultivar timings, the 1:15 light-development suggestion floor and the illustrated curve are planning heuristics. Your explicit development override takes precedence.</p><div class="footer">Roast record · Grams & Fahrenheit</div></section></div>`;
|
||||
}
|
||||
export function renderReportHtml(plan, name = "") {
|
||||
return `<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>${e(name || plan.fields?.["0.1"] || "Roast plan")}</title><style>body{margin:0}${REPORT_CSS}</style></head><body>${renderReportBody(plan, name)}</body></html>`;
|
||||
|
||||
Reference in new issue
Block a user