marketing/src_static/autocart.js

2548 lines
107 KiB
JavaScript
Raw Normal View History

2026-08-22 18:51:25 +00:00
"use strict";
2026-08-21 15:23:20 +00:00
const host = 'https://local.hvatilo.ru:8055';
2026-08-21 14:52:45 +00:00
// 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() {
2026-08-22 18:51:25 +00:00
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();
});
2026-08-21 14:52:45 +00:00
}
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() {
2026-08-22 18:51:25 +00:00
document.getElementById('summary-loader').classList.remove('hidden');
document.getElementById('summary-details').classList.add('hidden');
2026-08-21 14:52:45 +00:00
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();
2026-08-22 18:51:25 +00:00
} else {
document.getElementsByClassName('cart-footer')[0].classList.add('visible');
document.getElementById('summary-loader').classList.add('hidden');
document.getElementById('summary-details').classList.remove('hidden');
2026-08-21 14:52:45 +00:00
}
})
.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();
2026-08-22 18:51:25 +00:00
return d;
})
.catch(() => {
console.log('start catch')
return new Promise((resolve, reject) => {
setTimeout(() => {
console.log('end catch')
reject('end catch')
}, 1000);
})
2026-08-21 14:52:45 +00:00
})
}
2026-08-22 18:51:25 +00:00
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
};
2026-08-21 14:52:45 +00:00
const loadInitData = (async function() {
return fetch(host + '/api/promo/get_initial_data', {credentials: 'include'})
.then(async response => {
const data = await response.json();
2026-08-22 18:51:25 +00:00
if (response.status === 400) {
return requestWithRefresh(host + '/api/guest/clear_cookie', {method: 'POST'})
.then(r => {document.location.reload()});
}
2026-08-21 14:52:45 +00:00
if (response.status === 401 && data.error_message !== 'need_refresh') {
2026-08-22 18:51:25 +00:00
await initToken();
await new Promise(resolve => setTimeout(() => resolve(), 100));
return fetch(host + '/api/promo/get_initial_data', {credentials: 'include'})
.then(r => r.json());
2026-08-21 14:52:45 +00:00
}
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 => {
2026-08-22 18:51:25 +00:00
// return Promise.resolve(mockData);
return Promise.resolve({
cart: [],
users: [],
initial_groups: [],
available_groups: [],
days: 7,
});
2026-08-21 14:52:45 +00:00
})
});
2026-08-22 18:51:25 +00:00
/**
* Дебаунс с группировкой идентификаторов и очередью.
*
* @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);
}
});
};
}
2026-08-21 14:52:45 +00:00
function debounce(func, delay) {
let timeoutId;
2026-08-22 18:51:25 +00:00
let rejectPrevious; // функция для отклонения предыдущего промиса
2026-08-21 14:52:45 +00:00
return function (...args) {
2026-08-22 18:51:25 +00:00
// Если есть ожидающий промис, отклоняем его
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);
});
2026-08-21 14:52:45 +00:00
};
}
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 + ' шт';
2026-08-21 15:23:20 +00:00
li.innerHTML = cartPolicy.createHTML(`
2026-08-21 14:52:45 +00:00
<div class="item-image"><img src="${item.image}" alt=""></div>
<div class="item-details">
<div class="item-name">${item.name}</div>
<div class="item-price">${amount} &times; ${Math.round(item.price * 100) / 100} </div>
</div>
<div class="menu-btn-wrapper">
<button class="menu-btn" data-index="${index}">&#8942;</button>
<div class="menu-dropdown">
<ul>
<li class="menu-replace">Заменить</li>
<li class="menu-remove">Удалить</li>
</ul>
</div>
</div>
2026-08-21 15:23:20 +00:00
`);
2026-08-21 14:52:45 +00:00
return li;
}
let cartList;
let scrollWrapper;
let showMoreBtn;
let remainingSpan;
let initialData_g;
2026-08-28 06:41:34 +00:00
const cartPolicy = window.trustedTypes.createPolicy('cartPolicy', {
2026-08-21 15:23:20 +00:00
createHTML: (string) => string
});
2026-08-21 14:52:45 +00:00
function renderProducts() {
2026-08-24 03:45:21 +00:00
const existingNodesByKey = new Map();
Array.from(cartList.children).forEach(node => {
existingNodesByKey.set(node.dataset.productKey, node);
});
2026-08-21 14:52:45 +00:00
visibleProducts.forEach((item, index) => {
2026-08-24 03:45:21 +00:00
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();
2026-08-21 14:52:45 +00:00
});
const remaining = allProducts.length - visibleProducts.length;
if(remaining > 0) {
showMoreBtn.classList.remove('hidden');
remainingSpan.textContent = remaining;
} else {
showMoreBtn.classList.add('hidden');
}
2026-08-24 03:45:21 +00:00
2026-08-21 14:52:45 +00:00
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) {
2026-08-22 18:51:25 +00:00
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(`<span>${allProducts.length} ${declension(allProducts.length, ['товар', 'товара', 'товаров'])}</span> <b>&bull;</b> <span>${totalPrice}</span> <div class="summary-excess">С запасами на ${x} ${declension(x, ['день', 'дня', 'дней'])}</div>`);
document.getElementById('total-price').innerHTML = cartPolicy.createHTML(`${totalPrice}`);
2026-08-21 14:52:45 +00:00
}
}
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');
2026-08-22 18:51:25 +00:00
const isCloseModal = e.target.closest('.product-modal-overlay');
2026-08-21 14:52:45 +00:00
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);
2026-08-24 03:45:21 +00:00
visibleProducts = allProducts.slice(0, visibleProducts ? visibleProducts.length : 5);
2026-08-21 14:52:45 +00:00
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()});
}
2026-08-22 18:51:25 +00:00
if (isCloseModal) {
document.getElementById('modalProductOverlay').classList.remove('show');
}
2026-08-21 14:52:45 +00:00
});
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() {
2026-08-21 15:23:20 +00:00
familyList.innerHTML = cartPolicy.createHTML('');
2026-08-21 14:52:45 +00:00
people.forEach((person, index) => {
2026-08-22 18:51:25 +00:00
let avatar;
if (person.age < 16) {
avatar = '<img src="/images/broc-' +(person.gender === 'f' ? 'girl' : 'boy')+'.png" alt="ребёнок" />';
} else {
avatar = person.gender === 'f' ? '<img src="/images/broc-woman.png" alt="девушка" />' : '<img src="/images/broc-man.png" alt="парень" />';
}
2026-08-21 14:52:45 +00:00
const div = document.createElement('div');
div.className = 'family-member';
div.dataset.index = index;
2026-08-21 15:23:20 +00:00
div.innerHTML = cartPolicy.createHTML(`
2026-08-21 14:52:45 +00:00
<div class="avatar">${avatar}</div>
<div class="info">
<div class="name">${person.name}</div>
<div class="age">${person.age} лет</div>
</div>
<div class="delete-member" data-index="${index}">×</div>
2026-08-21 15:23:20 +00:00
`);
2026-08-21 14:52:45 +00:00
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;
// Очистка тегов внутри попапа
2026-08-21 15:23:20 +00:00
document.querySelectorAll('#personModal .tags-wrap').forEach(el => el.innerHTML = cartPolicy.createHTML(''));
2026-08-21 14:52:45 +00:00
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'}`;
2026-08-21 15:23:20 +00:00
tag.innerHTML = cartPolicy.createHTML(`${text} <span class="tag-close">×</span>`);
2026-08-21 14:52:45 +00:00
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();
2026-08-21 15:23:20 +00:00
listEl.innerHTML = cartPolicy.createHTML('');
2026-08-21 14:52:45 +00:00
// Ищем все уже выбранные теги в обеих группах внутри попапа
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());
});
2026-09-05 22:56:59 +00:00
const personData = { name, age, weight, height, gender, avoid: forbTags, favorite: favTags };
2026-08-21 14:52:45 +00:00
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];
2026-08-22 18:51:25 +00:00
// let selectedCategories = [];
2026-08-21 14:52:45 +00:00
let isCategoriesExpanded = false;
2026-08-22 18:51:25 +00:00
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)
2026-08-21 14:52:45 +00:00
}, 1500);
2026-08-22 18:51:25 +00:00
2026-08-21 14:52:45 +00:00
const categoriesGrid = document.getElementById('categories-grid');
2026-08-22 18:51:25 +00:00
const prefsAccordionBlock = document.getElementById('prefs-accordion');
const prefsDescription = document.getElementById('prefs-desc');
2026-08-21 14:52:45 +00:00
function renderCategories() {
2026-08-21 15:23:20 +00:00
categoriesGrid.innerHTML = cartPolicy.createHTML('');
2026-08-22 18:51:25 +00:00
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)
);
2026-08-21 14:52:45 +00:00
dataToShow.forEach(cat => {
const btn = document.createElement('button');
btn.className = `category-btn ${selectedCategories.includes(cat) ? 'active' : 'inactive'}`;
2026-08-22 18:51:25 +00:00
btn.innerHTML = cartPolicy.createHTML(`<span class="spacer-over">${cat}</span><span class="spacer-width">${cat}</span>`);
2026-08-21 14:52:45 +00:00
btn.dataset.category = cat;
btn.addEventListener('click', function() {
2026-08-22 18:51:25 +00:00
if (btn.classList.contains('loading-border')) {
return;
2026-08-21 14:52:45 +00:00
}
2026-08-22 18:51:25 +00:00
const catName = this.dataset.category;
btn.classList.add('loading-border');
sendUpdatedCategories(catName)
.finally(() => {
btn.classList.remove('loading-border');
});
2026-08-21 14:52:45 +00:00
});
categoriesGrid.appendChild(btn);
});
2026-08-22 18:51:25 +00:00
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) : '') +
' <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"\n' +
' stroke-linejoin="round">\n' +
' <polyline points="18 15 12 9 6 15"></polyline>\n' +
' </svg>');
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);
2026-08-21 14:52:45 +00:00
}
}
2026-08-22 18:51:25 +00:00
2026-08-21 14:52:45 +00:00
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);
});
2026-08-22 18:51:25 +00:00
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');
}