Correction PDF
This commit is contained in:
@@ -37,6 +37,19 @@ const splitTags = (str) => (str || '').split(/[,•]/).map(t => t.trim()).filter
|
||||
const esc = (s) => { const d = document.createElement('div'); d.textContent = (s ?? ''); return d.innerHTML; };
|
||||
function capFirst(s) { if (s == null) return ''; s = s.toString().trim(); if (!s) return ''; const f = s[0].toLocaleUpperCase('fr-FR'); return f + s.slice(1); }
|
||||
|
||||
const DEBOUNCE_MS = 250;
|
||||
function debounce(fn, wait = DEBOUNCE_MS) {
|
||||
let t; return (...args) => { clearTimeout(t); t = setTimeout(() => fn(...args), wait); };
|
||||
}
|
||||
function onlySearchActive() {
|
||||
return normalize(currentSearchTerm) !== '' &&
|
||||
appliedFilters.ingredients.length === 0 &&
|
||||
appliedFilters.tags.length === 0 &&
|
||||
appliedFilters.timeMin == null &&
|
||||
appliedFilters.timeMax == null;
|
||||
}
|
||||
|
||||
|
||||
/***********************
|
||||
* COLORS for tags (persistées localStorage)
|
||||
***********************/
|
||||
@@ -326,28 +339,44 @@ function buildRecipeCardOwned(recipe) {
|
||||
async function loadRecipes(page = 1) {
|
||||
try {
|
||||
const url = new URL('/HelloFresh/GetRecipesByPage', window.location.origin);
|
||||
url.searchParams.set('page', page); url.searchParams.set('count', countPerPage);
|
||||
const res = await fetch(url.toString()); if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
url.searchParams.set('page', page);
|
||||
url.searchParams.set('count', countPerPage);
|
||||
|
||||
// 👉 On laisse le serveur faire la recherche quand il n'y a QUE la recherche active
|
||||
if (onlySearchActive()) {
|
||||
url.searchParams.set('search', currentSearchTerm || '');
|
||||
}
|
||||
|
||||
const res = await fetch(url.toString());
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const data = await res.json();
|
||||
|
||||
const { recipes, currentPage: serverPage, lastPage } = data;
|
||||
currentPage = serverPage; lastPageGlobal = lastPage;
|
||||
|
||||
const grid = document.getElementById('recipeGrid'); grid.innerHTML = '';
|
||||
if (!recipes || recipes.length === 0) {
|
||||
grid.innerHTML = '<p class="placeholder">Aucune recette disponible pour le moment.</p>';
|
||||
renderPagination(currentPage, lastPageGlobal); return;
|
||||
renderPagination(currentPage, lastPageGlobal);
|
||||
return;
|
||||
}
|
||||
|
||||
recipes.forEach(r => grid.appendChild(buildRecipeCardList(r)));
|
||||
if (window.tippy) {
|
||||
tippy('[data-tippy-content]', { placement: 'top', arrow: true, delay: [100, 0], theme: 'light-border', maxWidth: 250, allowHTML: true });
|
||||
}
|
||||
await hydrateIngredientsForPage(recipes);
|
||||
refreshSelectedBorders();
|
||||
if ((currentSearchTerm ?? '').trim() !== '') applySearchFilter();
|
||||
|
||||
// ⚠️ si tu avais une ancienne fonction applySearchFilter(), on ne l'appelle plus ici.
|
||||
// if ((currentSearchTerm ?? '').trim() !== '' && typeof applySearchFilter === 'function') applySearchFilter();
|
||||
|
||||
refreshDoneFlagsOnVisibleCards();
|
||||
renderPagination(currentPage, lastPageGlobal);
|
||||
} catch (e) { console.error('loadRecipes error', e); }
|
||||
}
|
||||
|
||||
|
||||
async function loadRecipesOwned({ onEmpty = 'placeholder' } = {}) {
|
||||
const gridOwned = document.getElementById('recipeGridOwned'); if (!gridOwned) return false;
|
||||
const wasOpen = document.getElementById('recipeOwnedWrap')?.classList.contains('open');
|
||||
@@ -450,19 +479,40 @@ function anyFilterActive() {
|
||||
appliedFilters.timeMin != null ||
|
||||
appliedFilters.timeMax != null;
|
||||
}
|
||||
async function fetchAllRecipes() {
|
||||
if (ALL_RECIPES_CACHE) return ALL_RECIPES_CACHE;
|
||||
|
||||
async function fetchAllRecipes(opts = {}) {
|
||||
const search = (opts.search || '').trim();
|
||||
if (!search && ALL_RECIPES_CACHE) return ALL_RECIPES_CACHE;
|
||||
|
||||
let page = 1, last = 1, all = [];
|
||||
do {
|
||||
const url = new URL('/HelloFresh/GetRecipesByPage', window.location.origin);
|
||||
url.searchParams.set('page', page); url.searchParams.set('count', countPerPage);
|
||||
const res = await fetch(url.toString()); if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
url.searchParams.set('page', page);
|
||||
url.searchParams.set('count', countPerPage);
|
||||
if (search) url.searchParams.set('search', search);
|
||||
|
||||
const res = await fetch(url.toString());
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
const data = await res.json();
|
||||
all.push(...(data.recipes || []));
|
||||
last = data.lastPage || 1; page++;
|
||||
last = data.lastPage || 1;
|
||||
page++;
|
||||
} while (page <= last);
|
||||
ALL_RECIPES_CACHE = all; return all;
|
||||
|
||||
if (!search) ALL_RECIPES_CACHE = all;
|
||||
return all;
|
||||
}
|
||||
|
||||
async function fetchDetailsBatched(ids, batchSize = 150) {
|
||||
const out = [];
|
||||
for (let i = 0; i < ids.length; i += batchSize) {
|
||||
const part = ids.slice(i, i + batchSize);
|
||||
const res = await fetch(`/HelloFresh/GetRecipesDetails?ids=${encodeURIComponent(part.join(','))}`);
|
||||
if (res.ok) out.push(...(await res.json()));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
function recipeMatchesFilters(cardMeta) {
|
||||
const term = normalize(currentSearchTerm);
|
||||
if (term && !cardMeta.name.includes(term)) return false;
|
||||
@@ -487,14 +537,16 @@ function extractIngredientNames(ingredientsJson) {
|
||||
} catch { }
|
||||
return [...out];
|
||||
}
|
||||
|
||||
async function buildMetaFor(recipes) {
|
||||
const ids = recipes.map(r => r.id).join(',');
|
||||
const ids = recipes.map(r => r.id);
|
||||
const metaById = {};
|
||||
|
||||
try {
|
||||
const res = await fetch(`/HelloFresh/GetRecipesDetails?ids=${encodeURIComponent(ids)}`);
|
||||
const details = await res.json(); // [{id, ingredientsJson}]
|
||||
const details = await fetchDetailsBatched(ids, 150); // ← chunks pour éviter les URLs géantes
|
||||
details.forEach(d => { metaById[d.id] = { ing: extractIngredientNames(d.ingredientsJson).map(normalize) }; });
|
||||
} catch (e) { console.warn('details fail', e); }
|
||||
|
||||
recipes.forEach(r => {
|
||||
const tagsVal = (r.tags ?? r.Tags ?? '').trim();
|
||||
const tags = splitTags(tagsVal).map(normalize);
|
||||
@@ -502,24 +554,45 @@ async function buildMetaFor(recipes) {
|
||||
const name = normalize(r.name || '');
|
||||
metaById[r.id] = Object.assign({ tags, time, name, ing: [] }, metaById[r.id] || {});
|
||||
});
|
||||
|
||||
return metaById;
|
||||
}
|
||||
|
||||
async function applyAllFilters() {
|
||||
if (!anyFilterActive()) {
|
||||
const hasSearch = normalize(currentSearchTerm) !== '';
|
||||
const hasOtherFilters = appliedFilters.ingredients.length || appliedFilters.tags.length ||
|
||||
appliedFilters.timeMin != null || appliedFilters.timeMax != null;
|
||||
|
||||
// A) Rien d'actif → liste classique
|
||||
if (!hasSearch && !hasOtherFilters) {
|
||||
CURRENT_CLIENT_LIST = null;
|
||||
await loadRecipes(1);
|
||||
updateActiveFilterBadge(); // peut masquer le badge si rien d’actif
|
||||
updateActiveFilterBadge();
|
||||
return;
|
||||
}
|
||||
const all = await fetchAllRecipes();
|
||||
|
||||
// B) Uniquement la recherche → serveur (rapide, paginé)
|
||||
if (hasSearch && !hasOtherFilters) {
|
||||
CURRENT_CLIENT_LIST = null; // on revient au flux serveur paginé
|
||||
currentPage = 1;
|
||||
await loadRecipes(1); // loadRecipes enverra ?search=...
|
||||
updateActiveFilterBadge();
|
||||
return;
|
||||
}
|
||||
|
||||
// C) Recherche + (tags/ingrédients/temps) OU filtres sans recherche
|
||||
// → on réduit d'abord côté serveur avec ?search=..., puis filtre côté client
|
||||
const all = await fetchAllRecipes({ search: hasSearch ? currentSearchTerm : '' });
|
||||
const meta = await buildMetaFor(all);
|
||||
const filtered = all.filter(r => recipeMatchesFilters(meta[r.id] || {}));
|
||||
|
||||
CURRENT_CLIENT_LIST = filtered;
|
||||
currentPage = 1;
|
||||
lastPageGlobal = Math.max(1, Math.ceil(filtered.length / countPerPage));
|
||||
await renderClientPage();
|
||||
updateActiveFilterBadge(); // badge = basé sur appliedFilters UNIQUEMENT
|
||||
updateActiveFilterBadge();
|
||||
}
|
||||
|
||||
async function renderClientPage() {
|
||||
const list = CURRENT_CLIENT_LIST || [];
|
||||
const start = (currentPage - 1) * countPerPage;
|
||||
@@ -809,9 +882,10 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Live search
|
||||
const searchInput = document.querySelector('#divSearch input');
|
||||
if (searchInput) {
|
||||
searchInput.addEventListener('input', async (e) => {
|
||||
const debounced = debounce(async () => { await applyAllFilters(); }, 250);
|
||||
searchInput.addEventListener('input', (e) => {
|
||||
currentSearchTerm = e.target.value;
|
||||
await applyAllFilters(); // search est immédiat
|
||||
debounced();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user