feat: bring urgency sync, background auto-sync, recipe mealplan chip, screensaver fix
This commit is contained in:
+144
-12
@@ -1350,6 +1350,7 @@ function generateRecipe(PDO $db): void {
|
|||||||
$appliances = $input['appliances'] ?? [];
|
$appliances = $input['appliances'] ?? [];
|
||||||
$dietaryRestrictions = $input['dietary_restrictions'] ?? '';
|
$dietaryRestrictions = $input['dietary_restrictions'] ?? '';
|
||||||
$todayRecipes = $input['today_recipes'] ?? [];
|
$todayRecipes = $input['today_recipes'] ?? [];
|
||||||
|
$mealPlanType = $input['meal_plan_type'] ?? ''; // e.g. 'pasta', 'pesce', 'legumi', ...
|
||||||
|
|
||||||
// Fetch all inventory items with expiry info
|
// Fetch all inventory items with expiry info
|
||||||
$stmt = $db->query("
|
$stmt = $db->query("
|
||||||
@@ -1503,6 +1504,79 @@ function generateRecipe(PDO $db): void {
|
|||||||
$dietaryText = "\n\nRESTRIZIONI ALIMENTARI:\n{$dietaryRestrictions}\nRispetta SEMPRE queste restrizioni.";
|
$dietaryText = "\n\nRESTRIZIONI ALIMENTARI:\n{$dietaryRestrictions}\nRispetta SEMPRE queste restrizioni.";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Weekly meal plan type hint
|
||||||
|
$mealPlanTypeLabels = [
|
||||||
|
'pasta' => 'Pasta (primo piatto a base di pasta)',
|
||||||
|
'riso' => 'Riso (risotto, insalata di riso, riso saltato, ecc.)',
|
||||||
|
'carne' => 'Carne (secondo piatto a base di carne)',
|
||||||
|
'pesce' => 'Pesce (secondo piatto a base di pesce o frutti di mare)',
|
||||||
|
'legumi' => 'Legumi (zuppa, insalata, hummus, pasta e fagioli, ecc.)',
|
||||||
|
'uova' => 'Uova (frittata, uova strapazzate, quiche, ecc.)',
|
||||||
|
'formaggio' => 'Formaggio (fonduta, gnocchi al formaggio, torta salata, ecc.)',
|
||||||
|
'pizza' => 'Pizza o focaccia (impastata in casa o usi ingredienti simili)',
|
||||||
|
'affettati' => 'Affettati (tagliere misto, piadina, panino, ecc.)',
|
||||||
|
'verdure' => 'Verdure (piatto principale a base di verdure, contorno abbondante)',
|
||||||
|
'zuppa' => 'Zuppa o minestra (zuppe, vellutate, minestrone)',
|
||||||
|
'insalata' => 'Insalata (insalata mista, insalata di riso o pasta, poke)',
|
||||||
|
'pane' => 'Pane / Sandwich (toast, tramezzino, bruschette)',
|
||||||
|
'dolce' => 'Dolce o dessert',
|
||||||
|
'libero' => '',
|
||||||
|
];
|
||||||
|
|
||||||
|
// Keywords to match inventory names against each meal plan type
|
||||||
|
$typeKeywords = [
|
||||||
|
'pesce' => ['tonno', 'salmone', 'merluzzo', 'branzino', 'orata', 'sardine', 'acciughe', 'alici', 'gamberi', 'cozze', 'vongole', 'polpo', 'calamari', 'seppia', 'sgombro', 'trota', 'baccalà', 'dentice', 'spigola', 'pesce'],
|
||||||
|
'carne' => ['pollo', 'manzo', 'maiale', 'vitello', 'agnello', 'tacchino', 'salsiccia', 'hamburger', 'bistecca', 'cotoletta', 'pancetta', 'speck', 'carne', 'arrosto', 'filetto', 'lonza', 'braciola'],
|
||||||
|
'pasta' => ['pasta', 'spaghetti', 'penne', 'rigatoni', 'fusilli', 'tagliatelle', 'lasagne', 'farfalle', 'orecchiette', 'bucatini', 'linguine', 'maccheroni', 'gnocchi', 'pennette', 'bavette'],
|
||||||
|
'riso' => ['riso', 'basmati', 'arborio', 'carnaroli', 'parboiled', 'riso integrale'],
|
||||||
|
'legumi' => ['fagioli', 'ceci', 'lenticchie', 'piselli', 'fave', 'lupini', 'soia', 'legumi', 'borlotti', 'cannellini', 'azuki'],
|
||||||
|
'uova' => ['uova', 'uovo'],
|
||||||
|
'formaggio' => ['formaggio', 'parmigiano', 'mozzarella', 'ricotta', 'pecorino', 'grana', 'gorgonzola', 'scamorza', 'fontina', 'emmental', 'asiago', 'provola', 'provolone', 'taleggio', 'stracchino'],
|
||||||
|
'pizza' => ['farina', 'lievito', 'pizza', 'focaccia'],
|
||||||
|
'affettati' => ['prosciutto', 'salame', 'bresaola', 'mortadella', 'speck', 'coppa', 'affettati', 'wurstel', 'würstel', 'piadina', 'pancetta cotta'],
|
||||||
|
'verdure' => ['zucchine', 'zucchina', 'melanzane', 'peperoni', 'spinaci', 'cavolfiore', 'broccoli', 'carote', 'zucca', 'bietole', 'cavolo', 'carciofi', 'asparagi', 'lattuga', 'rucola', 'radicchio', 'cicoria', 'finocchio', 'cipolla', 'porri', 'verdure'],
|
||||||
|
'zuppa' => ['brodo', 'zuppa', 'minestra', 'minestrone', 'vellutata', 'orzo', 'farro', 'fagioli', 'ceci', 'lenticchie'],
|
||||||
|
'insalata' => ['insalata', 'lattuga', 'rucola', 'spinaci', 'radicchio', 'misticanza', 'valeriana', 'songino'],
|
||||||
|
'pane' => ['pane', 'pancarrè', 'baguette', 'toast', 'tramezzino', 'crackers', 'grissini', 'ciabatta', 'rosetta'],
|
||||||
|
'dolce' => ['cioccolato', 'cacao', 'zucchero', 'miele', 'marmellata', 'nutella', 'creme caramel', 'savoiardi', 'biscotti', 'pan di spagna', 'panna'],
|
||||||
|
];
|
||||||
|
|
||||||
|
$mealPlanText = '';
|
||||||
|
$mealPlanRule = '';
|
||||||
|
if (!empty($mealPlanType) && isset($mealPlanTypeLabels[$mealPlanType]) && $mealPlanTypeLabels[$mealPlanType] !== '') {
|
||||||
|
$hint = $mealPlanTypeLabels[$mealPlanType];
|
||||||
|
|
||||||
|
// Scan inventory for ingredients matching this meal plan type
|
||||||
|
$matchingItems = [];
|
||||||
|
if (isset($typeKeywords[$mealPlanType])) {
|
||||||
|
foreach ($items as $item) {
|
||||||
|
$nameLower = mb_strtolower($item['name'] . ' ' . ($item['brand'] ?? ''));
|
||||||
|
foreach ($typeKeywords[$mealPlanType] as $kw) {
|
||||||
|
if (mb_strpos($nameLower, $kw) !== false) {
|
||||||
|
$entry = "→ {$item['name']}" . ($item['brand'] ? " ({$item['brand']})" : '') . ": {$item['quantity']} {$item['unit']}";
|
||||||
|
if (!empty($item['expiry_date'])) {
|
||||||
|
$dl = intval($item['days_left']);
|
||||||
|
$entry .= $dl < 0 ? " [SCADUTO]" : " [scade tra $dl giorni]";
|
||||||
|
}
|
||||||
|
$matchingItems[] = $entry;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$matchingItems = array_unique($matchingItems);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($matchingItems)) {
|
||||||
|
$matchingList = implode("\n", $matchingItems);
|
||||||
|
$matchingBlock = "Ingredienti disponibili in dispensa compatibili con questa tipologia (usa almeno uno di questi come BASE della ricetta):\n{$matchingList}";
|
||||||
|
} else {
|
||||||
|
$matchingBlock = "Nessun ingrediente perfettamente corrispondente trovato — usa la cosa più affine disponibile e segnalalo in nutrition_note.";
|
||||||
|
}
|
||||||
|
|
||||||
|
$mealPlanText = "\n\n🎯 TIPOLOGIA PASTO PIANIFICATA — OBBLIGATORIA:\nOggi questo pasto DEVE essere: {$hint}\nQuesta è una regola del piano alimentare personale dell'utente, NON un suggerimento.\n{$matchingBlock}";
|
||||||
|
$mealPlanRule = "0. TIPOLOGIA PASTO OBBLIGATORIA: la ricetta DEVE rispettare il tipo pianificato ({$hint}). Usa gli ingredienti compatibili evidenziati sopra come base principale del piatto. Non ignorare questa regola.\n ";
|
||||||
|
}
|
||||||
|
|
||||||
// Today's previous recipes from DB - avoid repetition
|
// Today's previous recipes from DB - avoid repetition
|
||||||
$todayText = '';
|
$todayText = '';
|
||||||
$today = date('Y-m-d');
|
$today = date('Y-m-d');
|
||||||
@@ -1542,10 +1616,10 @@ function generateRecipe(PDO $db): void {
|
|||||||
|
|
||||||
$prompt = <<<PROMPT
|
$prompt = <<<PROMPT
|
||||||
Sei un nutrizionista e chef italiano esperto. Genera UNA ricetta per $mealLabel per $persons persona/e usando PRINCIPALMENTE gli ingredienti disponibili nella dispensa dell'utente.
|
Sei un nutrizionista e chef italiano esperto. Genera UNA ricetta per $mealLabel per $persons persona/e usando PRINCIPALMENTE gli ingredienti disponibili nella dispensa dell'utente.
|
||||||
{$extraRulesText}{$appliancesText}{$dietaryText}{$varietyText}{$mustUseText}
|
{$extraRulesText}{$appliancesText}{$dietaryText}{$mealPlanText}{$varietyText}{$mustUseText}
|
||||||
|
|
||||||
REGOLE IMPORTANTI:
|
REGOLE IMPORTANTI:
|
||||||
1. ORDINE DI PRIORITÀ INGREDIENTI (dal più urgente al meno urgente) — gli ingredienti nella lista sono già ordinati per priorità:
|
{$mealPlanRule}1. ORDINE DI PRIORITÀ INGREDIENTI (dal più urgente al meno urgente) — gli ingredienti nella lista sono già ordinati per priorità:
|
||||||
a) PRODOTTI SCADUTI: se ancora commestibili, usali SUBITO — hanno la priorità massima assoluta
|
a) PRODOTTI SCADUTI: se ancora commestibili, usali SUBITO — hanno la priorità massima assoluta
|
||||||
b) PRODOTTI IN SCADENZA IMMINENTE (≤3 giorni): usa questi per primi dopo gli scaduti
|
b) PRODOTTI IN SCADENZA IMMINENTE (≤3 giorni): usa questi per primi dopo gli scaduti
|
||||||
c) PRODOTTI IN SCADENZA RAVVICINATA (≤7 giorni): poi questi
|
c) PRODOTTI IN SCADENZA RAVVICINATA (≤7 giorni): poi questi
|
||||||
@@ -2165,15 +2239,16 @@ function bringAddItems(): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$added = 0;
|
$added = 0;
|
||||||
|
$updated = 0;
|
||||||
$skipped = 0;
|
$skipped = 0;
|
||||||
$errors = [];
|
$errors = [];
|
||||||
|
|
||||||
// Fetch current list to check for duplicates
|
// Fetch current list to check for duplicates and existing specs
|
||||||
$existingNames = [];
|
$existingItems = []; // strtolower(name) => specification
|
||||||
$listData = bringRequest('GET', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}");
|
$listData = bringRequest('GET', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}");
|
||||||
if ($listData && isset($listData['purchase'])) {
|
if ($listData && isset($listData['purchase'])) {
|
||||||
foreach ($listData['purchase'] as $existingItem) {
|
foreach ($listData['purchase'] as $existingItem) {
|
||||||
$existingNames[] = strtolower($existingItem['name'] ?? '');
|
$existingItems[strtolower($existingItem['name'] ?? '')] = $existingItem['specification'] ?? '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2183,14 +2258,26 @@ function bringAddItems(): void {
|
|||||||
|
|
||||||
// Map Italian name to Bring! catalog key (German) for proper recognition
|
// Map Italian name to Bring! catalog key (German) for proper recognition
|
||||||
$bringName = italianToBring($name);
|
$bringName = italianToBring($name);
|
||||||
|
$bringKey = strtolower($bringName);
|
||||||
|
$spec = $item['specification'] ?? '';
|
||||||
|
$update_spec = $item['update_spec'] ?? false; // explicit flag to force spec update
|
||||||
|
|
||||||
// Skip if already on the list
|
if (array_key_exists($bringKey, $existingItems)) {
|
||||||
if (in_array(strtolower($bringName), $existingNames)) {
|
// Item already on the list — only update if specification changed and update_spec requested
|
||||||
$skipped++;
|
if ($update_spec && $existingItems[$bringKey] !== $spec) {
|
||||||
|
$body = http_build_query([
|
||||||
|
'uuid' => $listUUID,
|
||||||
|
'purchase' => $bringName,
|
||||||
|
'specification' => $spec,
|
||||||
|
]);
|
||||||
|
$result = bringRequest('PUT', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}", $body);
|
||||||
|
if ($result !== null) $updated++;
|
||||||
|
} else {
|
||||||
|
$skipped++;
|
||||||
|
}
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
$spec = $item['specification'] ?? '';
|
|
||||||
$body = http_build_query([
|
$body = http_build_query([
|
||||||
'uuid' => $listUUID,
|
'uuid' => $listUUID,
|
||||||
'purchase' => $bringName,
|
'purchase' => $bringName,
|
||||||
@@ -2205,7 +2292,11 @@ function bringAddItems(): void {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
echo json_encode(['success' => true, 'added' => $added, 'skipped' => $skipped, 'errors' => $errors]);
|
if ($added > 0 || $updated > 0) {
|
||||||
|
// Invalidate cache so next smart_shopping request reflects the updated Bring! list
|
||||||
|
@unlink(__DIR__ . '/../data/smart_shopping_cache.json');
|
||||||
|
}
|
||||||
|
echo json_encode(['success' => true, 'added' => $added, 'updated' => $updated, 'skipped' => $skipped, 'errors' => $errors]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function bringRemoveItem(): void {
|
function bringRemoveItem(): void {
|
||||||
@@ -2234,6 +2325,10 @@ function bringRemoveItem(): void {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
$result = bringRequest('PUT', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}", $body);
|
$result = bringRequest('PUT', "https://api.getbring.com/rest/v2/bringlists/{$listUUID}", $body);
|
||||||
|
if ($result !== null) {
|
||||||
|
// Invalidate cache so next smart_shopping request reflects the updated Bring! list
|
||||||
|
@unlink(__DIR__ . '/../data/smart_shopping_cache.json');
|
||||||
|
}
|
||||||
echo json_encode(['success' => $result !== null]);
|
echo json_encode(['success' => $result !== null]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2305,6 +2400,43 @@ function smartShoppingCached(PDO $db): void {
|
|||||||
* Smart Shopping List: analyzes usage frequency, stock levels, expiry to produce
|
* Smart Shopping List: analyzes usage frequency, stock levels, expiry to produce
|
||||||
* intelligent urgency-ranked shopping recommendations.
|
* intelligent urgency-ranked shopping recommendations.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Token-based fuzzy match: returns true if the product name shares at least one
|
||||||
|
* significant word (> 2 chars, not a stopword) with any key in $bringItems.
|
||||||
|
* Mirrors the JS _findSimilarItem / _nameTokens logic.
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Strict matching: returns true only when a Bring item's name "covers" the product name,
|
||||||
|
* i.e. the FIRST significant token of the product matches the FIRST significant token of
|
||||||
|
* a Bring item name. This prevents false positives like "Früchte/Frutta" matching the
|
||||||
|
* product "Muesli Frutta Secca" (which has "frutta" as a secondary token, not the first).
|
||||||
|
* Mirrors JS _matchBringToSmart / _syncOnBringFlags logic.
|
||||||
|
*/
|
||||||
|
function _productOnBring(string $productName, array $bringItems): bool {
|
||||||
|
// Exact key match (both German raw and Italian translated keys are stored)
|
||||||
|
if (isset($bringItems[mb_strtolower($productName)])) return true;
|
||||||
|
static $stop = ['di','del','della','dei','degli','dalle','delle','da','in','con','per','su',
|
||||||
|
'a','e','il','lo','la','i','gli','le','un','uno','una','al','alle','agli','allo'];
|
||||||
|
$tokenize = function(string $s) use ($stop): array {
|
||||||
|
$clean = mb_strtolower(preg_replace('/[^\p{L}\s]/u', ' ', $s));
|
||||||
|
return array_values(array_filter(
|
||||||
|
preg_split('/\s+/', trim($clean)),
|
||||||
|
fn($t) => mb_strlen($t) > 2 && !in_array($t, $stop)
|
||||||
|
));
|
||||||
|
};
|
||||||
|
$pTokens = $tokenize($productName);
|
||||||
|
if (empty($pTokens)) return false;
|
||||||
|
$pFirst = $pTokens[0];
|
||||||
|
foreach (array_keys($bringItems) as $bKey) {
|
||||||
|
$bTokens = $tokenize($bKey);
|
||||||
|
if (empty($bTokens)) continue;
|
||||||
|
// First token of product must equal first token of Bring item
|
||||||
|
if ($bTokens[0] === $pFirst) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
function smartShopping(PDO $db): void {
|
function smartShopping(PDO $db): void {
|
||||||
$now = time();
|
$now = time();
|
||||||
$today = date('Y-m-d');
|
$today = date('Y-m-d');
|
||||||
@@ -2513,8 +2645,8 @@ function smartShopping(PDO $db): void {
|
|||||||
if ($useCount >= 8) $score += 15;
|
if ($useCount >= 8) $score += 15;
|
||||||
elseif ($useCount >= 5) $score += 10;
|
elseif ($useCount >= 5) $score += 10;
|
||||||
|
|
||||||
// Is already on Bring?
|
// Is already on Bring? (fuzzy token match — mirrors JS _findSimilarItem logic)
|
||||||
$onBring = isset($bringItems[mb_strtolower($p['name'])]);
|
$onBring = _productOnBring($p['name'], $bringItems);
|
||||||
|
|
||||||
$items[] = [
|
$items[] = [
|
||||||
'product_id' => $pid,
|
'product_id' => $pid,
|
||||||
|
|||||||
@@ -2970,6 +2970,155 @@ body {
|
|||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== WEEKLY MEAL PLAN ===== */
|
||||||
|
.mplan-grid {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
}
|
||||||
|
.mplan-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 4px 2px;
|
||||||
|
padding-left: 44px;
|
||||||
|
}
|
||||||
|
.mplan-col-header {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--text-muted);
|
||||||
|
}
|
||||||
|
.mplan-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 5px 4px;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
background: var(--bg);
|
||||||
|
}
|
||||||
|
.mplan-row-today {
|
||||||
|
background: rgba(45,80,22,0.08);
|
||||||
|
outline: 2px solid var(--primary-light);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
}
|
||||||
|
.mplan-day-name {
|
||||||
|
width: 36px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: var(--text-muted);
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.mplan-badge {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 3px;
|
||||||
|
background: var(--primary);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 7px 6px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
cursor: pointer;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
transition: background 0.15s, transform 0.1s;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
.mplan-badge:active { transform: scale(0.93); }
|
||||||
|
.mplan-badge-pranzo { background: var(--primary-light); }
|
||||||
|
.mplan-badge-cena { background: #1e3a6e; }
|
||||||
|
|
||||||
|
/* Meal plan picker popup */
|
||||||
|
.mplan-picker {
|
||||||
|
position: fixed;
|
||||||
|
z-index: 600;
|
||||||
|
background: var(--bg-card);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: var(--radius);
|
||||||
|
box-shadow: var(--shadow-lg);
|
||||||
|
padding: 12px;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 8px;
|
||||||
|
left: 50% !important;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
width: min(320px, 92vw);
|
||||||
|
}
|
||||||
|
.mplan-pick-btn {
|
||||||
|
background: var(--bg);
|
||||||
|
border: 1.5px solid var(--border);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 7px 12px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.15s;
|
||||||
|
}
|
||||||
|
.mplan-pick-btn.active {
|
||||||
|
background: var(--primary);
|
||||||
|
color: #fff;
|
||||||
|
border-color: var(--primary);
|
||||||
|
}
|
||||||
|
.mplan-pick-btn:active { opacity: 0.7; }
|
||||||
|
|
||||||
|
/* Legend in settings */
|
||||||
|
.mplan-legend {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recipe dialog banner (top strip) */
|
||||||
|
.recipe-mealplan-banner {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 10px;
|
||||||
|
background: var(--primary);
|
||||||
|
color: #fff;
|
||||||
|
padding: 11px 20px;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 700;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: var(--radius) var(--radius) 0 0;
|
||||||
|
margin: -20px -20px 16px;
|
||||||
|
letter-spacing: 0.2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Recipe dialog hint */
|
||||||
|
.recipe-mealplan-hint {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
background: rgba(45,80,22,0.07);
|
||||||
|
border: 1px solid rgba(45,80,22,0.18);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
padding: 8px 12px;
|
||||||
|
margin: 4px 0 12px;
|
||||||
|
font-size: 0.88rem;
|
||||||
|
}
|
||||||
|
.mplan-hint-badge {
|
||||||
|
background: var(--primary);
|
||||||
|
color: #fff;
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 3px 10px;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
font-weight: 700;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
.mplan-hint-label {
|
||||||
|
color: var(--text-muted);
|
||||||
|
font-size: 0.80rem;
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== COOKING MODE ===== */
|
/* ===== COOKING MODE ===== */
|
||||||
.cooking-overlay {
|
.cooking-overlay {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
@@ -3191,6 +3340,29 @@ body {
|
|||||||
to { opacity: 0.5; transform: scale(1.06); }
|
to { opacity: 0.5; transform: scale(1.06); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== COOKING SCREEN FLASH ===== */
|
||||||
|
.cooking-flash-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 50;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
.cooking-flash-overlay.flash-warning {
|
||||||
|
animation: cookFlashOrange 1.1s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
.cooking-flash-overlay.flash-done {
|
||||||
|
animation: cookFlashRed 0.65s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
@keyframes cookFlashOrange {
|
||||||
|
0%, 100% { background: transparent; }
|
||||||
|
50% { background: rgba(249, 115, 22, 0.28); }
|
||||||
|
}
|
||||||
|
@keyframes cookFlashRed {
|
||||||
|
0%, 100% { background: transparent; }
|
||||||
|
50% { background: rgba(220, 38, 38, 0.42); }
|
||||||
|
}
|
||||||
|
|
||||||
.ctimer-btns {
|
.ctimer-btns {
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
@@ -3664,6 +3836,23 @@ body {
|
|||||||
border-color: var(--primary);
|
border-color: var(--primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Meal-plan chip: visually highlighted to stand out as the planned food type */
|
||||||
|
.recipe-opt-mealplan-chip {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
background: rgba(100, 60, 20, 0.07);
|
||||||
|
border-color: #b07830;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.recipe-opt-mealplan-chip:has(input:checked) {
|
||||||
|
background: rgba(120, 70, 10, 0.13);
|
||||||
|
border-color: #c08020;
|
||||||
|
}
|
||||||
|
.recipe-opt-mealplan-chip:has(input:not(:checked)) {
|
||||||
|
opacity: 0.6;
|
||||||
|
text-decoration: line-through;
|
||||||
|
text-decoration-color: #c08020;
|
||||||
|
}
|
||||||
|
|
||||||
.recipe-option-chip input[type="checkbox"] {
|
.recipe-option-chip input[type="checkbox"] {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
@@ -4628,6 +4817,24 @@ body {
|
|||||||
.screensaver-fact.visible {
|
.screensaver-fact.visible {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
.screensaver-mealplan {
|
||||||
|
text-align: center;
|
||||||
|
user-select: none;
|
||||||
|
margin: -8px 0 4px;
|
||||||
|
}
|
||||||
|
.screensaver-mealplan-badge {
|
||||||
|
display: inline-block;
|
||||||
|
background: rgba(255,255,255,0.10);
|
||||||
|
color: rgba(255,255,255,0.70);
|
||||||
|
border: 1px solid rgba(255,255,255,0.18);
|
||||||
|
border-radius: 28px;
|
||||||
|
padding: 8px 24px;
|
||||||
|
font-size: 1.35rem;
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: 0.4px;
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
}
|
||||||
.screensaver-shortcuts {
|
.screensaver-shortcuts {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
bottom: max(32px, env(safe-area-inset-bottom, 32px));
|
bottom: max(32px, env(safe-area-inset-bottom, 32px));
|
||||||
|
|||||||
+552
-19
@@ -633,6 +633,21 @@ async function loadSettingsUI() {
|
|||||||
loadCameraDevices();
|
loadCameraDevices();
|
||||||
renderAppliances(s.appliances || []);
|
renderAppliances(s.appliances || []);
|
||||||
loadSpesaSettings();
|
loadSpesaSettings();
|
||||||
|
const mealPlanEnabled = s.meal_plan_enabled !== false;
|
||||||
|
const mpEnabledEl = document.getElementById('setting-meal-plan-enabled');
|
||||||
|
if (mpEnabledEl) mpEnabledEl.checked = mealPlanEnabled;
|
||||||
|
const mpConfigSection = document.getElementById('meal-plan-config-section');
|
||||||
|
if (mpConfigSection) mpConfigSection.style.display = mealPlanEnabled ? '' : 'none';
|
||||||
|
const mpLegendCard = document.getElementById('meal-plan-legend-card');
|
||||||
|
if (mpLegendCard) mpLegendCard.style.display = mealPlanEnabled ? '' : 'none';
|
||||||
|
renderMealPlanEditor();
|
||||||
|
// Render legend
|
||||||
|
const legend = document.querySelector('.mplan-legend');
|
||||||
|
if (legend) {
|
||||||
|
legend.innerHTML = MEAL_PLAN_TYPES.map(t =>
|
||||||
|
`<span class="mplan-badge" style="opacity:0.85">${t.icon} ${t.label}</span>`
|
||||||
|
).join('');
|
||||||
|
}
|
||||||
|
|
||||||
// Load server-side settings if not already set locally
|
// Load server-side settings if not already set locally
|
||||||
try {
|
try {
|
||||||
@@ -730,6 +745,9 @@ async function saveSettings() {
|
|||||||
s.dietary = document.getElementById('setting-dietary').value.trim();
|
s.dietary = document.getElementById('setting-dietary').value.trim();
|
||||||
// Camera
|
// Camera
|
||||||
s.camera_facing = document.getElementById('setting-camera-facing').value;
|
s.camera_facing = document.getElementById('setting-camera-facing').value;
|
||||||
|
// Meal plan enabled toggle
|
||||||
|
const mpEnabledEl = document.getElementById('setting-meal-plan-enabled');
|
||||||
|
if (mpEnabledEl) s.meal_plan_enabled = mpEnabledEl.checked;
|
||||||
// Save spesa AI prompt if the field exists
|
// Save spesa AI prompt if the field exists
|
||||||
const spesaPromptEl = document.getElementById('setting-spesa-ai-prompt');
|
const spesaPromptEl = document.getElementById('setting-spesa-ai-prompt');
|
||||||
if (spesaPromptEl) s.spesa_ai_prompt = spesaPromptEl.value.trim();
|
if (spesaPromptEl) s.spesa_ai_prompt = spesaPromptEl.value.trim();
|
||||||
@@ -3661,6 +3679,7 @@ function _nameTokens(name) {
|
|||||||
* Check whether `name` matches any item in `list` (array of {name}).
|
* Check whether `name` matches any item in `list` (array of {name}).
|
||||||
* Returns the matching item or null.
|
* Returns the matching item or null.
|
||||||
* A match = at least one significant token in common.
|
* A match = at least one significant token in common.
|
||||||
|
* NOTE: intentionally loose — use _matchBringToSmart for display/urgency matching.
|
||||||
*/
|
*/
|
||||||
function _findSimilarItem(name, list) {
|
function _findSimilarItem(name, list) {
|
||||||
const tokens = _nameTokens(name);
|
const tokens = _nameTokens(name);
|
||||||
@@ -3671,6 +3690,40 @@ function _findSimilarItem(name, list) {
|
|||||||
}) || null;
|
}) || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Strict matching: find the smart item that corresponds to a Bring item by name.
|
||||||
|
* Rules (in order):
|
||||||
|
* 1. Exact case-insensitive match.
|
||||||
|
* 2. First significant token of both names must be identical
|
||||||
|
* ("Latte" → "Latte Parzialmente Scremato" ✓; "Frutta" ≠ "Muesli Frutta Secca" ✗).
|
||||||
|
* 3. For multi-token Bring names: all Bring tokens appear in the smart item tokens.
|
||||||
|
* This avoids false positives when a generic word ("frutta", "noci") appears as a
|
||||||
|
* secondary word inside an unrelated long product name.
|
||||||
|
*/
|
||||||
|
function _matchBringToSmart(bringName, smartItems) {
|
||||||
|
const bLower = bringName.toLowerCase();
|
||||||
|
const exact = smartItems.find(sd => sd.name.toLowerCase() === bLower);
|
||||||
|
if (exact) return exact;
|
||||||
|
const bTokens = _nameTokens(bringName);
|
||||||
|
if (bTokens.length === 0) return null;
|
||||||
|
const bFirst = bTokens[0];
|
||||||
|
// Rule 2: first token match
|
||||||
|
const firstMatch = smartItems.find(sd => {
|
||||||
|
const sdTokens = _nameTokens(sd.name);
|
||||||
|
return sdTokens.length > 0 && sdTokens[0] === bFirst;
|
||||||
|
});
|
||||||
|
if (firstMatch) return firstMatch;
|
||||||
|
// Rule 3: multi-token full subset
|
||||||
|
if (bTokens.length >= 2) {
|
||||||
|
const allMatch = smartItems.find(sd => {
|
||||||
|
const sdTokens = _nameTokens(sd.name);
|
||||||
|
return bTokens.every(t => sdTokens.includes(t));
|
||||||
|
});
|
||||||
|
if (allMatch) return allMatch;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
function showLowStockBringPrompt(result, afterCallback) {
|
function showLowStockBringPrompt(result, afterCallback) {
|
||||||
const name = result.product_name || currentProduct?.name || '';
|
const name = result.product_name || currentProduct?.name || '';
|
||||||
const unit = result.product_unit || currentProduct?.unit || 'pz';
|
const unit = result.product_unit || currentProduct?.unit || 'pz';
|
||||||
@@ -4288,6 +4341,19 @@ function toggleShoppingTag(itemIdx, tag) {
|
|||||||
if (existing.length) tags[key] = existing;
|
if (existing.length) tags[key] = existing;
|
||||||
else delete tags[key];
|
else delete tags[key];
|
||||||
localStorage.setItem('shopping_tags', JSON.stringify(tags));
|
localStorage.setItem('shopping_tags', JSON.stringify(tags));
|
||||||
|
|
||||||
|
// Sync urgente/presto tag to Bring specification so it's visible in the Bring app
|
||||||
|
if (tag === 'urgente' && shoppingListUUID) {
|
||||||
|
const isNowUrgent = existing.includes('urgente');
|
||||||
|
const newSpec = isNowUrgent ? '⚡ Urgente' : '';
|
||||||
|
api('bring_add', {}, 'POST', {
|
||||||
|
items: [{ name: item.name, specification: newSpec, update_spec: true }],
|
||||||
|
listUUID: shoppingListUUID,
|
||||||
|
}).catch(() => {});
|
||||||
|
// Update local item spec for immediate re-render
|
||||||
|
item.specification = newSpec;
|
||||||
|
}
|
||||||
|
|
||||||
renderShoppingItems();
|
renderShoppingItems();
|
||||||
} catch (e) { console.error('toggleShoppingTag', e); }
|
} catch (e) { console.error('toggleShoppingTag', e); }
|
||||||
}
|
}
|
||||||
@@ -4320,12 +4386,24 @@ async function confirmShoppingItemFound() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ===== AUTO-ADD CRITICAL ITEMS TO BRING! =====
|
// ===== AUTO-ADD CRITICAL ITEMS TO BRING! =====
|
||||||
|
|
||||||
|
/** Build a Bring specification string that encodes urgency + optional brand. */
|
||||||
|
function _urgencyToSpec(urgency, brand) {
|
||||||
|
const urgencyLabels = { critical: '⚡ Urgente', high: '🟠 Presto', medium: '', low: '' };
|
||||||
|
const urgLabel = urgencyLabels[urgency] || '';
|
||||||
|
if (urgLabel && brand) return `${urgLabel} · ${brand}`;
|
||||||
|
if (urgLabel) return urgLabel;
|
||||||
|
return brand || '';
|
||||||
|
}
|
||||||
|
|
||||||
async function autoAddCriticalItems() {
|
async function autoAddCriticalItems() {
|
||||||
if (sessionStorage.getItem('_autoAddedCritical')) return;
|
// Time-based guard: run at most once every 10 minutes (not session-based, so new critical items get added promptly)
|
||||||
sessionStorage.setItem('_autoAddedCritical', '1');
|
const lastRun = parseInt(localStorage.getItem('_autoAddedCriticalTs') || '0');
|
||||||
|
if (Date.now() - lastRun < 10 * 60 * 1000) return;
|
||||||
|
localStorage.setItem('_autoAddedCriticalTs', String(Date.now()));
|
||||||
const critical = smartShoppingItems.filter(i => i.urgency === 'critical' && !i.on_bring);
|
const critical = smartShoppingItems.filter(i => i.urgency === 'critical' && !i.on_bring);
|
||||||
if (critical.length === 0) return;
|
if (critical.length === 0) return;
|
||||||
const itemsToAdd = critical.map(i => ({ name: i.name, specification: i.brand || '' }));
|
const itemsToAdd = critical.map(i => ({ name: i.name, specification: _urgencyToSpec(i.urgency, i.brand) }));
|
||||||
try {
|
try {
|
||||||
const result = await api('bring_add', {}, 'POST', { items: itemsToAdd, listUUID: shoppingListUUID });
|
const result = await api('bring_add', {}, 'POST', { items: itemsToAdd, listUUID: shoppingListUUID });
|
||||||
if (result.success && result.added > 0) {
|
if (result.success && result.added > 0) {
|
||||||
@@ -4353,7 +4431,7 @@ async function cleanupObsoleteBringItems() {
|
|||||||
// Load all products from our DB to cross-reference
|
// Load all products from our DB to cross-reference
|
||||||
let allProducts = [];
|
let allProducts = [];
|
||||||
try {
|
try {
|
||||||
const res = await api('products');
|
const res = await api('products_list');
|
||||||
allProducts = res.products || res.data || [];
|
allProducts = res.products || res.data || [];
|
||||||
} catch (e) { return; }
|
} catch (e) { return; }
|
||||||
if (!allProducts.length) return;
|
if (!allProducts.length) return;
|
||||||
@@ -4529,6 +4607,28 @@ function _updateSmartUrgencyBadge() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync the on_bring flag for every smartShoppingItem against the current shoppingItems list.
|
||||||
|
* The server cache can be up to 10 min old so on_bring may be stale — this corrects it
|
||||||
|
* client-side using strict first-token matching: a Bring item matches a smart item only when
|
||||||
|
* the first significant token of the Bring item's name equals the first significant token of
|
||||||
|
* the smart item's name (or exact name match). This avoids false positives like
|
||||||
|
* "Frutta" (fresh fruit on Bring) matching "Muesli Frutta Secca" (a different product).
|
||||||
|
*/
|
||||||
|
function _syncOnBringFlags() {
|
||||||
|
for (const si of smartShoppingItems) {
|
||||||
|
const siLower = si.name.toLowerCase();
|
||||||
|
const siFirst = _nameTokens(si.name)[0];
|
||||||
|
si.on_bring = !!(
|
||||||
|
shoppingItems.find(bi => bi.name.toLowerCase() === siLower) ||
|
||||||
|
(siFirst && shoppingItems.find(bi => {
|
||||||
|
const biFirst = _nameTokens(bi.name)[0];
|
||||||
|
return biFirst === siFirst;
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function _renderSmartLastUpdate() {
|
function _renderSmartLastUpdate() {
|
||||||
const el = document.getElementById('smart-last-update');
|
const el = document.getElementById('smart-last-update');
|
||||||
if (!el || !_smartShoppingLastFetch) return;
|
if (!el || !_smartShoppingLastFetch) return;
|
||||||
@@ -4704,7 +4804,7 @@ async function addSmartToBring() {
|
|||||||
if (item) {
|
if (item) {
|
||||||
itemsToAdd.push({
|
itemsToAdd.push({
|
||||||
name: item.name,
|
name: item.name,
|
||||||
specification: item.brand || '',
|
specification: _urgencyToSpec(item.urgency, item.brand),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -4759,6 +4859,67 @@ async function loadShoppingCount() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync local 'urgente' tag from Bring specification.
|
||||||
|
* If a Bring item's specification contains 'urgente', ensure the local tag is set.
|
||||||
|
* If a Bring item's specification is empty/cleared, remove the local urgente tag
|
||||||
|
* UNLESS smart shopping considers it critical (to avoid losing urgency on stale specs).
|
||||||
|
*/
|
||||||
|
function _syncTagsFromBringSpec() {
|
||||||
|
try {
|
||||||
|
const tags = JSON.parse(localStorage.getItem('shopping_tags') || '{}');
|
||||||
|
let changed = false;
|
||||||
|
for (const item of shoppingItems) {
|
||||||
|
const key = item.name.toLowerCase();
|
||||||
|
const spec = (item.specification || '').toLowerCase();
|
||||||
|
const existing = tags[key] || [];
|
||||||
|
const hasUrgente = existing.includes('urgente');
|
||||||
|
const smartMatch = _matchBringToSmart(item.name, smartShoppingItems);
|
||||||
|
const smartIsCritical = smartMatch && (smartMatch.urgency === 'critical' || smartMatch.urgency === 'high');
|
||||||
|
if ((spec.includes('urgente') || spec.includes('presto') || smartIsCritical) && !hasUrgente) {
|
||||||
|
existing.push('urgente');
|
||||||
|
tags[key] = existing;
|
||||||
|
changed = true;
|
||||||
|
} else if (!spec.includes('urgente') && !spec.includes('presto') && !smartIsCritical && hasUrgente) {
|
||||||
|
existing.splice(existing.indexOf('urgente'), 1);
|
||||||
|
if (existing.length) tags[key] = existing;
|
||||||
|
else delete tags[key];
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (changed) localStorage.setItem('shopping_tags', JSON.stringify(tags));
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* After smart shopping loads, push urgency specifications to Bring for all matched items.
|
||||||
|
* This makes urgency visible in the native Bring app via the item specification field.
|
||||||
|
* Only updates if the spec has changed (to avoid unnecessary API calls).
|
||||||
|
*/
|
||||||
|
async function autoSyncUrgencySpecs() {
|
||||||
|
if (!shoppingListUUID || !smartShoppingItems.length) return;
|
||||||
|
const toUpdate = [];
|
||||||
|
for (const item of shoppingItems) {
|
||||||
|
const smartMatch = _matchBringToSmart(item.name, smartShoppingItems);
|
||||||
|
if (!smartMatch) continue;
|
||||||
|
const expectedSpec = _urgencyToSpec(smartMatch.urgency, '');
|
||||||
|
const currentSpec = (item.specification || '').toLowerCase();
|
||||||
|
// Only update if urgency marker changed (don't clobber user-set spec info that isn't urgency)
|
||||||
|
const currentHasUrgencyMarker = currentSpec.includes('urgente') || currentSpec.includes('presto');
|
||||||
|
const needsUpdate = expectedSpec && !currentHasUrgencyMarker;
|
||||||
|
const needsClear = !expectedSpec && currentHasUrgencyMarker;
|
||||||
|
if (needsUpdate || needsClear) {
|
||||||
|
toUpdate.push({ name: item.name, specification: expectedSpec, update_spec: true });
|
||||||
|
// Optimistically update local item so re-render is immediate
|
||||||
|
item.specification = expectedSpec;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (toUpdate.length === 0) return;
|
||||||
|
try {
|
||||||
|
await api('bring_add', {}, 'POST', { items: toUpdate, listUUID: shoppingListUUID });
|
||||||
|
} catch (e) { /* ignore - sync is best-effort */ }
|
||||||
|
}
|
||||||
|
|
||||||
async function loadShoppingList() {
|
async function loadShoppingList() {
|
||||||
const statusEl = document.getElementById('bring-status');
|
const statusEl = document.getElementById('bring-status');
|
||||||
const currentEl = document.getElementById('shopping-current');
|
const currentEl = document.getElementById('shopping-current');
|
||||||
@@ -4794,19 +4955,23 @@ async function loadShoppingList() {
|
|||||||
if (pricesChanged) saveShoppingPrices();
|
if (pricesChanged) saveShoppingPrices();
|
||||||
|
|
||||||
loadShoppingPrices();
|
loadShoppingPrices();
|
||||||
|
// Sync urgente local tags from Bring specification (items marked urgent by us or manually)
|
||||||
|
_syncTagsFromBringSpec();
|
||||||
renderShoppingItems();
|
renderShoppingItems();
|
||||||
currentEl.style.display = 'block';
|
currentEl.style.display = 'block';
|
||||||
|
|
||||||
// Load smart shopping predictions, then re-render to show badges + auto-add critical
|
// Load smart shopping predictions, then re-render to show badges + auto-add critical
|
||||||
loadSmartShopping().then(() => {
|
loadSmartShopping().then(() => {
|
||||||
|
_syncOnBringFlags(); // sync on_bring against current Bring list before any logic reads it
|
||||||
|
_syncTagsFromBringSpec(); // re-sync tags now that smart data is available
|
||||||
|
autoSyncUrgencySpecs(); // push urgency specs to Bring for matched items
|
||||||
|
renderSmartShopping(); // re-render smart tab with corrected on_bring flags
|
||||||
|
updateShoppingTabCounts(); // update tab badges with corrected counts
|
||||||
autoAddCriticalItems();
|
autoAddCriticalItems();
|
||||||
cleanupObsoleteBringItems();
|
cleanupObsoleteBringItems();
|
||||||
renderShoppingItems(); // re-render to apply urgency badges from smart data
|
renderShoppingItems(); // re-render shopping tab with urgency badges
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show tabs once data is ready
|
|
||||||
updateShoppingTabCounts();
|
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Bring! error:', err);
|
console.error('Bring! error:', err);
|
||||||
statusEl.style.display = 'block';
|
statusEl.style.display = 'block';
|
||||||
@@ -4814,6 +4979,24 @@ async function loadShoppingList() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Return the spec text to show in the UI, stripping urgency markers (those are shown as badges). */
|
||||||
|
function _specDisplayText(spec) {
|
||||||
|
if (!spec) return '';
|
||||||
|
// Strip known urgency prefixes set by _urgencyToSpec (case-insensitive, then trim separator)
|
||||||
|
const lower = spec.toLowerCase();
|
||||||
|
for (const prefix of ['⚡ urgente', '🟠 presto']) {
|
||||||
|
if (lower.startsWith(prefix)) {
|
||||||
|
return spec.slice(prefix.length).replace(/^\s*[·\-]\s*/, '').trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return spec;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the spec for price search, stripping urgency markers that would confuse the AI. */
|
||||||
|
function _cleanSpecForSearch(spec) {
|
||||||
|
return _specDisplayText(spec);
|
||||||
|
}
|
||||||
|
|
||||||
async function renderShoppingItems() {
|
async function renderShoppingItems() {
|
||||||
const container = document.getElementById('shopping-items');
|
const container = document.getElementById('shopping-items');
|
||||||
const countEl = document.getElementById('shopping-count');
|
const countEl = document.getElementById('shopping-count');
|
||||||
@@ -4855,10 +5038,17 @@ async function renderShoppingItems() {
|
|||||||
low: { icon: '🟢', label: 'Ok', cls: 'badge-low' },
|
low: { icon: '🟢', label: 'Ok', cls: 'badge-low' },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map each item to its section + urgency
|
// Map each item to its section + urgency (strict first-token matching to avoid false positives)
|
||||||
|
// Also derive urgency from Bring specification if smart matching fails
|
||||||
const enriched = shoppingItems.map((item, idx) => {
|
const enriched = shoppingItems.map((item, idx) => {
|
||||||
const smartData = smartShoppingItems.find(sd => sd.name.toLowerCase() === item.name.toLowerCase());
|
const smartData = _matchBringToSmart(item.name, smartShoppingItems);
|
||||||
const urgency = smartData?.urgency || null;
|
let urgency = smartData?.urgency || null;
|
||||||
|
// Fallback: read urgency from Bring specification (set by our app when adding)
|
||||||
|
if (!urgency && item.specification) {
|
||||||
|
const spec = item.specification.toLowerCase();
|
||||||
|
if (spec.includes('urgente')) urgency = 'critical';
|
||||||
|
else if (spec.includes('presto')) urgency = 'high';
|
||||||
|
}
|
||||||
const sec = getItemSection(item.name);
|
const sec = getItemSection(item.name);
|
||||||
return { item, idx, smartData, urgency, sec };
|
return { item, idx, smartData, urgency, sec };
|
||||||
});
|
});
|
||||||
@@ -4965,7 +5155,7 @@ async function renderShoppingItems() {
|
|||||||
<span class="shopping-item-name">${escapeHtml(item.name)}</span>
|
<span class="shopping-item-name">${escapeHtml(item.name)}</span>
|
||||||
<span class="shopping-item-scan-hint">📷</span>
|
<span class="shopping-item-scan-hint">📷</span>
|
||||||
</div>
|
</div>
|
||||||
${item.specification ? `<div class="shopping-item-spec">${escapeHtml(item.specification)}</div>` : ''}
|
${_specDisplayText(item.specification) ? `<div class="shopping-item-spec">${escapeHtml(_specDisplayText(item.specification))}</div>` : ''}
|
||||||
${(urgencyBadge || freqBadge || localTagHtml) ? `<div class="shopping-item-badges">${urgencyBadge}${freqBadge}${localTagHtml}</div>` : ''}
|
${(urgencyBadge || freqBadge || localTagHtml) ? `<div class="shopping-item-badges">${urgencyBadge}${freqBadge}${localTagHtml}</div>` : ''}
|
||||||
${detailHtml}
|
${detailHtml}
|
||||||
</div>
|
</div>
|
||||||
@@ -5052,9 +5242,9 @@ async function searchItemPrice(idx, force = false) {
|
|||||||
renderShoppingItems();
|
renderShoppingItems();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Send item name as query, spec separately for AI selection
|
// Send item name as query, spec separately for AI selection (strip urgency markers)
|
||||||
const searchQ = item.name;
|
const searchQ = item.name;
|
||||||
const spec = item.specification || '';
|
const spec = _cleanSpecForSearch(item.specification || '');
|
||||||
|
|
||||||
const s2 = getSettings();
|
const s2 = getSettings();
|
||||||
const aiPrompt = s2.spesa_ai_prompt || '';
|
const aiPrompt = s2.spesa_ai_prompt || '';
|
||||||
@@ -5064,7 +5254,7 @@ async function searchItemPrice(idx, force = false) {
|
|||||||
prompt: aiPrompt
|
prompt: aiPrompt
|
||||||
});
|
});
|
||||||
if (res.success && res.product) {
|
if (res.success && res.product) {
|
||||||
shoppingPrices[priceKey] = { searched: true, product: res.product, spec: item.specification || '' };
|
shoppingPrices[priceKey] = { searched: true, product: res.product, spec: _cleanSpecForSearch(item.specification || '') };
|
||||||
} else {
|
} else {
|
||||||
shoppingPrices[priceKey] = { searched: true, product: null };
|
shoppingPrices[priceKey] = { searched: true, product: null };
|
||||||
}
|
}
|
||||||
@@ -5120,11 +5310,11 @@ async function searchAllPrices() {
|
|||||||
const aiPrompt = s.spesa_ai_prompt || '';
|
const aiPrompt = s.spesa_ai_prompt || '';
|
||||||
const res = await api(`${provider}_search`, {
|
const res = await api(`${provider}_search`, {
|
||||||
q: item.name,
|
q: item.name,
|
||||||
spec: item.specification || '',
|
spec: _cleanSpecForSearch(item.specification || ''),
|
||||||
prompt: aiPrompt
|
prompt: aiPrompt
|
||||||
});
|
});
|
||||||
if (res.success && res.product) {
|
if (res.success && res.product) {
|
||||||
shoppingPrices[priceKey] = { searched: true, product: res.product, spec: item.specification || '' };
|
shoppingPrices[priceKey] = { searched: true, product: res.product, spec: _cleanSpecForSearch(item.specification || '') };
|
||||||
} else {
|
} else {
|
||||||
shoppingPrices[priceKey] = { searched: true, product: null };
|
shoppingPrices[priceKey] = { searched: true, product: null };
|
||||||
}
|
}
|
||||||
@@ -5564,6 +5754,161 @@ async function loadLog(more = false) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ===== WEEKLY MEAL PLAN =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All selectable meal categories per slot.
|
||||||
|
* id must be URL-safe; icon + label shown in UI.
|
||||||
|
*/
|
||||||
|
const MEAL_PLAN_TYPES = [
|
||||||
|
{ id: 'pasta', icon: '🍝', label: 'Pasta' },
|
||||||
|
{ id: 'riso', icon: '🍚', label: 'Riso' },
|
||||||
|
{ id: 'carne', icon: '🥩', label: 'Carne' },
|
||||||
|
{ id: 'pesce', icon: '🐟', label: 'Pesce' },
|
||||||
|
{ id: 'legumi', icon: '🫘', label: 'Legumi' },
|
||||||
|
{ id: 'uova', icon: '🥚', label: 'Uova' },
|
||||||
|
{ id: 'formaggio', icon: '🧀', label: 'Formaggio' },
|
||||||
|
{ id: 'pizza', icon: '🍕', label: 'Pizza' },
|
||||||
|
{ id: 'affettati', icon: '🥓', label: 'Affettati' },
|
||||||
|
{ id: 'verdure', icon: '🥦', label: 'Verdure' },
|
||||||
|
{ id: 'zuppa', icon: '🍲', label: 'Zuppa' },
|
||||||
|
{ id: 'insalata', icon: '🥗', label: 'Insalata' },
|
||||||
|
{ id: 'pane', icon: '🥪', label: 'Pane/Sandwich' },
|
||||||
|
{ id: 'dolce', icon: '🍰', label: 'Dolce' },
|
||||||
|
{ id: 'libero', icon: '🎲', label: 'Libero' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const MEAL_PLAN_TYPE_MAP = {};
|
||||||
|
MEAL_PLAN_TYPES.forEach(t => { MEAL_PLAN_TYPE_MAP[t.id] = t; });
|
||||||
|
|
||||||
|
const WEEK_DAYS = ['Lunedì','Martedì','Mercoledì','Giovedì','Venerdì','Sabato','Domenica'];
|
||||||
|
const WEEK_DAYS_SHORT = ['Lun','Mar','Mer','Gio','Ven','Sab','Dom'];
|
||||||
|
|
||||||
|
/** Default weekly plan as requested. */
|
||||||
|
const DEFAULT_MEAL_PLAN = {
|
||||||
|
1: { pranzo: 'pasta', cena: 'pesce' },
|
||||||
|
2: { pranzo: 'riso', cena: 'carne' },
|
||||||
|
3: { pranzo: 'legumi', cena: 'uova' },
|
||||||
|
4: { pranzo: 'pasta', cena: 'pesce' },
|
||||||
|
5: { pranzo: 'riso', cena: 'formaggio' },
|
||||||
|
6: { pranzo: 'legumi', cena: 'pizza' },
|
||||||
|
0: { pranzo: 'carne', cena: 'affettati' }, // 0 = Sunday (getDay())
|
||||||
|
};
|
||||||
|
|
||||||
|
function getMealPlan() {
|
||||||
|
const s = getSettings();
|
||||||
|
return s.meal_plan || DEFAULT_MEAL_PLAN;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return today's planned meal type for a given slot ('pranzo'|'cena'), or null. */
|
||||||
|
function getTodayMealPlanType(slot) {
|
||||||
|
const s = getSettings();
|
||||||
|
if (s.meal_plan_enabled === false) return null;
|
||||||
|
const dow = new Date().getDay(); // 0=Sun,1=Mon,...,6=Sat
|
||||||
|
const plan = getMealPlan();
|
||||||
|
return plan[dow]?.[slot] || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Toggle handler for the enable/disable switch in settings. */
|
||||||
|
function onMealPlanEnabledChange(el) {
|
||||||
|
const s = getSettings();
|
||||||
|
s.meal_plan_enabled = el.checked;
|
||||||
|
saveSettingsToStorage(s);
|
||||||
|
const mpConfigSection = document.getElementById('meal-plan-config-section');
|
||||||
|
if (mpConfigSection) mpConfigSection.style.display = el.checked ? '' : 'none';
|
||||||
|
const mpLegendCard = document.getElementById('meal-plan-legend-card');
|
||||||
|
if (mpLegendCard) mpLegendCard.style.display = el.checked ? '' : 'none';
|
||||||
|
// Close picker if open
|
||||||
|
const picker = document.getElementById('meal-plan-picker');
|
||||||
|
if (picker) picker.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the weekly meal plan editor into #meal-plan-grid.
|
||||||
|
* Each cell shows the current type badge + a picker dropdown.
|
||||||
|
*/
|
||||||
|
function renderMealPlanEditor() {
|
||||||
|
const container = document.getElementById('meal-plan-grid');
|
||||||
|
if (!container) return;
|
||||||
|
const plan = getMealPlan();
|
||||||
|
// JS getDay: 0=Sun … but we display Mon-Sun (1..6,0)
|
||||||
|
const dayOrder = [1,2,3,4,5,6,0];
|
||||||
|
const today = new Date().getDay();
|
||||||
|
|
||||||
|
const header = `<div class="mplan-header">
|
||||||
|
<span class="mplan-col-header">🌤️ Pranzo</span>
|
||||||
|
<span class="mplan-col-header">🌙 Cena</span>
|
||||||
|
</div>`;
|
||||||
|
|
||||||
|
const rows = dayOrder.map((dow, i) => {
|
||||||
|
const pranzo = plan[dow]?.pranzo || 'libero';
|
||||||
|
const cena = plan[dow]?.cena || 'libero';
|
||||||
|
const pt = MEAL_PLAN_TYPE_MAP[pranzo] || MEAL_PLAN_TYPE_MAP.libero;
|
||||||
|
const ct = MEAL_PLAN_TYPE_MAP[cena] || MEAL_PLAN_TYPE_MAP.libero;
|
||||||
|
const todayClass = dow === today ? ' mplan-row-today' : '';
|
||||||
|
return `<div class="mplan-row${todayClass}">
|
||||||
|
<div class="mplan-day-name">${WEEK_DAYS_SHORT[i]}</div>
|
||||||
|
<span class="mplan-badge mplan-badge-pranzo" onclick="openMealPlanPicker(${dow},'pranzo',this)">${pt.icon} ${pt.label}</span>
|
||||||
|
<span class="mplan-badge mplan-badge-cena" onclick="openMealPlanPicker(${dow},'cena',this)">${ct.icon} ${ct.label}</span>
|
||||||
|
</div>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
container.innerHTML = header + rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
let _mplanPickerTarget = null; // {dow, slot, badgeEl}
|
||||||
|
function openMealPlanPicker(dow, slot, badgeEl) {
|
||||||
|
// Close any open picker first
|
||||||
|
closeMealPlanPicker();
|
||||||
|
_mplanPickerTarget = { dow, slot, badgeEl };
|
||||||
|
const picker = document.getElementById('meal-plan-picker');
|
||||||
|
if (!picker) return;
|
||||||
|
const plan = getMealPlan();
|
||||||
|
const current = plan[dow]?.[slot] || 'libero';
|
||||||
|
picker.innerHTML = MEAL_PLAN_TYPES.map(t =>
|
||||||
|
`<button class="mplan-pick-btn${t.id === current ? ' active' : ''}" onclick="selectMealPlanType(${dow},'${slot}','${t.id}')">${t.icon} ${t.label}</button>`
|
||||||
|
).join('');
|
||||||
|
// Position vertically near the badge, centered horizontally (CSS handles centering)
|
||||||
|
const rect = badgeEl.getBoundingClientRect();
|
||||||
|
const pickerEl = picker;
|
||||||
|
// Show first to measure height
|
||||||
|
pickerEl.style.display = 'flex';
|
||||||
|
const pickerH = pickerEl.offsetHeight || 160;
|
||||||
|
const spaceBelow = window.innerHeight - rect.bottom - 8;
|
||||||
|
const top = spaceBelow >= pickerH
|
||||||
|
? rect.bottom + 8
|
||||||
|
: Math.max(8, rect.top - pickerH - 8);
|
||||||
|
pickerEl.style.top = top + 'px';
|
||||||
|
// Close on outside tap
|
||||||
|
setTimeout(() => document.addEventListener('click', _mplanPickerOutside, { once: true }), 0);
|
||||||
|
}
|
||||||
|
function _mplanPickerOutside(e) {
|
||||||
|
const picker = document.getElementById('meal-plan-picker');
|
||||||
|
if (picker && !picker.contains(e.target)) closeMealPlanPicker();
|
||||||
|
}
|
||||||
|
function closeMealPlanPicker() {
|
||||||
|
const picker = document.getElementById('meal-plan-picker');
|
||||||
|
if (picker) picker.style.display = 'none';
|
||||||
|
_mplanPickerTarget = null;
|
||||||
|
document.removeEventListener('click', _mplanPickerOutside);
|
||||||
|
}
|
||||||
|
function selectMealPlanType(dow, slot, typeId) {
|
||||||
|
const s = getSettings();
|
||||||
|
if (!s.meal_plan) s.meal_plan = JSON.parse(JSON.stringify(DEFAULT_MEAL_PLAN));
|
||||||
|
if (!s.meal_plan[dow]) s.meal_plan[dow] = {};
|
||||||
|
s.meal_plan[dow][slot] = typeId;
|
||||||
|
saveSettingsToStorage(s);
|
||||||
|
closeMealPlanPicker();
|
||||||
|
renderMealPlanEditor();
|
||||||
|
}
|
||||||
|
function resetMealPlan() {
|
||||||
|
const s = getSettings();
|
||||||
|
s.meal_plan = JSON.parse(JSON.stringify(DEFAULT_MEAL_PLAN));
|
||||||
|
saveSettingsToStorage(s);
|
||||||
|
renderMealPlanEditor();
|
||||||
|
showToast('Piano settimanale ripristinato', 'success');
|
||||||
|
}
|
||||||
|
|
||||||
// ===== RECIPE GENERATION =====
|
// ===== RECIPE GENERATION =====
|
||||||
const MEAL_TYPES = [
|
const MEAL_TYPES = [
|
||||||
{ id: 'colazione', icon: '☀️', label: 'Colazione', from: 6, to: 11 },
|
{ id: 'colazione', icon: '☀️', label: 'Colazione', from: 6, to: 11 },
|
||||||
@@ -5710,6 +6055,9 @@ function openRecipeDialog() {
|
|||||||
}
|
}
|
||||||
updateRecipeMealTitle();
|
updateRecipeMealTitle();
|
||||||
|
|
||||||
|
// Show today's meal plan hint
|
||||||
|
_renderMealPlanHint(meal);
|
||||||
|
|
||||||
// Check for cached recipe matching current meal type
|
// Check for cached recipe matching current meal type
|
||||||
if (_cachedRecipe && _cachedRecipe.meal === meal && _cachedRecipe.recipe) {
|
if (_cachedRecipe && _cachedRecipe.meal === meal && _cachedRecipe.recipe) {
|
||||||
document.getElementById('recipe-ask').style.display = 'none';
|
document.getElementById('recipe-ask').style.display = 'none';
|
||||||
@@ -6377,6 +6725,7 @@ function removeCookingTimer(id) {
|
|||||||
if (t && t.interval) clearInterval(t.interval);
|
if (t && t.interval) clearInterval(t.interval);
|
||||||
_cookingTimers = _cookingTimers.filter(t => t.id !== id);
|
_cookingTimers = _cookingTimers.filter(t => t.id !== id);
|
||||||
renderTimersBar();
|
renderTimersBar();
|
||||||
|
_updateScreenFlash();
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleCookingTimerById(id) {
|
function toggleCookingTimerById(id) {
|
||||||
@@ -6431,6 +6780,25 @@ function _updateTimerCard(id) {
|
|||||||
}
|
}
|
||||||
toggleBtn.textContent = t.running ? '⏸' : '▶';
|
toggleBtn.textContent = t.running ? '⏸' : '▶';
|
||||||
toggleBtn.classList.toggle('running', t.running);
|
toggleBtn.classList.toggle('running', t.running);
|
||||||
|
_updateScreenFlash();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Update the full-screen colour flash based on the worst active timer state. */
|
||||||
|
function _updateScreenFlash() {
|
||||||
|
const flashEl = document.getElementById('cooking-flash-overlay');
|
||||||
|
if (!flashEl) return;
|
||||||
|
let hasDone = false, hasWarning = false;
|
||||||
|
for (const t of _cookingTimers) {
|
||||||
|
if (t.seconds <= 0) { hasDone = true; break; }
|
||||||
|
if (t.seconds <= 30 && t.running) hasWarning = true;
|
||||||
|
}
|
||||||
|
if (hasDone) {
|
||||||
|
flashEl.className = 'cooking-flash-overlay flash-done';
|
||||||
|
} else if (hasWarning) {
|
||||||
|
flashEl.className = 'cooking-flash-overlay flash-warning';
|
||||||
|
} else {
|
||||||
|
flashEl.className = 'cooking-flash-overlay';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function renderTimersBar() {
|
function renderTimersBar() {
|
||||||
@@ -6466,6 +6834,7 @@ function clearAllCookingTimers() {
|
|||||||
_cookingSuggestedLabel = '';
|
_cookingSuggestedLabel = '';
|
||||||
const bar = document.getElementById('cooking-timers-bar');
|
const bar = document.getElementById('cooking-timers-bar');
|
||||||
if (bar) { bar.style.display = 'none'; bar.innerHTML = ''; }
|
if (bar) { bar.style.display = 'none'; bar.innerHTML = ''; }
|
||||||
|
_updateScreenFlash();
|
||||||
}
|
}
|
||||||
// ===== END COOKING TIMER SYSTEM =====
|
// ===== END COOKING TIMER SYSTEM =====
|
||||||
|
|
||||||
@@ -6511,6 +6880,48 @@ function cookingUseIngredient(idx, productId, location, qtyNumber, btn) {
|
|||||||
function updateRecipeMealTitle() {
|
function updateRecipeMealTitle() {
|
||||||
const meal = getSelectedMealType();
|
const meal = getSelectedMealType();
|
||||||
document.getElementById('recipe-meal-title').textContent = MEAL_LABELS[meal] || '🍳 Ricetta';
|
document.getElementById('recipe-meal-title').textContent = MEAL_LABELS[meal] || '🍳 Ricetta';
|
||||||
|
_renderMealPlanHint(meal);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Show/hide the meal-plan badge hint + top banner in the recipe dialog. */
|
||||||
|
function _renderMealPlanHint(mealSlot) {
|
||||||
|
const el = document.getElementById('recipe-mealplan-hint');
|
||||||
|
const banner = document.getElementById('recipe-mealplan-banner');
|
||||||
|
const chipWrap = document.getElementById('recipe-opt-mealplan-wrap');
|
||||||
|
const chipLabel = document.getElementById('recipe-opt-mealplan-label');
|
||||||
|
const chipCb = document.getElementById('recipe-opt-mealplan');
|
||||||
|
// mealSlot = 'pranzo' or 'cena' (from getMealType/getSelectedMealType)
|
||||||
|
const typeId = (mealSlot === 'pranzo' || mealSlot === 'cena')
|
||||||
|
? getTodayMealPlanType(mealSlot)
|
||||||
|
: null;
|
||||||
|
if (!typeId || typeId === 'libero') {
|
||||||
|
if (el) el.style.display = 'none';
|
||||||
|
if (banner) banner.style.display = 'none';
|
||||||
|
if (chipWrap) chipWrap.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const t = MEAL_PLAN_TYPE_MAP[typeId];
|
||||||
|
if (!t) {
|
||||||
|
if (el) el.style.display = 'none';
|
||||||
|
if (banner) banner.style.display = 'none';
|
||||||
|
if (chipWrap) chipWrap.style.display = 'none';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (el) {
|
||||||
|
el.innerHTML = `<span class="mplan-hint-badge">${t.icon} ${t.label}</span> <span class="mplan-hint-label">suggerito dal piano settimanale</span>`;
|
||||||
|
el.style.display = 'flex';
|
||||||
|
}
|
||||||
|
if (banner) {
|
||||||
|
const slotLabel = mealSlot === 'pranzo' ? '🌤️ Pranzo' : '🌙 Cena';
|
||||||
|
banner.innerHTML = `<span style="opacity:0.75;font-weight:500">${slotLabel}</span><span style="opacity:0.45">·</span><span>${t.icon} ${t.label}</span>`;
|
||||||
|
banner.style.display = 'flex';
|
||||||
|
}
|
||||||
|
// Show the meal-plan chip (active by default, user can uncheck to ignore the plan)
|
||||||
|
if (chipWrap) {
|
||||||
|
chipWrap.style.display = '';
|
||||||
|
if (chipLabel) chipLabel.textContent = `${t.icon} ${t.label}`;
|
||||||
|
if (chipCb) chipCb.checked = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function regenerateRecipe() {
|
function regenerateRecipe() {
|
||||||
@@ -6535,6 +6946,15 @@ async function generateRecipe() {
|
|||||||
const meal = getSelectedMealType();
|
const meal = getSelectedMealType();
|
||||||
const persons = parseInt(document.getElementById('recipe-persons').value) || 1;
|
const persons = parseInt(document.getElementById('recipe-persons').value) || 1;
|
||||||
const settings = getSettings();
|
const settings = getSettings();
|
||||||
|
|
||||||
|
// Determine meal plan type for today's selected slot,
|
||||||
|
// but only if the user has NOT unchecked the meal-plan chip
|
||||||
|
const mealPlanChipWrap = document.getElementById('recipe-opt-mealplan-wrap');
|
||||||
|
const mealPlanCb = document.getElementById('recipe-opt-mealplan');
|
||||||
|
const mealPlanChipActive = !mealPlanChipWrap || mealPlanChipWrap.style.display === 'none' || (mealPlanCb && mealPlanCb.checked);
|
||||||
|
const mealPlanType = mealPlanChipActive && (meal === 'pranzo' || meal === 'cena')
|
||||||
|
? (getTodayMealPlanType(meal) || null)
|
||||||
|
: null;
|
||||||
|
|
||||||
// Gather active options from checkboxes
|
// Gather active options from checkboxes
|
||||||
const options = [];
|
const options = [];
|
||||||
@@ -6562,7 +6982,8 @@ async function generateRecipe() {
|
|||||||
options,
|
options,
|
||||||
appliances: settings.appliances || [],
|
appliances: settings.appliances || [],
|
||||||
dietary_restrictions: settings.dietary_restrictions || '',
|
dietary_restrictions: settings.dietary_restrictions || '',
|
||||||
today_recipes: await getTodayRecipeTitles()
|
today_recipes: await getTodayRecipeTitles(),
|
||||||
|
meal_plan_type: mealPlanType,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!result.success) {
|
if (!result.success) {
|
||||||
@@ -6817,6 +7238,25 @@ function updateScreensaverClock() {
|
|||||||
const date = now.toLocaleDateString('it-IT', { weekday: 'long', day: 'numeric', month: 'long' });
|
const date = now.toLocaleDateString('it-IT', { weekday: 'long', day: 'numeric', month: 'long' });
|
||||||
const el = document.getElementById('screensaver-clock');
|
const el = document.getElementById('screensaver-clock');
|
||||||
if (el) el.innerHTML = `${time}<div class="screensaver-date">${date}</div>`;
|
if (el) el.innerHTML = `${time}<div class="screensaver-date">${date}</div>`;
|
||||||
|
updateScreensaverMealPlan();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Show/hide the planned meal type badge on the screensaver based on current time slot. */
|
||||||
|
function updateScreensaverMealPlan() {
|
||||||
|
const el = document.getElementById('screensaver-mealplan');
|
||||||
|
if (!el) return;
|
||||||
|
const s = getSettings();
|
||||||
|
if (s.meal_plan_enabled === false) { el.style.display = 'none'; return; }
|
||||||
|
const hour = new Date().getHours();
|
||||||
|
// Before 15:00 show pranzo, from 15:00 onwards show cena
|
||||||
|
const slot = hour < 15 ? 'pranzo' : 'cena';
|
||||||
|
const typeId = getTodayMealPlanType(slot);
|
||||||
|
if (!typeId || typeId === 'libero') { el.style.display = 'none'; return; }
|
||||||
|
const t = MEAL_PLAN_TYPE_MAP[typeId];
|
||||||
|
if (!t) { el.style.display = 'none'; return; }
|
||||||
|
const slotLabel = slot === 'pranzo' ? '🌤️ Pranzo' : '🌙 Cena';
|
||||||
|
el.innerHTML = `<span class="screensaver-mealplan-badge">${slotLabel} · ${t.icon} ${t.label}</span>`;
|
||||||
|
el.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
function dismissScreensaver(targetPage) {
|
function dismissScreensaver(targetPage) {
|
||||||
@@ -7315,14 +7755,107 @@ function initInactivityWatcher() {
|
|||||||
|
|
||||||
// ===== INITIALIZATION =====
|
// ===== INITIALIZATION =====
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
// Migrate old session-based flags to time-based
|
||||||
|
if (sessionStorage.getItem('_autoAddedCritical')) {
|
||||||
|
sessionStorage.removeItem('_autoAddedCritical');
|
||||||
|
}
|
||||||
|
// One-time reset of bg sync timestamp so first load always triggers a sync
|
||||||
|
if (!localStorage.getItem('_bgBringSyncReset_v1')) {
|
||||||
|
localStorage.removeItem('_bgBringSyncTs');
|
||||||
|
localStorage.setItem('_bgBringSyncReset_v1', '1');
|
||||||
|
}
|
||||||
syncSettingsFromDB();
|
syncSettingsFromDB();
|
||||||
showPage('dashboard');
|
showPage('dashboard');
|
||||||
initInactivityWatcher();
|
initInactivityWatcher();
|
||||||
initSpesaMode();
|
initSpesaMode();
|
||||||
initScreensaverShortcuts();
|
initScreensaverShortcuts();
|
||||||
startBgShoppingRefresh();
|
startBgShoppingRefresh();
|
||||||
|
|
||||||
|
// Auto-refresh Bring list when user returns to the browser tab (e.g. was in the Bring app)
|
||||||
|
document.addEventListener('visibilitychange', () => {
|
||||||
|
if (!document.hidden && _currentPageId === 'shopping') {
|
||||||
|
loadShoppingList();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Silent background sync: update urgency specs on Bring and add missing critical items
|
||||||
|
// Runs once at startup (time-gated: max every 10 min) without affecting the UI
|
||||||
|
_backgroundBringSync();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Background sync at startup:
|
||||||
|
* 1. Fetches Bring list + smart shopping in parallel
|
||||||
|
* 2. Adds any critical items missing from Bring
|
||||||
|
* 3. Updates urgency specs for items already on Bring that need it
|
||||||
|
* Fully silent — no toasts, no loading spinners.
|
||||||
|
*/
|
||||||
|
async function _backgroundBringSync() {
|
||||||
|
const lastRun = parseInt(localStorage.getItem('_bgBringSyncTs') || '0');
|
||||||
|
if (Date.now() - lastRun < 10 * 60 * 1000) return;
|
||||||
|
localStorage.setItem('_bgBringSyncTs', String(Date.now()));
|
||||||
|
|
||||||
|
try {
|
||||||
|
const [bringData, smartData] = await Promise.all([
|
||||||
|
api('bring_list').catch(() => null),
|
||||||
|
api('smart_shopping').catch(() => null),
|
||||||
|
]);
|
||||||
|
|
||||||
|
if (!bringData?.success || !smartData?.success) return;
|
||||||
|
|
||||||
|
const listUUID = bringData.listUUID;
|
||||||
|
const bringItems = bringData.purchase || [];
|
||||||
|
const smartItems = smartData.items || [];
|
||||||
|
|
||||||
|
if (!listUUID || !smartItems.length) return;
|
||||||
|
|
||||||
|
// Update local smart cache so other functions can use it
|
||||||
|
if (!smartShoppingItems.length) {
|
||||||
|
smartShoppingItems = smartItems;
|
||||||
|
_smartShoppingLastFetch = Date.now();
|
||||||
|
}
|
||||||
|
if (!shoppingListUUID) shoppingListUUID = listUUID;
|
||||||
|
if (!shoppingItems.length) shoppingItems = bringItems;
|
||||||
|
|
||||||
|
const toAdd = []; // new items not yet on Bring
|
||||||
|
const toUpdate = []; // items on Bring that need spec updated
|
||||||
|
|
||||||
|
for (const si of smartItems) {
|
||||||
|
if (si.urgency === 'none' || si.urgency === 'low') continue;
|
||||||
|
const expectedSpec = _urgencyToSpec(si.urgency, '');
|
||||||
|
const bringMatch = bringItems.find(bi => {
|
||||||
|
const biL = bi.name.toLowerCase();
|
||||||
|
const siL = si.name.toLowerCase();
|
||||||
|
if (biL === siL) return true;
|
||||||
|
const biFirst = _nameTokens(bi.name)[0];
|
||||||
|
const siFirst = _nameTokens(si.name)[0];
|
||||||
|
return biFirst && biFirst === siFirst;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!bringMatch) {
|
||||||
|
// Not on Bring — add if critical
|
||||||
|
if (si.urgency === 'critical') {
|
||||||
|
toAdd.push({ name: si.name, specification: expectedSpec });
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// On Bring — update spec if urgency marker is missing/wrong
|
||||||
|
const currentSpec = (bringMatch.specification || '').toLowerCase();
|
||||||
|
const hasUrgencyMarker = currentSpec.includes('urgente') || currentSpec.includes('presto');
|
||||||
|
if (!hasUrgencyMarker && expectedSpec) {
|
||||||
|
toUpdate.push({ name: si.name, specification: expectedSpec, update_spec: true });
|
||||||
|
bringMatch.specification = expectedSpec; // update local copy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allChanges = [...toAdd, ...toUpdate];
|
||||||
|
if (allChanges.length === 0) return;
|
||||||
|
|
||||||
|
await api('bring_add', {}, 'POST', { items: allChanges, listUUID });
|
||||||
|
logOperation('bg_bring_sync', { added: toAdd.map(i=>i.name), updated: toUpdate.map(i=>i.name) });
|
||||||
|
} catch (e) { /* silent — best effort */ }
|
||||||
|
}
|
||||||
|
|
||||||
// ===== DUPLICLICK (SPESA ONLINE) =====
|
// ===== DUPLICLICK (SPESA ONLINE) =====
|
||||||
|
|
||||||
function selectSpesaProvider(btn, provider) {
|
function selectSpesaProvider(btn, provider) {
|
||||||
|
|||||||
+968
@@ -0,0 +1,968 @@
|
|||||||
|
[2026-04-01 05:55:02] OK — 15 items cached
|
||||||
|
[2026-04-01 06:00:02] OK — 15 items cached
|
||||||
|
[2026-04-01 06:05:02] OK — 15 items cached
|
||||||
|
[2026-04-01 06:10:01] OK — 15 items cached
|
||||||
|
[2026-04-01 06:15:03] OK — 15 items cached
|
||||||
|
[2026-04-01 06:20:01] OK — 15 items cached
|
||||||
|
[2026-04-01 06:25:02] OK — 15 items cached
|
||||||
|
[2026-04-01 06:30:02] OK — 15 items cached
|
||||||
|
[2026-04-01 06:35:01] OK — 15 items cached
|
||||||
|
[2026-04-01 06:40:02] OK — 15 items cached
|
||||||
|
[2026-04-01 06:45:01] OK — 15 items cached
|
||||||
|
[2026-04-01 06:50:02] OK — 15 items cached
|
||||||
|
[2026-04-01 06:55:02] OK — 15 items cached
|
||||||
|
[2026-04-01 07:00:02] OK — 15 items cached
|
||||||
|
[2026-04-01 07:05:01] OK — 15 items cached
|
||||||
|
[2026-04-01 07:10:02] OK — 15 items cached
|
||||||
|
[2026-04-01 07:15:02] OK — 15 items cached
|
||||||
|
[2026-04-01 07:20:02] OK — 15 items cached
|
||||||
|
[2026-04-01 07:25:01] OK — 15 items cached
|
||||||
|
[2026-04-01 07:30:02] OK — 15 items cached
|
||||||
|
[2026-04-01 07:35:02] OK — 15 items cached
|
||||||
|
[2026-04-01 07:40:01] OK — 15 items cached
|
||||||
|
[2026-04-01 07:45:02] OK — 15 items cached
|
||||||
|
[2026-04-01 07:50:02] OK — 15 items cached
|
||||||
|
[2026-04-01 07:55:01] OK — 15 items cached
|
||||||
|
[2026-04-01 08:00:02] OK — 15 items cached
|
||||||
|
[2026-04-01 08:05:01] OK — 15 items cached
|
||||||
|
[2026-04-01 08:10:02] OK — 15 items cached
|
||||||
|
[2026-04-01 08:15:02] OK — 15 items cached
|
||||||
|
[2026-04-01 08:20:02] OK — 15 items cached
|
||||||
|
[2026-04-01 08:25:02] OK — 15 items cached
|
||||||
|
[2026-04-01 08:30:02] OK — 15 items cached
|
||||||
|
[2026-04-01 08:35:01] OK — 15 items cached
|
||||||
|
[2026-04-01 08:40:02] OK — 15 items cached
|
||||||
|
[2026-04-01 08:45:02] OK — 15 items cached
|
||||||
|
[2026-04-01 08:50:01] OK — 15 items cached
|
||||||
|
[2026-04-01 08:55:02] OK — 15 items cached
|
||||||
|
[2026-04-01 09:00:02] OK — 15 items cached
|
||||||
|
[2026-04-01 09:05:01] OK — 15 items cached
|
||||||
|
[2026-04-01 09:10:02] OK — 15 items cached
|
||||||
|
[2026-04-01 09:15:02] OK — 16 items cached
|
||||||
|
[2026-04-01 09:20:01] OK — 16 items cached
|
||||||
|
[2026-04-01 09:25:01] OK — 16 items cached
|
||||||
|
[2026-04-01 09:30:02] OK — 16 items cached
|
||||||
|
[2026-04-01 09:35:01] OK — 16 items cached
|
||||||
|
[2026-04-01 09:40:02] OK — 16 items cached
|
||||||
|
[2026-04-01 09:45:02] OK — 16 items cached
|
||||||
|
[2026-04-01 09:50:01] OK — 16 items cached
|
||||||
|
[2026-04-01 09:55:02] OK — 16 items cached
|
||||||
|
[2026-04-01 10:00:02] OK — 16 items cached
|
||||||
|
[2026-04-01 10:05:01] OK — 16 items cached
|
||||||
|
[2026-04-01 10:10:02] OK — 16 items cached
|
||||||
|
[2026-04-01 10:15:02] OK — 16 items cached
|
||||||
|
[2026-04-01 10:20:02] OK — 16 items cached
|
||||||
|
[2026-04-01 10:25:01] OK — 16 items cached
|
||||||
|
[2026-04-01 10:30:02] OK — 16 items cached
|
||||||
|
[2026-04-01 10:35:02] OK — 16 items cached
|
||||||
|
[2026-04-01 10:40:01] OK — 16 items cached
|
||||||
|
[2026-04-01 10:45:02] OK — 16 items cached
|
||||||
|
[2026-04-01 10:50:01] OK — 16 items cached
|
||||||
|
[2026-04-01 10:55:02] OK — 16 items cached
|
||||||
|
[2026-04-01 11:00:02] OK — 16 items cached
|
||||||
|
[2026-04-01 11:05:01] OK — 16 items cached
|
||||||
|
[2026-04-01 11:10:02] OK — 16 items cached
|
||||||
|
[2026-04-01 11:15:02] OK — 16 items cached
|
||||||
|
[2026-04-01 11:20:01] OK — 16 items cached
|
||||||
|
[2026-04-01 11:25:02] OK — 16 items cached
|
||||||
|
[2026-04-01 11:30:01] OK — 16 items cached
|
||||||
|
[2026-04-01 11:35:02] OK — 16 items cached
|
||||||
|
[2026-04-01 11:40:02] OK — 16 items cached
|
||||||
|
[2026-04-01 11:45:01] OK — 16 items cached
|
||||||
|
[2026-04-01 11:50:02] OK — 16 items cached
|
||||||
|
[2026-04-01 11:55:02] OK — 16 items cached
|
||||||
|
[2026-04-01 12:00:01] OK — 16 items cached
|
||||||
|
[2026-04-01 12:05:02] OK — 16 items cached
|
||||||
|
[2026-04-01 12:10:01] OK — 16 items cached
|
||||||
|
[2026-04-01 12:15:03] OK — 16 items cached
|
||||||
|
[2026-04-01 12:20:01] OK — 16 items cached
|
||||||
|
[2026-04-01 12:25:02] OK — 16 items cached
|
||||||
|
[2026-04-01 12:30:02] OK — 16 items cached
|
||||||
|
[2026-04-01 12:35:01] OK — 15 items cached
|
||||||
|
[2026-04-01 12:40:02] OK — 14 items cached
|
||||||
|
[2026-04-01 12:45:02] OK — 13 items cached
|
||||||
|
[2026-04-01 12:50:02] OK — 13 items cached
|
||||||
|
[2026-04-01 12:55:01] OK — 13 items cached
|
||||||
|
[2026-04-01 13:00:02] OK — 13 items cached
|
||||||
|
[2026-04-01 13:05:02] OK — 13 items cached
|
||||||
|
[2026-04-01 13:10:01] OK — 13 items cached
|
||||||
|
[2026-04-01 13:15:02] OK — 13 items cached
|
||||||
|
[2026-04-01 13:20:01] OK — 13 items cached
|
||||||
|
[2026-04-01 13:25:02] OK — 13 items cached
|
||||||
|
[2026-04-01 13:30:02] OK — 13 items cached
|
||||||
|
[2026-04-01 13:35:02] OK — 13 items cached
|
||||||
|
[2026-04-01 13:40:01] OK — 13 items cached
|
||||||
|
[2026-04-01 13:45:02] OK — 13 items cached
|
||||||
|
[2026-04-01 13:50:02] OK — 13 items cached
|
||||||
|
[2026-04-01 13:55:01] OK — 13 items cached
|
||||||
|
[2026-04-01 14:00:02] OK — 13 items cached
|
||||||
|
[2026-04-01 14:05:02] OK — 13 items cached
|
||||||
|
[2026-04-01 14:10:02] OK — 13 items cached
|
||||||
|
[2026-04-01 14:15:02] OK — 13 items cached
|
||||||
|
[2026-04-01 14:20:01] OK — 13 items cached
|
||||||
|
[2026-04-01 14:25:02] OK — 13 items cached
|
||||||
|
[2026-04-01 14:30:02] OK — 13 items cached
|
||||||
|
[2026-04-01 14:35:02] OK — 13 items cached
|
||||||
|
[2026-04-01 14:40:06] OK — 13 items cached
|
||||||
|
[2026-04-01 14:45:01] OK — 13 items cached
|
||||||
|
[2026-04-01 14:50:02] OK — 13 items cached
|
||||||
|
[2026-04-01 14:55:02] OK — 13 items cached
|
||||||
|
[2026-04-01 15:00:02] OK — 13 items cached
|
||||||
|
[2026-04-01 15:05:01] OK — 13 items cached
|
||||||
|
[2026-04-01 15:10:02] OK — 13 items cached
|
||||||
|
[2026-04-01 15:15:02] OK — 13 items cached
|
||||||
|
[2026-04-01 15:20:02] OK — 13 items cached
|
||||||
|
[2026-04-01 15:25:02] OK — 13 items cached
|
||||||
|
[2026-04-01 15:30:01] OK — 13 items cached
|
||||||
|
[2026-04-01 15:35:02] OK — 13 items cached
|
||||||
|
[2026-04-01 15:40:02] OK — 13 items cached
|
||||||
|
[2026-04-01 15:45:01] OK — 13 items cached
|
||||||
|
[2026-04-01 15:50:02] OK — 13 items cached
|
||||||
|
[2026-04-01 15:55:02] OK — 13 items cached
|
||||||
|
[2026-04-01 16:00:01] OK — 13 items cached
|
||||||
|
[2026-04-01 16:05:02] OK — 13 items cached
|
||||||
|
[2026-04-01 16:10:02] OK — 13 items cached
|
||||||
|
[2026-04-01 16:15:02] OK — 13 items cached
|
||||||
|
[2026-04-01 16:20:02] OK — 13 items cached
|
||||||
|
[2026-04-01 16:25:02] OK — 13 items cached
|
||||||
|
[2026-04-01 16:30:02] OK — 13 items cached
|
||||||
|
[2026-04-01 16:35:01] OK — 13 items cached
|
||||||
|
[2026-04-01 16:40:02] OK — 13 items cached
|
||||||
|
[2026-04-01 16:45:02] OK — 12 items cached
|
||||||
|
[2026-04-01 16:50:01] OK — 12 items cached
|
||||||
|
[2026-04-01 16:55:02] OK — 12 items cached
|
||||||
|
[2026-04-01 17:00:02] OK — 12 items cached
|
||||||
|
[2026-04-01 17:05:01] OK — 12 items cached
|
||||||
|
[2026-04-01 17:10:02] OK — 12 items cached
|
||||||
|
[2026-04-01 17:15:02] OK — 12 items cached
|
||||||
|
[2026-04-01 17:20:02] OK — 12 items cached
|
||||||
|
[2026-04-01 17:25:02] OK — 12 items cached
|
||||||
|
[2026-04-01 17:30:01] OK — 12 items cached
|
||||||
|
[2026-04-01 17:35:02] OK — 12 items cached
|
||||||
|
[2026-04-01 17:40:02] OK — 12 items cached
|
||||||
|
[2026-04-01 17:45:01] OK — 12 items cached
|
||||||
|
[2026-04-01 17:50:02] OK — 12 items cached
|
||||||
|
[2026-04-01 17:55:02] OK — 12 items cached
|
||||||
|
[2026-04-01 18:00:01] OK — 12 items cached
|
||||||
|
[2026-04-01 18:05:02] OK — 12 items cached
|
||||||
|
[2026-04-01 18:10:02] OK — 12 items cached
|
||||||
|
[2026-04-01 18:15:02] OK — 12 items cached
|
||||||
|
[2026-04-01 18:20:02] OK — 12 items cached
|
||||||
|
[2026-04-01 18:25:02] OK — 12 items cached
|
||||||
|
[2026-04-01 18:30:01] OK — 12 items cached
|
||||||
|
[2026-04-01 18:35:02] OK — 11 items cached
|
||||||
|
[2026-04-01 18:40:02] OK — 11 items cached
|
||||||
|
[2026-04-01 18:45:02] OK — 11 items cached
|
||||||
|
[2026-04-01 18:50:01] OK — 11 items cached
|
||||||
|
[2026-04-01 18:55:02] OK — 11 items cached
|
||||||
|
[2026-04-01 19:00:02] OK — 11 items cached
|
||||||
|
[2026-04-01 19:05:02] OK — 11 items cached
|
||||||
|
[2026-04-01 19:10:01] OK — 11 items cached
|
||||||
|
[2026-04-01 19:15:02] OK — 11 items cached
|
||||||
|
[2026-04-01 19:20:02] OK — 11 items cached
|
||||||
|
[2026-04-01 19:25:02] OK — 11 items cached
|
||||||
|
[2026-04-01 19:30:02] OK — 11 items cached
|
||||||
|
[2026-04-01 19:35:01] OK — 11 items cached
|
||||||
|
[2026-04-01 19:40:02] OK — 11 items cached
|
||||||
|
[2026-04-01 19:45:02] OK — 11 items cached
|
||||||
|
[2026-04-01 19:50:02] OK — 11 items cached
|
||||||
|
[2026-04-01 19:55:01] OK — 11 items cached
|
||||||
|
[2026-04-01 20:00:02] OK — 11 items cached
|
||||||
|
[2026-04-01 20:05:02] OK — 11 items cached
|
||||||
|
[2026-04-01 20:10:01] OK — 11 items cached
|
||||||
|
[2026-04-01 20:15:03] OK — 11 items cached
|
||||||
|
[2026-04-01 20:20:01] OK — 11 items cached
|
||||||
|
[2026-04-01 20:25:02] OK — 11 items cached
|
||||||
|
[2026-04-01 20:30:02] OK — 11 items cached
|
||||||
|
[2026-04-01 20:35:01] OK — 11 items cached
|
||||||
|
[2026-04-01 20:40:02] OK — 11 items cached
|
||||||
|
[2026-04-01 20:45:02] OK — 11 items cached
|
||||||
|
[2026-04-01 20:50:01] OK — 11 items cached
|
||||||
|
[2026-04-01 20:55:02] OK — 11 items cached
|
||||||
|
[2026-04-01 21:00:02] OK — 11 items cached
|
||||||
|
[2026-04-01 21:05:01] OK — 11 items cached
|
||||||
|
[2026-04-01 21:10:02] OK — 11 items cached
|
||||||
|
[2026-04-01 21:15:02] OK — 11 items cached
|
||||||
|
[2026-04-01 21:20:02] OK — 11 items cached
|
||||||
|
[2026-04-01 21:25:02] OK — 11 items cached
|
||||||
|
[2026-04-01 21:30:01] OK — 11 items cached
|
||||||
|
[2026-04-01 21:35:02] OK — 11 items cached
|
||||||
|
[2026-04-01 21:40:01] OK — 11 items cached
|
||||||
|
[2026-04-01 21:45:01] OK — 11 items cached
|
||||||
|
[2026-04-01 21:50:02] OK — 11 items cached
|
||||||
|
[2026-04-01 21:55:01] OK — 11 items cached
|
||||||
|
[2026-04-01 22:00:02] OK — 11 items cached
|
||||||
|
[2026-04-01 22:05:02] OK — 11 items cached
|
||||||
|
[2026-04-01 22:10:01] OK — 11 items cached
|
||||||
|
[2026-04-01 22:15:03] OK — 11 items cached
|
||||||
|
[2026-04-01 22:20:01] OK — 11 items cached
|
||||||
|
[2026-04-01 22:25:02] OK — 11 items cached
|
||||||
|
[2026-04-01 22:30:01] OK — 11 items cached
|
||||||
|
[2026-04-01 22:35:02] OK — 11 items cached
|
||||||
|
[2026-04-01 22:40:02] OK — 11 items cached
|
||||||
|
[2026-04-01 22:45:01] OK — 11 items cached
|
||||||
|
[2026-04-01 22:50:02] OK — 11 items cached
|
||||||
|
[2026-04-01 22:55:02] OK — 11 items cached
|
||||||
|
[2026-04-01 23:00:01] OK — 11 items cached
|
||||||
|
[2026-04-01 23:05:02] OK — 11 items cached
|
||||||
|
[2026-04-01 23:10:02] OK — 11 items cached
|
||||||
|
[2026-04-01 23:15:02] OK — 11 items cached
|
||||||
|
[2026-04-01 23:20:01] OK — 11 items cached
|
||||||
|
[2026-04-01 23:25:02] OK — 11 items cached
|
||||||
|
[2026-04-01 23:30:02] OK — 11 items cached
|
||||||
|
[2026-04-01 23:35:01] OK — 11 items cached
|
||||||
|
[2026-04-01 23:40:02] OK — 11 items cached
|
||||||
|
[2026-04-01 23:45:02] OK — 11 items cached
|
||||||
|
[2026-04-01 23:50:01] OK — 11 items cached
|
||||||
|
[2026-04-01 23:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 00:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 00:05:01] OK — 11 items cached
|
||||||
|
[2026-04-02 00:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 00:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 00:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 00:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 00:30:01] OK — 11 items cached
|
||||||
|
[2026-04-02 00:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 00:40:01] OK — 11 items cached
|
||||||
|
[2026-04-02 00:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 00:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 00:55:01] OK — 11 items cached
|
||||||
|
[2026-04-02 01:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 01:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 01:10:01] OK — 11 items cached
|
||||||
|
[2026-04-02 01:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 01:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 01:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 01:30:01] OK — 11 items cached
|
||||||
|
[2026-04-02 01:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 01:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 01:45:01] OK — 11 items cached
|
||||||
|
[2026-04-02 01:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 01:55:01] OK — 11 items cached
|
||||||
|
[2026-04-02 02:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 02:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 02:10:01] OK — 11 items cached
|
||||||
|
[2026-04-02 02:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 02:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 02:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 02:30:01] OK — 11 items cached
|
||||||
|
[2026-04-02 02:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 02:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 02:45:01] OK — 11 items cached
|
||||||
|
[2026-04-02 02:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 02:55:01] OK — 11 items cached
|
||||||
|
[2026-04-02 03:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 03:05:01] OK — 11 items cached
|
||||||
|
[2026-04-02 03:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 03:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 03:20:01] OK — 11 items cached
|
||||||
|
[2026-04-02 03:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 03:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 03:35:01] OK — 11 items cached
|
||||||
|
[2026-04-02 03:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 03:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 03:50:01] OK — 11 items cached
|
||||||
|
[2026-04-02 03:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 04:00:01] OK — 11 items cached
|
||||||
|
[2026-04-02 04:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 04:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 04:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 04:20:01] OK — 11 items cached
|
||||||
|
[2026-04-02 04:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 04:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 04:35:01] OK — 11 items cached
|
||||||
|
[2026-04-02 04:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 04:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 04:50:01] OK — 11 items cached
|
||||||
|
[2026-04-02 04:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 05:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 05:05:01] OK — 11 items cached
|
||||||
|
[2026-04-02 05:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 05:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 05:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 05:25:01] OK — 11 items cached
|
||||||
|
[2026-04-02 05:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 05:35:01] OK — 11 items cached
|
||||||
|
[2026-04-02 05:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 05:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 05:50:01] OK — 11 items cached
|
||||||
|
[2026-04-02 05:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 06:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 06:05:01] OK — 11 items cached
|
||||||
|
[2026-04-02 06:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 06:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 06:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 06:25:01] OK — 11 items cached
|
||||||
|
[2026-04-02 06:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 06:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 06:40:01] OK — 11 items cached
|
||||||
|
[2026-04-02 06:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 06:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 06:55:01] OK — 11 items cached
|
||||||
|
[2026-04-02 07:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 07:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 07:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 07:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 07:20:01] OK — 11 items cached
|
||||||
|
[2026-04-02 07:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 07:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 07:35:01] OK — 11 items cached
|
||||||
|
[2026-04-02 07:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 07:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 07:50:01] OK — 11 items cached
|
||||||
|
[2026-04-02 07:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 08:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 08:05:01] OK — 11 items cached
|
||||||
|
[2026-04-02 08:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 08:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 08:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 08:25:01] OK — 11 items cached
|
||||||
|
[2026-04-02 08:30:01] OK — 11 items cached
|
||||||
|
[2026-04-02 08:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 08:40:01] OK — 11 items cached
|
||||||
|
[2026-04-02 08:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 08:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 08:55:01] OK — 11 items cached
|
||||||
|
[2026-04-02 09:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 09:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 09:10:01] OK — 11 items cached
|
||||||
|
[2026-04-02 09:15:03] OK — 11 items cached
|
||||||
|
[2026-04-02 09:20:01] OK — 11 items cached
|
||||||
|
[2026-04-02 09:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 09:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 09:35:01] OK — 11 items cached
|
||||||
|
[2026-04-02 09:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 09:45:01] OK — 11 items cached
|
||||||
|
[2026-04-02 09:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 09:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 10:00:01] OK — 11 items cached
|
||||||
|
[2026-04-02 10:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 10:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 10:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 10:20:01] OK — 11 items cached
|
||||||
|
[2026-04-02 10:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 10:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 10:35:01] OK — 11 items cached
|
||||||
|
[2026-04-02 10:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 10:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 10:50:01] OK — 11 items cached
|
||||||
|
[2026-04-02 10:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 11:00:01] OK — 11 items cached
|
||||||
|
[2026-04-02 11:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 11:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 11:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 11:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 11:25:01] OK — 11 items cached
|
||||||
|
[2026-04-02 11:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 11:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 11:40:01] OK — 11 items cached
|
||||||
|
[2026-04-02 11:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 11:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 11:55:01] OK — 11 items cached
|
||||||
|
[2026-04-02 12:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 12:05:01] OK — 11 items cached
|
||||||
|
[2026-04-02 12:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 12:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 12:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 12:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 12:30:01] OK — 11 items cached
|
||||||
|
[2026-04-02 12:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 12:40:01] OK — 11 items cached
|
||||||
|
[2026-04-02 12:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 12:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 12:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 13:00:01] OK — 11 items cached
|
||||||
|
[2026-04-02 13:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 13:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 13:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 13:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 13:25:01] OK — 11 items cached
|
||||||
|
[2026-04-02 13:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 13:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 13:40:01] OK — 11 items cached
|
||||||
|
[2026-04-02 13:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 13:50:01] OK — 10 items cached
|
||||||
|
[2026-04-02 13:55:02] OK — 10 items cached
|
||||||
|
[2026-04-02 14:00:02] OK — 10 items cached
|
||||||
|
[2026-04-02 14:05:01] OK — 10 items cached
|
||||||
|
[2026-04-02 14:10:01] OK — 10 items cached
|
||||||
|
[2026-04-02 14:15:02] OK — 10 items cached
|
||||||
|
[2026-04-02 14:20:02] OK — 10 items cached
|
||||||
|
[2026-04-02 14:25:02] OK — 10 items cached
|
||||||
|
[2026-04-02 14:30:01] OK — 10 items cached
|
||||||
|
[2026-04-02 14:35:02] OK — 10 items cached
|
||||||
|
[2026-04-02 14:40:02] OK — 10 items cached
|
||||||
|
[2026-04-02 14:45:06] OK — 10 items cached
|
||||||
|
[2026-04-02 14:50:01] OK — 10 items cached
|
||||||
|
[2026-04-02 14:55:02] OK — 10 items cached
|
||||||
|
[2026-04-02 15:00:02] OK — 10 items cached
|
||||||
|
[2026-04-02 15:05:01] OK — 10 items cached
|
||||||
|
[2026-04-02 15:10:02] OK — 10 items cached
|
||||||
|
[2026-04-02 15:15:02] OK — 10 items cached
|
||||||
|
[2026-04-02 15:20:02] OK — 10 items cached
|
||||||
|
[2026-04-02 15:25:02] OK — 10 items cached
|
||||||
|
[2026-04-02 15:30:01] OK — 10 items cached
|
||||||
|
[2026-04-02 15:35:02] OK — 10 items cached
|
||||||
|
[2026-04-02 15:40:02] OK — 10 items cached
|
||||||
|
[2026-04-02 15:45:01] OK — 10 items cached
|
||||||
|
[2026-04-02 15:50:02] OK — 10 items cached
|
||||||
|
[2026-04-02 15:55:02] OK — 10 items cached
|
||||||
|
[2026-04-02 16:00:01] OK — 10 items cached
|
||||||
|
[2026-04-02 16:05:02] OK — 10 items cached
|
||||||
|
[2026-04-02 16:10:02] OK — 10 items cached
|
||||||
|
[2026-04-02 16:15:02] OK — 10 items cached
|
||||||
|
[2026-04-02 16:20:02] OK — 10 items cached
|
||||||
|
[2026-04-02 16:25:01] OK — 10 items cached
|
||||||
|
[2026-04-02 16:30:02] OK — 10 items cached
|
||||||
|
[2026-04-02 16:35:02] OK — 10 items cached
|
||||||
|
[2026-04-02 16:40:02] OK — 10 items cached
|
||||||
|
[2026-04-02 16:45:01] OK — 10 items cached
|
||||||
|
[2026-04-02 16:50:03] OK — 10 items cached
|
||||||
|
[2026-04-02 16:55:01] OK — 10 items cached
|
||||||
|
[2026-04-02 17:00:02] OK — 10 items cached
|
||||||
|
[2026-04-02 17:05:02] OK — 10 items cached
|
||||||
|
[2026-04-02 17:10:01] OK — 10 items cached
|
||||||
|
[2026-04-02 17:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 17:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 17:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 17:30:01] OK — 11 items cached
|
||||||
|
[2026-04-02 17:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 17:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 17:45:01] OK — 11 items cached
|
||||||
|
[2026-04-02 17:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 17:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 18:00:01] OK — 11 items cached
|
||||||
|
[2026-04-02 18:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 18:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 18:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 18:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 18:25:01] OK — 11 items cached
|
||||||
|
[2026-04-02 18:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 18:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 18:40:01] OK — 11 items cached
|
||||||
|
[2026-04-02 18:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 18:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 18:55:01] OK — 11 items cached
|
||||||
|
[2026-04-02 19:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 19:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 19:10:01] OK — 11 items cached
|
||||||
|
[2026-04-02 19:15:03] OK — 11 items cached
|
||||||
|
[2026-04-02 19:20:01] OK — 11 items cached
|
||||||
|
[2026-04-02 19:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 19:30:01] OK — 11 items cached
|
||||||
|
[2026-04-02 19:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 19:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 19:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 19:50:01] OK — 11 items cached
|
||||||
|
[2026-04-02 19:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 20:00:01] OK — 11 items cached
|
||||||
|
[2026-04-02 20:05:01] OK — 11 items cached
|
||||||
|
[2026-04-02 20:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 20:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 20:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 20:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 20:30:01] OK — 11 items cached
|
||||||
|
[2026-04-02 20:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 20:40:01] OK — 11 items cached
|
||||||
|
[2026-04-02 20:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 20:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 20:55:01] OK — 11 items cached
|
||||||
|
[2026-04-02 21:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 21:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 21:10:01] OK — 11 items cached
|
||||||
|
[2026-04-02 21:15:03] OK — 11 items cached
|
||||||
|
[2026-04-02 21:20:01] OK — 11 items cached
|
||||||
|
[2026-04-02 21:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 21:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 21:35:01] OK — 11 items cached
|
||||||
|
[2026-04-02 21:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 21:45:01] OK — 11 items cached
|
||||||
|
[2026-04-02 21:50:02] OK — 11 items cached
|
||||||
|
[2026-04-02 21:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 22:00:01] OK — 11 items cached
|
||||||
|
[2026-04-02 22:05:02] OK — 11 items cached
|
||||||
|
[2026-04-02 22:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 22:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 22:20:01] OK — 11 items cached
|
||||||
|
[2026-04-02 22:25:02] OK — 11 items cached
|
||||||
|
[2026-04-02 22:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 22:35:01] OK — 11 items cached
|
||||||
|
[2026-04-02 22:40:02] OK — 11 items cached
|
||||||
|
[2026-04-02 22:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 22:50:01] OK — 11 items cached
|
||||||
|
[2026-04-02 22:55:02] OK — 11 items cached
|
||||||
|
[2026-04-02 23:00:02] OK — 11 items cached
|
||||||
|
[2026-04-02 23:05:01] OK — 11 items cached
|
||||||
|
[2026-04-02 23:10:02] OK — 11 items cached
|
||||||
|
[2026-04-02 23:15:02] OK — 11 items cached
|
||||||
|
[2026-04-02 23:20:02] OK — 11 items cached
|
||||||
|
[2026-04-02 23:25:01] OK — 11 items cached
|
||||||
|
[2026-04-02 23:30:02] OK — 11 items cached
|
||||||
|
[2026-04-02 23:35:02] OK — 11 items cached
|
||||||
|
[2026-04-02 23:40:01] OK — 11 items cached
|
||||||
|
[2026-04-02 23:45:02] OK — 11 items cached
|
||||||
|
[2026-04-02 23:50:01] OK — 11 items cached
|
||||||
|
[2026-04-02 23:55:02] OK — 11 items cached
|
||||||
|
[2026-04-03 00:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 00:05:01] OK — 12 items cached
|
||||||
|
[2026-04-03 00:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 00:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 00:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 00:25:01] OK — 12 items cached
|
||||||
|
[2026-04-03 00:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 00:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 00:40:01] OK — 12 items cached
|
||||||
|
[2026-04-03 00:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 00:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 00:55:01] OK — 12 items cached
|
||||||
|
[2026-04-03 01:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 01:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 01:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 01:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 01:20:01] OK — 12 items cached
|
||||||
|
[2026-04-03 01:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 01:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 01:35:01] OK — 12 items cached
|
||||||
|
[2026-04-03 01:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 01:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 01:50:01] OK — 12 items cached
|
||||||
|
[2026-04-03 01:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 02:00:01] OK — 12 items cached
|
||||||
|
[2026-04-03 02:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 02:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 02:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 02:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 02:25:01] OK — 12 items cached
|
||||||
|
[2026-04-03 02:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 02:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 02:40:01] OK — 12 items cached
|
||||||
|
[2026-04-03 02:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 02:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 02:55:01] OK — 12 items cached
|
||||||
|
[2026-04-03 03:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 03:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 03:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 03:15:03] OK — 12 items cached
|
||||||
|
[2026-04-03 03:20:01] OK — 12 items cached
|
||||||
|
[2026-04-03 03:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 03:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 03:35:01] OK — 12 items cached
|
||||||
|
[2026-04-03 03:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 03:45:01] OK — 12 items cached
|
||||||
|
[2026-04-03 03:50:01] OK — 12 items cached
|
||||||
|
[2026-04-03 03:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 04:00:01] OK — 12 items cached
|
||||||
|
[2026-04-03 04:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 04:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 04:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 04:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 04:25:01] OK — 12 items cached
|
||||||
|
[2026-04-03 04:30:02] OK — 11 items cached
|
||||||
|
[2026-04-03 04:35:01] OK — 11 items cached
|
||||||
|
[2026-04-03 04:40:02] OK — 11 items cached
|
||||||
|
[2026-04-03 04:45:02] OK — 11 items cached
|
||||||
|
[2026-04-03 04:50:01] OK — 11 items cached
|
||||||
|
[2026-04-03 04:55:02] OK — 11 items cached
|
||||||
|
[2026-04-03 05:00:02] OK — 11 items cached
|
||||||
|
[2026-04-03 05:05:01] OK — 11 items cached
|
||||||
|
[2026-04-03 05:10:02] OK — 11 items cached
|
||||||
|
[2026-04-03 05:15:02] OK — 11 items cached
|
||||||
|
[2026-04-03 05:20:02] OK — 11 items cached
|
||||||
|
[2026-04-03 05:25:01] OK — 11 items cached
|
||||||
|
[2026-04-03 05:30:02] OK — 11 items cached
|
||||||
|
[2026-04-03 05:35:02] OK — 11 items cached
|
||||||
|
[2026-04-03 05:40:01] OK — 12 items cached
|
||||||
|
[2026-04-03 05:45:01] OK — 12 items cached
|
||||||
|
[2026-04-03 05:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 05:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 06:00:01] OK — 12 items cached
|
||||||
|
[2026-04-03 06:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 06:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 06:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 06:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 06:25:01] OK — 12 items cached
|
||||||
|
[2026-04-03 06:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 06:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 06:40:01] OK — 12 items cached
|
||||||
|
[2026-04-03 06:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 06:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 06:55:01] OK — 12 items cached
|
||||||
|
[2026-04-03 07:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 07:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 07:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 07:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 07:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 07:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 07:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 07:35:01] OK — 12 items cached
|
||||||
|
[2026-04-03 07:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 07:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 07:50:01] OK — 12 items cached
|
||||||
|
[2026-04-03 07:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 08:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 08:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 08:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 08:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 08:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 08:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 08:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 08:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 08:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 08:45:01] OK — 12 items cached
|
||||||
|
[2026-04-03 08:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 08:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 09:00:01] OK — 12 items cached
|
||||||
|
[2026-04-03 09:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 09:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 09:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 09:20:01] OK — 12 items cached
|
||||||
|
[2026-04-03 09:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 09:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 09:35:01] OK — 12 items cached
|
||||||
|
[2026-04-03 09:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 09:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 09:50:01] OK — 12 items cached
|
||||||
|
[2026-04-03 09:55:02] OK — 13 items cached
|
||||||
|
[2026-04-03 10:00:02] OK — 13 items cached
|
||||||
|
[2026-04-03 10:05:01] OK — 12 items cached
|
||||||
|
[2026-04-03 10:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 10:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 10:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 10:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 10:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 10:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 10:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 10:45:01] OK — 12 items cached
|
||||||
|
[2026-04-03 10:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 10:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 11:00:01] OK — 12 items cached
|
||||||
|
[2026-04-03 11:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 11:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 11:15:03] OK — 12 items cached
|
||||||
|
[2026-04-03 11:20:01] OK — 12 items cached
|
||||||
|
[2026-04-03 11:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 11:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 11:35:01] OK — 12 items cached
|
||||||
|
[2026-04-03 11:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 11:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 11:50:01] OK — 12 items cached
|
||||||
|
[2026-04-03 11:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 12:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 12:05:01] OK — 12 items cached
|
||||||
|
[2026-04-03 12:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 12:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 12:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 12:25:01] OK — 12 items cached
|
||||||
|
[2026-04-03 12:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 12:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 12:40:01] OK — 12 items cached
|
||||||
|
[2026-04-03 12:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 12:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 12:55:01] OK — 12 items cached
|
||||||
|
[2026-04-03 13:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 13:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 13:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 13:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 13:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 13:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 13:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 13:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 13:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 13:45:01] OK — 12 items cached
|
||||||
|
[2026-04-03 13:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 13:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 14:00:01] OK — 12 items cached
|
||||||
|
[2026-04-03 14:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 14:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 14:15:03] OK — 12 items cached
|
||||||
|
[2026-04-03 14:20:01] OK — 12 items cached
|
||||||
|
[2026-04-03 14:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 14:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 14:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 14:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 14:45:01] OK — 12 items cached
|
||||||
|
[2026-04-03 14:50:07] OK — 12 items cached
|
||||||
|
[2026-04-03 14:55:01] OK — 12 items cached
|
||||||
|
[2026-04-03 15:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 15:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 15:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 15:15:03] OK — 12 items cached
|
||||||
|
[2026-04-03 15:20:01] OK — 12 items cached
|
||||||
|
[2026-04-03 15:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 15:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 15:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 15:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 15:45:01] OK — 12 items cached
|
||||||
|
[2026-04-03 15:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 15:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 16:00:01] OK — 12 items cached
|
||||||
|
[2026-04-03 16:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 16:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 16:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 16:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 16:25:01] OK — 12 items cached
|
||||||
|
[2026-04-03 16:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 16:35:01] OK — 12 items cached
|
||||||
|
[2026-04-03 16:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 16:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 16:50:01] OK — 12 items cached
|
||||||
|
[2026-04-03 16:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 17:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 17:05:01] OK — 12 items cached
|
||||||
|
[2026-04-03 17:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 17:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 17:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 17:25:01] OK — 12 items cached
|
||||||
|
[2026-04-03 17:30:02] OK — 12 items cached
|
||||||
|
[2026-04-03 17:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 17:40:01] OK — 12 items cached
|
||||||
|
[2026-04-03 17:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 17:50:01] OK — 12 items cached
|
||||||
|
[2026-04-03 17:55:02] OK — 12 items cached
|
||||||
|
[2026-04-03 18:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 18:05:01] OK — 12 items cached
|
||||||
|
[2026-04-03 18:10:02] OK — 12 items cached
|
||||||
|
[2026-04-03 18:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 18:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 18:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 18:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 18:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 18:40:01] OK — 12 items cached
|
||||||
|
[2026-04-03 18:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 18:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 18:55:01] OK — 12 items cached
|
||||||
|
[2026-04-03 19:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 19:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 19:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 19:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 19:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 19:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 19:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 19:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 19:40:01] OK — 12 items cached
|
||||||
|
[2026-04-03 19:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 19:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 19:55:01] OK — 12 items cached
|
||||||
|
[2026-04-03 20:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 20:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 20:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 20:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 20:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 20:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 20:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 20:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 20:40:01] OK — 12 items cached
|
||||||
|
[2026-04-03 20:45:02] OK — 12 items cached
|
||||||
|
[2026-04-03 20:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 20:55:01] OK — 12 items cached
|
||||||
|
[2026-04-03 21:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 21:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 21:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 21:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 21:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 21:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 21:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 21:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 21:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 21:45:01] OK — 12 items cached
|
||||||
|
[2026-04-03 21:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 21:55:01] OK — 12 items cached
|
||||||
|
[2026-04-03 22:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 22:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 22:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 22:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 22:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 22:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 22:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 22:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 22:40:02] OK — 12 items cached
|
||||||
|
[2026-04-03 22:45:01] OK — 12 items cached
|
||||||
|
[2026-04-03 22:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 22:55:01] OK — 12 items cached
|
||||||
|
[2026-04-03 23:00:02] OK — 12 items cached
|
||||||
|
[2026-04-03 23:05:02] OK — 12 items cached
|
||||||
|
[2026-04-03 23:10:01] OK — 12 items cached
|
||||||
|
[2026-04-03 23:15:02] OK — 12 items cached
|
||||||
|
[2026-04-03 23:20:02] OK — 12 items cached
|
||||||
|
[2026-04-03 23:25:02] OK — 12 items cached
|
||||||
|
[2026-04-03 23:30:01] OK — 12 items cached
|
||||||
|
[2026-04-03 23:35:02] OK — 12 items cached
|
||||||
|
[2026-04-03 23:40:01] OK — 12 items cached
|
||||||
|
[2026-04-03 23:45:01] OK — 12 items cached
|
||||||
|
[2026-04-03 23:50:02] OK — 12 items cached
|
||||||
|
[2026-04-03 23:55:01] OK — 12 items cached
|
||||||
|
[2026-04-04 00:00:02] OK — 12 items cached
|
||||||
|
[2026-04-04 00:05:02] OK — 12 items cached
|
||||||
|
[2026-04-04 00:10:01] OK — 12 items cached
|
||||||
|
[2026-04-04 00:15:03] OK — 12 items cached
|
||||||
|
[2026-04-04 00:20:01] OK — 12 items cached
|
||||||
|
[2026-04-04 00:25:02] OK — 12 items cached
|
||||||
|
[2026-04-04 00:30:01] OK — 12 items cached
|
||||||
|
[2026-04-04 00:35:02] OK — 12 items cached
|
||||||
|
[2026-04-04 00:40:02] OK — 12 items cached
|
||||||
|
[2026-04-04 00:45:01] OK — 12 items cached
|
||||||
|
[2026-04-04 00:50:02] OK — 12 items cached
|
||||||
|
[2026-04-04 00:55:02] OK — 12 items cached
|
||||||
|
[2026-04-04 01:00:01] OK — 12 items cached
|
||||||
|
[2026-04-04 01:05:02] OK — 12 items cached
|
||||||
|
[2026-04-04 01:10:01] OK — 12 items cached
|
||||||
|
[2026-04-04 01:15:03] OK — 12 items cached
|
||||||
|
[2026-04-04 01:20:01] OK — 12 items cached
|
||||||
|
[2026-04-04 01:25:02] OK — 12 items cached
|
||||||
|
[2026-04-04 01:30:01] OK — 12 items cached
|
||||||
|
[2026-04-04 01:35:02] OK — 12 items cached
|
||||||
|
[2026-04-04 01:40:02] OK — 12 items cached
|
||||||
|
[2026-04-04 01:45:01] OK — 12 items cached
|
||||||
|
[2026-04-04 01:50:02] OK — 12 items cached
|
||||||
|
[2026-04-04 01:55:01] OK — 12 items cached
|
||||||
|
[2026-04-04 02:00:01] OK — 12 items cached
|
||||||
|
[2026-04-04 02:05:02] OK — 12 items cached
|
||||||
|
[2026-04-04 02:10:01] OK — 12 items cached
|
||||||
|
[2026-04-04 02:15:02] OK — 12 items cached
|
||||||
|
[2026-04-04 02:20:02] OK — 12 items cached
|
||||||
|
[2026-04-04 02:25:02] OK — 12 items cached
|
||||||
|
[2026-04-04 02:30:01] OK — 12 items cached
|
||||||
|
[2026-04-04 02:35:02] OK — 12 items cached
|
||||||
|
[2026-04-04 02:40:02] OK — 12 items cached
|
||||||
|
[2026-04-04 02:45:01] OK — 12 items cached
|
||||||
|
[2026-04-04 02:50:02] OK — 12 items cached
|
||||||
|
[2026-04-04 02:55:01] OK — 12 items cached
|
||||||
|
[2026-04-04 03:00:02] OK — 12 items cached
|
||||||
|
[2026-04-04 03:05:02] OK — 12 items cached
|
||||||
|
[2026-04-04 03:10:02] OK — 12 items cached
|
||||||
|
[2026-04-04 03:15:02] OK — 12 items cached
|
||||||
|
[2026-04-04 03:20:02] OK — 12 items cached
|
||||||
|
[2026-04-04 03:25:01] OK — 12 items cached
|
||||||
|
[2026-04-04 03:30:02] OK — 12 items cached
|
||||||
|
[2026-04-04 03:35:01] OK — 12 items cached
|
||||||
|
[2026-04-04 03:40:02] OK — 12 items cached
|
||||||
|
[2026-04-04 03:45:02] OK — 12 items cached
|
||||||
|
[2026-04-04 03:50:01] OK — 12 items cached
|
||||||
|
[2026-04-04 03:55:02] OK — 12 items cached
|
||||||
|
[2026-04-04 04:00:02] OK — 12 items cached
|
||||||
|
[2026-04-04 04:05:01] OK — 12 items cached
|
||||||
|
[2026-04-04 04:10:02] OK — 12 items cached
|
||||||
|
[2026-04-04 04:15:02] OK — 12 items cached
|
||||||
|
[2026-04-04 04:20:02] OK — 12 items cached
|
||||||
|
[2026-04-04 04:25:01] OK — 12 items cached
|
||||||
|
[2026-04-04 04:30:02] OK — 12 items cached
|
||||||
|
[2026-04-04 04:35:02] OK — 12 items cached
|
||||||
|
[2026-04-04 04:40:01] OK — 12 items cached
|
||||||
|
[2026-04-04 04:45:02] OK — 12 items cached
|
||||||
|
[2026-04-04 04:50:01] OK — 12 items cached
|
||||||
|
[2026-04-04 04:55:01] OK — 12 items cached
|
||||||
|
[2026-04-04 05:00:02] OK — 12 items cached
|
||||||
|
[2026-04-04 05:05:01] OK — 12 items cached
|
||||||
|
[2026-04-04 05:10:02] OK — 12 items cached
|
||||||
|
[2026-04-04 05:15:02] OK — 12 items cached
|
||||||
|
[2026-04-04 05:20:02] OK — 12 items cached
|
||||||
|
[2026-04-04 05:25:01] OK — 12 items cached
|
||||||
|
[2026-04-04 05:30:02] OK — 12 items cached
|
||||||
|
[2026-04-04 05:35:02] OK — 12 items cached
|
||||||
|
[2026-04-04 05:40:01] OK — 12 items cached
|
||||||
|
[2026-04-04 05:45:02] OK — 12 items cached
|
||||||
|
[2026-04-04 05:50:02] OK — 12 items cached
|
||||||
|
[2026-04-04 05:55:01] OK — 12 items cached
|
||||||
|
[2026-04-04 06:00:02] OK — 12 items cached
|
||||||
|
[2026-04-04 06:05:01] OK — 11 items cached
|
||||||
|
[2026-04-04 06:10:02] OK — 11 items cached
|
||||||
|
[2026-04-04 06:15:03] OK — 11 items cached
|
||||||
|
[2026-04-04 06:20:01] OK — 11 items cached
|
||||||
|
[2026-04-04 06:25:02] OK — 11 items cached
|
||||||
|
[2026-04-04 06:30:02] OK — 11 items cached
|
||||||
|
[2026-04-04 06:35:01] OK — 11 items cached
|
||||||
|
[2026-04-04 06:40:02] OK — 11 items cached
|
||||||
|
[2026-04-04 06:45:01] OK — 11 items cached
|
||||||
|
[2026-04-04 06:50:02] OK — 11 items cached
|
||||||
|
[2026-04-04 06:55:02] OK — 11 items cached
|
||||||
|
[2026-04-04 07:00:01] OK — 11 items cached
|
||||||
|
[2026-04-04 07:05:02] OK — 11 items cached
|
||||||
|
[2026-04-04 07:10:01] OK — 11 items cached
|
||||||
|
[2026-04-04 07:15:02] OK — 11 items cached
|
||||||
|
[2026-04-04 07:20:01] OK — 11 items cached
|
||||||
|
[2026-04-04 07:25:02] OK — 11 items cached
|
||||||
|
[2026-04-04 07:30:02] OK — 11 items cached
|
||||||
|
[2026-04-04 07:35:01] OK — 11 items cached
|
||||||
|
[2026-04-04 07:40:02] OK — 11 items cached
|
||||||
|
[2026-04-04 07:45:01] OK — 11 items cached
|
||||||
|
[2026-04-04 07:50:02] OK — 11 items cached
|
||||||
|
[2026-04-04 07:55:02] OK — 11 items cached
|
||||||
|
[2026-04-04 08:00:01] OK — 11 items cached
|
||||||
|
[2026-04-04 08:05:02] OK — 11 items cached
|
||||||
|
[2026-04-04 08:10:02] OK — 11 items cached
|
||||||
|
[2026-04-04 08:15:02] OK — 11 items cached
|
||||||
|
[2026-04-04 08:20:02] OK — 11 items cached
|
||||||
|
[2026-04-04 08:25:02] OK — 11 items cached
|
||||||
|
[2026-04-04 08:30:01] OK — 11 items cached
|
||||||
|
[2026-04-04 08:35:02] OK — 11 items cached
|
||||||
|
[2026-04-04 08:40:02] OK — 11 items cached
|
||||||
|
[2026-04-04 08:45:01] OK — 11 items cached
|
||||||
|
[2026-04-04 08:50:02] OK — 11 items cached
|
||||||
|
[2026-04-04 08:55:01] OK — 11 items cached
|
||||||
|
[2026-04-04 09:00:02] OK — 11 items cached
|
||||||
|
[2026-04-04 09:05:02] OK — 11 items cached
|
||||||
|
[2026-04-04 09:10:02] OK — 11 items cached
|
||||||
|
[2026-04-04 09:15:02] OK — 11 items cached
|
||||||
|
[2026-04-04 09:20:01] OK — 11 items cached
|
||||||
|
[2026-04-04 09:25:02] OK — 11 items cached
|
||||||
|
[2026-04-04 09:30:02] OK — 11 items cached
|
||||||
|
[2026-04-04 09:35:01] OK — 11 items cached
|
||||||
|
[2026-04-04 09:40:02] OK — 11 items cached
|
||||||
|
[2026-04-04 09:45:02] OK — 11 items cached
|
||||||
|
[2026-04-04 09:50:01] OK — 11 items cached
|
||||||
|
[2026-04-04 09:55:02] OK — 11 items cached
|
||||||
|
[2026-04-04 10:00:02] OK — 11 items cached
|
||||||
|
[2026-04-04 10:05:01] OK — 11 items cached
|
||||||
|
[2026-04-04 10:10:02] OK — 11 items cached
|
||||||
|
[2026-04-04 10:15:02] OK — 11 items cached
|
||||||
|
[2026-04-04 10:20:02] OK — 11 items cached
|
||||||
|
[2026-04-04 10:25:01] OK — 11 items cached
|
||||||
|
[2026-04-04 10:30:02] OK — 11 items cached
|
||||||
|
[2026-04-04 10:35:02] OK — 11 items cached
|
||||||
|
[2026-04-04 10:40:01] OK — 11 items cached
|
||||||
|
[2026-04-04 10:45:02] OK — 11 items cached
|
||||||
|
[2026-04-04 10:50:02] OK — 11 items cached
|
||||||
|
[2026-04-04 10:55:01] OK — 11 items cached
|
||||||
|
[2026-04-04 11:00:02] OK — 11 items cached
|
||||||
|
[2026-04-04 11:05:02] OK — 9 items cached
|
||||||
|
[2026-04-04 11:10:01] OK — 9 items cached
|
||||||
|
[2026-04-04 11:15:02] OK — 9 items cached
|
||||||
|
[2026-04-04 11:20:02] OK — 9 items cached
|
||||||
|
[2026-04-04 11:25:02] OK — 9 items cached
|
||||||
|
[2026-04-04 11:30:01] OK — 9 items cached
|
||||||
|
[2026-04-04 11:35:02] OK — 9 items cached
|
||||||
|
[2026-04-04 11:40:02] OK — 10 items cached
|
||||||
|
[2026-04-04 11:45:01] OK — 10 items cached
|
||||||
|
[2026-04-04 11:50:02] OK — 10 items cached
|
||||||
|
[2026-04-04 11:55:02] OK — 10 items cached
|
||||||
|
[2026-04-04 12:00:01] OK — 10 items cached
|
||||||
|
[2026-04-04 12:05:02] OK — 10 items cached
|
||||||
|
[2026-04-04 12:10:02] OK — 10 items cached
|
||||||
|
[2026-04-04 12:15:02] OK — 10 items cached
|
||||||
|
[2026-04-04 12:20:01] OK — 11 items cached
|
||||||
|
[2026-04-04 12:25:01] OK — 11 items cached
|
||||||
|
[2026-04-04 12:30:02] OK — 11 items cached
|
||||||
|
[2026-04-04 12:35:02] OK — 11 items cached
|
||||||
|
[2026-04-04 12:40:01] OK — 11 items cached
|
||||||
|
[2026-04-04 12:45:02] OK — 11 items cached
|
||||||
|
[2026-04-04 12:50:02] OK — 11 items cached
|
||||||
|
[2026-04-04 12:55:01] OK — 11 items cached
|
||||||
|
[2026-04-04 13:00:02] OK — 11 items cached
|
||||||
|
[2026-04-04 13:05:02] OK — 11 items cached
|
||||||
|
[2026-04-04 13:10:01] OK — 11 items cached
|
||||||
|
[2026-04-04 13:15:02] OK — 11 items cached
|
||||||
|
[2026-04-04 13:20:02] OK — 11 items cached
|
||||||
|
[2026-04-04 13:25:02] OK — 11 items cached
|
||||||
|
[2026-04-04 13:30:01] OK — 11 items cached
|
||||||
|
[2026-04-04 13:35:02] OK — 11 items cached
|
||||||
|
[2026-04-04 13:40:02] OK — 11 items cached
|
||||||
|
[2026-04-04 13:45:01] OK — 11 items cached
|
||||||
|
[2026-04-04 13:50:02] OK — 11 items cached
|
||||||
|
[2026-04-04 13:55:02] OK — 11 items cached
|
||||||
|
[2026-04-04 14:00:01] OK — 11 items cached
|
||||||
|
[2026-04-04 14:05:02] OK — 11 items cached
|
||||||
|
[2026-04-04 14:10:02] OK — 11 items cached
|
||||||
|
[2026-04-04 14:15:02] OK — 11 items cached
|
||||||
|
[2026-04-04 14:20:02] OK — 11 items cached
|
||||||
|
[2026-04-04 14:25:01] OK — 11 items cached
|
||||||
|
[2026-04-04 14:30:02] OK — 11 items cached
|
||||||
Binary file not shown.
+36
@@ -643,6 +643,7 @@
|
|||||||
<button class="settings-tab active" onclick="switchSettingsTab(this, 'tab-api')" data-tab="tab-api" title="API Keys">🔑</button>
|
<button class="settings-tab active" onclick="switchSettingsTab(this, 'tab-api')" data-tab="tab-api" title="API Keys">🔑</button>
|
||||||
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-bring')" data-tab="tab-bring" title="Bring!">🛒</button>
|
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-bring')" data-tab="tab-bring" title="Bring!">🛒</button>
|
||||||
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-recipe')" data-tab="tab-recipe" title="Ricette">🍳</button>
|
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-recipe')" data-tab="tab-recipe" title="Ricette">🍳</button>
|
||||||
|
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-mealplan')" data-tab="tab-mealplan" title="Piano Settimanale">📅</button>
|
||||||
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-appliances')" data-tab="tab-appliances" title="Elettrodomestici">🔌</button>
|
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-appliances')" data-tab="tab-appliances" title="Elettrodomestici">🔌</button>
|
||||||
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-spesa')" data-tab="tab-spesa" title="Spesa Online">🛍️</button>
|
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-spesa')" data-tab="tab-spesa" title="Spesa Online">🛍️</button>
|
||||||
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-camera')" data-tab="tab-camera" title="Fotocamera">📷</button>
|
<button class="settings-tab" onclick="switchSettingsTab(this, 'tab-camera')" data-tab="tab-camera" title="Fotocamera">📷</button>
|
||||||
@@ -707,6 +708,36 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<!-- Weekly Meal Plan Tab -->
|
||||||
|
<div class="settings-panel" id="tab-mealplan">
|
||||||
|
<div class="settings-card">
|
||||||
|
<h4>📅 Piano Pasti Settimanale</h4>
|
||||||
|
<p class="settings-hint">Imposta la tipologia di pasto per ogni giorno. Sarà usata come guida nella generazione delle ricette.</p>
|
||||||
|
<div class="form-group" style="margin-bottom:10px">
|
||||||
|
<label class="toggle-row">
|
||||||
|
<span>✅ Attiva piano pasti settimanale</span>
|
||||||
|
<span class="toggle-switch">
|
||||||
|
<input type="checkbox" id="setting-meal-plan-enabled" onchange="onMealPlanEnabledChange(this)">
|
||||||
|
<span class="toggle-slider"></span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div id="meal-plan-config-section">
|
||||||
|
<div id="meal-plan-grid" class="mplan-grid"></div>
|
||||||
|
<div id="meal-plan-picker" class="mplan-picker" style="display:none"></div>
|
||||||
|
<div style="margin-top:12px; display:flex; gap:8px; flex-wrap:wrap">
|
||||||
|
<button class="btn btn-small btn-secondary" onclick="resetMealPlan()">↺ Ripristina default</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-hint" style="margin-top:10px">
|
||||||
|
🌤️ = Pranzo · 🌙 = Cena · Tocca un badge per cambiarlo.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-card" id="meal-plan-legend-card">
|
||||||
|
<h4>📋 Tipologie disponibili</h4>
|
||||||
|
<div class="mplan-legend"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<!-- Appliances Tab -->
|
<!-- Appliances Tab -->
|
||||||
<div class="settings-panel" id="tab-appliances">
|
<div class="settings-panel" id="tab-appliances">
|
||||||
<div class="settings-card">
|
<div class="settings-card">
|
||||||
@@ -884,6 +915,7 @@
|
|||||||
<!-- Recipe Dialog -->
|
<!-- Recipe Dialog -->
|
||||||
<div class="modal-overlay" id="recipe-overlay" style="display:none" onclick="closeRecipeDialog()">
|
<div class="modal-overlay" id="recipe-overlay" style="display:none" onclick="closeRecipeDialog()">
|
||||||
<div class="modal-content recipe-dialog" onclick="event.stopPropagation()">
|
<div class="modal-content recipe-dialog" onclick="event.stopPropagation()">
|
||||||
|
<div id="recipe-mealplan-banner" class="recipe-mealplan-banner" style="display:none"></div>
|
||||||
<div id="recipe-ask" class="recipe-ask">
|
<div id="recipe-ask" class="recipe-ask">
|
||||||
<h3 id="recipe-meal-title">🍳 Ricetta</h3>
|
<h3 id="recipe-meal-title">🍳 Ricetta</h3>
|
||||||
<p class="recipe-desc">Genero una ricetta sana con gli ingredienti in dispensa, dando priorità a quelli in scadenza.</p>
|
<p class="recipe-desc">Genero una ricetta sana con gli ingredienti in dispensa, dando priorità a quelli in scadenza.</p>
|
||||||
@@ -891,6 +923,7 @@
|
|||||||
<label>🕐 Per quale pasto?</label>
|
<label>🕐 Per quale pasto?</label>
|
||||||
<div class="recipe-meal-grid" id="recipe-meal-grid" onchange="updateRecipeMealTitle()"></div>
|
<div class="recipe-meal-grid" id="recipe-meal-grid" onchange="updateRecipeMealTitle()"></div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="recipe-mealplan-hint" class="recipe-mealplan-hint" style="display:none"></div>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label>👥 Quante persone?</label>
|
<label>👥 Quante persone?</label>
|
||||||
<div class="qty-control">
|
<div class="qty-control">
|
||||||
@@ -902,6 +935,7 @@
|
|||||||
<div class="form-group" style="text-align:left">
|
<div class="form-group" style="text-align:left">
|
||||||
<label>🎯 Tipo di pasto</label>
|
<label>🎯 Tipo di pasto</label>
|
||||||
<div class="recipe-options-grid">
|
<div class="recipe-options-grid">
|
||||||
|
<label class="recipe-option-chip recipe-opt-mealplan-chip" id="recipe-opt-mealplan-wrap" style="display:none"><input type="checkbox" id="recipe-opt-mealplan" checked> <span id="recipe-opt-mealplan-label"></span></label>
|
||||||
<label class="recipe-option-chip"><input type="checkbox" id="recipe-opt-veloce"> ⚡ Pasto Veloce</label>
|
<label class="recipe-option-chip"><input type="checkbox" id="recipe-opt-veloce"> ⚡ Pasto Veloce</label>
|
||||||
<label class="recipe-option-chip"><input type="checkbox" id="recipe-opt-pocafame"> 🥗 Poca Fame</label>
|
<label class="recipe-option-chip"><input type="checkbox" id="recipe-opt-pocafame"> 🥗 Poca Fame</label>
|
||||||
<label class="recipe-option-chip"><input type="checkbox" id="recipe-opt-scadenze"> ⏰ Priorità Scadenze</label>
|
<label class="recipe-option-chip"><input type="checkbox" id="recipe-opt-scadenze"> ⏰ Priorità Scadenze</label>
|
||||||
@@ -954,6 +988,7 @@
|
|||||||
<div id="screensaver" class="screensaver-overlay" style="display:none">
|
<div id="screensaver" class="screensaver-overlay" style="display:none">
|
||||||
<div class="screensaver-content">
|
<div class="screensaver-content">
|
||||||
<div class="screensaver-clock" id="screensaver-clock"></div>
|
<div class="screensaver-clock" id="screensaver-clock"></div>
|
||||||
|
<div id="screensaver-mealplan" class="screensaver-mealplan" style="display:none"></div>
|
||||||
<div class="screensaver-fact" id="screensaver-fact"></div>
|
<div class="screensaver-fact" id="screensaver-fact"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="screensaver-shortcuts">
|
<div class="screensaver-shortcuts">
|
||||||
@@ -968,6 +1003,7 @@
|
|||||||
|
|
||||||
<!-- ===== COOKING MODE OVERLAY ===== -->
|
<!-- ===== COOKING MODE OVERLAY ===== -->
|
||||||
<div id="cooking-overlay" class="cooking-overlay" style="display:none" aria-live="polite">
|
<div id="cooking-overlay" class="cooking-overlay" style="display:none" aria-live="polite">
|
||||||
|
<div id="cooking-flash-overlay" class="cooking-flash-overlay"></div>
|
||||||
<div class="cooking-header">
|
<div class="cooking-header">
|
||||||
<button class="cooking-close-btn" onclick="closeCookingMode()" title="Chiudi">✕</button>
|
<button class="cooking-close-btn" onclick="closeCookingMode()" title="Chiudi">✕</button>
|
||||||
<span class="cooking-title" id="cooking-title"></span>
|
<span class="cooking-title" id="cooking-title"></span>
|
||||||
|
|||||||
Reference in New Issue
Block a user