diff --git a/frontend/css/styles.css b/frontend/css/styles.css index c511f76..b3e822f 100644 --- a/frontend/css/styles.css +++ b/frontend/css/styles.css @@ -1452,6 +1452,11 @@ select { flex-wrap: wrap; gap: 6px; padding: 8px 14px; + cursor: pointer; +} +.nutrient-strip.nutrient-collapsed { + max-height: 36px; + overflow: hidden; } .nut-item { font-size: 12px; diff --git a/frontend/data/products_seed.db b/frontend/data/products_seed.db index 1bea9ca..66145d6 100644 Binary files a/frontend/data/products_seed.db and b/frontend/data/products_seed.db differ diff --git a/frontend/js/db.js b/frontend/js/db.js index fd12329..7e8daea 100644 --- a/frontend/js/db.js +++ b/frontend/js/db.js @@ -110,7 +110,9 @@ async function searchProducts(query, limit) { return select( `SELECT id, name, brand, category, weight_g, kcal_per_100g, protein_per_100g, fat_per_100g, carbs_per_100g, fiber_per_100g, - composition + composition, nutrients_guessed, + vit_a_mcg, vit_c_mg, vit_b1_mg, vit_b2_mg, vit_b3_mg, vit_e_mg, + calcium_mg, iron_mg, magnesium_mg, potassium_mg, sodium_mg, phosphorus_mg FROM products WHERE name_lower LIKE ?1 GROUP BY COALESCE(store_id, id) @@ -152,7 +154,8 @@ async function addProduct(p) { p.source ?? "manual", ] ); - return r.lastInsertId; + const rows = await select("SELECT id FROM products ORDER BY id DESC LIMIT 1"); + return rows[0]?.id; } // --------------------------------------------------------------------------- @@ -540,7 +543,10 @@ async function getSupplementTotalsForDate(date) { SUM(COALESCE(sl.dose_g,1) * s.molybdenum_mcg) as molybdenum_mcg, SUM(COALESCE(sl.dose_g,1) * s.iodine_mg) as iodine_mg, SUM(COALESCE(sl.dose_g,1) * s.phosphorus_mg) as phosphorus_mg, - SUM(COALESCE(sl.dose_g,1) * s.carotene_mcg) as carotene_mcg + SUM(COALESCE(sl.dose_g,1) * s.carotene_mcg) as carotene_mcg, + SUM(COALESCE(sl.dose_g,1) * s.omega3_mg) as omega3_mg, + SUM(COALESCE(sl.dose_g,1) * s.omega6_mg) as omega6_mg, + SUM(COALESCE(sl.dose_g,1) * s.omega9_mg) as omega9_mg FROM supplement_log sl JOIN supplements s ON sl.supplement_id = s.id WHERE sl.date = ?1`, @@ -1120,6 +1126,9 @@ async function runMigrations() { await addSuppCol("lutein_mg", "REAL"); // лютеин — no RDI await addSuppCol("lycopene_mg", "REAL"); // ликопин — no RDI await addSuppCol("carotene_mcg", "REAL"); // бета-каротин + await addSuppCol("omega3_mg", "REAL"); // омега-3 + await addSuppCol("omega6_mg", "REAL"); // омега-6 + await addSuppCol("omega9_mg", "REAL"); // омега-9 console.log("[db.js] recipe migrations done, checking seq repair..."); diff --git a/frontend/js/main.js b/frontend/js/main.js index f248f58..84015d3 100644 --- a/frontend/js/main.js +++ b/frontend/js/main.js @@ -338,7 +338,7 @@ async function renderToday(container) {

Нутриенты

-
+
@@ -430,6 +430,8 @@ async function renderToday(container) { const nutrients = calcDailyNutrients(food, suppTotals); dailyNutEl.innerHTML = renderNutrientChips(nutrients) || `
нет данных о нутриентах
`; + // Tap to expand/collapse + dailyNutEl.onclick = () => dailyNutEl.classList.toggle("nutrient-collapsed"); } container.querySelector("#add-weight-btn").onclick = () => showWeightModal(); @@ -893,11 +895,25 @@ function showFoodModal() {
- +
- - + + +
+
+
+ + +
+
+ + +
+
+ + +
@@ -925,9 +941,22 @@ function showFoodModal() { await DB.addFoodEntry(state.date, time, selectedProduct.id, amountG, null, unitId, unitAmount); } else { const desc = overlay.querySelector("#m-desc").value.trim(); - const kcal = parseFloat(overlay.querySelector("#m-kcal").value) || null; if (!desc) return false; - await DB.addFoodManual(state.date, time, desc, kcal); + const kcal = parseFloat(overlay.querySelector("#m-kcal").value) || null; + const protein = parseFloat(overlay.querySelector("#m-protein").value) || null; + const fat = parseFloat(overlay.querySelector("#m-fat").value) || null; + const carbs = parseFloat(overlay.querySelector("#m-carbs").value) || null; + const amount = rawAmount; + if (kcal || protein || fat || carbs) { + // Create a custom product in DB, then log it + const pid = await DB.addProduct({ + name: desc, kcal_per_100g: kcal, protein_per_100g: protein, + fat_per_100g: fat, carbs_per_100g: carbs, source: "manual", + }); + await DB.addFoodEntry(state.date, time, pid, amount, null); + } else { + await DB.addFoodManual(state.date, time, desc, kcal); + } } }); @@ -1242,6 +1271,18 @@ async function renderProducts(container) { function renderProdDetails(p) { const r = v => v != null ? v : "—"; + // Micronutrient chips for this product (per 100g) + const RDI = BMR.RDI; + const microChips = []; + for (const [key, rdi] of Object.entries(RDI)) { + if (!p[key]) continue; + const pct = Math.round(p[key] / rdi * 100); + if (pct < 1) continue; + const label = NUTRIENT_LABELS[key] || key; + const cls = pct < 30 ? "nut-low" : pct < 80 ? "nut-mid" : "nut-ok"; + microChips.push({ pct, html: `
${label} ${pct}%
` }); + } + microChips.sort((a, b) => a.pct - b.pct); return `
@@ -1250,6 +1291,7 @@ async function renderProducts(container) {
${r(p.carbs_per_100g)}У
${r(p.fiber_per_100g)}Кл
+ ${microChips.length ? `
${microChips.map(c => c.html).join("")}
` : ""} ${p.composition ? `
${esc(p.composition)}
` : ""}
`; @@ -3245,25 +3287,40 @@ async function showLogSupplementModal() { // --------------------------------------------------------------------------- const MICRO_FIELDS = [ - { key: "vit_a_mcg", label: "Вит A (мкг)" }, - { key: "vit_c_mg", label: "Вит C (мг)" }, - { key: "vit_d_mcg", label: "Вит D (мкг)" }, - { key: "vit_e_mg", label: "Вит E (мг)" }, - { key: "vit_k_mcg", label: "Вит K (мкг)" }, - { key: "vit_b1_mg", label: "B1 (мг)" }, - { key: "vit_b2_mg", label: "B2 (мг)" }, - { key: "vit_b3_mg", label: "B3 (мг)" }, - { key: "vit_b6_mg", label: "B6 (мг)" }, - { key: "vit_b12_mcg", label: "B12 (мкг)" }, - { key: "folate_mcg", label: "Фолат (мкг)" }, - { key: "calcium_mg", label: "Ca (мг)" }, - { key: "iron_mg", label: "Fe (мг)" }, - { key: "magnesium_mg", label: "Mg (мг)" }, - { key: "zinc_mg", label: "Zn (мг)" }, - { key: "copper_mg", label: "Cu (мг)" }, - { key: "selenium_mcg", label: "Se (мкг)" }, - { key: "potassium_mg", label: "K (мг)" }, - { key: "sodium_mg", label: "Na (мг)" }, + { key: "vit_a_mcg", label: "Вит A (мкг РЭ)" }, + { key: "vit_c_mg", label: "Вит C (мг)" }, + { key: "vit_d_mcg", label: "Вит D3 (мкг)" }, + { key: "vit_e_mg", label: "Вит E (мг ТЭ)" }, + { key: "vit_k_mcg", label: "Вит K (мкг)" }, + { key: "vit_b1_mg", label: "B1 (мг)" }, + { key: "vit_b2_mg", label: "B2 (мг)" }, + { key: "vit_b3_mg", label: "B3/PP (мг)" }, + { key: "vit_b5_mg", label: "B5 (мг)" }, + { key: "vit_b6_mg", label: "B6 (мг)" }, + { key: "vit_b7_mcg", label: "B7/биотин (мкг)" }, + { key: "folate_mcg", label: "B9/фолат (мкг)" }, + { key: "vit_b12_mcg", label: "B12 (мкг)" }, + { key: "calcium_mg", label: "Ca (мг)" }, + { key: "magnesium_mg", label: "Mg (мг)" }, + { key: "iron_mg", label: "Fe (мг)" }, + { key: "zinc_mg", label: "Zn (мг)" }, + { key: "copper_mg", label: "Cu (мг)" }, + { key: "selenium_mcg", label: "Se (мкг)" }, + { key: "potassium_mg", label: "K (мг)" }, + { key: "sodium_mg", label: "Na (мг)" }, + { key: "phosphorus_mg", label: "P (мг)" }, + { key: "iodine_mg", label: "Йод (мг)" }, + { key: "chromium_mg", label: "Хром (мг)" }, + { key: "manganese_mg", label: "Mn (мг)" }, + { key: "molybdenum_mcg", label: "Mo (мкг)" }, + { key: "carotene_mcg", label: "b-каротин (мкг)" }, + { key: "omega3_mg", label: "Омега-3 (мг)" }, + { key: "omega6_mg", label: "Омега-6 (мг)" }, + { key: "omega9_mg", label: "Омега-9 (мг)" }, + { key: "silicon_mg", label: "Кремний (мг)" }, + { key: "vanadium_mg", label: "Ванадий (мг)" }, + { key: "lutein_mg", label: "Лютеин (мг)" }, + { key: "lycopene_mg", label: "Ликопин (мг)" }, ]; async function renderSupplements(container) {