Microgreens CoPilot
Sign in to continue
Passcode is only required to create a new account. Existing users can just sign in.

Microgreens CoPilot

Units: Imperial (oz/lb) Saved

Products

Pack sizes, seeding rate, expected yield/tray, grow-days. Build mixes from existing crops.
VarietySizesSeeding rateYieldGrow-daysTypeNotes
'; var w = window.open('', '_blank'); if (!w || w.closed) { alert('Pop-up blocked. Please allow pop-ups for printing.'); return; } w.document.open(); w.document.write(html); w.document.close(); setTimeout(function(){ try { w.focus(); w.print(); } catch(e){} }, 80); } catch (e) { console.error('[pack-print] failed:', e); } } function wire(){ var btn = document.getElementById('pack-print'); if (btn && !btn.__wired){ btn.__wired = true; btn.addEventListener('click', function(e){ e.preventDefault(); doPackPrint(); }); } } if (document.readyState === 'complete' || document.readyState === 'interactive') { setTimeout(wire, 0); } else { document.addEventListener('DOMContentLoaded', wire); } if (typeof window.refresh === 'function' && !window.__PACK_PRINT_WRAP_REFRESH__){ var _rf = window.refresh; window.refresh = function(tab){ var out = _rf.apply(this, arguments); if (tab === 'pack' || tab === 'tab-pack') setTimeout(wire, 0); return out; }; window.__PACK_PRINT_WRAP_REFRESH__ = true; } })(); `; const w = window.open("", "_blank"); if(!w) return; w.document.open(); w.document.write(html); w.document.close(); w.focus(); w.print(); }; } } // ---------- 4) Inventory: fix Edit; add quick "Add to quantity"; link Products -> Inventory ---------- // Data model additions on product: // p.inv_seed_id, p.inv_soil_id, p.inv_fert_id (inventory item ids/names) // p.seed_per_tray_g, p.soil_per_tray_g, p.fert_per_tray_g (numbers) // We expose these in the Product editor. function inventoryByName(name){ return (state.inventory||[]).find(i=> i.name===name) || null; } function inventoryAllNames(){ return (state.inventory||[]).map(i=> i.name); } function adjustInventory(name, delta){ const it = inventoryByName(name); if(!it) return false; it.qty = (parseFloat(it.qty||0) + parseFloat(delta||0)); save(); return true; } window.consumeInventoryFor = function(productName, trays){ try{ const p = productByName(productName); if(!p) return; // Use ONLY explicit links & amounts; no auto-linking, no defaults const seedPer = Number(p.seed_per_tray_g || 0); const soilPer = Number(p.soil_per_tray_g || 0); const fertPer = Number(p.fert_per_tray_g || 0); const adj = (id, amt, fallbackUnit)=>{ const it = itemById(id); if(!it) return; const unit = (it.unit || fallbackUnit); const inItemUnit = toItemUnit(amt, unit); if(typeof window.adjustInventory === 'function') window.adjustInventory(it.id, -inItemUnit); }; if(trays){ if(p.inv_seed_id && seedPer>0) adj(p.inv_seed_id, seedPer*trays, 'g'); if(p.inv_soil_id && soilPer>0) adj(p.inv_soil_id, soilPer*trays, 'L'); if(p.inv_fert_id && fertPer>0) adj(p.inv_fert_id, fertPer*trays, 'g'); } }catch(e){ console.warn('consumeInventoryFor failed', e); } } ; // Inventory UI enhancements function enhanceInventoryUI(){ const tab = document.getElementById("tab-inventory"); if(!tab) return; // Fix per-row Edit buttons (ensure they open an edit modal that persists) tab.querySelectorAll("button[data-edit], .inv-edit").forEach(b=>{ b.onclick = ()=>{ const row = b.closest("tr"); if(!row) return; const name = row.querySelector("td:nth-child(1)")?.textContent?.trim(); const item = inventoryByName(name); if(!item) return; const qty = prompt(`Edit quantity for "${name}"`, String(item.qty||0)); if(qty!=null){ item.qty = parseFloat(qty)||0; save(); // naive re-render location.reload(); } }; }); // Add quick "Add to quantity" controls if(!document.getElementById("inv-quick-add")){ const container = document.createElement("div"); container.id = "inv-quick-add"; container.style.margin = "10px 0"; const names = inventoryAllNames(); container.innerHTML = `
`; const hdr = tab.querySelector("h2") || tab.firstChild; if(hdr && hdr.parentElement){ hdr.parentElement.insertBefore(container, hdr.nextSibling); } document.getElementById("inv-qa-apply").onclick = ()=>{ const name = document.getElementById("inv-qa-name").value; const delta = parseFloat(document.getElementById("inv-qa-delta").value||"0"); if(!name || !delta) return; adjustInventory(name, delta); alert(`Added ${delta} to ${name}`); location.reload(); }; } } // Product editor: expose inventory mapping and per-tray usage const _openProduct = window.openProduct; window.openProduct = function(id){ _openProduct(id); const modal = document.querySelector(".modal"); if(!modal) return; let p = (state.products||[]).find(x=> x.id===id) || null; if(!p){ const title = modal.querySelector("h2")?.textContent||""; const name = title.replace(/^Edit\s*—\s*/,"").trim(); if(name) p = (state.products||[]).find(x=> x.name===name) || null; } if(!p) return; p.steps = p.steps || []; // Insert inventory mapping block if not present if(!modal.querySelector("#pm-inv-link")){ const body = modal.querySelector(".body") || modal; const names = inventoryAllNames(); const block = document.createElement("div"); block.id = "pm-inv-link"; block.style.marginTop = "12px"; block.innerHTML = `

