"use strict"; const host = 'https://local.hvatilo.ru:8055'; // Returns a function, that, when invoked, will only be triggered at most once // during a given window of time. Normally, the throttled function will run // as much as it can, without ever going more than once per `wait` duration; // but if you'd like to disable the execution on the leading edge, pass // `{leading: false}`. To disable execution on the trailing edge, ditto. function throttle(func, wait, options) { var context, args, result; var timeout = null; var previous = 0; if (!options) options = {}; var later = function() { previous = options.leading === false ? 0 : Date.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; return function() { var now = Date.now(); if (!previous && options.leading === false) previous = now; var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); timeout = null; } previous = now; result = func.apply(context, args); if (!timeout) context = args = null; } else if (!timeout && options.trailing !== false) { timeout = setTimeout(later, remaining); } return result; }; } function refreshToken() { return fetch(host + '/api/guest/refresh_cookie', {method: 'POST', credentials: 'include'}) .then(response => { if (response.status === 400) { return fetch(host + '/api/guest/clear_cookie', {method: 'POST', credentials: 'include'}) .then(async (d) => { await new Promise(resolve => setTimeout(resolve, 100)); return initToken(); }); } return response.json(); }); } function initToken() { return fetch(host + '/api/guest/init_cookie?utm_source=hvatilo_promo', {method: 'POST', credentials: 'include'}).then(response => { return response.json(); }); } const requestWithRefresh = async function(url, options) { return fetch(url, {...options, credentials: 'include'}) .then(async response => response.json()) .then(async data => { if (data.error_message === 'need_refresh') { return refreshToken() .then((r) => { console.log('url', url); return fetch(url, {...options, credentials: 'include'}).then(response => response.json()); }); } return data; }) } const replaceProduct = (async function(product_name) { return requestWithRefresh(host + '/api/promo/replace_item', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({product_name}) }) .then(async () => { updateCart(); }) }) const deleteProduct = (async function(product_name) { return requestWithRefresh(host + '/api/promo/delete_item', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({product_name}) }) }) const updateCart = throttle(async function() { document.getElementById('summary-loader').classList.remove('hidden'); document.getElementById('summary-details').classList.add('hidden'); return requestWithRefresh(host + '/api/promo/get_updates', {headers: { 'Content-Type': 'application/json' }}).then(d => { if (d.status !== 'complete') { initialData_g.excess = d.payload.excess; initialData_g.precision = d.payload.precision; if (d.status === 'clear') { allProducts = []; visibleProducts = []; renderProducts(); allProducts = d.payload.chunk; visibleProducts = d.payload.chunk; } if (d.status === 'data') { d.payload.chunk.forEach(addedProduct => { let idx = allProducts.findIndex(p => p.name === addedProduct.name) if (idx === -1) { allProducts.push({...addedProduct, image: addedProduct.image?.replace('{SIZE}', '65,fit')}); if (visibleProducts.length === allProducts.length - 1) { visibleProducts = allProducts; } } else { allProducts[idx].n += addedProduct.n; if (idx < visibleProducts.length) { visibleProducts[idx] = allProducts[idx]; } } }) } if (d.status !== 'waiting') { renderProducts(); } updateCart(); } else { document.getElementsByClassName('cart-footer')[0].classList.add('visible'); document.getElementById('summary-loader').classList.add('hidden'); document.getElementById('summary-details').classList.remove('hidden'); } }) .catch(() => { updateCart(); }) }, 3000); const setOption = (key, value) => { return requestWithRefresh(host + '/api/promo/set_option', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({[key]: value}) }) .then(async d => { wholeRender(await loadInitData()); updateCart(); return d; }) .catch(() => { console.log('start catch') return new Promise((resolve, reject) => { setTimeout(() => { console.log('end catch') reject('end catch') }, 1000); }) }) } const mockData = { "existing": 5432, "available_groups": [ "Приправы", "Овощи", "Напитки", "Сыр", "Птица в лотках", "Сладости", "Подсластители", "Кисломолочные", "Масло для готовки", "Ягоды", "Соусы", "Для готовки", "Горячие напитки", "Засоленные", "Морепродукты", "Кисломолочное", "Кисломолочные напитки", "Алкоголь", "Хлебцы", "Сладкое-сладкое", "Капуста", "Детское питание", "Овощи для готовки", "Гарнир", "Хлеб", "Консервы", "Суп", "Острое", "Вермишель", "Рыба", "Сливки", "Грибы", "Салат", "Субпродукты", "Картофель", "Молочные", "Морковь", "Жевательная резинка", "Фрукты", "Зелень", "Специальное питание", "Яйца" ], "initial_groups": [ "Овощи", "Овощи для готовки", "Молочные", "Птица в лотках", "Гарнир", "Хлеб", "Кисломолочные", "Яйца", "Рыба", "Масло для готовки" ], "days": 7, "users": [ { "id": -1, "name": "Иван", "gender": "m", "age": 30, "weight": 87, "height": 180, "avoid": [ "Детское питание" ], "favorite": [] }, { "id": -2, "name": "Мария", "gender": "f", "age": 28, "weight": 65, "height": 160, "avoid": [ "Детское питание" ], "favorite": [ "Яблоко" ] }, { "id": -3, "name": "Пётр", "gender": "m", "age": 10, "weight": 45, "height": 140, "avoid": [ "Алкоголь", "Засоленные", "Острое" ], "favorite": [] } ], "cart": [ [ { "name": "Овощи для жарки, 1кг", "n": 1200, "price": 23.799, "total_price": 142.79399999999998, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/64\/79\/226479.jpg", "unit": "g" }, { "name": "Картофель фри, 1кг", "n": 300, "price": 21.799, "total_price": 43.598, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/66\/06\/226606.jpg", "unit": "g" }, { "name": "Свекла соломка уп, 300-500г", "n": 400, "price": 27.99, "total_price": 111.96, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/46\/04\/384604.jpg", "unit": "g" }, { "name": "Молоко ультрапастеризованное Ам-Ам Мамина забота обогащённое витамином C, 8+ месяцев, 205г", "n": 1, "price": 47.49, "total_price": 47.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/23\/26\/4852326.jpg", "unit": "pcs" }, { "name": "Смесь Волшебное дерево спагетти болоньезе, 30г", "n": 1, "price": 72.49, "total_price": 72.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/98\/53\/4399853.jpg", "unit": "pcs" }, { "name": "Хренодер ФЭГ ядрёный, 240г", "n": 1, "price": 131.99, "total_price": 131.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/40\/74\/274074.jpg", "unit": "pcs" }, { "name": "Огурцы свежие с укропом, вес", "n": 2000, "price": 41.99, "total_price": 419.90000000000003, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/36\/73\/483673.jpg", "unit": "g" }, { "name": "Продукт кисломолочный творожный Exponenta Топленое молоко 0%, 250г", "n": 2, "price": 191.99, "total_price": 383.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/03\/28\/4180328.jpg", "unit": "pcs" } ], [ { "name": "Мороженое сливочное Петрохолод Ленинградское Ванильное в шоколадной глазури, 80г", "n": 2, "price": 47.99, "total_price": 95.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/17\/67\/2091767.jpg", "unit": "pcs" }, { "name": "Капуста белокочанная резаная уп, 400-800г", "n": 1800, "price": 31.99, "total_price": 383.88, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/60\/12\/4476012.jpg", "unit": "g" }, { "name": "Крем-суп протеиновый Bionova Томатный, 20г", "n": 1, "price": 71.99, "total_price": 71.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/66\/98\/546698.jpg", "unit": "pcs" }, { "name": "Креветки королевские варёно-мороженые Agama очищенные, 300г", "n": 1, "price": 769.99, "total_price": 769.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/13\/54\/1221354.jpg", "unit": "pcs" }, { "name": "Чипсы нори Naitori со вкусом краба, 3г", "n": 1, "price": 54.99, "total_price": 54.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/65\/19\/4276519.jpg", "unit": "pcs" }, { "name": "Салат Белая Дача Botanicum мини руккола, айсберг, романо, 75г", "n": 1, "price": 184.99, "total_price": 184.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/51\/87\/4055187.jpg", "unit": "pcs" }, { "name": "Каша рисовая безмолочная HiPP Organic фрукты-злаки с 5 месяцев, 190г", "n": 1, "price": 216.99, "total_price": 216.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/48\/03\/4224803.jpg", "unit": "pcs" }, { "name": "Стебель сельдерея в нарезке, вес", "n": 300, "price": 45.999, "total_price": 137.997, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/28\/08\/422808.jpg", "unit": "g" }, { "name": "Йогурт двухслойный Савушкин манго 2%, 120г", "n": 1, "price": 32.99, "total_price": 32.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/67\/70\/4596770.jpg", "unit": "pcs" } ], [ { "name": "Котлеты куриные Троекурово, 500г", "n": 3, "price": 109.99, "total_price": 329.96999999999997, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/71\/57\/407157.jpg", "unit": "pcs" }, { "name": "Макаронные изделия Ваш выбор Перья, 400г", "n": 2, "price": 19.99, "total_price": 39.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/27\/72\/3422772.jpg", "unit": "pcs" }, { "name": "Хлеб украинский новый Пеко в упаковке, 650г", "n": 2, "price": 25.99, "total_price": 51.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/27\/15\/412715.jpg", "unit": "pcs" }, { "name": "Фарш из минтая охлаждённый, вес", "n": 600, "price": 59.999, "total_price": 359.994, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/82\/35\/318235.jpg", "unit": "g" }, { "name": "Мороженое сливочное На сливочках Ежевика-Малина, 85г", "n": 6, "price": 92.99, "total_price": 557.9399999999999, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/62\/38\/4816238.jpg", "unit": "pcs" }, { "name": "Салат Белая Дача Пряная рукола, 50г", "n": 3, "price": 216.99, "total_price": 650.97, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/37\/03\/4663703.jpg", "unit": "pcs" }, { "name": "Семена горчицы Мастер Дак, 15 г", "n": 1, "price": 6.49, "total_price": 6.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/82\/27\/4858227.jpg", "unit": "pcs" }, { "name": "Конфета 35 со вкусом Шоколада, 20г", "n": 2, "price": 19.99, "total_price": 39.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/54\/45\/3505445.jpg", "unit": "pcs" }, { "name": "Сыр Зеленый в порошке с пажитником 1,4%, 50г", "n": 2, "price": 173.99, "total_price": 347.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/33\/28\/3943328.jpg", "unit": "pcs" }, { "name": "Смесь грибная быстрозамороженная И Зимой И Летом, 300г", "n": 1, "price": 351.99, "total_price": 351.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/82\/32\/4838232.jpg", "unit": "pcs" }, { "name": "Паста соевая Miso тёмная, 1кг", "n": 1, "price": 449.99, "total_price": 449.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/43\/13\/424313.jpg", "unit": "pcs" }, { "name": "Клюква дикорастущая Глобус, 300г", "n": 1, "price": 609.99, "total_price": 609.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/90\/39\/3389039.jpg", "unit": "pcs" }, { "name": "Соус соевый Тай-Со Классический, 250мл", "n": 1, "price": 69.99, "total_price": 69.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/92\/03\/2489203.jpg", "unit": "pcs" }, { "name": "Смесь овощная Мексиканская, 1кг", "n": 1200, "price": 27.399, "total_price": 164.394, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/63\/91\/226391.jpg", "unit": "g" }, { "name": "Мороженое сливочное Ля Фам Сладкий лимон, 70г", "n": 1, "price": 51.49, "total_price": 51.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/69\/64\/4296964.jpg", "unit": "pcs" }, { "name": "Каша мультизлаковая безмолочная Сады Придонья яблоко-банан-малина с 6 месяцев, 0,125л", "n": 1, "price": 32.99, "total_price": 32.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/34\/03\/713403.jpg", "unit": "pcs" }, { "name": "Кальмар гигантский Глобус без кожицы, 185г", "n": 1, "price": 199.99, "total_price": 199.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/27\/07\/4752707.jpg", "unit": "pcs" }, { "name": "Треска тихоокеанская мороженая Глобус филе-кусок с кожей, 500г", "n": 1, "price": 709.99, "total_price": 709.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/99\/73\/1389973.jpg", "unit": "pcs" }, { "name": "Тунец полосатый натуральный Морской котик стерилизованный филе, 170г", "n": 1, "price": 203.99, "total_price": 203.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/86\/75\/508675.jpg", "unit": "pcs" }, { "name": "Крем-суп протеиновый Bionova Сырный, 20г", "n": 1, "price": 72.99, "total_price": 72.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/67\/00\/546700.jpg", "unit": "pcs" }, { "name": "Салат Tango mix Белая Дача рукола, мангольд, 50г", "n": 1, "price": 218.99, "total_price": 218.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/11\/08\/4401108.jpg", "unit": "pcs" }, { "name": "Картофель отварной уп, 500-650г", "n": 2400, "price": 35.99, "total_price": 431.88, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/60\/81\/4476081.jpg", "unit": "g" }, { "name": "Йогурт греческий Venn's натуральный 0,1%, 130г", "n": 1, "price": 56.99, "total_price": 56.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/02\/47\/4160247.jpg", "unit": "pcs" } ], [ { "name": "Лук репчатый, вес", "n": 900, "price": 4.899, "total_price": 44.091, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/74\/39\/287439.jpg", "unit": "g" }, { "name": "Какао+ Экологика стевия + пребиотик, 125г", "n": 1, "price": 569.99, "total_price": 569.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/64\/31\/846431.jpg", "unit": "pcs" }, { "name": "Дрожжи хлебопекарные Ваш выбор сухие, 12г", "n": 3, "price": 8.99, "total_price": 26.97, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/79\/28\/4157928.jpg", "unit": "pcs" }, { "name": "Смесь овощная Лечо, 1кг", "n": 1200, "price": 27.299, "total_price": 163.79399999999998, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/64\/80\/226480.jpg", "unit": "g" }, { "name": "Огурцы солёные Традиции вкуса, 300г", "n": 1, "price": 121.99, "total_price": 121.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/81\/54\/868154.jpg", "unit": "pcs" }, { "name": "Маршмеллоу Мини Guandy белый ванильный, 75г", "n": 1, "price": 94.49, "total_price": 94.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/86\/03\/1578603.jpg", "unit": "pcs" }, { "name": "Перец красный Мастер Дак молотый, 10г", "n": 1, "price": 8.99, "total_price": 8.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/23\/41\/252341.jpg", "unit": "pcs" }, { "name": "Молоко сгущенное цельное Алексеевское с сахаром, стик 8,5%, 7г", "n": 1, "price": 4.99, "total_price": 4.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/56\/82\/4095682.jpg", "unit": "pcs" }, { "name": "Овощное ассорти острое ФЭГ, 800г", "n": 1, "price": 195.99, "total_price": 195.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/05\/48\/300548.jpg", "unit": "pcs" }, { "name": "Концентрированный сухой бульон Ассорти Kruglie Sutki Коллаген, 75г", "n": 1, "price": 749.99, "total_price": 749.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/10\/08\/prd_810461008.jpg", "unit": "pcs" }, { "name": "Хлебцы экструзионные Три Грации с томатом и прованскими травами, 55г", "n": 1, "price": 43.49, "total_price": 43.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/63\/61\/4106361.jpg", "unit": "pcs" }, { "name": "Соус соевый Yatomi, 30г", "n": 1, "price": 31.99, "total_price": 31.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/83\/72\/3778372.jpg", "unit": "pcs" }, { "name": "Помидоры протертые Burcu, 350г", "n": 1, "price": 204.99, "total_price": 204.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/09\/82\/4400982.jpg", "unit": "pcs" }, { "name": "Чеснок резаный LiViAnTa, 100г", "n": 1, "price": 203.99, "total_price": 203.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/89\/78\/4818978.jpg", "unit": "pcs" }, { "name": "Творог обезжиренный Рузское молоко, 200г", "n": 1, "price": 145.99, "total_price": 145.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/46\/06\/644606.jpg", "unit": "pcs" } ], [ { "name": "Молоко из Вологды с витаминами и йодом для хороших оценок 3,2%, 200мл", "n": 9, "price": 39.99, "total_price": 359.91, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/02\/64\/4160264.jpg", "unit": "pcs" }, { "name": "Продукт йогуртный Нежный с соком клубники 1,2%, 100г", "n": 8, "price": 23.49, "total_price": 187.92, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/50\/67\/4895067.jpg", "unit": "pcs" }, { "name": "Яйца куриные столовые Синявинское С0 белое, 30г", "n": 2, "price": 389.99, "total_price": 779.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/38\/51\/4233851.jpg", "unit": "pcs" }, { "name": "Шеи цыплят-бройлеров охлаждённые Троекурово, 500г", "n": 3, "price": 109.99, "total_price": 329.96999999999997, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/79\/84\/3967984.jpg", "unit": "pcs" }, { "name": "Хлебцы хрустящие Ого! Столовые, 75г", "n": 4, "price": 32.99, "total_price": 131.96, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/25\/46\/3432546.jpg", "unit": "pcs" }, { "name": "Творог обезжиренный Козельский Живой, 180г", "n": 2, "price": 103.99, "total_price": 207.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/24\/06\/1502406.jpg", "unit": "pcs" }, { "name": "Мята перечная сушёная Приправыч, 6г", "n": 1, "price": 23.99, "total_price": 23.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/91\/92\/4359192.jpg", "unit": "pcs" }, { "name": "Микрозелень редиса санго MaxiFermer, 50г", "n": 4, "price": 215.99, "total_price": 863.96, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/54\/88\/4385488.jpg", "unit": "pcs" }, { "name": "Перец Mikado Халапеньо резанный зеленый, 350мл", "n": 1, "price": 217.99, "total_price": 217.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/78\/05\/4887805.jpg", "unit": "pcs" }, { "name": "Ветчина Сочная Мираторг нарезка, 160г", "n": 1, "price": 179.99, "total_price": 179.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/04\/69\/3460469.jpg", "unit": "pcs" }, { "name": "Лук зелёный Глобус перо, 250г", "n": 1, "price": 171.99, "total_price": 171.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/19\/63\/271963.jpg", "unit": "pcs" }, { "name": "Щавель Богослово Арго свежий, 100г", "n": 3, "price": 217.99, "total_price": 653.97, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/77\/01\/277701.jpg", "unit": "pcs" }, { "name": "Яблоки Айдаред, вес", "n": 1600, "price": 20.999000000000002, "total_price": 167.99200000000002, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/10\/40\/3991040.jpg", "unit": "g" }, { "name": "Яблочные слайсы Яблоков, 25г", "n": 3, "price": 96.49, "total_price": 289.46999999999997, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/32\/15\/3693215.jpg", "unit": "pcs" }, { "name": "Борщ быстрорастворимый Кнорр Чашка Супа с сухариками, 14,8г", "n": 2, "price": 28.49, "total_price": 56.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/70\/36\/4457036.jpg", "unit": "pcs" }, { "name": "Весенние овощи, 1кг", "n": 8100, "price": 29.199, "total_price": 788.373, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/64\/70\/226470.jpg", "unit": "g" }, { "name": "Васаби Глобус, 5г", "n": 2, "price": 19.99, "total_price": 39.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/77\/17\/4757717.jpg", "unit": "pcs" }, { "name": "Желе Jele Beautie cо вкусом клубники, 140г", "n": 2, "price": 169.99, "total_price": 339.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/15\/40\/4661540.jpg", "unit": "pcs" }, { "name": "Шампанское Champagne M. Ferat & Fils Brut Blanc de Blancs Premier Cru белое брют 12% алк., Франция, 750мл", "n": 1, "price": 8869.99, "total_price": 8869.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/44\/86\/4704486.jpg", "unit": "pcs" }, { "name": "Пастила Пастилушка яблоко-вишня, 1кг", "n": 50, "price": 73.999, "total_price": 73.999, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/44\/09\/4274409.jpg", "unit": "g" }, { "name": "Перец Халапеньо Kühne резаный, 330г", "n": 1, "price": 478.99, "total_price": 478.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/51\/55\/375155.jpg", "unit": "pcs" }, { "name": "Салатный микс Браво Белая Дача Шпинат, мангольд, рукола, 50г", "n": 1, "price": 213.99, "total_price": 213.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/36\/97\/4663697.jpg", "unit": "pcs" }, { "name": "Закуска Пиканта Астраханская, 460г", "n": 1, "price": 161.99, "total_price": 161.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/14\/17\/3421417.jpg", "unit": "pcs" }, { "name": "Семя горчицы Приправыч, 20г", "n": 1, "price": 13.49, "total_price": 13.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/91\/74\/4359174.jpg", "unit": "pcs" }, { "name": "Напиток спортивный ProteinRex BCAA 6000мг бабл-гам, 500мл", "n": 2, "price": 109.99, "total_price": 219.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/00\/15\/4740015.jpg", "unit": "pcs" }, { "name": "Дрожжи универсальные Dr. Bakers быстродействующие, 7г", "n": 1, "price": 29.99, "total_price": 29.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/22\/31\/3612231.jpg", "unit": "pcs" }, { "name": "Сироп низкокалорийный Mr. Djemius Zero кленовый без сахара, 330г", "n": 1, "price": 351.99, "total_price": 351.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/40\/14\/4314014.jpg", "unit": "pcs" }, { "name": "Тыква уп, 0,4-1кг", "n": 800, "price": 55.99, "total_price": 223.96, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/18\/37\/411837.jpg", "unit": "g" }, { "name": "Ветчина варёная по-клински Клинский Для тостов, нарезка, 120г", "n": 2, "price": 121.99, "total_price": 243.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/72\/11\/3007211.jpg", "unit": "pcs" }, { "name": "Шиповник сушёный ФруктОрешки, 200г", "n": 1, "price": 106.99, "total_price": 106.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/58\/79\/255879.jpg", "unit": "pcs" }, { "name": "Чипсы нори Sen Soy Оригинал, 4,5г", "n": 3, "price": 81.49, "total_price": 244.46999999999997, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/21\/64\/4042164.jpg", "unit": "pcs" }, { "name": "Грибное ассорти в маринаде Lutik, 530г", "n": 1, "price": 264.99, "total_price": 264.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/72\/51\/3497251.jpg", "unit": "pcs" }, { "name": "Морковь отварная кубик уп, 450-550г", "n": 1200, "price": 35.99, "total_price": 215.94, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/46\/13\/384613.jpg", "unit": "g" }, { "name": "Баклажаны резаные, вес", "n": 800, "price": 47.999, "total_price": 191.996, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/28\/01\/422801.jpg", "unit": "g" }, { "name": "Гречка Preston с мясом, 40г", "n": 1, "price": 32.49, "total_price": 32.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/55\/57\/1225557.jpg", "unit": "pcs" }, { "name": "Салака мороженая, вес", "n": 100, "price": 31.599, "total_price": 31.599, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/49\/13\/264913.jpg", "unit": "g" }, { "name": "Печенье StMichel сливочное с шоколадом, 132г", "n": 1, "price": 519.99, "total_price": 519.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/22\/80\/4842280.jpg", "unit": "pcs" }, { "name": "Дрожжи Парфэ Декор быстродействующие, 11г", "n": 1, "price": 18.49, "total_price": 18.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/83\/18\/4868318.jpg", "unit": "pcs" }, { "name": "Микс водорослей Морская капуста с фукусом Здоровье для салата, 400г", "n": 1, "price": 182.99, "total_price": 182.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/29\/00\/1582900.jpg", "unit": "pcs" }, { "name": "Чипсы нори Глобус Токи поки, 5г", "n": 1, "price": 62.99, "total_price": 62.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/57\/65\/4435765.jpg", "unit": "pcs" }, { "name": "Шиповник сушёный Isfarafood отборный, 500г", "n": 1, "price": 238.99, "total_price": 238.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/08\/01\/700801.jpg", "unit": "pcs" }, { "name": "Морковь резаная палочками уп, 200-300г", "n": 100, "price": 39.99, "total_price": 39.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/46\/01\/384601.jpg", "unit": "g" }, { "name": "Молоко Parmalat 0,5%, 1л", "n": 1, "price": 149.99, "total_price": 149.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/21\/68\/3872168.jpg", "unit": "pcs" } ], [ { "name": "Путассу замороженная неразделанная, вес", "n": 2000, "price": 16.899, "total_price": 168.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/91\/35\/4289135.jpg", "unit": "g" }, { "name": "Приправа для гриля Мастер Дак, 15г", "n": 2, "price": 14.49, "total_price": 28.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/53\/55\/4725355.jpg", "unit": "pcs" }, { "name": "Паста соевая мисо Wanjia Shinshumiso светлая, 1кг", "n": 1, "price": 458.99, "total_price": 458.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/26\/72\/3832672.jpg", "unit": "pcs" }, { "name": "Ламинария Вегана, 150г", "n": 1, "price": 629.99, "total_price": 629.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/85\/35\/148535.jpg", "unit": "pcs" }, { "name": "Псиллиум Look Green, 120г", "n": 1, "price": 539.99, "total_price": 539.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/87\/76\/4838776.jpg", "unit": "pcs" }, { "name": "Какао-напиток растворимый Шокомишка, 20*14г", "n": 4, "price": 15.49, "total_price": 61.96, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/30\/61\/4773061.jpg", "unit": "pcs" }, { "name": "Соус Терияки Yatomi, 30г", "n": 5, "price": 19.99, "total_price": 99.94999999999999, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/75\/26\/3607526.jpg", "unit": "pcs" }, { "name": "Васаби Yatomi, 5г", "n": 3, "price": 21.49, "total_price": 64.47, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/83\/74\/3778374.jpg", "unit": "pcs" }, { "name": "Шампиньоны резаные, 1кг", "n": 2000, "price": 31.999000000000002, "total_price": 319.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/66\/05\/226605.jpg", "unit": "g" }, { "name": "Халапеньо красный Глобус, 360г", "n": 2, "price": 146.99, "total_price": 293.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/45\/28\/4324528.jpg", "unit": "pcs" }, { "name": "Капуста квашеная, вес", "n": 200, "price": 39.99, "total_price": 79.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/07\/57\/470757.jpg", "unit": "g" }, { "name": "Крем-суп протеиновый Bionova Грибной, 20г", "n": 3, "price": 65.49, "total_price": 196.46999999999997, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/66\/96\/546696.jpg", "unit": "pcs" }, { "name": "Молоко для питания детей Эконива с 3 лет 3,2%, 200мл", "n": 8, "price": 39.99, "total_price": 319.92, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/70\/45\/617045.jpg", "unit": "pcs" }, { "name": "Йогурт Простоквашино с клубникой 2,9%, 110г", "n": 4, "price": 29.99, "total_price": 119.96, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/42\/39\/3474239.jpg", "unit": "pcs" }, { "name": "Конфеты вафельные Обыкновенное чудо сливочное, 55г", "n": 2, "price": 39.49, "total_price": 78.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/46\/99\/144699.jpg", "unit": "pcs" }, { "name": "Макаронные изделия Ваш выбор Рожки, 400г", "n": 2, "price": 18.99, "total_price": 37.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/20\/43\/3952043.jpg", "unit": "pcs" }, { "name": "Огурцы соленые корнишоны, вес", "n": 200, "price": 45.99, "total_price": 91.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/86\/07\/488607.jpg", "unit": "g" }, { "name": "Тефтели из мяса цыплёнка-бройлера Троекурово, 350г", "n": 1, "price": 139.99, "total_price": 139.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/85\/06\/3068506.jpg", "unit": "pcs" }, { "name": "Мороженое сливочное Ла-ла-бу-бу стакан вафельный шоко-бабл с шариками, 70г", "n": 2, "price": 81.49, "total_price": 162.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/63\/22\/4816322.jpg", "unit": "pcs" }, { "name": "Пастила Пастилушка кусочки, 40г", "n": 1, "price": 36.49, "total_price": 36.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/44\/11\/4274411.jpg", "unit": "pcs" }, { "name": "Смесь для пудинга Haas Шоколадный, 40г", "n": 1, "price": 38.99, "total_price": 38.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/71\/91\/3927191.jpg", "unit": "pcs" }, { "name": "Батончик фруктовый Pikki Чернослив, 25г", "n": 1, "price": 54.49, "total_price": 54.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/59\/84\/915984.jpg", "unit": "pcs" }, { "name": "Стрипсы куриные Троекурово острые, 350г", "n": 1, "price": 199.99, "total_price": 199.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/23\/28\/1342328.jpg", "unit": "pcs" }, { "name": "Молоко обезжиренное Viola UHT 0,05%, 971мл", "n": 2, "price": 149.99, "total_price": 299.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/20\/70\/3902070.jpg", "unit": "pcs" }, { "name": "Свёкла отварная уп, 500-600г", "n": 400, "price": 35.99, "total_price": 71.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/64\/71\/1156471.jpg", "unit": "g" } ], [ { "name": "Хлеб пшеничный белый Пеко, 380г", "n": 1, "price": 21.99, "total_price": 21.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/90\/88\/3829088.jpg", "unit": "pcs" }, { "name": "Чипсы из морской капусты Kakao Friends классические, в ассортименте, 2г", "n": 7, "price": 59.49, "total_price": 416.43, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/15\/37\/3801537.jpg", "unit": "pcs" }, { "name": "Цикорий растворимый Elite без кофеина, 75г", "n": 1, "price": 76.99, "total_price": 76.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/11\/03\/2931103.jpg", "unit": "pcs" }, { "name": "Пастила Зеленика финиковая, 5шт, 60г", "n": 1, "price": 88.49, "total_price": 88.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/68\/34\/4736834.jpg", "unit": "pcs" }, { "name": "Протертая мякоть томатов Пиканта с базиликом Пассата, 220г", "n": 2, "price": 104.99, "total_price": 209.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/25\/96\/4762596.jpg", "unit": "pcs" }, { "name": "Салат Латук свежий листовой в горшочке, 100г", "n": 3, "price": 82.49, "total_price": 247.46999999999997, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/98\/29\/4979829.jpg", "unit": "pcs" }, { "name": "Грибы лисички сухие Грибной дождь, 30г", "n": 1, "price": 483.99, "total_price": 483.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/65\/56\/4816556.jpg", "unit": "pcs" }, { "name": "Микрозелень подсолнечника MaxiFermer, 50г", "n": 2, "price": 215.99, "total_price": 431.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/54\/92\/4385492.jpg", "unit": "pcs" }, { "name": "Карамель Кремка с молочным вкусом, вес", "n": 90, "price": 37.39, "total_price": 112.17, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/80\/91\/4468091.jpg", "unit": "g" }, { "name": "Продукт йогуртный Сливочное лакомство Fruttis в ассортименте: Клубника, Персик 5%, 115г", "n": 5, "price": 36.49, "total_price": 182.45000000000002, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/33\/89\/4143389.jpg", "unit": "pcs" }, { "name": "Творог мягкий обезжиренный Простоквашино, 120г", "n": 3, "price": 47.99, "total_price": 143.97, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/00\/33\/4260033.jpg", "unit": "pcs" }, { "name": "Чипсы нори Naitori со вкусом кимчи, 3г", "n": 3, "price": 52.99, "total_price": 158.97, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/65\/21\/4276521.jpg", "unit": "pcs" }, { "name": "Набор для тушения из цыплят-бройлеров Верхневолжская птицефабрика уп, 0,8-1,1кг", "n": 2500, "price": 99.99, "total_price": 999.9, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/82\/33\/3168233.jpg", "unit": "g" }, { "name": "Хлеб Станичный, половинка, 350г", "n": 1, "price": 47.49, "total_price": 47.49, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/36\/97\/403697.jpg", "unit": "pcs" }, { "name": "Паприка Мастер Дак молотая, 10г", "n": 2, "price": 9.99, "total_price": 19.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/23\/43\/252343.jpg", "unit": "pcs" }, { "name": "Смесь компотная, 1кг", "n": 450, "price": 39.999, "total_price": 119.99700000000001, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/58\/02\/4235802.jpg", "unit": "g" }, { "name": "Лук зелёный Глобус, 100г", "n": 2, "price": 89.99, "total_price": 179.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/09\/59\/380959.jpg", "unit": "pcs" }, { "name": "Суп белковый Ironman Fit куриный с ароматными травами, 20г", "n": 2, "price": 93.49, "total_price": 186.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/75\/87\/1867587.jpg", "unit": "pcs" }, { "name": "Цветная капуста, 1кг", "n": 1200, "price": 34.599000000000004, "total_price": 207.59400000000002, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/74\/50\/377450.jpg", "unit": "g" }, { "name": "Молоко из Вологды для детей с 3 лет 3,2%, 200мл", "n": 3, "price": 39.99, "total_price": 119.97, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/02\/65\/4160265.jpg", "unit": "pcs" }, { "name": "Отруби овсяные Мистраль Энергия и бодрость, 30г", "n": 1, "price": 59.99, "total_price": 59.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/50\/98\/1785098.jpg", "unit": "pcs" }, { "name": "Икра трески Беринг подкопченная, 125г", "n": 1, "price": 168.99, "total_price": 168.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/05\/68\/260568.jpg", "unit": "pcs" }, { "name": "Ветчина Мясной Дом Бородина в синюге уп, 0,9-1,1кг", "n": 50, "price": 999.99, "total_price": 999.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/73\/17\/3937317.jpg", "unit": "g" }, { "name": "Продукт йогуртный Нежный с соком вишни 1,2%, 100г", "n": 2, "price": 23.99, "total_price": 47.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/50\/63\/4895063.jpg", "unit": "pcs" }, { "name": "Хамса пряного посола, вес", "n": 100, "price": 29.99, "total_price": 29.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/26\/37\/322637.jpg", "unit": "g" }, { "name": "Печенье протеиновое Butter Wave сливочное, 36г", "n": 1, "price": 103.99, "total_price": 103.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/62\/32\/3786232.jpg", "unit": "pcs" }, { "name": "Килька пряного посола, вес", "n": 50, "price": 34.99, "total_price": 34.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/27\/91\/422791.jpg", "unit": "g" }, { "name": "Масло сливочное Лебедяньмолоко Крестьянское 72,5%, 180г", "n": 1, "price": 169.99, "total_price": 169.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/19\/38\/4461938.jpg", "unit": "pcs" }, { "name": "Отбивная из индейки охлажденная Пава Пава, 300г", "n": 3, "price": 309.99, "total_price": 929.97, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/48\/32\/3844832.jpg", "unit": "pcs" }, { "name": "Свёкла отварная соломка, вес", "n": 450, "price": 35.999, "total_price": 107.99700000000001, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/60\/65\/4476065.jpg", "unit": "g" }, { "name": "Лук красный, кольца уп, 400-600г", "n": 200, "price": 34.99, "total_price": 69.98, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/46\/03\/384603.jpg", "unit": "g" }, { "name": "Крупа из полбы Чёрный хлеб дроблёная, 500г", "n": 1, "price": 285.99, "total_price": 285.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/96\/65\/689665.jpg", "unit": "pcs" }, { "name": "Молоко безлактозное Protein Max Molvest 0,5%, 450г", "n": 1, "price": 114.99, "total_price": 114.99, "currency": "rub", "image": "https:\/\/hvatilo.ru\/static\/retail\/ru\/image.globus\/{SIZE}\/mobile\/26\/27\/4872627.jpg", "unit": "pcs" } ] ], "excess": 0.5362191665220106, "precision": 98.14517264634198 }; const loadInitData = (async function() { return fetch(host + '/api/promo/get_initial_data', {credentials: 'include'}) .then(async response => { const data = await response.json(); if (response.status === 400) { return requestWithRefresh(host + '/api/guest/clear_cookie', {method: 'POST'}) .then(r => {document.location.reload()}); } if (response.status === 401 && data.error_message !== 'need_refresh') { await initToken(); await new Promise(resolve => setTimeout(() => resolve(), 100)); return fetch(host + '/api/promo/get_initial_data', {credentials: 'include'}) .then(r => r.json()); } if (response.status === 403 || data.error_message === 'need_refresh') { return await refreshToken() .then((r) => { return fetch(host + '/api/promo/get_initial_data', {credentials: 'include'}) .then(r => r.json()); }); } return data; }) .then((data) => { updateCart(); return data; }) .catch(error => { // return Promise.resolve(mockData); return Promise.resolve({ cart: [], users: [], initial_groups: [], available_groups: [], days: 7, }); }) }); /** * Дебаунс с группировкой идентификаторов и очередью. * * @param {Function} func - функция, принимающая массив ID и возвращающая Promise * @param {number} delay - задержка в мс между последним кликом и отправкой запроса * @returns {Function} - дебаунсированная функция, принимающая один ID и возвращающая Promise */ function debounceBatch(func, delay) { let timerId = null; let pendingBatch = null; // ожидает таймера let runningBatch = null; // выполняется let nextBatch = null; // накапливается во время выполнения function processBatch(batch) { runningBatch = batch; const { ids, promises } = batch; let result; try { result = func(ids); } catch (err) { promises.forEach(p => p.reject(err)); finishBatch(); return; } if (result && typeof result.then === 'function') { result .then(data => promises.forEach(p => p.resolve(data))) .catch(err => promises.forEach(p => p.reject(err))) .finally(finishBatch); } else { promises.forEach(p => p.resolve(result)); finishBatch(); } } function finishBatch() { runningBatch = null; // Если есть накопленный следующий батч – переносим в ожидающие и запускаем таймер if (nextBatch && nextBatch.ids.length > 0) { pendingBatch = nextBatch; nextBatch = null; timerId = setTimeout(() => { timerId = null; const batch = pendingBatch; pendingBatch = null; processBatch(batch); }, delay); } } return function debounced(id) { return new Promise((resolve, reject) => { if (runningBatch) { // Во время выполнения – добавляем в nextBatch if (!nextBatch) { nextBatch = { ids: [], promises: [] }; } nextBatch.ids.push(id); nextBatch.promises.push({ resolve, reject }); } else if (pendingBatch) { // Ожидает таймера – добавляем и перезапускаем таймер pendingBatch.ids.push(id); pendingBatch.promises.push({ resolve, reject }); clearTimeout(timerId); timerId = setTimeout(() => { timerId = null; const batch = pendingBatch; pendingBatch = null; processBatch(batch); }, delay); } else { // Ничего нет – создаём новый ожидающий батч pendingBatch = { ids: [id], promises: [{ resolve, reject }] }; timerId = setTimeout(() => { timerId = null; const batch = pendingBatch; pendingBatch = null; processBatch(batch); }, delay); } }); }; } function debounce(func, delay) { let timeoutId; let rejectPrevious; // функция для отклонения предыдущего промиса return function (...args) { // Если есть ожидающий промис, отклоняем его if (rejectPrevious) { rejectPrevious(new Error('Debounced function cancelled')); rejectPrevious = null; } // Создаём новый промис для текущего вызова return new Promise((resolve, reject) => { // Сохраняем reject, чтобы можно было отклонить этот промис позже rejectPrevious = reject; // Очищаем старый таймер и устанавливаем новый clearTimeout(timeoutId); timeoutId = setTimeout(() => { try { // Вызываем оригинальную функцию с переданными аргументами и контекстом const result = func.apply(this, args); // Если результат — промис, дожидаемся его if (result && typeof result.then === 'function') { result.then(resolve).catch(reject); } else { resolve(result); } } catch (error) { reject(error); } finally { // Сбрасываем, чтобы не отклонить случайно после выполнения rejectPrevious = null; } }, delay); }); }; } let allProducts; let visibleProducts; function renderProduct(item, index) { const li = document.createElement('li'); li.className = 'cart-item'; const amount = item.unit === 'g' ? item.n + ' г' : item.n + ' шт'; li.innerHTML = cartPolicy.createHTML(`
${item.name}
${amount} × ${Math.round(item.price * 100) / 100} ₽
`); return li; } let cartList; let scrollWrapper; let showMoreBtn; let remainingSpan; let initialData_g; const cartPolicy = window.trustedTypes.createPolicy('cartPolicy', { createHTML: (string) => string }); function renderProducts() { const existingNodesByKey = new Map(); Array.from(cartList.children).forEach(node => { existingNodesByKey.set(node.dataset.productKey, node); }); visibleProducts.forEach((item, index) => { const key = item.name; const signature = JSON.stringify({ name: item.name, n: item.n, price: item.price, total_price: item.total_price, currency: item.currency, image: item.image, unit: item.unit, }); let node = existingNodesByKey.get(key); if (!node || node.dataset.productSignature !== signature) { const oldNode = node; node = renderProduct(item, index); node.dataset.productKey = key; node.dataset.productSignature = signature; node.getElementsByClassName('item-image')[0].addEventListener('click', () => openModal( item.image.replace('65,fit', '335,fit'), item.name, item.n, item.total_price, () => { console.log('replace') }, () => { console.log('another') }, )); if (oldNode) { oldNode.replaceWith(node); } } else { const menuBtn = node.querySelector('.menu-btn'); if (menuBtn) { menuBtn.dataset.index = index; } } const currentNodeAtPosition = cartList.children[index]; if (currentNodeAtPosition !== node) { cartList.insertBefore(node, currentNodeAtPosition || null); } existingNodesByKey.delete(key); }); existingNodesByKey.forEach(node => { node.remove(); }); const remaining = allProducts.length - visibleProducts.length; if(remaining > 0) { showMoreBtn.classList.remove('hidden'); remainingSpan.textContent = remaining; } else { showMoreBtn.classList.add('hidden'); } updateSummaryCount(); calculateListHeight(); } function updateSummaryCount() { const totalPrice = new Intl.NumberFormat("ru-RU", { style: "currency", currency: "RUB" }).format( Math.round(allProducts.reduce((sum, item) => sum + item.total_price, 0) * 100) / 100, ); const x = Math.round(initialData_g.days * initialData_g.excess + initialData_g.days); if(allProducts.length === 0) { document.getElementById('summary-details').innerHTML = cartPolicy.createHTML(``); document.getElementById('empty-cart-loader').classList.remove('hidden'); } else { document.getElementById('empty-cart-loader').classList.add('hidden'); document.getElementById('summary-details').innerHTML = cartPolicy.createHTML(`${allProducts.length} ${declension(allProducts.length, ['товар', 'товара', 'товаров'])} ${totalPrice}
С запасами на ${x} ${declension(x, ['день', 'дня', 'дней'])}
`); document.getElementById('total-price').innerHTML = cartPolicy.createHTML(`${totalPrice}`); } } function calculateListHeight() { const leftCol = document.querySelector('.col-left'); if (!leftCol) return; const title = leftCol.querySelector('h1'); const subtitle = leftCol.querySelector('.subtitle'); const summary = leftCol.querySelector('.cart-summary'); const footer = leftCol.querySelector('.cart-footer'); const showMore = leftCol.querySelector('.show-more'); let fixedHeight = 0; if(title) fixedHeight += title.offsetHeight + 6; if(subtitle) fixedHeight += subtitle.offsetHeight + 28; if(summary) fixedHeight += summary.offsetHeight + 24; if(showMore && !showMore.classList.contains('hidden')) fixedHeight += showMore.offsetHeight; if(footer) fixedHeight += footer.offsetHeight + 20; const paddingY = 60; const rect = leftCol.getBoundingClientRect(); const availHeight = window.innerHeight - rect.top - paddingY - 20; const listHeight = availHeight - fixedHeight; if(listHeight > 100) { scrollWrapper.style.maxHeight = `${listHeight}px`; scrollWrapper.style.overflowY = 'auto'; } } function declension(number, titles) { const cases = [2, 0, 1, 1, 1, 2]; const idx = (number % 100 > 4 && number % 100 < 20) ? 2 : cases[Math.min(number % 10, 5)]; return titles[idx]; } function wholeRender(initialData) { initialData_g = initialData; document.querySelector('.summary-days').textContent = `${initialData_g.days} ${declension(initialData_g.days, ['день', 'дня', 'дней'])}`; allProducts = (Array.isArray(initialData_g.cart) ? initialData_g.cart : [] ?? []).flat().map(item => { return {...item, image: item.image?.replace('{SIZE}', '65,fit')} }); visibleProducts = allProducts.slice(0, visibleProducts ? visibleProducts.length : 5); document.querySelectorAll('.days-options .day-btn').forEach(btn => { if (btn.innerText.trim() === `${initialData_g.days}`) { btn.classList.add('active'); } }); // Массив людей теперь содержит поля запрещенных и любимых продуктов console.log(initialData_g); let people = initialData_g.users; // --- 1. Рендеринг списка продуктов (Левая часть) --- cartList = document.getElementById('cart-list'); scrollWrapper = document.getElementById('scroll-wrapper'); showMoreBtn = document.getElementById('show-more-btn'); remainingSpan = document.getElementById('remaining-count'); renderProducts(); // --- 2. Меню (Заменить и Удалить) --- document.addEventListener('click', function(e) { const menuBtn = e.target.closest('.menu-btn'); const menuDropdown = e.target.closest('.menu-dropdown'); const isReplace = e.target.closest('.menu-replace'); const isRemove = e.target.closest('.menu-remove'); const isClearAll = e.target.closest('.reorder-btn'); const isCloseModal = e.target.closest('.product-modal-overlay'); if(menuBtn) { const dropdown = menuBtn.nextElementSibling; const isOpen = dropdown.classList.contains('active'); document.querySelectorAll('.menu-dropdown.active').forEach(el => el.classList.remove('active')); if(!isOpen) { dropdown.classList.add('active'); } e.stopPropagation(); } else if(!menuDropdown) { document.querySelectorAll('.menu-dropdown.active').forEach(el => el.classList.remove('active')); } if(isReplace) { const wrapper = isReplace.closest('.menu-btn-wrapper'); const li = wrapper.closest('.cart-item'); li.classList.add('replacing'); const index = Array.from(cartList.children).indexOf(li); replaceProduct(allProducts[index].name).then(() => { li.remove(); document.querySelectorAll('.menu-dropdown.active').forEach(el => el.classList.remove('active')); allProducts.splice(index, 1); visibleProducts = allProducts.slice(0, visibleProducts ? visibleProducts.length : 5); renderProducts(); calculateListHeight(); }); } if(isRemove) { const wrapper = isRemove.closest('.menu-btn-wrapper'); const li = wrapper.closest('.cart-item'); li.classList.add('replacing'); const index = Array.from(cartList.children).indexOf(li); deleteProduct(allProducts[index].name).then(() => { li.remove(); document.querySelectorAll('.menu-dropdown.active').forEach(el => el.classList.remove('active')); visibleProducts.splice(index, 1); allProducts.splice(index, 1); if(visibleProducts.length < allProducts.length) { visibleProducts.push(allProducts[visibleProducts.length]); } renderProducts(); calculateListHeight(); }) } if (isClearAll) { requestWithRefresh(host + '/api/guest/clear_cookie', {method: 'POST'}) .then(r => {document.location.reload()}); } if (isCloseModal) { document.getElementById('modalProductOverlay').classList.remove('show'); } }); showMoreBtn.addEventListener('click', () => { while(visibleProducts.length < allProducts.length) { visibleProducts.push(allProducts[visibleProducts.length]); } renderProducts(); scrollWrapper.style.maxHeight = '500px'; calculateListHeight(); }); // --- 3. Фиксированная высота --- window.addEventListener('resize', calculateListHeight); calculateListHeight(); // --- 4. Аккордеон (активна только одна вкладка) --- document.querySelectorAll('.accordion-header').forEach(header => { header.addEventListener('click', function() { const item = this.closest('.accordion-item'); const isOpen = item.classList.contains('open'); document.querySelectorAll('.accordion-item').forEach(el => el.classList.remove('open')); if(!isOpen) { item.classList.add('open'); } }); }); // --- 5. Дни --- document.querySelectorAll('.day-btn').forEach(btn => { btn.addEventListener('click', function() { const parent = this.closest('.days-options'); parent.querySelectorAll('.day-btn').forEach(b => b.classList.remove('active')); this.classList.add('active'); setOption('days', this.dataset.days); document.querySelector('.summary-days').textContent = `${this.dataset.days} ${declension(this.dataset.days, ['день', 'дня', 'дней'])}`; }); }); // --- 6. Семья с возможностью редактирования --- const familyList = document.getElementById('family-list'); const familyDesc = document.getElementById('family-desc'); let editingIndex = null; function renderPeople() { familyList.innerHTML = cartPolicy.createHTML(''); people.forEach((person, index) => { let avatar; if (person.age < 16) { avatar = 'ребёнок'; } else { avatar = person.gender === 'f' ? 'девушка' : 'парень'; } const div = document.createElement('div'); div.className = 'family-member'; div.dataset.index = index; div.innerHTML = cartPolicy.createHTML(`
${avatar}
${person.name}
${person.age} лет
×
`); familyList.appendChild(div); }); const count = people.length; familyDesc.textContent = `${count} ${declension(count, ['человек', 'человека', 'человек'])}`; if(count === 0) familyDesc.textContent = 'Не указаны'; document.querySelectorAll('.delete-member').forEach(btn => { btn.addEventListener('click', function(e) { e.stopPropagation(); const idx = parseInt(this.dataset.index); people.splice(idx, 1); renderPeople(); setOption('users', people); }); }); } renderPeople(); familyList.addEventListener('click', function(e) { const member = e.target.closest('.family-member'); if (!member) return; if (e.target.closest('.delete-member')) return; const idx = parseInt(member.dataset.index); openEditModal(idx); }); // --- 7. Попап (Добавление и Редактирование) + Логика Тегов --- const modal = document.getElementById('personModal'); const addBtn = document.getElementById('addMemberBtn'); const closeBtn = document.getElementById('closeModalBtn'); const saveBtn = document.getElementById('savePersonBtn'); const modalTitle = document.getElementById('modalTitle'); const genderBtns = document.querySelectorAll('.gender-btn'); const availableTags = initialData_g.available_groups; function openEditModal(index = null) { editingIndex = index; // Очистка тегов внутри попапа document.querySelectorAll('#personModal .tags-wrap').forEach(el => el.innerHTML = cartPolicy.createHTML('')); if (editingIndex !== null) { modalTitle.textContent = 'Редактировать человека'; const p = people[editingIndex]; document.getElementById('inputName').value = p.name; document.getElementById('inputAge').value = p.age; document.getElementById('inputWeight').value = p.weight; document.getElementById('inputHeight').value = p.height; genderBtns.forEach(b => { b.classList.toggle('active', b.dataset.gender === p.gender); }); // Рендерим сохраненные теги if(p.avoid) p.avoid.forEach(tag => addTagToModal(tag, 'avoid', false)); if(p.favorite) p.favorite.forEach(tag => addTagToModal(tag, 'favorite', false)); } else { modalTitle.textContent = 'Добавить человека'; document.getElementById('inputName').value = ''; document.getElementById('inputAge').value = ''; document.getElementById('inputWeight').value = ''; document.getElementById('inputHeight').value = ''; genderBtns.forEach(b => b.classList.remove('active')); document.querySelector('.gender-btn[data-gender="m"]').classList.add('active'); } modal.style.display = 'flex'; calculateListHeight(); } addBtn.addEventListener('click', () => openEditModal(null)); function closeModal() { modal.style.display = 'none'; } closeBtn.addEventListener('click', closeModal); modal.addEventListener('click', function(e) { if (e.target === this) closeModal(); }); genderBtns.forEach(btn => { btn.addEventListener('click', function() { genderBtns.forEach(b => b.classList.remove('active')); this.classList.add('active'); }); }); // Логика добавления тега в попап function addTagToModal(text, type, saveToPeople = true) { const tagsWrap = document.querySelector(`#personModal .tags-wrap[data-target="${type}"]`); // Проверка дубликата в своей группе const existingInGroup = tagsWrap.querySelectorAll('.tag'); for(let t of existingInGroup) { if(t.textContent.trim().replace('×','').trim() === text) return; } // Взаимоисключение: если тег есть в противоположной группе - удалить его оттуда const oppositeType = type === 'avoid' ? 'favorite' : 'avoid'; const oppositeWrap = document.querySelector(`#personModal .tags-wrap[data-target="${oppositeType}"]`); const oppositeTags = oppositeWrap.querySelectorAll('.tag'); for(let t of oppositeTags) { if(t.textContent.trim().replace('×','').trim() === text) { t.remove(); break; } } const tag = document.createElement('div'); tag.className = `tag ${type === 'avoid' ? 'tag-red' : 'tag-green'}`; tag.innerHTML = cartPolicy.createHTML(`${text} ×`); tag.querySelector('.tag-close').addEventListener('click', function() { tag.remove(); }); tagsWrap.appendChild(tag); } // Автокомплит для полей в попапе document.querySelectorAll('#personModal .pref-input').forEach(input => { const type = input.dataset.type; const listEl = document.querySelector(`#personModal .autocomplete-list[data-type="${type}"]`); const tagsWrap = document.querySelector(`#personModal .tags-wrap[data-target="${type}"]`); input.addEventListener('input', function() { const val = this.value.toLowerCase(); listEl.innerHTML = cartPolicy.createHTML(''); // Ищем все уже выбранные теги в обеих группах внутри попапа const allSelectedTags = new Set(); document.querySelectorAll('#personModal .tags-wrap .tag').forEach(tagEl => { const tagText = tagEl.textContent.trim().replace('×', '').trim(); if(tagText) allSelectedTags.add(tagText); }); if(val.length > 0) { const matches = availableTags.filter(tag => tag.toLowerCase().includes(val) && !allSelectedTags.has(tag) ); if(matches.length > 0) { listEl.classList.add('active'); matches.forEach(match => { const div = document.createElement('div'); div.textContent = match; div.addEventListener('click', function() { addTagToModal(match, type); input.value = ''; listEl.classList.remove('active'); }); listEl.appendChild(div); }); } else { listEl.classList.remove('active'); } } else { listEl.classList.remove('active'); } }); document.addEventListener('click', function(e) { if(!input.contains(e.target) && !listEl.contains(e.target)) { listEl.classList.remove('active'); } }); }); // Сохранение пользователя (сбор тегов из DOM попапа) saveBtn.addEventListener('click', () => { const name = document.getElementById('inputName').value.trim(); const age = document.getElementById('inputAge').value.trim(); const weight = document.getElementById('inputWeight').value.trim(); const height = document.getElementById('inputHeight').value.trim(); const gender = document.querySelector('.gender-btn.active').dataset.gender; if(!name) { alert('Пожалуйста, введите имя.'); return; } if(!age || parseInt(age) < 7) { alert('Возраст должен быть от 7 лет.'); return; } // Сбор тегов const forbTags = []; document.querySelector('#personModal .tags-wrap[data-target="avoid"]').querySelectorAll('.tag').forEach(el => { forbTags.push(el.textContent.trim().replace('×', '').trim()); }); const favTags = []; document.querySelector('#personModal .tags-wrap[data-target="favorite"]').querySelectorAll('.tag').forEach(el => { favTags.push(el.textContent.trim().replace('×', '').trim()); }); const personData = { name, age, weight, height, gender, avoided: forbTags, favorite: favTags }; if (editingIndex !== null) { people[editingIndex] = personData; editingIndex = null; } else { people.push(personData); } renderPeople(); setOption('users', people); closeModal(); }); // --- 8. Блок: Категории --- const categories = initialData_g.available_groups; const initialSelected = initialData_g.initial_groups; let selectedCategories = [...initialSelected]; // let selectedCategories = []; let isCategoriesExpanded = false; const maxVisibleCategories = 7; const sendUpdatedCategories = debounceBatch((categoriesNames) => { let localCategories = [...selectedCategories] categoriesNames.forEach((categoryName) => { if (localCategories.includes(categoryName)) { localCategories.splice(localCategories.indexOf(categoryName), 1); } else { localCategories.push(categoryName); } }) console.log('send', localCategories); return setOption('categories', localCategories) }, 1500); const categoriesGrid = document.getElementById('categories-grid'); const prefsAccordionBlock = document.getElementById('prefs-accordion'); const prefsDescription = document.getElementById('prefs-desc'); function renderCategories() { categoriesGrid.innerHTML = cartPolicy.createHTML(''); if (availableTags.length > 0) { prefsAccordionBlock.classList.add('visible'); } else { prefsAccordionBlock.classList.remove('visible'); } if (selectedCategories.length > 0) { document.getElementById('preferences-icon').classList.add('bg-green-light'); document.getElementById('preferences-icon').classList.remove('bg-gray-light'); if (selectedCategories.length === 1) { prefsDescription.innerText = 'Выбрана 1 категория'; } else { prefsDescription.innerText = 'Выбраны ' + selectedCategories.length + ' ' + declension(selectedCategories.length, ['категория', 'категории', 'категорий']); } } else { document.getElementById('preferences-icon').classList.add('bg-gray-light'); document.getElementById('preferences-icon').classList.remove('bg-green-light'); prefsDescription.innerText = 'Не указаны'; } const dataToShow = isCategoriesExpanded ? categories : ( (selectedCategories.length > 0 ? selectedCategories : categories).slice(0, maxVisibleCategories) ); dataToShow.forEach(cat => { const btn = document.createElement('button'); btn.className = `category-btn ${selectedCategories.includes(cat) ? 'active' : 'inactive'}`; btn.innerHTML = cartPolicy.createHTML(`${cat}${cat}`); btn.dataset.category = cat; btn.addEventListener('click', function() { if (btn.classList.contains('loading-border')) { return; } const catName = this.dataset.category; btn.classList.add('loading-border'); sendUpdatedCategories(catName) .finally(() => { btn.classList.remove('loading-border'); }); }); categoriesGrid.appendChild(btn); }); if (dataToShow.length > 0) { const toggleBtn = document.createElement('button'); toggleBtn.className = 'toggle-categories-btn' toggleBtn.id = 'expand-categories' toggleBtn.innerHTML = cartPolicy.createHTML( (!isCategoriesExpanded && dataToShow.length < selectedCategories.length ? '+' + (selectedCategories.length - dataToShow.length) : '') + ' \n' + ' \n' + ' '); if (isCategoriesExpanded) { toggleBtn.classList.remove('collapsed'); toggleBtn.classList.remove('show-more'); } else { toggleBtn.classList.add('collapsed'); if (dataToShow.length < selectedCategories.length) { toggleBtn.classList.add('show-more'); } } toggleBtn.addEventListener('click', function () { isCategoriesExpanded = !isCategoriesExpanded; renderCategories(); }); categoriesGrid.appendChild(toggleBtn); } } renderCategories(); } document.addEventListener('DOMContentLoaded', async () => { wholeRender(await loadInitData()); }); document.addEventListener('DOMContentLoaded', function() { const popup = document.getElementById('cookie-popup'); const acceptBtn = document.getElementById('cookie-accept'); // Проверяем, согласился ли пользователь ранее if (!localStorage.getItem('cookie_accepted')) { // Небольшая задержка для красоты появления setTimeout(() => { popup.classList.add('show'); }, 500); } // Обработчик нажатия на кнопку "Хорошо" acceptBtn.addEventListener('click', function() { // Сохраняем согласие в браузер localStorage.setItem('cookie_accepted', 'true'); // Запускаем анимацию исчезновения popup.classList.remove('show'); popup.classList.add('hide'); // Полностью удаляем из DOM после завершения анимации setTimeout(() => { popup.style.display = 'none'; }, 400); }); document.getElementById('modalProductOverlay').addEventListener('click', function(e) { if (e.target === this) { this.classList.remove('active'); } }); }); function openModal(imageSrc, name, quantity, price, onReplace, onAnother) { // Получаем элементы по новым id const overlay = document.getElementById('modalProductOverlay'); const img = document.getElementById('modalProductImage'); const title = document.getElementById('modalProductTitle'); const qtySpan = document.getElementById('modalProductQuantity'); const priceSpan = document.getElementById('modalProductPrice'); const replaceBtn = document.getElementById('modalProductReplaceBtn'); const anotherBtn = document.getElementById('modalProductAnotherBtn'); // Заполняем данными img.src = imageSrc || ''; img.alt = name || 'Товар'; title.textContent = name || 'Без названия'; qtySpan.textContent = quantity !== undefined ? quantity : '—'; priceSpan.textContent = new Intl.NumberFormat("ru-RU", { style: "currency", currency: "RUB" }).format( Math.round(price * 100) / 100, ); // Удаляем старые обработчики, если они были (храним в свойствах кнопок) if (replaceBtn._listener) { replaceBtn.removeEventListener('click', replaceBtn._listener); delete replaceBtn._listener; } if (anotherBtn._listener) { anotherBtn.removeEventListener('click', anotherBtn._listener); delete anotherBtn._listener; } // Создаём новые обработчики, которые вызывают переданные колбэки const replaceHandler = function() { if (typeof onReplace === 'function') onReplace(); }; const anotherHandler = function() { if (typeof onAnother === 'function') onAnother(); }; // Сохраняем ссылки и добавляем слушатели replaceBtn._listener = replaceHandler; anotherBtn._listener = anotherHandler; replaceBtn.addEventListener('click', replaceHandler); anotherBtn.addEventListener('click', anotherHandler); // Показываем модалку overlay.classList.add('show'); }