View raw

1 <!-- app/views/recipes/show.html.erb --> 2 <%# Controls header %> 3 <div class="card shadow my-4"> 4 <div class="card-header d-flex flex-wrap gap-2 justify-content-between align-items-center"> 5 <h5 class="mb-0">Ferti© Recipe</h5> 6 7 <div class="d-flex flex-wrap gap-2 align-items-center"> 8 <%= form_with url: ferti_recipe_path, method: :get, local: true, class: "d-flex flex-wrap gap-2 align-items-center", id: "recipe-form" do %> 9 <div class="d-flex align-items-center gap-2"> 10 <label class="mb-0 small text-nowrap" for="volume_select">Volume</label> 11 <select id="volume_select" name="volume" class="form-select form-select-sm"> 12 <% options = [["100 000 L", 100_000], ["200 000 L", 200_000], ["300 000 L", 300_000]] %> 13 <% current_volume = (params[:volume].presence || 100_000).to_i %> 14 <% options.each do |label, val| %> 15 <option value="<%= val %>" <%= 'selected' if val == current_volume %>><%= label %></option> 16 <% end %> 17 </select> 18 </div> 19 20 <div class="vr" style="height: 24px;"></div> 21 22 <div class="d-flex align-items-center gap-2"> 23 <label class="mb-0 small text-nowrap" for="portions_input">Portions</label> 24 <input id="portions_input" type="number" min="2" max="10" step="1" 25 class="form-control form-control-sm" value="<%= params[:portions].presence || 2 %>"> 26 </div> 27 <% end %> 28 29 <%= link_to "Back", root_path, class: "btn btn-sm btn-secondary" %> 30 </div> 31 </div> 32 33 <div class="table-responsive"> 34 <table class="table table-sm align-middle mb-0" id="recipe-table"> 35 <thead class="table-light"> 36 <tr> 37 <th>Product</th> 38 <th class="text-muted">Fertilizer</th> 39 <th class="text-end">Qty (kg)</th> 40 <th class="text-end">Per portion (kg)</th> 41 </tr> 42 </thead> 43 <tbody> 44 <% @recipe.each do |component, kg| %> 45 <% prod_name = commercial_name_for(component) %> 46 <tr data-total-kg="<%= kg %>"> 47 <td><strong><%= prod_name %></strong></td> 48 <td class="text-muted"><small><%= component.name %></small></td> 49 <td class="text-end fw-semibold"><%= fmt_kg(kg) %></td> 50 <td class="text-end per-portion-cell"><%= fmt_kg(kg / (params[:portions].presence || 2).to_f) %></td> 51 </tr> 52 <% end %> 53 <% if @recipe.blank? %> 54 <tr><td colspan="4" class="text-center text-muted">No supplementation required.</td></tr> 55 <% end %> 56 </tbody> 57 </table> 58 </div> 59 60 <div class="card-footer small text-muted"> 61 Recipe based on latest measurement: <%= @latest.measured_on %> 62 </div> 63 </div> 64 65 <%# Tiny vanilla JS: auto-submit on volume change; live per-portion update %> 66 <script> 67 (function() { 68 const form = document.getElementById('recipe-form'); 69 const volumeSelect = document.getElementById('volume_select'); 70 const portionsInput = document.getElementById('portions_input'); 71 const rows = document.querySelectorAll('#recipe-table tbody tr[data-total-kg]'); 72 73 function clampPortions(n) { 74 n = parseInt(n || 2, 10); 75 if (isNaN(n)) n = 2; 76 return Math.min(10, Math.max(2, n)); 77 } 78 79 function updatePerPortion() { 80 const n = clampPortions(portionsInput.value); 81 portionsInput.value = n; 82 rows.forEach(row => { 83 const total = parseFloat(row.getAttribute('data-total-kg') || '0'); 84 const cell = row.querySelector('.per-portion-cell'); 85 const per = (n > 0) ? (total / n) : 0; 86 cell.textContent = formatKg(per); 87 }); 88 } 89 90 function formatKg(v) { 91 // match Rails number_with_precision(... precision: 2, strip zeros-ish) 92 const s = (Math.round(v * 100) / 100).toFixed(2); 93 return s.replace(/\.00$/, '').replace(/(\.\d)0$/, '$1'); 94 } 95 96 volumeSelect.addEventListener('change', () => { 97 // submit with selected volume, keeping portions as a query param 98 const portions = clampPortions(portionsInput.value); 99 const url = new URL(form.action, window.location.origin); 100 url.searchParams.set('volume', volumeSelect.value); 101 url.searchParams.set('portions', portions); 102 window.location.assign(url.toString()); 103 }); 104 105 portionsInput.addEventListener('input', updatePerPortion); 106 updatePerPortion(); 107 })(); 108 </script> 109