mirror of
https://github.com/badlogic/heissepreise.git
synced 2024-06-28 03:16:12 +02:00
Binary compression (it's worse), unit prices in charts, small improvements.
This commit is contained in:
parent
c97c8116f6
commit
ea5c133003
133
analysis.js
133
analysis.js
|
@ -148,6 +148,139 @@ function sortItems(items) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function compressBinary(items) {
|
||||||
|
const buffer = [];
|
||||||
|
|
||||||
|
for (const item of items) {
|
||||||
|
// Serialize 'bio', 'isWeighted', and 'unit' into a single byte
|
||||||
|
let flagsByte = 0;
|
||||||
|
if (item.bio) flagsByte |= 1;
|
||||||
|
if (item.isWeighted) flagsByte |= 2;
|
||||||
|
if (item.unit === "ml") flagsByte |= 4;
|
||||||
|
if (item.unit === "stk") flagsByte |= 8;
|
||||||
|
buffer.push(flagsByte);
|
||||||
|
|
||||||
|
// Serialize 'quantity' as a 4-byte float
|
||||||
|
const quantityBuffer = Buffer.allocUnsafe(4);
|
||||||
|
quantityBuffer.writeFloatLE(item.quantity, 0);
|
||||||
|
buffer.push(...quantityBuffer);
|
||||||
|
|
||||||
|
// Serialize 'price' as a 4-byte float
|
||||||
|
const priceBuffer = Buffer.allocUnsafe(4);
|
||||||
|
priceBuffer.writeFloatLE(item.price, 0);
|
||||||
|
buffer.push(...priceBuffer);
|
||||||
|
|
||||||
|
// Serialize 'store' as a byte
|
||||||
|
const storeByte = STORE_KEYS.findIndex((store) => store == item.store);
|
||||||
|
buffer.push(storeByte);
|
||||||
|
|
||||||
|
// Serialize 'name' as UTF-8 with 2 bytes encoding the string length
|
||||||
|
const nameBuffer = Buffer.from(item.name, "utf8");
|
||||||
|
const nameLengthBuffer = Buffer.allocUnsafe(2);
|
||||||
|
nameLengthBuffer.writeUInt16LE(nameBuffer.length, 0);
|
||||||
|
buffer.push(...nameLengthBuffer, ...nameBuffer);
|
||||||
|
|
||||||
|
// Serialize 'url' as UTF-8 with 2 bytes encoding the string length
|
||||||
|
if (item.url !== undefined) {
|
||||||
|
const urlBuffer = Buffer.from(item.url, "utf8");
|
||||||
|
const urlLengthBuffer = Buffer.allocUnsafe(2);
|
||||||
|
urlLengthBuffer.writeUInt16LE(urlBuffer.length, 0);
|
||||||
|
buffer.push(...urlLengthBuffer, ...urlBuffer);
|
||||||
|
} else {
|
||||||
|
const urlLengthBuffer = Buffer.allocUnsafe(2).fill(0);
|
||||||
|
buffer.push(...urlLengthBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Serialize 'priceHistory' array
|
||||||
|
const priceHistoryLengthBuffer = Buffer.allocUnsafe(2);
|
||||||
|
priceHistoryLengthBuffer.writeUInt16LE(item.priceHistory.length, 0);
|
||||||
|
buffer.push(...priceHistoryLengthBuffer);
|
||||||
|
|
||||||
|
for (const priceEntry of item.priceHistory) {
|
||||||
|
// Serialize price as a 4-byte float
|
||||||
|
const priceEntryBuffer = Buffer.allocUnsafe(4);
|
||||||
|
priceEntryBuffer.writeFloatLE(priceEntry.price, 0);
|
||||||
|
buffer.push(...priceEntryBuffer);
|
||||||
|
|
||||||
|
// Calculate the days since 2000-01-01
|
||||||
|
const entryDate = new Date(priceEntry.date);
|
||||||
|
const baseDate = new Date("2000-01-01");
|
||||||
|
const daysSince2000 = Math.floor((entryDate - baseDate) / (1000 * 60 * 60 * 24));
|
||||||
|
|
||||||
|
// Serialize days as a 32-bit integer
|
||||||
|
const daysBuffer = Buffer.allocUnsafe(4);
|
||||||
|
daysBuffer.writeInt32LE(daysSince2000, 0);
|
||||||
|
buffer.push(...daysBuffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Buffer.from(buffer);
|
||||||
|
}
|
||||||
|
exports.compressBinary = compressBinary;
|
||||||
|
|
||||||
|
function decompressBinary(buffer) {
|
||||||
|
const objects = [];
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
while (offset < buffer.length) {
|
||||||
|
const obj = {};
|
||||||
|
|
||||||
|
// Deserialize 'bio', 'isWeighted', and 'unit' from the single byte
|
||||||
|
const flagsByte = buffer[offset++];
|
||||||
|
obj.bio = (flagsByte & 1) !== 0;
|
||||||
|
obj.isWeighted = (flagsByte & 2) !== 0;
|
||||||
|
obj.unit = (flagsByte & 4) !== 0 ? "ml" : (flagsByte & 8) !== 0 ? "stk" : "g";
|
||||||
|
|
||||||
|
// Deserialize 'quantity' as a 4-byte float
|
||||||
|
obj.quantity = buffer.readFloatLE(offset);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// Deserialize 'price' as a 4-byte float
|
||||||
|
obj.price = buffer.readFloatLE(offset);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// Deserialize 'store' as a byte
|
||||||
|
obj.store = STORE_KEYS[buffer[offset++]];
|
||||||
|
|
||||||
|
// Deserialize 'name' as UTF-8 with 2 bytes encoding the string length
|
||||||
|
const nameLength = buffer.readUInt16LE(offset);
|
||||||
|
offset += 2;
|
||||||
|
obj.name = buffer.toString("utf8", offset, offset + nameLength);
|
||||||
|
offset += nameLength;
|
||||||
|
|
||||||
|
// Deserialize 'url' as UTF-8 with 2 bytes encoding the string length (or undefined if length is 0)
|
||||||
|
const urlLength = buffer.readUInt16LE(offset);
|
||||||
|
offset += 2;
|
||||||
|
obj.url = urlLength !== 0 ? buffer.toString("utf8", offset, offset + urlLength) : undefined;
|
||||||
|
offset += urlLength;
|
||||||
|
|
||||||
|
// Deserialize 'priceHistory' array
|
||||||
|
const priceHistoryLength = buffer.readUInt16LE(offset);
|
||||||
|
offset += 2;
|
||||||
|
obj.priceHistory = [];
|
||||||
|
|
||||||
|
for (let i = 0; i < priceHistoryLength; i++) {
|
||||||
|
// Deserialize price as a 4-byte float
|
||||||
|
const price = buffer.readFloatLE(offset);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// Deserialize days as a 32-bit integer
|
||||||
|
const daysSince2000 = buffer.readInt32LE(offset);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// Calculate the date from days since 2000-01-01
|
||||||
|
const baseDate = new Date("2000-01-01");
|
||||||
|
const entryDate = new Date(baseDate.getTime() + daysSince2000 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
obj.priceHistory.push({ date: entryDate.toISOString().substring(0, 10), price });
|
||||||
|
}
|
||||||
|
|
||||||
|
objects.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
|
|
||||||
// Keep this in sync with utils.js:decompress
|
// Keep this in sync with utils.js:decompress
|
||||||
function compress(items) {
|
function compress(items) {
|
||||||
const compressed = {
|
const compressed = {
|
||||||
|
|
|
@ -13,6 +13,7 @@ function copyItemsToSite(dataDir) {
|
||||||
for (const store of analysis.STORE_KEYS) {
|
for (const store of analysis.STORE_KEYS) {
|
||||||
const storeItems = items.filter((item) => item.store === store);
|
const storeItems = items.filter((item) => item.store === store);
|
||||||
analysis.writeJSON(`site/output/data/latest-canonical.${store}.compressed.json`, storeItems, false, 0, true);
|
analysis.writeJSON(`site/output/data/latest-canonical.${store}.compressed.json`, storeItems, false, 0, true);
|
||||||
|
fs.writeFileSync(`site/output/data/latest-canonical.${store}.bin.json`, analysis.compressBinary(storeItems));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
48
site/cart.js
48
site/cart.js
|
@ -48,7 +48,7 @@ class CartHeader extends View {
|
||||||
} else {
|
} else {
|
||||||
carts.push(cart);
|
carts.push(cart);
|
||||||
}
|
}
|
||||||
// model.carts.save();
|
models.carts.save();
|
||||||
location.href = location.pathname + "?name=" + encodeURIComponent(cart.name);
|
location.href = location.pathname + "?name=" + encodeURIComponent(cart.name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -65,7 +65,7 @@ class CartHeader extends View {
|
||||||
for (const cartItem of cart.items) {
|
for (const cartItem of cart.items) {
|
||||||
link += cartItem.store + cartItem.id + ";";
|
link += cartItem.store + cartItem.id + ";";
|
||||||
}
|
}
|
||||||
elements.share.href = "cart.html?cart=" + link;
|
elements.share.href = "cart.html?cart=" + link + (this.stateToUrl ? stateToUrl() : "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,50 @@ function loadCart() {
|
||||||
cartList.classList.remove("hidden");
|
cartList.classList.remove("hidden");
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const itemsFilter = cartFilter;
|
||||||
|
const itemsList = cartList;
|
||||||
|
const itemsChart = cartList.querySelector("items-chart");
|
||||||
|
itemsList.elements.sort.value = "store-and-name";
|
||||||
|
let baseUrl = location.href.split("&")[0];
|
||||||
|
|
||||||
|
const stateToUrl = () => {
|
||||||
|
const filterState = itemsFilter.shareableState;
|
||||||
|
const listState = itemsList.shareableState;
|
||||||
|
const chartState = itemsChart.shareableState;
|
||||||
|
const chartedItems = cart.filteredItems
|
||||||
|
.filter((item) => item.chart)
|
||||||
|
.map((item) => item.store + item.id)
|
||||||
|
.join(";");
|
||||||
|
return baseUrl + "&f=" + filterState + "&l=" + listState + "&c=" + chartState + "&d=" + chartedItems;
|
||||||
|
};
|
||||||
|
cartHeader.stateToUrl = stateToUrl;
|
||||||
|
itemsFilter.addEventListener("x-change", () => {
|
||||||
|
const url = stateToUrl();
|
||||||
|
history.pushState({}, null, url);
|
||||||
|
cartHeader.render();
|
||||||
|
});
|
||||||
|
itemsList.addEventListener("x-change", () => {
|
||||||
|
const url = stateToUrl();
|
||||||
|
history.pushState({}, null, url);
|
||||||
|
cartHeader.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
const f = getQueryParameter("f");
|
||||||
|
const l = getQueryParameter("l");
|
||||||
|
const c = getQueryParameter("c");
|
||||||
|
const d = getQueryParameter("d");
|
||||||
|
|
||||||
|
if (f) itemsFilter.shareableState = f;
|
||||||
|
if (l) itemsList.shareableState = l;
|
||||||
|
if (c) itemsChart.shareableState = c;
|
||||||
|
if (d) {
|
||||||
|
cart.items.lookup = {};
|
||||||
|
for (const item of cart.items) cart.items.lookup[item.store + item.id] = item;
|
||||||
|
for (const id of d.split(";")) {
|
||||||
|
cart.items.lookup[id].chart = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
cartList.model = cartFilter.model = cart;
|
cartList.model = cartFilter.model = cart;
|
||||||
productsList.model = productsFilter.model = models.items;
|
productsList.model = productsFilter.model = models.items;
|
||||||
|
if (c || d) itemsChart.render();
|
||||||
})();
|
})();
|
||||||
|
|
|
@ -1,6 +1,78 @@
|
||||||
const { deltaTime, log } = require("../js/misc");
|
const { deltaTime, log } = require("../js/misc");
|
||||||
const { stores, STORE_KEYS } = require("./stores");
|
const { stores, STORE_KEYS } = require("./stores");
|
||||||
const { Model } = require("./model");
|
const { Model } = require("./model");
|
||||||
|
const { Settings } = require("./settings");
|
||||||
|
|
||||||
|
function decompressBinary(buffer) {
|
||||||
|
const objects = [];
|
||||||
|
let offset = 0;
|
||||||
|
const view = new DataView(buffer);
|
||||||
|
const baseDate = new Date("2000-01-01");
|
||||||
|
const textDecoder = new TextDecoder("utf-8");
|
||||||
|
|
||||||
|
while (offset < buffer.byteLength) {
|
||||||
|
const obj = {};
|
||||||
|
|
||||||
|
// Deserialize 'bio', 'isWeighted', and 'unit' from the single byte
|
||||||
|
const flagsByte = view.getUint8(offset++);
|
||||||
|
obj.bio = (flagsByte & 1) !== 0;
|
||||||
|
obj.isWeighted = (flagsByte & 2) !== 0;
|
||||||
|
obj.unit = (flagsByte & 4) !== 0 ? "ml" : (flagsByte & 8) !== 0 ? "stk" : "g";
|
||||||
|
|
||||||
|
// Deserialize 'quantity' as a 4-byte float
|
||||||
|
obj.quantity = view.getFloat32(offset, true);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// Deserialize 'price' as a 4-byte float
|
||||||
|
obj.price = view.getFloat32(offset, true);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// Deserialize 'store' as a byte
|
||||||
|
obj.store = STORE_KEYS[view.getUint8(offset++)];
|
||||||
|
|
||||||
|
// Deserialize 'name' as UTF-8 with 2 bytes encoding the string length
|
||||||
|
const nameLength = view.getUint16(offset, true);
|
||||||
|
offset += 2;
|
||||||
|
const nameBuffer = new Uint8Array(buffer, offset, nameLength);
|
||||||
|
obj.name = textDecoder.decode(nameBuffer);
|
||||||
|
offset += nameLength;
|
||||||
|
|
||||||
|
// Deserialize 'url' as UTF-8 with 2 bytes encoding the string length (or undefined if length is 0)
|
||||||
|
const urlLength = view.getUint16(offset, true);
|
||||||
|
offset += 2;
|
||||||
|
if (urlLength !== 0) {
|
||||||
|
const urlBuffer = new Uint8Array(buffer, offset, urlLength);
|
||||||
|
obj.url = textDecoder.decode(urlBuffer);
|
||||||
|
} else {
|
||||||
|
obj.url = undefined;
|
||||||
|
}
|
||||||
|
offset += urlLength;
|
||||||
|
|
||||||
|
// Deserialize 'priceHistory' array
|
||||||
|
const priceHistoryLength = view.getUint16(offset, true);
|
||||||
|
offset += 2;
|
||||||
|
obj.priceHistory = new Array(priceHistoryLength);
|
||||||
|
|
||||||
|
for (let i = 0; i < priceHistoryLength; i++) {
|
||||||
|
// Deserialize price as a 4-byte float
|
||||||
|
const price = view.getFloat32(offset, true);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// Deserialize days as a 32-bit integer
|
||||||
|
const daysSince2000 = view.getInt32(offset, true);
|
||||||
|
offset += 4;
|
||||||
|
|
||||||
|
// Calculate the date from days since 2000-01-01
|
||||||
|
const entryDate = new Date(baseDate.getTime() + daysSince2000 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
obj.priceHistory[i] = { date: entryDate.toISOString().substring(0, 10), price };
|
||||||
|
}
|
||||||
|
|
||||||
|
objects.push(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
|
|
||||||
function decompress(compressedItems) {
|
function decompress(compressedItems) {
|
||||||
const storeLookup = compressedItems.stores;
|
const storeLookup = compressedItems.stores;
|
||||||
|
@ -126,16 +198,31 @@ class Items extends Model {
|
||||||
|
|
||||||
async load() {
|
async load() {
|
||||||
let start = performance.now();
|
let start = performance.now();
|
||||||
|
const settings = new Settings();
|
||||||
const compressedItemsPerStore = [];
|
const compressedItemsPerStore = [];
|
||||||
for (const store of STORE_KEYS) {
|
for (const store of STORE_KEYS) {
|
||||||
compressedItemsPerStore.push(
|
compressedItemsPerStore.push(
|
||||||
new Promise(async (resolve) => {
|
new Promise(async (resolve) => {
|
||||||
const start = performance.now();
|
let start = performance.now();
|
||||||
try {
|
try {
|
||||||
|
const useJSON = settings.useJson;
|
||||||
|
if (useJSON) {
|
||||||
const response = await fetch(`data/latest-canonical.${store}.compressed.json`);
|
const response = await fetch(`data/latest-canonical.${store}.compressed.json`);
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
log(`Items - loading compressed items for ${store} took ${deltaTime(start)} secs`);
|
log(`Items - loading compressed items for ${store} took ${deltaTime(start)} secs`);
|
||||||
resolve(decompress(json));
|
start = performance.now();
|
||||||
|
let items = decompress(json);
|
||||||
|
log(`Items - Decompressing items for ${store} took ${deltaTime(start)} secs`);
|
||||||
|
resolve(items);
|
||||||
|
} else {
|
||||||
|
const response = await fetch(`data/latest-canonical.${store}.bin.json`);
|
||||||
|
const binary = await response.arrayBuffer();
|
||||||
|
log(`Items - loading compressed binary items for ${store} took ${deltaTime(start)} secs`);
|
||||||
|
start = performance.now();
|
||||||
|
let items = decompressBinary(binary);
|
||||||
|
log(`Items - Decompressing items for ${store} took ${deltaTime(start)} secs`);
|
||||||
|
resolve(items);
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(`Items - error while loading compressed items for ${store} ${e.message}`);
|
log(`Items - error while loading compressed items for ${store} ${e.message}`);
|
||||||
resolve([]);
|
resolve([]);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
const { STORE_KEYS, stores } = require("./stores");
|
const { STORE_KEYS, stores } = require("./stores");
|
||||||
const { Model } = require("./model");
|
const { Model } = require("./model");
|
||||||
|
|
||||||
export class Settings extends Model {
|
class Settings extends Model {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
this.startDate = "2017-01-01";
|
this.startDate = "2017-01-01";
|
||||||
|
@ -9,6 +9,7 @@ export class Settings extends Model {
|
||||||
STORE_KEYS.forEach((store) => {
|
STORE_KEYS.forEach((store) => {
|
||||||
this[store] = stores[store].defaultChecked;
|
this[store] = stores[store].defaultChecked;
|
||||||
});
|
});
|
||||||
|
this.jsonData = true;
|
||||||
|
|
||||||
let settings = localStorage.getItem("settings");
|
let settings = localStorage.getItem("settings");
|
||||||
if (settings) {
|
if (settings) {
|
||||||
|
@ -30,3 +31,5 @@ export class Settings extends Model {
|
||||||
localStorage.setItem("settings", JSON.stringify(settings));
|
localStorage.setItem("settings", JSON.stringify(settings));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
exports.Settings = Settings;
|
||||||
|
|
|
@ -37,6 +37,8 @@ class SettingsView extends View {
|
||||||
<option value="lines">Linien</option>
|
<option value="lines">Linien</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
<custom-checkbox x-id="useJson" x-change x-state checked label="Daten als JSON downloaden">
|
||||||
|
</custom-checkbox>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
this.setupEventHandlers();
|
this.setupEventHandlers();
|
||||||
|
|
|
@ -12,6 +12,7 @@ class ItemsChart extends View {
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
this.unitPrice = false;
|
||||||
this.innerHTML = /*html*/ `
|
this.innerHTML = /*html*/ `
|
||||||
<div class="bg-stone-200 p-4 mx-auto">
|
<div class="bg-stone-200 p-4 mx-auto">
|
||||||
<div class="w-full h-[calc(100vw*0.66)] md:h-[calc(100vw*0.5)] lg:h-[calc(100vw*0.30)]" style="position: relative;">
|
<div class="w-full h-[calc(100vw*0.66)] md:h-[calc(100vw*0.5)] lg:h-[calc(100vw*0.30)]" style="position: relative;">
|
||||||
|
@ -41,9 +42,11 @@ class ItemsChart extends View {
|
||||||
calculateOverallPriceChanges(items, onlyToday, startDate, endDate) {
|
calculateOverallPriceChanges(items, onlyToday, startDate, endDate) {
|
||||||
if (items.length == 0) return { dates: [], changes: [] };
|
if (items.length == 0) return { dates: [], changes: [] };
|
||||||
|
|
||||||
|
const getPrice = this.unitPrice ? (o) => o.unitPrice : (o) => o.price;
|
||||||
|
|
||||||
if (onlyToday) {
|
if (onlyToday) {
|
||||||
let sum = 0;
|
let sum = 0;
|
||||||
for (const item of items) sum += item.price;
|
for (const item of items) sum += getPrice(item);
|
||||||
return [{ date: today(), price: sum }];
|
return [{ date: today(), price: sum }];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,8 +70,8 @@ class ItemsChart extends View {
|
||||||
}
|
}
|
||||||
for (let i = 0; i < uniqueDates.length; i++) {
|
for (let i = 0; i < uniqueDates.length; i++) {
|
||||||
const priceObj = product.priceHistoryLookup[uniqueDates[i]];
|
const priceObj = product.priceHistoryLookup[uniqueDates[i]];
|
||||||
if (!price && priceObj) price = priceObj.price;
|
if (!price && priceObj) price = getPrice(priceObj);
|
||||||
priceScratch[i] = priceObj ? priceObj.price : null;
|
priceScratch[i] = priceObj ? getPrice(priceObj) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (let i = 0; i < priceScratch.length; i++) {
|
for (let i = 0; i < priceScratch.length; i++) {
|
||||||
|
@ -90,6 +93,7 @@ class ItemsChart extends View {
|
||||||
}
|
}
|
||||||
|
|
||||||
renderChart(items, chartType) {
|
renderChart(items, chartType) {
|
||||||
|
const getPrice = this.unitPrice ? (o) => o.unitPrice : (o) => o.price;
|
||||||
const canvasDom = this.elements.canvas;
|
const canvasDom = this.elements.canvas;
|
||||||
const noData = this.elements.noData;
|
const noData = this.elements.noData;
|
||||||
if (items.length === 0) {
|
if (items.length === 0) {
|
||||||
|
@ -113,7 +117,7 @@ class ItemsChart extends View {
|
||||||
data: prices.map((price) => {
|
data: prices.map((price) => {
|
||||||
return {
|
return {
|
||||||
x: moment(price.date),
|
x: moment(price.date),
|
||||||
y: price.price,
|
y: getPrice(price),
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
|
@ -212,11 +216,12 @@ class ItemsChart extends View {
|
||||||
log("ItemsChart - Calculating overall sum per store took " + ((performance.now() - now) / 1000).toFixed(2) + " secs");
|
log("ItemsChart - Calculating overall sum per store took " + ((performance.now() - now) / 1000).toFixed(2) + " secs");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getPrice = this.unitPrice ? (o) => o.unitPrice : (o) => o.price;
|
||||||
items.forEach((item) => {
|
items.forEach((item) => {
|
||||||
if (item.chart) {
|
if (item.chart) {
|
||||||
const chartItem = {
|
const chartItem = {
|
||||||
name: item.store + " " + item.name,
|
name: item.store + " " + item.name,
|
||||||
priceHistory: onlyToday ? [{ date: today(), price: item.price }] : item.priceHistory,
|
priceHistory: onlyToday ? [{ date: today(), price: getPrice(item) }] : item.priceHistory,
|
||||||
};
|
};
|
||||||
itemsToShow.push(chartItem);
|
itemsToShow.push(chartItem);
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,7 +129,10 @@ class ItemsFilter extends View {
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
const elements = this.elements;
|
const elements = this.elements;
|
||||||
this.model.totalItems = this.model.items.length;
|
this.model.totalItems = this.model.items.length;
|
||||||
let filteredItems = [...this.model.items];
|
let filteredItems = new Array(this.model.items.length);
|
||||||
|
for (let i = 0; i < this.model.items.length; i++) {
|
||||||
|
filteredItems[i] = this.model.items[i];
|
||||||
|
}
|
||||||
let query = elements.query.value.trim();
|
let query = elements.query.value.trim();
|
||||||
if (query.length == 0 && this._emptyQuery) {
|
if (query.length == 0 && this._emptyQuery) {
|
||||||
this.model.removeListener(this._listener);
|
this.model.removeListener(this._listener);
|
||||||
|
@ -189,8 +192,7 @@ class ItemsFilter extends View {
|
||||||
// Don't apply store and misc filters if query is an alasql query.
|
// Don't apply store and misc filters if query is an alasql query.
|
||||||
if (query.charAt(0) != "!") {
|
if (query.charAt(0) != "!") {
|
||||||
if (this._filterByStores) {
|
if (this._filterByStores) {
|
||||||
const checkedStores = this.checkedStores;
|
filteredItems = filteredItems.filter((item) => elements[item.store].checked);
|
||||||
filteredItems = filteredItems.filter((item) => checkedStores.includes(item.store));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._filterByMisc) {
|
if (this._filterByMisc) {
|
||||||
|
@ -228,6 +230,7 @@ class ItemsFilter extends View {
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
const elements = this.elements;
|
const elements = this.elements;
|
||||||
const items = this.model.items;
|
const items = this.model.items;
|
||||||
|
if (this._filterByPriceChanges) {
|
||||||
const dates = {};
|
const dates = {};
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.priceHistory.length == 1) continue;
|
if (item.priceHistory.length == 1) continue;
|
||||||
|
@ -251,12 +254,9 @@ class ItemsFilter extends View {
|
||||||
dateDom.innerText = `${date} (${dates[date]})`;
|
dateDom.innerText = `${date} (${dates[date]})`;
|
||||||
priceChangesDates.append(dateDom);
|
priceChangesDates.append(dateDom);
|
||||||
}
|
}
|
||||||
|
|
||||||
log(`ItemsFilter - rendering items filter took ${deltaTime(start)}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get checkedStores() {
|
log(`ItemsFilter - rendering items filter took ${deltaTime(start)}`);
|
||||||
return STORE_KEYS.filter((store) => this.elements[store].checked);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get shareableState() {
|
get shareableState() {
|
||||||
|
|
|
@ -5,6 +5,8 @@ const { View } = require("./view");
|
||||||
const { ItemsChart } = require("./items-chart");
|
const { ItemsChart } = require("./items-chart");
|
||||||
|
|
||||||
class ItemsList extends View {
|
class ItemsList extends View {
|
||||||
|
static priceTypeId = 0;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -27,8 +29,10 @@ class ItemsList extends View {
|
||||||
<custom-checkbox x-id="enableChart" x-change x-state label="Diagramm" class="${
|
<custom-checkbox x-id="enableChart" x-change x-state label="Diagramm" class="${
|
||||||
this._chart ? "" : "hidden"
|
this._chart ? "" : "hidden"
|
||||||
}"></custom-checkbox>
|
}"></custom-checkbox>
|
||||||
<label><input x-id="salesPrice" x-change x-state type="radio" name="priceType" checked> Verkaufspreis</label>
|
<label><input x-id="salesPrice" x-change x-state type="radio" name="priceType${
|
||||||
<label><input x-id="unitPrice" x-change x-state type="radio" name="priceType"> Mengenpreis</label>
|
ItemsList.priceTypeId
|
||||||
|
}" checked> Verkaufspreis</label>
|
||||||
|
<label><input x-id="unitPrice" x-change x-state type="radio" name="priceType${ItemsList.priceTypeId++}"> Mengenpreis</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<label class="${hideSort}">
|
<label class="${hideSort}">
|
||||||
|
@ -83,6 +87,18 @@ class ItemsList extends View {
|
||||||
elements.tableBody.querySelectorAll(".priceinfo").forEach((el) => (showAll ? el.classList.remove("hidden") : el.classList.add("hidden")));
|
elements.tableBody.querySelectorAll(".priceinfo").forEach((el) => (showAll ? el.classList.remove("hidden") : el.classList.add("hidden")));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
elements.chart.unitPrice = elements.unitPrice.checked;
|
||||||
|
|
||||||
|
elements.unitPrice.addEventListener("change", () => {
|
||||||
|
elements.chart.unitPrice = elements.unitPrice.checked;
|
||||||
|
elements.chart.render();
|
||||||
|
});
|
||||||
|
|
||||||
|
elements.salesPrice.addEventListener("change", () => {
|
||||||
|
elements.chart.unitPrice = elements.unitPrice.checked;
|
||||||
|
elements.chart.render();
|
||||||
|
});
|
||||||
|
|
||||||
this.setupEventHandlers();
|
this.setupEventHandlers();
|
||||||
|
|
||||||
this.addEventListener("x-change", (event) => {
|
this.addEventListener("x-change", (event) => {
|
||||||
|
@ -369,6 +385,7 @@ class ItemsList extends View {
|
||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
const elements = this.elements;
|
const elements = this.elements;
|
||||||
if (!this.model) return;
|
if (!this.model) return;
|
||||||
|
elements.chart.unitPrice = elements.unitPrice.checked;
|
||||||
if (this.model.filteredItems.length != 0 && this.model.filteredItems.length <= (isMobile() ? 200 : 1500)) {
|
if (this.model.filteredItems.length != 0 && this.model.filteredItems.length <= (isMobile() ? 200 : 1500)) {
|
||||||
elements.nameSimilarity.removeAttribute("disabled");
|
elements.nameSimilarity.removeAttribute("disabled");
|
||||||
} else {
|
} else {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user