Files
santeh-ray/static/js/favorites.js
Azimkin 29d8302a4f
All checks were successful
Build and Push Docker Image / build (push) Successful in 51s
feat: add favorites feature for users
- Implemented `Favorite` model with unique user-item constraint and timestamped records
- Added `favorites:index` and `favorites:toggle` views with AJAX support for toggling favorites
- Built templates and front-end assets for the favorites page and integration with item grids
- Extended header with favorites link and dynamic counter
- Added tests covering models, views, and templates for the favorites functionality
- Updated admin to enable management of favorites via Django admin interface
2026-05-10 20:06:12 +02:00

115 lines
3.9 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.
document.addEventListener('DOMContentLoaded', function () {
const favoriteForms = document.querySelectorAll('[data-favorite-form]');
const pluralizeFavorites = function (count) {
const value = Math.abs(Number(count) || 0);
const lastTwo = value % 100;
if (lastTwo >= 11 && lastTwo <= 14) {
return 'товаров';
}
switch (value % 10) {
case 1:
return 'товар';
case 2:
case 3:
case 4:
return 'товара';
default:
return 'товаров';
}
};
const updateFavoritesCounter = function (favoriteCount) {
const valueNode = document.querySelector('[data-favorites-count-value]');
const labelNode = document.querySelector('[data-favorites-count-label]');
if (valueNode) {
valueNode.textContent = String(favoriteCount);
}
if (labelNode) {
labelNode.textContent = pluralizeFavorites(favoriteCount);
}
};
const ensureEmptyFavoritesState = function () {
const grid = document.querySelector('[data-favorites-grid]');
if (!grid) {
return;
}
const cards = grid.querySelectorAll('[data-item-card]');
if (cards.length > 0) {
return;
}
const emptyMessage = grid.dataset.emptyMessage || 'В избранном пока нет товаров.';
grid.innerHTML = '<p class="item-list-empty">' + emptyMessage + '</p>';
};
const syncButtonState = function (button, payload) {
const isFavorite = Boolean(payload.is_favorite);
const addLabel = button.dataset.labelAdd || 'Добавить в избранное';
const removeLabel = button.dataset.labelRemove || 'Удалить из избранного';
const label = isFavorite ? removeLabel : addLabel;
button.classList.toggle('is-active', isFavorite);
button.setAttribute('title', label);
button.setAttribute('aria-label', label);
};
favoriteForms.forEach(function (form) {
form.addEventListener('submit', async function (event) {
event.preventDefault();
const button = form.querySelector('[data-favorite-button]');
if (!button || form.dataset.loading === '1') {
return;
}
form.dataset.loading = '1';
button.disabled = true;
try {
const response = await fetch(form.action, {
method: 'POST',
body: new FormData(form),
headers: {
'X-Requested-With': 'XMLHttpRequest',
'Accept': 'application/json',
},
credentials: 'same-origin',
});
if (!response.ok) {
throw new Error('Failed to update favorites');
}
const payload = await response.json();
const card = form.closest('[data-item-card]');
const favoritesPage = Boolean(document.querySelector('[data-favorites-page]'));
syncButtonState(button, payload);
updateFavoritesCounter(payload.favorite_count);
if (card) {
card.classList.toggle('is-favorite', Boolean(payload.is_favorite));
}
if (favoritesPage && !payload.is_favorite && card) {
card.remove();
ensureEmptyFavoritesState();
}
} catch (error) {
console.error(error);
HTMLFormElement.prototype.submit.call(form);
return;
} finally {
form.dataset.loading = '0';
button.disabled = false;
}
});
});
});