diff options
Diffstat (limited to 'app/views/dashboard/_nutrient_profile_allocator.html.erb')
-rw-r--r-- | app/views/dashboard/_nutrient_profile_allocator.html.erb | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/app/views/dashboard/_nutrient_profile_allocator.html.erb b/app/views/dashboard/_nutrient_profile_allocator.html.erb new file mode 100644 index 0000000..d402ace --- /dev/null +++ b/app/views/dashboard/_nutrient_profile_allocator.html.erb @@ -0,0 +1,187 @@ +<%# Props: nutrient_profiles: ActiveRecord::Relation<NutrientProfile> %> +<%# Fallback if controller didn't set @nutrient_profiles yet %> +<% profiles = (local_assigns[:nutrient_profiles] || []).presence || [] %> + +<%# We'll render a form purely for structure (no real submit yet) %> +<%= form_with url: "#", method: :post, local: true, html: { id: "np-mix-form", "data-controller": "np-mix" } do %> + <div class="card shadow"> + <div class="card-body"> + + <div class="d-flex justify-content-between align-items-center mb-2"> + <div class="small text-muted"> + Choisissez des <strong>profils de croissance</strong> et répartissez-les pour totaliser <strong>100%</strong>. + </div> + <div> + Somme : <span id="np-mix-sum" class="badge bg-secondary">0%</span> + </div> + </div> + + <div id="np-mix-rows" class="vstack gap-2"> + <%# Rows are injected by JS from the template below, including defaults %> + </div> + + <div class="mt-3 d-flex gap-2"> + <button type="button" class="btn btn-outline-primary" id="np-mix-add"> + + Ajouter un profil + </button> + + <%# Placeholder "save" button for later backend wiring; disabled until total == 100 %> + <button type="submit" class="btn btn-success ms-auto" id="np-mix-save" disabled> + Enregistrer (à venir) + </button> + </div> + </div> + </div> + + <%# --- Hidden template for a single row --- %> + <template id="np-mix-row-template"> + <div class="np-mix-row d-flex align-items-center gap-2 border rounded p-2"> + <button type="button" class="btn btn-outline-danger btn-sm np-mix-delete" aria-label="Supprimer la ligne"> + Suppr. + </button> + + <div class="flex-grow-1"> + <select name="mix[items][][profile_id]" class="form-select form-select-sm np-mix-select" required> + <% if profiles.any? %> + <% profiles.each do |p| %> + <option value="<%= p.id %>"><%= p.name %></option> + <% end %> + <% else %> + <%# If no collection provided yet, at least show placeholders to demo the UI %> + <option value="">-- Sélectionner un profil --</option> + <option value="gen-croissance">Générique croissance</option> + <option value="tomate-cycle">Tomate (cycle entier)</option> + <option value="gen-floraison">Générique floraison</option> + <% end %> + </select> + </div> + + <div class="input-group input-group-sm" style="max-width: 140px;"> + <input type="number" + name="mix[items][][percentage]" + class="form-control text-end np-mix-percent" + min="0" max="100" step="1" value="0" required> + <span class="input-group-text">%</span> + </div> + </div> + </template> + + <%# --- Defaults to inject on load --- %> + <script type="application/json" id="np-mix-defaults"> + { + "items": [ + { "name": "G\u00E9n\u00E9rique croissance", "percent": 50 }, + { "name": "Tomate (cycle entier)", "percent": 30 }, + { "name": "G\u00E9n\u00E9rique floraison", "percent": 20 } + ] + } + </script> + + <%# --- Tiny inline JS to keep this self-contained (no Stimulus required) --- %> + <script> + (() => { + const rowsContainer = document.getElementById('np-mix-rows'); + const addBtn = document.getElementById('np-mix-add'); + const saveBtn = document.getElementById('np-mix-save'); + const sumBadge = document.getElementById('np-mix-sum'); + const tpl = document.getElementById('np-mix-row-template'); + const defaultsJSON = document.getElementById('np-mix-defaults')?.textContent || "{}"; + const defaults = JSON.parse(defaultsJSON); + + function currentSum() { + return Array.from(rowsContainer.querySelectorAll('.np-mix-percent')) + .reduce((acc, el) => acc + (parseFloat(el.value) || 0), 0); + } + + function refreshSum() { + const sum = currentSum(); + sumBadge.textContent = `${sum}%`; + sumBadge.classList.remove('bg-secondary','bg-danger','bg-success','bg-warning'); + + if (sum === 100) { + sumBadge.classList.add('bg-success'); + saveBtn?.removeAttribute('disabled'); + } else if (sum > 100) { + sumBadge.classList.add('bg-danger'); + saveBtn?.setAttribute('disabled', 'disabled'); + } else { + sumBadge.classList.add('bg-warning'); + saveBtn?.setAttribute('disabled', 'disabled'); + } + } + + function setSelectByName(selectEl, targetName) { + // Try to match by visible name; fall back to first option. + const options = Array.from(selectEl.options); + const found = options.find(o => o.text.trim().toLowerCase() === String(targetName || '').trim().toLowerCase()); + if (found) { + selectEl.value = found.value; + } + } + + function installRow({ name = null, percent = 0 } = {}) { + const node = tpl.content.firstElementChild.cloneNode(true); + + // Hook up events + node.querySelector('.np-mix-delete').addEventListener('click', () => { + node.remove(); + refreshSum(); + }); + + const selectEl = node.querySelector('.np-mix-select'); + const percentEl = node.querySelector('.np-mix-percent'); + + // Default selection (by name) and percent + if (name) setSelectByName(selectEl, name); + percentEl.value = percent; + + // Input events + selectEl.addEventListener('change', () => { /* reserved for later linkage */ }); + percentEl.addEventListener('input', () => { + // Clamp and refresh + let v = parseFloat(percentEl.value); + if (isNaN(v)) v = 0; + v = Math.max(0, Math.min(100, Math.round(v))); + percentEl.value = v; + refreshSum(); + }); + + rowsContainer.appendChild(node); + } + + // Init with three defaults + const items = (defaults && defaults.items) ? defaults.items : []; + if (items.length) { + items.forEach(it => installRow({ name: it.name, percent: it.percent })); + } else { + // Fallback: create three blank rows + for (let i = 0; i < 3; i++) installRow(); + } + refreshSum(); + + // Add new blank row + addBtn.addEventListener('click', () => { + installRow({ name: null, percent: 0 }); + refreshSum(); + // Scroll to the new row on mobile for better UX + rowsContainer.lastElementChild?.scrollIntoView({ behavior: 'smooth', block: 'center' }); + }); + + // Prevent real submit for now (frontend only) + document.getElementById('np-mix-form')?.addEventListener('submit', (e) => { + e.preventDefault(); + // Later: wire to Turbo/JSON post. For now just a gentle nudge. + saveBtn.textContent = 'Enregistrer (backend à venir)'; + saveBtn.blur(); + }); + })(); + </script> + + <style> + /* Small touch targets & tidy spacing on mobile */ + @media (max-width: 576px) { + .np-mix-row { padding: .5rem; } + .np-mix-row .btn { padding: .25rem .5rem; } + } + </style> +<% end %> |