Inventory Linking

`; body.appendChild(block); function persist(){ p.inv_seed_id = document.getElementById("pm-inv-seed").value || ""; p.inv_soil_id = document.getElementById("pm-inv-soil").value || ""; p.inv_fert_id = document.getElementById("pm-inv-fert").value || ""; p.seed_per_tray_g = parseFloat(document.getElementById("pm-seed-per-tray").value||"0"); p.soil_per_tray_g = parseFloat(document.getElementById("pm-soil-per-tray").value||"0"); p.fert_per_tray_g = parseFloat(document.getElementById("pm-fert-per-tray").value||"0"); save(); } block.querySelectorAll("select, input").forEach(el=> el.addEventListener("input", persist)); // Ensure Save flushes these values const saveBtn = modal.querySelector("#pm-save"); if(saveBtn){ const orig = saveBtn.onclick; saveBtn.onclick = ()=>{ persist(); if(typeof orig==="function") orig(); }; } } }; // Optional: add "Seeded" action in Plan Upcoming to deduct inventory now // (in case Daily Task Sheet isn't wired yet) const _renderPlan = window.renderPlan; window.renderPlan = function(){ try { _renderPlan(); } catch(e){} const extra = document.getElementById("plan-extras"); if(!extra) return; // add seeded buttons to Upcoming rows extra.querySelectorAll("h3 + table tbody").forEach((tbody, idx)=>{ // idx 0 = Currently growing, idx 1 = Upcoming if(idx!==1) return; const header = tbody.parentElement.querySelector("thead tr"); if(header && !header.querySelector(".seeded-col")){ const th = document.createElement("th"); th.className="seeded-col"; th.textContent="Seeded"; header.appendChild(th); } Array.from(tbody.querySelectorAll("tr")).forEach(tr=>{ if(tr.querySelector(".seeded-mark")) return; const td = document.createElement("td"); td.className="seeded-mark"; const btn = document.createElement("button"); btn.className="btn"; btn.textContent="Mark seeded"; btn.onclick = ()=>{ // Expect columns: Variety | Trays | Seed on | Harvest date (from v4.3) const variety = tr.children[0]?.textContent?.trim(); const traysText = tr.children[1]?.textContent?.trim()||"0"; const trays = parseInt(traysText,10)||0; if(!variety || trays<=0) return alert("Nothing to mark."); if(confirm(`Deduct inventory for ${trays} tray(s) of ${variety}?`)){ window.consumeInventoryFor(variety, trays); btn.disabled = true; btn.textContent = "Seeded ✓"; } }; td.appendChild(btn); tr.appendChild(td); }); }); }; // Run initializers if(document.readyState === "loading"){ document.addEventListener("DOMContentLoaded", ()=>{ stripCustomerFields(); stripOrderFields(); hideMainPlanTable(); addPackPrint(); enhanceInventoryUI(); }); }else{ stripCustomerFields(); stripOrderFields(); hideMainPlanTable(); addPackPrint(); enhanceInventoryUI(); } })();