summaryrefslogtreecommitdiff
path: root/app/views/dashboard/_nutrient_profile_allocator.html.erb
diff options
context:
space:
mode:
Diffstat (limited to 'app/views/dashboard/_nutrient_profile_allocator.html.erb')
-rw-r--r--app/views/dashboard/_nutrient_profile_allocator.html.erb187
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&nbsp;: <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 %>
Copyright 2019--2025 Marius PETER