Ajout
This commit is contained in:
@@ -84,6 +84,7 @@ public class HelloFreshController : AppController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ❌ Supprime la limite de 3 portions
|
||||||
[HttpPost("SaveRecipe")]
|
[HttpPost("SaveRecipe")]
|
||||||
public IActionResult SaveRecipe([FromBody] string idSavingRecette)
|
public IActionResult SaveRecipe([FromBody] string idSavingRecette)
|
||||||
{
|
{
|
||||||
@@ -94,7 +95,6 @@ public class HelloFreshController : AppController
|
|||||||
if (string.IsNullOrWhiteSpace(idSavingRecette)) return BadRequest("Id invalide.");
|
if (string.IsNullOrWhiteSpace(idSavingRecette)) return BadRequest("Id invalide.");
|
||||||
|
|
||||||
var current = _context.SavingRecettes.Count(s => s.UserId == userId && s.IdSavingRecette == idSavingRecette);
|
var current = _context.SavingRecettes.Count(s => s.UserId == userId && s.IdSavingRecette == idSavingRecette);
|
||||||
if (current >= 3) return Ok(new { message = "cap", qty = 3 });
|
|
||||||
|
|
||||||
_context.SavingRecettes.Add(new SavingRecette { IdSavingRecette = idSavingRecette, UserId = userId.Value });
|
_context.SavingRecettes.Add(new SavingRecette { IdSavingRecette = idSavingRecette, UserId = userId.Value });
|
||||||
_context.SaveChanges();
|
_context.SaveChanges();
|
||||||
@@ -115,12 +115,10 @@ public class HelloFreshController : AppController
|
|||||||
var sessionUserId = HttpContext.Session.GetInt32("UserId");
|
var sessionUserId = HttpContext.Session.GetInt32("UserId");
|
||||||
if (sessionUserId == null) return Unauthorized("Utilisateur non connecté.");
|
if (sessionUserId == null) return Unauthorized("Utilisateur non connecté.");
|
||||||
|
|
||||||
// petite fonction pour normaliser les clés de regroupement par nom
|
|
||||||
static string Key(string? s) => (s ?? "").Trim().ToLowerInvariant();
|
static string Key(string? s) => (s ?? "").Trim().ToLowerInvariant();
|
||||||
|
|
||||||
if (isUserImportant)
|
if (isUserImportant)
|
||||||
{
|
{
|
||||||
// Mes recettes : on inner-join sur Recettes, on matérialise puis on regroupe par NOM
|
|
||||||
var rows = (
|
var rows = (
|
||||||
from s in _context.SavingRecettes.AsNoTracking()
|
from s in _context.SavingRecettes.AsNoTracking()
|
||||||
where s.UserId == sessionUserId
|
where s.UserId == sessionUserId
|
||||||
@@ -141,7 +139,6 @@ public class HelloFreshController : AppController
|
|||||||
.GroupBy(x => Key(x.Name))
|
.GroupBy(x => Key(x.Name))
|
||||||
.Select(g =>
|
.Select(g =>
|
||||||
{
|
{
|
||||||
// on choisit un représentant qui a un PDF si possible
|
|
||||||
var withPdf = g.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.Pdf));
|
var withPdf = g.FirstOrDefault(x => !string.IsNullOrWhiteSpace(x.Pdf));
|
||||||
var pick = withPdf ?? g.OrderByDescending(x => x.Id).First();
|
var pick = withPdf ?? g.OrderByDescending(x => x.Id).First();
|
||||||
|
|
||||||
@@ -163,7 +160,6 @@ public class HelloFreshController : AppController
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Tous les utilisateurs : on joint aussi sur Users, puis on regroupe par NOM
|
|
||||||
var rows = (
|
var rows = (
|
||||||
from s in _context.SavingRecettes.AsNoTracking()
|
from s in _context.SavingRecettes.AsNoTracking()
|
||||||
join r in _context.Recettes.AsNoTracking() on s.IdSavingRecette equals r.Id
|
join r in _context.Recettes.AsNoTracking() on s.IdSavingRecette equals r.Id
|
||||||
@@ -212,7 +208,6 @@ public class HelloFreshController : AppController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
[HttpPost("DeleteRecipesOwned")]
|
[HttpPost("DeleteRecipesOwned")]
|
||||||
public IActionResult DeleteRecipesOwned([FromBody] string idSavingRecette)
|
public IActionResult DeleteRecipesOwned([FromBody] string idSavingRecette)
|
||||||
{
|
{
|
||||||
@@ -287,7 +282,7 @@ public class HelloFreshController : AppController
|
|||||||
client.DefaultRequestHeaders.Accept.ParseAdd("application/pdf");
|
client.DefaultRequestHeaders.Accept.ParseAdd("application/pdf");
|
||||||
client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("fr-FR,fr;q=0.9,en;q=0.8");
|
client.DefaultRequestHeaders.AcceptLanguage.ParseAdd("fr-FR,fr;q=0.9,en;q=0.8");
|
||||||
|
|
||||||
using var resp = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
|
using var resp = await client.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, HttpContext.RequestAborted);
|
||||||
if (!resp.IsSuccessStatusCode)
|
if (!resp.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
var preview = await resp.Content.ReadAsStringAsync();
|
var preview = await resp.Content.ReadAsStringAsync();
|
||||||
@@ -301,17 +296,16 @@ public class HelloFreshController : AppController
|
|||||||
|| uri.AbsolutePath.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase);
|
|| uri.AbsolutePath.EndsWith(".pdf", StringComparison.OrdinalIgnoreCase);
|
||||||
if (!isPdf) return BadRequest($"Le document récupéré n'est pas un PDF. Content-Type: {mediaType}");
|
if (!isPdf) return BadRequest($"Le document récupéré n'est pas un PDF. Content-Type: {mediaType}");
|
||||||
|
|
||||||
|
await using var upstream = await resp.Content.ReadAsStreamAsync(HttpContext.RequestAborted);
|
||||||
var ms = new MemoryStream();
|
var ms = new MemoryStream();
|
||||||
await resp.Content.CopyToAsync(ms);
|
await upstream.CopyToAsync(ms, HttpContext.RequestAborted);
|
||||||
ms.Position = 0;
|
ms.Position = 0;
|
||||||
|
|
||||||
if (resp.Content.Headers.ContentLength is long len)
|
|
||||||
Response.ContentLength = len;
|
|
||||||
|
|
||||||
Response.Headers["Cache-Control"] = "public, max-age=3600";
|
Response.Headers["Cache-Control"] = "public, max-age=3600";
|
||||||
Response.Headers["Content-Disposition"] = "inline; filename=\"recette.pdf\"";
|
Response.Headers["Content-Disposition"] = "inline; filename=\"recette.pdf\"";
|
||||||
|
|
||||||
return File(ms, "application/pdf");
|
// ✅ Range activé → scroll multi-pages iOS/iframe/pdf.js OK
|
||||||
|
return new FileStreamResult(ms, "application/pdf") { EnableRangeProcessing = true };
|
||||||
}
|
}
|
||||||
catch (TaskCanceledException)
|
catch (TaskCanceledException)
|
||||||
{
|
{
|
||||||
@@ -417,17 +411,21 @@ public class HelloFreshController : AppController
|
|||||||
.Select(r => new { r.Id, r.Ingredients })
|
.Select(r => new { r.Id, r.Ingredients })
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var portionById = perRecipe.ToDictionary(x => x.RecetteId, x => Math.Min(4, Math.Max(1, x.Portions)));
|
// ✅ on ne borne plus à 4 pour le calcul; si >4, on prend la table "4" et on scale
|
||||||
|
var portionsById = perRecipe.ToDictionary(x => x.RecetteId, x => Math.Max(1, x.Portions));
|
||||||
|
|
||||||
var rawItems = new List<IngredientItemDto>();
|
var rawItems = new List<IngredientItemDto>();
|
||||||
foreach (var r in recettes)
|
foreach (var r in recettes)
|
||||||
{
|
{
|
||||||
if (!portionById.TryGetValue(r.Id, out var portions)) portions = 1;
|
if (!portionsById.TryGetValue(r.Id, out var portions)) portions = 1;
|
||||||
|
|
||||||
|
int basePortions = Math.Min(4, Math.Max(1, portions));
|
||||||
|
double scale = portions <= 4 ? 1.0 : (double)portions / 4.0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var doc = JsonDocument.Parse(r.Ingredients ?? "{}");
|
using var doc = JsonDocument.Parse(r.Ingredients ?? "{}");
|
||||||
if (!doc.RootElement.TryGetProperty(portions.ToString(), out var arr) || arr.ValueKind != JsonValueKind.Array)
|
if (!doc.RootElement.TryGetProperty(basePortions.ToString(), out var arr) || arr.ValueKind != JsonValueKind.Array)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (var el in arr.EnumerateArray())
|
foreach (var el in arr.EnumerateArray())
|
||||||
@@ -435,9 +433,22 @@ public class HelloFreshController : AppController
|
|||||||
var name = el.TryGetProperty("Name", out var n) ? n.GetString() ?? "" : "";
|
var name = el.TryGetProperty("Name", out var n) ? n.GetString() ?? "" : "";
|
||||||
var qty = el.TryGetProperty("Quantity", out var q) ? q.GetString() : null;
|
var qty = el.TryGetProperty("Quantity", out var q) ? q.GetString() : null;
|
||||||
var img = el.TryGetProperty("Image", out var im) ? im.GetString() : null;
|
var img = el.TryGetProperty("Image", out var im) ? im.GetString() : null;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||||
|
|
||||||
|
// ✅ scale si >4 et quantité numérique
|
||||||
|
if (scale != 1.0 && !string.IsNullOrWhiteSpace(qty))
|
||||||
|
{
|
||||||
|
var parsed = ParseQuantity(qty);
|
||||||
|
if (parsed.Value is double v)
|
||||||
|
{
|
||||||
|
var scaled = v * scale;
|
||||||
|
var qtyStr = ((scaled % 1.0) == 0.0)
|
||||||
|
? scaled.ToString("0", System.Globalization.CultureInfo.InvariantCulture)
|
||||||
|
: scaled.ToString("0.##", System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
qty = string.IsNullOrWhiteSpace(parsed.Unit) ? qtyStr : $"{qtyStr} {parsed.Unit}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rawItems.Add(new IngredientItemDto
|
rawItems.Add(new IngredientItemDto
|
||||||
{
|
{
|
||||||
Name = name.Trim(),
|
Name = name.Trim(),
|
||||||
@@ -483,7 +494,6 @@ public class HelloFreshController : AppController
|
|||||||
var userId = HttpContext.Session.GetInt32("UserId");
|
var userId = HttpContext.Session.GetInt32("UserId");
|
||||||
if (userId == null) return Unauthorized("Utilisateur non connecté.");
|
if (userId == null) return Unauthorized("Utilisateur non connecté.");
|
||||||
|
|
||||||
// 1) Reconstituer la liste agrégée comme dans AggregatedIngredients
|
|
||||||
var ownedNames = _context.Ingredients.Select(i => i.NameOwnedIngredients)
|
var ownedNames = _context.Ingredients.Select(i => i.NameOwnedIngredients)
|
||||||
.ToList()
|
.ToList()
|
||||||
.Select(s => (s ?? "").Trim().ToLowerInvariant())
|
.Select(s => (s ?? "").Trim().ToLowerInvariant())
|
||||||
@@ -503,16 +513,20 @@ public class HelloFreshController : AppController
|
|||||||
.Select(r => new { r.Id, r.Ingredients })
|
.Select(r => new { r.Id, r.Ingredients })
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
var portionById = perRecipe.ToDictionary(x => x.RecetteId, x => Math.Min(4, Math.Max(1, x.Portions)));
|
var portionsById = perRecipe.ToDictionary(x => x.RecetteId, x => Math.Max(1, x.Portions));
|
||||||
|
|
||||||
var rawItems = new List<IngredientItemDto>();
|
var rawItems = new List<IngredientItemDto>();
|
||||||
foreach (var r in recettes)
|
foreach (var r in recettes)
|
||||||
{
|
{
|
||||||
if (!portionById.TryGetValue(r.Id, out var portions)) portions = 1;
|
if (!portionsById.TryGetValue(r.Id, out var portions)) portions = 1;
|
||||||
|
|
||||||
|
int basePortions = Math.Min(4, Math.Max(1, portions));
|
||||||
|
double scale = portions <= 4 ? 1.0 : (double)portions / 4.0;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
using var doc = JsonDocument.Parse(r.Ingredients ?? "{}");
|
using var doc = JsonDocument.Parse(r.Ingredients ?? "{}");
|
||||||
if (!doc.RootElement.TryGetProperty(portions.ToString(), out var arr) || arr.ValueKind != JsonValueKind.Array)
|
if (!doc.RootElement.TryGetProperty(basePortions.ToString(), out var arr) || arr.ValueKind != JsonValueKind.Array)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
foreach (var el in arr.EnumerateArray())
|
foreach (var el in arr.EnumerateArray())
|
||||||
@@ -520,8 +534,21 @@ public class HelloFreshController : AppController
|
|||||||
var name = el.TryGetProperty("Name", out var n) ? n.GetString() ?? "" : "";
|
var name = el.TryGetProperty("Name", out var n) ? n.GetString() ?? "" : "";
|
||||||
var qty = el.TryGetProperty("Quantity", out var q) ? q.GetString() : null;
|
var qty = el.TryGetProperty("Quantity", out var q) ? q.GetString() : null;
|
||||||
var img = el.TryGetProperty("Image", out var im) ? im.GetString() : null;
|
var img = el.TryGetProperty("Image", out var im) ? im.GetString() : null;
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(name)) continue;
|
if (string.IsNullOrWhiteSpace(name)) continue;
|
||||||
|
|
||||||
|
if (scale != 1.0 && !string.IsNullOrWhiteSpace(qty))
|
||||||
|
{
|
||||||
|
var parsed = ParseQuantity(qty);
|
||||||
|
if (parsed.Value is double v)
|
||||||
|
{
|
||||||
|
var scaled = v * scale;
|
||||||
|
var qtyStr = ((scaled % 1.0) == 0.0)
|
||||||
|
? scaled.ToString("0", System.Globalization.CultureInfo.InvariantCulture)
|
||||||
|
: scaled.ToString("0.##", System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
qty = string.IsNullOrWhiteSpace(parsed.Unit) ? qtyStr : $"{qtyStr} {parsed.Unit}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
rawItems.Add(new IngredientItemDto
|
rawItems.Add(new IngredientItemDto
|
||||||
{
|
{
|
||||||
Name = name.Trim(),
|
Name = name.Trim(),
|
||||||
@@ -537,9 +564,8 @@ public class HelloFreshController : AppController
|
|||||||
var aggregated = AggregateIngredients(rawItems, ownedNames);
|
var aggregated = AggregateIngredients(rawItems, ownedNames);
|
||||||
var allowedNames = aggregated.Select(a => a.Name).Distinct().ToList();
|
var allowedNames = aggregated.Select(a => a.Name).Distinct().ToList();
|
||||||
|
|
||||||
// 2) Appel interne à notre IA selector /api/ai/select (même host)
|
|
||||||
var client = httpClientFactory.CreateClient();
|
var client = httpClientFactory.CreateClient();
|
||||||
client.Timeout = TimeSpan.FromSeconds(60); // on laisse confortable
|
client.Timeout = TimeSpan.FromSeconds(60);
|
||||||
|
|
||||||
var baseUrl = $"{Request.Scheme}://{Request.Host}";
|
var baseUrl = $"{Request.Scheme}://{Request.Host}";
|
||||||
var reqBody = new
|
var reqBody = new
|
||||||
@@ -559,7 +585,6 @@ public class HelloFreshController : AppController
|
|||||||
var raw = await resp.Content.ReadAsStringAsync();
|
var raw = await resp.Content.ReadAsStringAsync();
|
||||||
if (!resp.IsSuccessStatusCode)
|
if (!resp.IsSuccessStatusCode)
|
||||||
{
|
{
|
||||||
// fallback live (contient)
|
|
||||||
var nq = (body?.Query ?? "").Trim().ToLowerInvariant();
|
var nq = (body?.Query ?? "").Trim().ToLowerInvariant();
|
||||||
var fallback = string.IsNullOrEmpty(nq)
|
var fallback = string.IsNullOrEmpty(nq)
|
||||||
? aggregated
|
? aggregated
|
||||||
@@ -577,7 +602,6 @@ public class HelloFreshController : AppController
|
|||||||
? nEl.EnumerateArray().Select(x => x.GetString() ?? "").Where(s => !string.IsNullOrWhiteSpace(s)).ToList()
|
? nEl.EnumerateArray().Select(x => x.GetString() ?? "").Where(s => !string.IsNullOrWhiteSpace(s)).ToList()
|
||||||
: new List<string>();
|
: new List<string>();
|
||||||
|
|
||||||
// 3) Filtrer/ordonner selon la réponse IA
|
|
||||||
var order = names.Select((n, i) => new { n = n.Trim().ToLowerInvariant(), i })
|
var order = names.Select((n, i) => new { n = n.Trim().ToLowerInvariant(), i })
|
||||||
.ToDictionary(x => x.n, x => x.i);
|
.ToDictionary(x => x.n, x => x.i);
|
||||||
var selected = aggregated
|
var selected = aggregated
|
||||||
@@ -585,7 +609,6 @@ public class HelloFreshController : AppController
|
|||||||
.OrderBy(a => order[(a.Name ?? "").Trim().ToLowerInvariant()])
|
.OrderBy(a => order[(a.Name ?? "").Trim().ToLowerInvariant()])
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// Fallback si l’IA ne renvoie rien
|
|
||||||
if (selected.Count == 0)
|
if (selected.Count == 0)
|
||||||
{
|
{
|
||||||
var nq = (body?.Query ?? "").Trim().ToLowerInvariant();
|
var nq = (body?.Query ?? "").Trim().ToLowerInvariant();
|
||||||
@@ -625,7 +648,7 @@ public class HelloFreshController : AppController
|
|||||||
image = r.Image,
|
image = r.Image,
|
||||||
difficulte = r.Difficulte,
|
difficulte = r.Difficulte,
|
||||||
ingredientsJson = r.Ingredients,
|
ingredientsJson = r.Ingredients,
|
||||||
pdf = r.Pdf // 🟢 important pour le fallback
|
pdf = r.Pdf
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
@@ -676,10 +699,10 @@ public class HelloFreshController : AppController
|
|||||||
return StatusCode(500, new { message = "Erreur serveur", error = ex.Message });
|
return StatusCode(500, new { message = "Erreur serveur", error = ex.Message });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet("GetAllRecipes")]
|
[HttpGet("GetAllRecipes")]
|
||||||
public IActionResult GetAllRecipes()
|
public IActionResult GetAllRecipes()
|
||||||
{
|
{
|
||||||
// 1) Comptages par recette et utilisateur
|
|
||||||
var counts = _context.SavingRecettes
|
var counts = _context.SavingRecettes
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.GroupBy(s => new { s.IdSavingRecette, s.UserId })
|
.GroupBy(s => new { s.IdSavingRecette, s.UserId })
|
||||||
@@ -691,8 +714,6 @@ public class HelloFreshController : AppController
|
|||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 2) Recettes ✅ AJOUTE le champ JSON "IngredientsToHad"
|
|
||||||
// ⚠️ adapte "r.IngredientsToHad" si ton modèle a un autre nom (ex: IngredientsToHaveAtHome / Pantry / etc.)
|
|
||||||
var recettes = _context.Recettes
|
var recettes = _context.Recettes
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Select(r => new
|
.Select(r => new
|
||||||
@@ -702,11 +723,10 @@ public class HelloFreshController : AppController
|
|||||||
r.Image,
|
r.Image,
|
||||||
r.TempsDePreparation,
|
r.TempsDePreparation,
|
||||||
r.Tags,
|
r.Tags,
|
||||||
IngredientsToHad = r.IngredientsToHad // <— ICI
|
IngredientsToHad = r.IngredientsToHad
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 3) Users
|
|
||||||
var neededUserIds = counts.Select(x => x.UserId).Distinct().ToList();
|
var neededUserIds = counts.Select(x => x.UserId).Distinct().ToList();
|
||||||
|
|
||||||
var users = _layout.Users
|
var users = _layout.Users
|
||||||
@@ -715,7 +735,6 @@ public class HelloFreshController : AppController
|
|||||||
.Select(u => new { u.Id, u.Username })
|
.Select(u => new { u.Id, u.Username })
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 4) Join en mémoire ✅ propage "IngredientsToHad"
|
|
||||||
var baseRows =
|
var baseRows =
|
||||||
from c in counts
|
from c in counts
|
||||||
join r in recettes on c.RecetteId equals r.Id
|
join r in recettes on c.RecetteId equals r.Id
|
||||||
@@ -729,11 +748,10 @@ public class HelloFreshController : AppController
|
|||||||
r.TempsDePreparation,
|
r.TempsDePreparation,
|
||||||
c.Portions,
|
c.Portions,
|
||||||
r.Tags,
|
r.Tags,
|
||||||
r.IngredientsToHad, // <— ICI
|
r.IngredientsToHad,
|
||||||
User = u?.Username
|
User = u?.Username
|
||||||
};
|
};
|
||||||
|
|
||||||
// 5) Regroupement final par RecetteId
|
|
||||||
var result = baseRows
|
var result = baseRows
|
||||||
.GroupBy(x => x.RecetteId)
|
.GroupBy(x => x.RecetteId)
|
||||||
.Select(g => new
|
.Select(g => new
|
||||||
@@ -748,8 +766,6 @@ public class HelloFreshController : AppController
|
|||||||
.Select(x => x.User)
|
.Select(x => x.User)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList(),
|
.ToList(),
|
||||||
|
|
||||||
// ✅ expose le JSON côté API en camelCase pour le front
|
|
||||||
ingredientsToHad = g.First().IngredientsToHad
|
ingredientsToHad = g.First().IngredientsToHad
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -757,9 +773,6 @@ public class HelloFreshController : AppController
|
|||||||
return Ok(result);
|
return Ok(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("/HelloFresh/GetAllRecipesWithUsers")]
|
[HttpGet("/HelloFresh/GetAllRecipesWithUsers")]
|
||||||
public IActionResult GetAllRecipesWithUsers()
|
public IActionResult GetAllRecipesWithUsers()
|
||||||
{
|
{
|
||||||
@@ -787,8 +800,6 @@ public class HelloFreshController : AppController
|
|||||||
return Ok(data);
|
return Ok(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpGet("GetHistory")]
|
[HttpGet("GetHistory")]
|
||||||
public IActionResult GetHistory([FromQuery] bool isUserImportant = true, [FromQuery] string? search = null)
|
public IActionResult GetHistory([FromQuery] bool isUserImportant = true, [FromQuery] string? search = null)
|
||||||
{
|
{
|
||||||
@@ -798,7 +809,6 @@ public class HelloFreshController : AppController
|
|||||||
if (isUserImportant && sessionUserId == null)
|
if (isUserImportant && sessionUserId == null)
|
||||||
return Unauthorized("Utilisateur non connecté.");
|
return Unauthorized("Utilisateur non connecté.");
|
||||||
|
|
||||||
// 1) Historique (matérialisé)
|
|
||||||
var histQ = _context.HistoriqueRecettes.AsNoTracking();
|
var histQ = _context.HistoriqueRecettes.AsNoTracking();
|
||||||
if (isUserImportant) histQ = histQ.Where(h => h.UserId == sessionUserId);
|
if (isUserImportant) histQ = histQ.Where(h => h.UserId == sessionUserId);
|
||||||
|
|
||||||
@@ -806,7 +816,6 @@ public class HelloFreshController : AppController
|
|||||||
.Select(h => new { h.DateHistorique, h.RecetteId, h.UserId })
|
.Select(h => new { h.DateHistorique, h.RecetteId, h.UserId })
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 2) Dictionnaire Recettes avec clé normalisée (trim+lower) pour éviter tout souci de casse/espaces
|
|
||||||
string K(string? s) => (s ?? "").Trim().ToLowerInvariant();
|
string K(string? s) => (s ?? "").Trim().ToLowerInvariant();
|
||||||
var recDict = _context.Recettes.AsNoTracking()
|
var recDict = _context.Recettes.AsNoTracking()
|
||||||
.Select(r => new { r.Id, r.Name, r.Image, r.TempsDePreparation })
|
.Select(r => new { r.Id, r.Name, r.Image, r.TempsDePreparation })
|
||||||
@@ -814,7 +823,6 @@ public class HelloFreshController : AppController
|
|||||||
.GroupBy(r => K(r.Id))
|
.GroupBy(r => K(r.Id))
|
||||||
.ToDictionary(g => g.Key, g => g.First());
|
.ToDictionary(g => g.Key, g => g.First());
|
||||||
|
|
||||||
// 3) “Join” en mémoire (pas de ?. dans l’expression EF)
|
|
||||||
var joined = hist.Select(h =>
|
var joined = hist.Select(h =>
|
||||||
{
|
{
|
||||||
recDict.TryGetValue(K(h.RecetteId), out var r);
|
recDict.TryGetValue(K(h.RecetteId), out var r);
|
||||||
@@ -829,14 +837,12 @@ public class HelloFreshController : AppController
|
|||||||
};
|
};
|
||||||
}).ToList();
|
}).ToList();
|
||||||
|
|
||||||
// 4) Filtre recherche serveur (optionnel)
|
|
||||||
if (!string.IsNullOrWhiteSpace(search))
|
if (!string.IsNullOrWhiteSpace(search))
|
||||||
{
|
{
|
||||||
var s = search.Trim().ToLowerInvariant();
|
var s = search.Trim().ToLowerInvariant();
|
||||||
joined = joined.Where(x => (x.Name ?? "").ToLowerInvariant().Contains(s)).ToList();
|
joined = joined.Where(x => (x.Name ?? "").ToLowerInvariant().Contains(s)).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5) Noms d’utilisateurs si “tous”
|
|
||||||
var userNames = new Dictionary<int, string>();
|
var userNames = new Dictionary<int, string>();
|
||||||
if (!isUserImportant)
|
if (!isUserImportant)
|
||||||
{
|
{
|
||||||
@@ -849,7 +855,6 @@ public class HelloFreshController : AppController
|
|||||||
.ToDictionary(g => g.Key, g => g.First().Username ?? $"User#{g.Key}");
|
.ToDictionary(g => g.Key, g => g.First().Username ?? $"User#{g.Key}");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6) Regroupement par Date + Recette
|
|
||||||
var rows = joined
|
var rows = joined
|
||||||
.GroupBy(x => new { x.Date, x.RecetteId })
|
.GroupBy(x => new { x.Date, x.RecetteId })
|
||||||
.Select(g => new
|
.Select(g => new
|
||||||
@@ -868,7 +873,6 @@ public class HelloFreshController : AppController
|
|||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 7) Mise en forme par date
|
|
||||||
var culture = new System.Globalization.CultureInfo("fr-FR");
|
var culture = new System.Globalization.CultureInfo("fr-FR");
|
||||||
var result = rows
|
var result = rows
|
||||||
.GroupBy(x => x.Date)
|
.GroupBy(x => x.Date)
|
||||||
@@ -889,8 +893,6 @@ public class HelloFreshController : AppController
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[HttpPost("ArchiveAll")]
|
[HttpPost("ArchiveAll")]
|
||||||
public IActionResult ArchiveAll()
|
public IActionResult ArchiveAll()
|
||||||
{
|
{
|
||||||
@@ -899,7 +901,6 @@ public class HelloFreshController : AppController
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// 1) Récup selections de l'utilisateur
|
|
||||||
var selections = _context.SavingRecettes
|
var selections = _context.SavingRecettes
|
||||||
.Where(s => s.UserId == userId)
|
.Where(s => s.UserId == userId)
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
@@ -908,16 +909,14 @@ public class HelloFreshController : AppController
|
|||||||
if (selections.Count == 0)
|
if (selections.Count == 0)
|
||||||
return Ok(new { message = "empty", archivedPortions = 0, archivedRecipes = 0, skippedIds = Array.Empty<string>() });
|
return Ok(new { message = "empty", archivedPortions = 0, archivedRecipes = 0, skippedIds = Array.Empty<string>() });
|
||||||
|
|
||||||
// 2) Groupes par recette (id string) = nb de portions
|
|
||||||
var groups = selections
|
var groups = selections
|
||||||
.GroupBy(s => s.IdSavingRecette)
|
.GroupBy(s => s.IdSavingRecette)
|
||||||
.Select(g => new { RecetteId = g.Key, Portions = g.Count() })
|
.Select(g => new { RecetteId = g.Key, Portions = g.Count() })
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 3) Filtrer sur ids valides (évite FK/erreurs)
|
|
||||||
var validIds = _context.Recettes
|
var validIds = _context.Recettes
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.Select(r => r.Id) // string
|
.Select(r => r.Id)
|
||||||
.ToHashSet(StringComparer.Ordinal);
|
.ToHashSet(StringComparer.Ordinal);
|
||||||
|
|
||||||
var validGroups = groups.Where(g => validIds.Contains(g.RecetteId)).ToList();
|
var validGroups = groups.Where(g => validIds.Contains(g.RecetteId)).ToList();
|
||||||
@@ -926,7 +925,6 @@ public class HelloFreshController : AppController
|
|||||||
if (validGroups.Count == 0)
|
if (validGroups.Count == 0)
|
||||||
return Ok(new { message = "nothing_valid", archivedPortions = 0, archivedRecipes = 0, skippedIds });
|
return Ok(new { message = "nothing_valid", archivedPortions = 0, archivedRecipes = 0, skippedIds });
|
||||||
|
|
||||||
// 4) Date Paris (DateOnly)
|
|
||||||
DateOnly todayParis;
|
DateOnly todayParis;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -937,7 +935,6 @@ public class HelloFreshController : AppController
|
|||||||
|
|
||||||
using var tx = _context.Database.BeginTransaction();
|
using var tx = _context.Database.BeginTransaction();
|
||||||
|
|
||||||
// 5) Préparer les insertions (1 ligne par portion)
|
|
||||||
var toInsert = new List<HistoriqueRecette>(capacity: validGroups.Sum(x => x.Portions));
|
var toInsert = new List<HistoriqueRecette>(capacity: validGroups.Sum(x => x.Portions));
|
||||||
foreach (var g in validGroups)
|
foreach (var g in validGroups)
|
||||||
{
|
{
|
||||||
@@ -945,14 +942,13 @@ public class HelloFreshController : AppController
|
|||||||
{
|
{
|
||||||
toInsert.Add(new HistoriqueRecette
|
toInsert.Add(new HistoriqueRecette
|
||||||
{
|
{
|
||||||
RecetteId = g.RecetteId, // string
|
RecetteId = g.RecetteId,
|
||||||
UserId = userId.Value,
|
UserId = userId.Value,
|
||||||
DateHistorique = todayParis
|
DateHistorique = todayParis
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6) Insert + Delete (seulement les rows correspondants aux ids valides)
|
|
||||||
if (toInsert.Count > 0)
|
if (toInsert.Count > 0)
|
||||||
_context.HistoriqueRecettes.AddRange(toInsert);
|
_context.HistoriqueRecettes.AddRange(toInsert);
|
||||||
|
|
||||||
@@ -962,11 +958,9 @@ public class HelloFreshController : AppController
|
|||||||
|
|
||||||
_context.SavingRecettes.RemoveRange(rowsToDelete);
|
_context.SavingRecettes.RemoveRange(rowsToDelete);
|
||||||
|
|
||||||
var archivedPortions = _context.SaveChanges(); // compte total opérations (insert + delete)
|
_context.SaveChanges();
|
||||||
tx.Commit();
|
tx.Commit();
|
||||||
|
|
||||||
// NB: archivedPortions = insertions + suppressions (EF renvoie le nb total de rows affectées)
|
|
||||||
// on renvoie plutôt nos compteurs fiables :
|
|
||||||
var archivedRecipes = validGroups.Count;
|
var archivedRecipes = validGroups.Count;
|
||||||
var archivedPieces = validGroups.Sum(g => g.Portions);
|
var archivedPieces = validGroups.Sum(g => g.Portions);
|
||||||
|
|
||||||
@@ -980,7 +974,6 @@ public class HelloFreshController : AppController
|
|||||||
}
|
}
|
||||||
catch (DbUpdateException dbex)
|
catch (DbUpdateException dbex)
|
||||||
{
|
{
|
||||||
// remonte l’essentiel pour debug front/logs
|
|
||||||
return StatusCode(500, new
|
return StatusCode(500, new
|
||||||
{
|
{
|
||||||
message = "archive_failed",
|
message = "archive_failed",
|
||||||
@@ -1001,17 +994,15 @@ public class HelloFreshController : AppController
|
|||||||
|
|
||||||
// POST /HelloFresh/HasHistory
|
// POST /HelloFresh/HasHistory
|
||||||
[HttpPost("HasHistory")]
|
[HttpPost("HasHistory")]
|
||||||
[IgnoreAntiforgeryToken] // garde si tu postes depuis du JS sans token
|
[IgnoreAntiforgeryToken]
|
||||||
public async Task<IActionResult> HasHistory([FromBody] string recipeId)
|
public async Task<IActionResult> HasHistory([FromBody] string recipeId)
|
||||||
{
|
{
|
||||||
// même logique que le reste de ton contrôleur : user via Session
|
|
||||||
var sessionUserId = HttpContext.Session.GetInt32("UserId");
|
var sessionUserId = HttpContext.Session.GetInt32("UserId");
|
||||||
if (sessionUserId == null) return Unauthorized("Utilisateur non connecté.");
|
if (sessionUserId == null) return Unauthorized("Utilisateur non connecté.");
|
||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(recipeId))
|
if (string.IsNullOrWhiteSpace(recipeId))
|
||||||
return Ok(new { exists = false });
|
return Ok(new { exists = false });
|
||||||
|
|
||||||
// ⚠️ compare sur la bonne colonne (RecetteId) et type string
|
|
||||||
bool exists = await _context.HistoriqueRecettes
|
bool exists = await _context.HistoriqueRecettes
|
||||||
.AsNoTracking()
|
.AsNoTracking()
|
||||||
.AnyAsync(h => h.RecetteId == recipeId && h.UserId == sessionUserId.Value);
|
.AnyAsync(h => h.RecetteId == recipeId && h.UserId == sessionUserId.Value);
|
||||||
@@ -1064,6 +1055,7 @@ public class HelloFreshController : AppController
|
|||||||
var list = tags.OrderBy(s => s, StringComparer.Create(new System.Globalization.CultureInfo("fr-FR"), true)).ToList();
|
var list = tags.OrderBy(s => s, StringComparer.Create(new System.Globalization.CultureInfo("fr-FR"), true)).ToList();
|
||||||
return Ok(list);
|
return Ok(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
// API: recettes choisies distinctes par nom, avec PDF
|
// API: recettes choisies distinctes par nom, avec PDF
|
||||||
[HttpGet("GetOwnedForReader")]
|
[HttpGet("GetOwnedForReader")]
|
||||||
public IActionResult GetOwnedForReader()
|
public IActionResult GetOwnedForReader()
|
||||||
@@ -1071,7 +1063,6 @@ public class HelloFreshController : AppController
|
|||||||
var userId = HttpContext.Session.GetInt32("UserId");
|
var userId = HttpContext.Session.GetInt32("UserId");
|
||||||
if (userId == null) return Unauthorized("Utilisateur non connecté.");
|
if (userId == null) return Unauthorized("Utilisateur non connecté.");
|
||||||
|
|
||||||
// 1) portions par recette de l'utilisateur
|
|
||||||
var owned = _context.SavingRecettes
|
var owned = _context.SavingRecettes
|
||||||
.Where(s => s.UserId == userId)
|
.Where(s => s.UserId == userId)
|
||||||
.GroupBy(s => s.IdSavingRecette)
|
.GroupBy(s => s.IdSavingRecette)
|
||||||
@@ -1080,7 +1071,6 @@ public class HelloFreshController : AppController
|
|||||||
|
|
||||||
if (owned.Count == 0) return Ok(Array.Empty<object>());
|
if (owned.Count == 0) return Ok(Array.Empty<object>());
|
||||||
|
|
||||||
// 2) join recettes (on ramène le PDF)
|
|
||||||
var joined = owned
|
var joined = owned
|
||||||
.Join(_context.Recettes,
|
.Join(_context.Recettes,
|
||||||
g => g.RecetteId,
|
g => g.RecetteId,
|
||||||
@@ -1096,7 +1086,6 @@ public class HelloFreshController : AppController
|
|||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// 3) dédoublonnage par nom (insensible à la casse/espaces) — on garde celui avec + de portions puis nom ASC
|
|
||||||
string Key(string s) => (s ?? "").Trim().ToLowerInvariant();
|
string Key(string s) => (s ?? "").Trim().ToLowerInvariant();
|
||||||
var distinct = joined
|
var distinct = joined
|
||||||
.GroupBy(x => Key(x.name))
|
.GroupBy(x => Key(x.name))
|
||||||
@@ -1106,4 +1095,4 @@ public class HelloFreshController : AppController
|
|||||||
|
|
||||||
return Ok(distinct);
|
return Ok(distinct);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
0
Views/Connections/3n2yu2pg.uts~
Normal file
0
Views/Connections/3n2yu2pg.uts~
Normal file
0
Views/Connections/psr2sgj0.jxy~
Normal file
0
Views/Connections/psr2sgj0.jxy~
Normal file
@@ -82,7 +82,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cui-cards {
|
.cui-cards {
|
||||||
padding: 10px;
|
padding: 5px;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
@@ -185,19 +185,16 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.cui-view {
|
.cui-view {
|
||||||
position: relative;
|
height: 100%;
|
||||||
flex: 1;
|
overflow: auto;
|
||||||
min-height: 0;
|
-webkit-overflow-scrolling: touch;
|
||||||
background: #0a0d13;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.cui-iframe {
|
.cui-iframe, .viewer-iframe {
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: 0;
|
border: 0;
|
||||||
display: none;
|
display: block;
|
||||||
}
|
}
|
||||||
|
|
||||||
.cui-placeholder {
|
.cui-placeholder {
|
||||||
@@ -295,6 +292,8 @@ body {
|
|||||||
/* quand la sidebar est réduite, même anneau interne */
|
/* quand la sidebar est réduite, même anneau interne */
|
||||||
.cui-root.collapsed .cui-card.active {
|
.cui-root.collapsed .cui-card.active {
|
||||||
box-shadow: 0 0 0 2px var(--accent) inset;
|
box-shadow: 0 0 0 2px var(--accent) inset;
|
||||||
|
width: max-content;
|
||||||
|
grid-template-columns: 29px 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Titre de la barre du haut : ellipsis propre si le nom est long */
|
/* Titre de la barre du haut : ellipsis propre si le nom est long */
|
||||||
|
|||||||
@@ -33,6 +33,15 @@
|
|||||||
const normalize = (s) => (s ?? "").toString().trim().toLowerCase();
|
const normalize = (s) => (s ?? "").toString().trim().toLowerCase();
|
||||||
const proxify = (url) => url ? `/HelloFresh/ProxyPdf?url=${encodeURIComponent(url)}` : "";
|
const proxify = (url) => url ? `/HelloFresh/ProxyPdf?url=${encodeURIComponent(url)}` : "";
|
||||||
|
|
||||||
|
// Détection iOS (iPhone/iPad et iPadOS mode desktop)
|
||||||
|
const IS_IOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.userAgent.includes("Mac") && "ontouchend" in document);
|
||||||
|
els.btnOpen?.addEventListener("click", () => {
|
||||||
|
const r = RECIPES[CUR]; if (!r?.pdf) return;
|
||||||
|
const u = `/HelloFresh/ProxyPdf?url=${encodeURIComponent(r.pdf)}`;
|
||||||
|
if (IS_IOS) window.open(u, "_blank", "noopener"); else openViewer();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
function buildPdfSrc(rawUrl) {
|
function buildPdfSrc(rawUrl) {
|
||||||
if (!rawUrl) return "";
|
if (!rawUrl) return "";
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ let currentPage = 1;
|
|||||||
let lastPageGlobal = 1;
|
let lastPageGlobal = 1;
|
||||||
const countPerPage = 12;
|
const countPerPage = 12;
|
||||||
let currentSearchTerm = '';
|
let currentSearchTerm = '';
|
||||||
const MAX_PORTIONS = 14;
|
|
||||||
let CURRENT_CLIENT_LIST = null; // liste filtrée paginée côté client
|
let CURRENT_CLIENT_LIST = null; // liste filtrée paginée côté client
|
||||||
let ALL_RECIPES_CACHE = null; // toutes les recettes (toutes pages)
|
let ALL_RECIPES_CACHE = null; // toutes les recettes (toutes pages)
|
||||||
|
|
||||||
@@ -19,10 +18,19 @@ const labelMaps = { ingredients: {}, tags: {} };
|
|||||||
// idRecette -> portions (0..3)
|
// idRecette -> portions (0..3)
|
||||||
const ownedMap = new Map();
|
const ownedMap = new Map();
|
||||||
|
|
||||||
|
|
||||||
|
const MAX_PER_RECIPE = null; // null = illimité par recette
|
||||||
|
const MAX_TOTAL = null; // null = illimité au total
|
||||||
|
const clampQty = (n) => {
|
||||||
|
const v = Number(n) || 0;
|
||||||
|
if (v < 0) return 0;
|
||||||
|
// si bornes définies, on clamp ; sinon on laisse passer
|
||||||
|
if (Number.isFinite(MAX_PER_RECIPE)) return Math.min(MAX_PER_RECIPE, v);
|
||||||
|
return v;
|
||||||
|
};
|
||||||
/***********************
|
/***********************
|
||||||
* UTILS
|
* UTILS
|
||||||
***********************/
|
***********************/
|
||||||
const clampQty = n => Math.max(0, Math.min(3, Number(n) || 0));
|
|
||||||
const truncate = (s, m) => (s && s.length > m ? s.slice(0, m) + '…' : (s ?? ''));
|
const truncate = (s, m) => (s && s.length > m ? s.slice(0, m) + '…' : (s ?? ''));
|
||||||
const normalize = (str) => (str ?? '').toString().normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim();
|
const normalize = (str) => (str ?? '').toString().normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase().trim();
|
||||||
const splitTags = (str) => (str || '').split(/[,•]/).map(t => t.trim()).filter(Boolean).filter(t => t !== '•');
|
const splitTags = (str) => (str || '').split(/[,•]/).map(t => t.trim()).filter(Boolean).filter(t => t !== '•');
|
||||||
@@ -232,7 +240,12 @@ function buildRecipeCardList(recipe) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const current = clampQty(ownedMap.get(String(recipe.id)) || 0);
|
const current = clampQty(ownedMap.get(String(recipe.id)) || 0);
|
||||||
if (current === 0 && !canAddPortion()) { alert(`Limite atteinte (${MAX_PORTIONS}).`); return; }
|
if (current === 0 && !canAddPortion(recipe.id)) {
|
||||||
|
// message seulement s'il y a vraiment une limite
|
||||||
|
if (Number.isFinite(MAX_TOTAL)) alert(`Limite atteinte (${MAX_TOTAL}).`);
|
||||||
|
else if (Number.isFinite(MAX_PER_RECIPE)) alert(`Limite par recette atteinte (${MAX_PER_RECIPE}).`);
|
||||||
|
return;
|
||||||
|
};
|
||||||
const selectedNow = current > 0;
|
const selectedNow = current > 0;
|
||||||
if (!selectedNow) await saveRecipe(recipe.id); else await clearRecipe(recipe.id);
|
if (!selectedNow) await saveRecipe(recipe.id); else await clearRecipe(recipe.id);
|
||||||
applySelectionUIById(recipe.id);
|
applySelectionUIById(recipe.id);
|
||||||
@@ -273,8 +286,15 @@ function buildRecipeCardOwned(recipe) {
|
|||||||
|
|
||||||
// plus/moins existants
|
// plus/moins existants
|
||||||
card.querySelector('.btn-minus')?.addEventListener('click', (e) => { e.stopPropagation(); removeRecette(recipe.id); updateOwnedCountUI(); if (getTotalPortionsFromMap() === 0) closeOwnedIfOpen(); });
|
card.querySelector('.btn-minus')?.addEventListener('click', (e) => { e.stopPropagation(); removeRecette(recipe.id); updateOwnedCountUI(); if (getTotalPortionsFromMap() === 0) closeOwnedIfOpen(); });
|
||||||
card.querySelector('.btn-plus')?.addEventListener('click', (e) => { e.stopPropagation(); if (!canAddPortion()) { alert(`Limite atteinte (${MAX_PORTIONS}).`); return; } saveRecipe(recipe.id); updateOwnedCountUI(); });
|
card.querySelector('.btn-plus')?.addEventListener('click', (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
if (!canAddPortion(recipe.id)) {
|
||||||
|
if (Number.isFinite(MAX_TOTAL)) alert(`Limite atteinte (${MAX_TOTAL}).`);
|
||||||
|
else if (Number.isFinite(MAX_PER_RECIPE)) alert(`Limite par recette atteinte (${MAX_PER_RECIPE}).`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveRecipe(recipe.id); updateOwnedCountUI();
|
||||||
|
});
|
||||||
// 🟢 Ctrl+clic = ouvrir le PDF (avec fallback GetRecipesDetails)
|
// 🟢 Ctrl+clic = ouvrir le PDF (avec fallback GetRecipesDetails)
|
||||||
card.addEventListener('click', async (e) => {
|
card.addEventListener('click', async (e) => {
|
||||||
if (!e.ctrlKey) return;
|
if (!e.ctrlKey) return;
|
||||||
@@ -743,12 +763,28 @@ function wireFooterButtons() {
|
|||||||
***********************/
|
***********************/
|
||||||
function getTotalPortionsFromMap() { let total = 0; ownedMap.forEach(v => { total += (Number(v) || 0); }); return total; }
|
function getTotalPortionsFromMap() { let total = 0; ownedMap.forEach(v => { total += (Number(v) || 0); }); return total; }
|
||||||
function updateOwnedCountUI() {
|
function updateOwnedCountUI() {
|
||||||
const el = document.getElementById('ownedCount'); if (!el) return;
|
const el = document.getElementById('ownedCount'); if (!el) return;
|
||||||
const total = getTotalPortionsFromMap(); el.textContent = `(${total}/${MAX_PORTIONS})`;
|
const total = getTotalPortionsFromMap();
|
||||||
el.style.color = (total >= MAX_PORTIONS) ? '#c62828' : '';
|
if (Number.isFinite(MAX_TOTAL)) {
|
||||||
|
el.textContent = `(${total}/${MAX_TOTAL})`;
|
||||||
|
el.style.color = (total >= MAX_TOTAL) ? '#c62828' : '';
|
||||||
|
} else {
|
||||||
|
// affichage sans borne
|
||||||
|
el.textContent = `(${total})`;
|
||||||
|
el.style.color = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function canAddPortion(forRecipeId) {
|
||||||
|
// limite globale
|
||||||
|
if (Number.isFinite(MAX_TOTAL) && getTotalPortionsFromMap() >= MAX_TOTAL) return false;
|
||||||
|
// limite par recette
|
||||||
|
if (Number.isFinite(MAX_PER_RECIPE)) {
|
||||||
|
const curr = Number(ownedMap.get(String(forRecipeId)) || 0);
|
||||||
|
return curr < MAX_PER_RECIPE;
|
||||||
|
}
|
||||||
|
// illimité
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
function canAddPortion() { return getTotalPortionsFromMap() < MAX_PORTIONS; }
|
|
||||||
|
|
||||||
/***********************
|
/***********************
|
||||||
* Ingredients meta attach (for page)
|
* Ingredients meta attach (for page)
|
||||||
***********************/
|
***********************/
|
||||||
|
|||||||
Reference in New Issue
Block a user