2023-05-18 18:14:51 +02:00
|
|
|
function currentDate() {
|
|
|
|
const currentDate = new Date();
|
|
|
|
const year = currentDate.getFullYear();
|
|
|
|
const month = String(currentDate.getMonth() + 1).padStart(2, '0');
|
|
|
|
const day = String(currentDate.getDate()).padStart(2, '0');
|
|
|
|
return `${year}-${month}-${day}`;
|
|
|
|
}
|
|
|
|
|
2023-05-19 16:01:43 +02:00
|
|
|
function getQueryParameter(name) {
|
|
|
|
const url = window.location.href;
|
|
|
|
const queryString = url.substring(url.indexOf('?') + 1);
|
|
|
|
const parameters = queryString.split('&');
|
|
|
|
|
|
|
|
for (var i = 0; i < parameters.length; i++) {
|
|
|
|
const parameter = parameters[i].split('=');
|
|
|
|
const paramName = decodeURIComponent(parameter[0]);
|
|
|
|
if (paramName == name) return decodeURIComponent(parameter[1]);
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2023-05-19 14:47:40 +02:00
|
|
|
function toNumber(value, defaultValue) {
|
|
|
|
try {
|
|
|
|
return Number.parseFloat(value);
|
|
|
|
} catch (e) {
|
|
|
|
return defaultValue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-18 18:14:51 +02:00
|
|
|
function dom(el, html) {
|
|
|
|
let element = document.createElement(el);
|
|
|
|
element.innerHTML = html;
|
|
|
|
return element;
|
|
|
|
}
|
|
|
|
|
2023-05-23 20:07:26 +02:00
|
|
|
async function loadItems() {
|
2023-05-26 09:16:26 +02:00
|
|
|
const response = await fetch("latest-canonical.json")
|
2023-05-23 20:07:26 +02:00
|
|
|
const items = await response.json();
|
|
|
|
|
|
|
|
for (item of items) {
|
|
|
|
item.search = item.name + " " + item.unit;
|
|
|
|
item.search = item.search.toLowerCase().replace(",", ".");
|
|
|
|
|
|
|
|
item.numPrices = item.priceHistory.length;
|
|
|
|
item.priceOldest = item.priceHistory[item.priceHistory.length - 1].price;
|
2023-05-26 11:28:40 +02:00
|
|
|
item.dateOldest = item.priceHistory[item.priceHistory.length - 1].date;
|
2023-05-23 20:07:26 +02:00
|
|
|
item.date = item.priceHistory[0].date;
|
2023-05-26 13:24:05 +02:00
|
|
|
let lastPrice = {price: item.price, date: item.date};
|
2023-05-23 20:07:26 +02:00
|
|
|
for (let i = 1; i < 10; i++) {
|
|
|
|
let price = item.priceHistory[i];
|
|
|
|
if (!price) price = lastPrice;
|
|
|
|
item["price" + (i + 1)] = price.price;
|
|
|
|
item["date" + (i + 1)] = price.date;
|
|
|
|
lastPrice = price;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return items;
|
|
|
|
}
|
|
|
|
|
2023-05-19 14:47:40 +02:00
|
|
|
let carts = [];
|
|
|
|
loadCarts();
|
|
|
|
|
|
|
|
function loadCarts() {
|
|
|
|
let val = localStorage.getItem("carts");
|
|
|
|
carts = val ? JSON.parse(val) : [];
|
|
|
|
}
|
|
|
|
|
|
|
|
function saveCarts() {
|
|
|
|
localStorage.setItem("carts", JSON.stringify(carts, null, 2));
|
|
|
|
}
|
|
|
|
|
|
|
|
function hasCart(name) {
|
|
|
|
for (cart of carts) {
|
|
|
|
if (cart.name = name) return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function addCart(name) {
|
|
|
|
carts.push({
|
|
|
|
name: name,
|
|
|
|
items: []
|
|
|
|
});
|
|
|
|
saveCarts();
|
|
|
|
}
|
|
|
|
|
|
|
|
function removeCart(name) {
|
2023-05-19 16:01:43 +02:00
|
|
|
carts = carts.filter(cart => cart.name != name);
|
2023-05-19 14:47:40 +02:00
|
|
|
saveCarts();
|
|
|
|
}
|
|
|
|
|
2023-05-18 18:20:58 +02:00
|
|
|
function itemToStoreLink(item) {
|
|
|
|
if (item.store == "spar")
|
|
|
|
return `<a target="_blank" href="https://www.interspar.at/shop/lebensmittel/search/?q=${encodeURIComponent(item.name)}">${item.name}</a>`;
|
|
|
|
if (item.store == "billa")
|
|
|
|
return `<a target="_blank" href="https://shop.billa.at/search/results?category=&searchTerm=${encodeURIComponent(item.name)}">${item.name}</a>`;
|
|
|
|
if (item.store == "hofer")
|
|
|
|
return `<a target="_blank" href="https://www.roksh.at/hofer/angebot/suche/${encodeURIComponent(item.name)}">${item.name}</a>`;
|
2023-05-25 12:28:12 +02:00
|
|
|
if (item.store == "dm")
|
|
|
|
return `<a target="_blank" href="https://www.dm.at/product-p${item.id}.html">${item.name}</a>`;
|
2023-05-24 20:02:45 +02:00
|
|
|
if (item.store == "lidl")
|
|
|
|
return `<a target="_blank" href="https://www.lidl.at${item.url}">${item.name}</a>`;
|
2023-05-26 00:34:26 +02:00
|
|
|
if (item.store == "mpreis")
|
|
|
|
return `<a target="_blank" href="https://www.mpreis.at/shop/p/${item.id}">${item.name}</a>`;
|
2023-05-18 18:20:58 +02:00
|
|
|
return item.name;
|
|
|
|
}
|
|
|
|
|
2023-05-18 18:14:51 +02:00
|
|
|
function itemToDOM(item) {
|
|
|
|
let storeDom = dom("td", item.store);
|
2023-05-25 13:32:53 +02:00
|
|
|
let nameDom = dom("td", `<div class="itemname">${itemToStoreLink(item)}</div>`);
|
2023-05-18 18:14:51 +02:00
|
|
|
let unitDom = dom("td", item.unit ? item.unit : "");
|
2023-05-25 07:03:21 +02:00
|
|
|
let increase = "";
|
|
|
|
if (item.priceHistory.length > 1) {
|
|
|
|
let percentageChange = Math.round((item.priceHistory[0].price - item.priceHistory[1].price) / item.priceHistory[1].price * 100);
|
2023-05-25 13:32:53 +02:00
|
|
|
increase = `<span class="${percentageChange > 0 ? "increase" : "decrease"}">${(percentageChange > 0 ? "+" + percentageChange : percentageChange)}%</span>`;
|
2023-05-25 07:03:21 +02:00
|
|
|
}
|
2023-05-25 14:20:50 +02:00
|
|
|
let priceDomText = `${Number(item.price).toFixed(2)} ${increase} ${item.priceHistory.length > 1 ? "(" + (item.priceHistory.length - 1) + ")" : ""}`;
|
2023-05-24 16:59:43 +02:00
|
|
|
let pricesText = "";
|
|
|
|
for (let i = 0; i < item.priceHistory.length; i++) {
|
|
|
|
const date = item.priceHistory[i].date;
|
|
|
|
const currPrice = item.priceHistory[i].price;
|
|
|
|
const lastPrice = item.priceHistory[i + 1] ? item.priceHistory[i + 1].price : currPrice;
|
|
|
|
const increase = Math.round((currPrice - lastPrice) / lastPrice * 100);
|
|
|
|
let priceColor = "black";
|
|
|
|
if (increase > 0) priceColor = "red";
|
|
|
|
if (increase < 0) priceColor = "green";
|
|
|
|
pricesText += `<span style="color: ${priceColor}">${date} ${currPrice} ${increase > 0 ? "+" + increase : increase}%</span>`;
|
|
|
|
if (i != item.priceHistory.length - 1) pricesText += "<br>";
|
|
|
|
}
|
|
|
|
let priceDom = dom("td", `${priceDomText}<div class="priceinfo hide">${pricesText}</div>`);
|
2023-05-18 18:14:51 +02:00
|
|
|
if (item.priceHistory.length > 1) {
|
|
|
|
priceDom.style["cursor"] = "pointer";
|
|
|
|
priceDom.addEventListener("click", () => {
|
2023-05-24 16:59:43 +02:00
|
|
|
const pricesDom = priceDom.querySelector(".priceinfo");
|
|
|
|
if (pricesDom.classList.contains("hide")) {
|
|
|
|
pricesDom.classList.remove("hide");
|
2023-05-18 18:14:51 +02:00
|
|
|
} else {
|
2023-05-24 16:59:43 +02:00
|
|
|
pricesDom.classList.add("hide");
|
2023-05-18 18:14:51 +02:00
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
let row = dom("tr", "");
|
2023-05-24 12:00:33 +02:00
|
|
|
switch (item.store) {
|
2023-05-19 16:37:46 +02:00
|
|
|
case "billa":
|
2023-05-19 19:39:26 +02:00
|
|
|
row.style["background"] = "rgb(255 255 225)";
|
2023-05-19 16:37:46 +02:00
|
|
|
break;
|
|
|
|
case "spar":
|
2023-05-19 19:39:26 +02:00
|
|
|
row.style["background"] = "rgb(225 244 225)";
|
2023-05-19 16:37:46 +02:00
|
|
|
break;
|
|
|
|
case "hofer":
|
2023-05-19 19:39:26 +02:00
|
|
|
row.style["background"] = "rgb(230 230 255)";
|
2023-05-19 16:37:46 +02:00
|
|
|
break;
|
2023-05-25 12:28:12 +02:00
|
|
|
case "dm":
|
|
|
|
row.style["background"] = "rgb(255 240 230)";
|
|
|
|
break;
|
2023-05-24 20:02:45 +02:00
|
|
|
case "lidl":
|
|
|
|
row.style["background"] = "rgb(255 225 225)";
|
|
|
|
break;
|
2023-05-26 00:34:26 +02:00
|
|
|
case "mpreis":
|
|
|
|
row.style["background"] = "rgb(255 230 230)";
|
2023-05-19 16:37:46 +02:00
|
|
|
}
|
2023-05-18 18:14:51 +02:00
|
|
|
row.appendChild(storeDom);
|
|
|
|
row.appendChild(nameDom);
|
|
|
|
row.appendChild(unitDom);
|
|
|
|
row.appendChild(priceDom);
|
|
|
|
return row;
|
|
|
|
}
|
|
|
|
|
2023-05-19 14:47:40 +02:00
|
|
|
let componentId = 0;
|
2023-05-18 18:14:51 +02:00
|
|
|
|
2023-05-26 00:34:26 +02:00
|
|
|
function searchItems(items, query, billa, spar, hofer, dm, lidl, mpreis, eigenmarken, minPrice, maxPrice, exact, bio) {
|
2023-05-23 11:21:48 +02:00
|
|
|
query = query.trim();
|
2023-05-19 14:47:40 +02:00
|
|
|
if (query.length < 3) return [];
|
2023-05-18 18:14:51 +02:00
|
|
|
|
2023-05-23 11:21:48 +02:00
|
|
|
if (query.charAt(0) == "!") {
|
|
|
|
query = query.substring(1);
|
|
|
|
try {
|
|
|
|
let hits = alasql("select * from ? where " + query, [items]);
|
|
|
|
if (hits.length > 1000) {
|
|
|
|
return hits.slice(0, 1000);
|
|
|
|
} else {
|
|
|
|
return hits;
|
|
|
|
}
|
|
|
|
} catch (e) {
|
2023-05-24 12:00:33 +02:00
|
|
|
throw e;
|
2023-05-23 11:21:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-05-20 15:47:07 +02:00
|
|
|
const tokens = query.split(/\s+/).map(token => token.toLowerCase().replace(",", "."));
|
2023-05-18 18:14:51 +02:00
|
|
|
|
2023-05-23 11:21:48 +02:00
|
|
|
let hits = [];
|
2023-05-19 14:47:40 +02:00
|
|
|
for (item of items) {
|
|
|
|
let allFound = true;
|
|
|
|
for (token of tokens) {
|
|
|
|
if (token.length == 0) continue;
|
|
|
|
const index = item.search.indexOf(token);
|
|
|
|
if (index < 0) {
|
|
|
|
allFound = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (exact) {
|
|
|
|
if (index > 0 && (item.search.charAt(index - 1) != " " && item.search.charAt(index - 1) != "-")) {
|
|
|
|
allFound = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (index + token.length < item.search.length && item.search.charAt(index + token.length) != " ") {
|
|
|
|
allFound = false;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2023-05-23 20:07:26 +02:00
|
|
|
if (allFound) {
|
|
|
|
const name = item.name.toLowerCase();
|
|
|
|
if (item.store == "billa" && !billa) continue;
|
|
|
|
if (item.store == "spar" && !spar) continue;
|
|
|
|
if (item.store == "hofer" && !hofer) continue;
|
2023-05-25 12:28:12 +02:00
|
|
|
if (item.store == "dm" && !dm) continue;
|
2023-05-24 20:02:45 +02:00
|
|
|
if (item.store == "lidl" && !lidl) continue;
|
2023-05-26 00:34:26 +02:00
|
|
|
if (item.store == "mpreis" && !mpreis) continue;
|
2023-05-23 20:07:26 +02:00
|
|
|
if (item.price < minPrice) continue;
|
|
|
|
if (item.price > maxPrice) continue;
|
|
|
|
if (eigenmarken && !(name.indexOf("clever") == 0 || name.indexOf("s-budget") == 0 || name.indexOf("milfina") == 0)) continue;
|
2023-05-24 17:16:57 +02:00
|
|
|
if (bio && !item.bio) continue;
|
2023-05-19 14:47:40 +02:00
|
|
|
hits.push(item);
|
2023-05-23 11:21:48 +02:00
|
|
|
}
|
2023-05-19 14:47:40 +02:00
|
|
|
}
|
2023-05-23 20:07:26 +02:00
|
|
|
return hits;
|
2023-05-23 11:21:48 +02:00
|
|
|
}
|
2023-05-19 14:47:40 +02:00
|
|
|
|
2023-05-23 20:07:26 +02:00
|
|
|
function newSearchComponent(parentElement, items, searched, filter, headerModifier, itemDomModifier) {
|
2023-05-19 14:47:40 +02:00
|
|
|
let id = componentId++;
|
|
|
|
parentElement.innerHTML = "";
|
|
|
|
parentElement.innerHTML = `
|
|
|
|
<input id="search-${id}" class="search" type="text" placeholder="Produkte suchen...">
|
2023-05-26 11:28:40 +02:00
|
|
|
<a id="querylink-${id}" class="hide">Query link</a>
|
2023-05-19 14:47:40 +02:00
|
|
|
<div class="filters">
|
|
|
|
<label><input id="billa-${id}" type="checkbox" checked="true"> Billa</label>
|
|
|
|
<label><input id="spar-${id}" type="checkbox" checked="true"> Spar</label>
|
|
|
|
<label><input id="hofer-${id}" type="checkbox" checked="true"> Hofer</label>
|
2023-05-25 12:28:12 +02:00
|
|
|
<label><input id="dm-${id}" type="checkbox" checked="true"> DM</label>
|
2023-05-24 20:02:45 +02:00
|
|
|
<label><input id="lidl-${id}" type="checkbox" checked="true"> Lidl</label>
|
2023-05-26 00:34:26 +02:00
|
|
|
<label><input id="mpreis-${id}" type="checkbox" checked="false"> MPREIS</label>
|
2023-05-26 08:21:02 +02:00
|
|
|
</div>
|
|
|
|
<div class="filters">
|
2023-05-19 14:47:40 +02:00
|
|
|
<label><input id="eigenmarken-${id}" type="checkbox"> Nur CLEVER / S-BUDGET / MILFINA</label>
|
2023-05-24 17:16:57 +02:00
|
|
|
<label><input id="bio-${id}" type="checkbox"> Nur Bio</label>
|
2023-05-19 14:47:40 +02:00
|
|
|
</div>
|
|
|
|
<div class="filters">
|
|
|
|
<label>Min € <input id="minprice-${id}" type="number" min="0" value="0"></label>
|
|
|
|
<label>Max € <input id="maxprice-${id}" type="number" min="0" value="100"></label>
|
|
|
|
<label><input id="exact-${id}" type="checkbox"> Exaktes Wort</label>
|
|
|
|
</div>
|
2023-05-23 11:21:48 +02:00
|
|
|
<div id="numresults-${id}"></div>
|
2023-05-19 14:47:40 +02:00
|
|
|
<table id="result-${id}"></table>
|
|
|
|
`;
|
|
|
|
|
|
|
|
const searchInput = parentElement.querySelector(`#search-${id}`);
|
2023-05-26 11:28:40 +02:00
|
|
|
const queryLink = parentElement.querySelector(`#querylink-${id}`);
|
2023-05-19 14:47:40 +02:00
|
|
|
const exact = parentElement.querySelector(`#exact-${id}`);
|
|
|
|
const table = parentElement.querySelector(`#result-${id}`);
|
|
|
|
const eigenmarken = parentElement.querySelector(`#eigenmarken-${id}`);
|
2023-05-24 17:16:57 +02:00
|
|
|
const bio = parentElement.querySelector(`#bio-${id}`);
|
2023-05-19 14:47:40 +02:00
|
|
|
const billa = parentElement.querySelector(`#billa-${id}`);
|
|
|
|
const spar = parentElement.querySelector(`#spar-${id}`);
|
|
|
|
const hofer = parentElement.querySelector(`#hofer-${id}`);
|
2023-05-25 12:28:12 +02:00
|
|
|
const dm = parentElement.querySelector(`#dm-${id}`);
|
2023-05-24 20:02:45 +02:00
|
|
|
const lidl = parentElement.querySelector(`#lidl-${id}`);
|
2023-05-26 00:34:26 +02:00
|
|
|
const mpreis = parentElement.querySelector(`#mpreis-${id}`);
|
2023-05-19 14:47:40 +02:00
|
|
|
const minPrice = parentElement.querySelector(`#minprice-${id}`);
|
|
|
|
const maxPrice = parentElement.querySelector(`#maxprice-${id}`);
|
2023-05-23 11:21:48 +02:00
|
|
|
const numResults = parentElement.querySelector(`#numresults-${id}`);
|
2023-05-19 14:47:40 +02:00
|
|
|
|
|
|
|
let search = (query) => {
|
2023-05-24 12:00:33 +02:00
|
|
|
let hits = [];
|
|
|
|
try {
|
|
|
|
hits = searchItems(items, query,
|
2023-05-26 08:21:02 +02:00
|
|
|
billa.checked, spar.checked, hofer.checked, dm.checked, lidl.checked, mpreis.checked,
|
2023-05-26 00:34:26 +02:00
|
|
|
eigenmarken.checked, toNumber(minPrice.value, 0), toNumber(maxPrice.value, 100), exact.checked, bio.checked
|
2023-05-24 12:00:33 +02:00
|
|
|
);
|
|
|
|
} catch (e) {
|
|
|
|
console.log("Query: " + query + "\n" + e.message);
|
|
|
|
}
|
2023-05-23 20:07:26 +02:00
|
|
|
if (searched) hits = searched(hits);
|
2023-05-19 14:47:40 +02:00
|
|
|
if (filter) hits = hits.filter(filter);
|
|
|
|
table.innerHTML = "";
|
2023-05-23 20:07:26 +02:00
|
|
|
if (hits.length == 0) {
|
|
|
|
numResults.innerHTML = "Resultate: 0";
|
|
|
|
return;
|
|
|
|
}
|
2023-05-23 11:21:48 +02:00
|
|
|
if (query.trim().charAt(0) != "!") hits.sort((a, b) => a.price - b.price);
|
2023-05-19 14:47:40 +02:00
|
|
|
|
2023-05-25 16:09:34 +02:00
|
|
|
let header = dom("tr", `<th>Kette</th><th>Name</th><th>Menge</th><th>Preis</th>`);
|
|
|
|
if (headerModifier) header = headerModifier(header);
|
2023-05-19 14:47:40 +02:00
|
|
|
table.appendChild(header);
|
|
|
|
|
2023-05-23 11:21:48 +02:00
|
|
|
let num = 0;
|
2023-05-19 14:47:40 +02:00
|
|
|
hits.forEach(hit => {
|
2023-05-19 16:01:43 +02:00
|
|
|
let itemDom = itemToDOM(hit);
|
2023-05-23 20:07:26 +02:00
|
|
|
if (itemDomModifier) itemDom = itemDomModifier(hit, itemDom, hits);
|
2023-05-19 16:01:43 +02:00
|
|
|
table.appendChild(itemDom);
|
2023-05-23 11:21:48 +02:00
|
|
|
num++;
|
2023-05-19 14:47:40 +02:00
|
|
|
});
|
2023-05-23 20:07:26 +02:00
|
|
|
numResults.innerHTML = "Resultate: " + num + (num == 1000 ? "+" : "");
|
2023-05-19 14:47:40 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
searchInput.addEventListener("input", (event) => {
|
2023-05-23 20:07:26 +02:00
|
|
|
const query = searchInput.value.trim();
|
|
|
|
if (query == 0) {
|
2023-05-19 14:47:40 +02:00
|
|
|
minPrice.value = 0;
|
|
|
|
maxPrice.value = 100;
|
|
|
|
}
|
2023-05-23 20:07:26 +02:00
|
|
|
if (query.length > 0 && query.charAt(0) == "!") {
|
|
|
|
parentElement.querySelectorAll(".filters").forEach(f => f.style.display = "none");
|
2023-05-26 11:28:40 +02:00
|
|
|
queryLink.classList.remove("hide");
|
|
|
|
queryLink.setAttribute("href", "/?q=" + encodeURIComponent(query));
|
2023-05-23 20:07:26 +02:00
|
|
|
} else {
|
|
|
|
parentElement.querySelectorAll(".filters").forEach(f => f.style.display = "block");
|
2023-05-26 11:28:40 +02:00
|
|
|
queryLink.classList.add("hide");
|
2023-05-23 20:07:26 +02:00
|
|
|
}
|
2023-05-19 14:47:40 +02:00
|
|
|
search(searchInput.value);
|
2023-05-18 18:14:51 +02:00
|
|
|
});
|
2023-05-19 14:47:40 +02:00
|
|
|
eigenmarken.addEventListener("change", () => search(searchInput.value));
|
2023-05-24 19:38:09 +02:00
|
|
|
bio.addEventListener("change", () => search(searchInput.value));
|
2023-05-19 14:47:40 +02:00
|
|
|
billa.addEventListener("change", () => search(searchInput.value));
|
|
|
|
spar.addEventListener("change", () => search(searchInput.value));
|
|
|
|
hofer.addEventListener("change", () => search(searchInput.value));
|
2023-05-25 12:28:12 +02:00
|
|
|
dm.addEventListener("change", () => search(searchInput.value));
|
2023-05-24 20:02:45 +02:00
|
|
|
lidl.addEventListener("change", () => search(searchInput.value));
|
2023-05-26 00:34:26 +02:00
|
|
|
mpreis.addEventListener("change", () => search(searchInput.value));
|
2023-05-19 14:47:40 +02:00
|
|
|
exact.addEventListener("change", () => search(searchInput.value));
|
|
|
|
minPrice.addEventListener("change", () => search(searchInput.value));
|
|
|
|
maxPrice.addEventListener("change", () => search(searchInput.value));
|
2023-05-18 18:14:51 +02:00
|
|
|
|
2023-05-19 14:47:40 +02:00
|
|
|
return () => search(searchInput.value);
|
2023-05-23 20:07:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function showChart(canvasDom, items) {
|
|
|
|
if (items.length == 0) {
|
|
|
|
canvasDom.style.display = "none";
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
canvasDom.style.display = "block";
|
|
|
|
}
|
|
|
|
|
|
|
|
const allDates = items.flatMap(product => product.priceHistory.map(item => item.date));
|
|
|
|
const uniqueDates = [...new Set(allDates)];
|
|
|
|
uniqueDates.sort();
|
|
|
|
|
|
|
|
const datasets = items.map(product => {
|
|
|
|
let price = null;
|
|
|
|
const prices = uniqueDates.map(date => {
|
|
|
|
const priceObj = product.priceHistory.find(item => item.date === date);
|
|
|
|
if (!price && priceObj) price = priceObj.price;
|
|
|
|
return priceObj ? priceObj.price : null;
|
|
|
|
});
|
|
|
|
|
|
|
|
for (let i = 0; i < prices.length; i++) {
|
2023-05-24 16:59:43 +02:00
|
|
|
if (prices[i] == null) {
|
2023-05-23 20:07:26 +02:00
|
|
|
prices[i] = price;
|
|
|
|
} else {
|
|
|
|
price = prices[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return {
|
|
|
|
label: product.name,
|
|
|
|
data: prices,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
const ctx = canvasDom.getContext('2d');
|
|
|
|
if (canvasDom.lastChart) canvasDom.lastChart.destroy();
|
|
|
|
canvasDom.lastChart = new Chart(ctx, {
|
|
|
|
type: 'line',
|
|
|
|
data: {
|
|
|
|
labels: uniqueDates,
|
|
|
|
datasets: datasets
|
|
|
|
},
|
|
|
|
options: {
|
|
|
|
responsive: true,
|
|
|
|
aspectRation: 16 / 9
|
|
|
|
}
|
|
|
|
});
|
2023-05-24 16:59:43 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
function calculateOverallPriceChanges(items) {
|
|
|
|
if (items.length == 0) return { dates: [], changes: [] };
|
|
|
|
const allDates = items.flatMap(product => product.priceHistory.map(item => item.date));
|
|
|
|
const uniqueDates = [...new Set(allDates)];
|
|
|
|
uniqueDates.sort();
|
|
|
|
|
|
|
|
const allPrices = items.map(product => {
|
|
|
|
let price = null;
|
|
|
|
const prices = uniqueDates.map(date => {
|
|
|
|
const priceObj = product.priceHistory.find(item => item.date === date);
|
|
|
|
if (!price && priceObj) price = priceObj.price;
|
|
|
|
return priceObj ? priceObj.price : null;
|
|
|
|
});
|
|
|
|
|
|
|
|
for (let i = 0; i < prices.length; i++) {
|
|
|
|
if (!prices[i]) {
|
|
|
|
prices[i] = price;
|
|
|
|
} else {
|
|
|
|
price = prices[i];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return prices;
|
|
|
|
});
|
|
|
|
|
|
|
|
const priceChanges = [];
|
|
|
|
for (let i = 0; i < uniqueDates.length; i++) {
|
|
|
|
let price = 0;
|
|
|
|
for (let j = 0; j < allPrices.length; j++) {
|
|
|
|
price += allPrices[j][i];
|
|
|
|
}
|
|
|
|
priceChanges.push({ date: uniqueDates[i], price });
|
|
|
|
}
|
|
|
|
|
|
|
|
return priceChanges;
|
2023-05-18 18:14:51 +02:00
|
|
|
}
|