marketing/src_static/autocart.js
2026-08-24 13:07:16 +03:00

2548 lines
107 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"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(`
<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>
`);
return li;
}
let cartList;
let scrollWrapper;
let showMoreBtn;
let remainingSpan;
let initialData_g;
const cartPolicy = 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(`<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}`);
}
}
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 = '<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="парень" />';
}
const div = document.createElement('div');
div.className = 'family-member';
div.dataset.index = index;
div.innerHTML = cartPolicy.createHTML(`
<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>
`);
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} <span class="tag-close">×</span>`);
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, avoid: 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(`<span class="spacer-over">${cat}</span><span class="spacer-width">${cat}</span>`);
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) : '') +
' <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);
}
}
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');
}