diff --git a/.gitignore b/.gitignore index 697c927..e30cd42 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ dist heissepreise.zip site/latest-canonical.json latest-canonical.json +access.log +billa-2020.csv +report.html +spar-2020.csv diff --git a/package-lock.json b/package-lock.json index e0788c8..e7e5bd7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "axios": "^1.4.0", "compression": "^1.7.4", "express": "^4.18.2", + "n-readlines": "^1.0.1", "nodemon": "^2.0.22" }, "bin": { @@ -1441,6 +1442,14 @@ "node": ">= 6" } }, + "node_modules/n-readlines": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/n-readlines/-/n-readlines-1.0.1.tgz", + "integrity": "sha512-z4SyAIVgMy7CkgsoNw7YVz40v0g4+WWvvqy8+ZdHrCtgevcEO758WQyrYcw3XPxcLxF+//RszTz/rO48nzD0wQ==", + "engines": { + "node": ">=6.x.x" + } + }, "node_modules/napi-build-utils": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz", diff --git a/package.json b/package.json index 83add59..5043010 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "axios": "^1.4.0", "compression": "^1.7.4", "express": "^4.18.2", + "n-readlines": "^1.0.1", "nodemon": "^2.0.22" }, "devDependencies": { diff --git a/site/cart.html b/site/cart.html index 202e2e2..39438d0 100644 --- a/site/cart.html +++ b/site/cart.html @@ -11,12 +11,12 @@
+

Heisse Preise

Produktsuche Tagespreisänderungen Warenkörbe
-

Heisse Preise

diff --git a/site/carts.html b/site/carts.html index e662815..11476de 100644 --- a/site/carts.html +++ b/site/carts.html @@ -11,15 +11,13 @@
+

Heisse Preise

-

Heisse Preise

-
- -
+
diff --git a/site/changes.html b/site/changes.html index 569f489..6fd924d 100644 --- a/site/changes.html +++ b/site/changes.html @@ -11,12 +11,12 @@
+

Heisse Preise

-

Heisse Preise

diff --git a/site/index.html b/site/index.html index 84ebfda..858df8d 100644 --- a/site/index.html +++ b/site/index.html @@ -11,12 +11,12 @@
+

Heisse Preise

-

Heisse Preise

diff --git a/site/style.css b/site/style.css index 6b75dba..71e01ee 100644 --- a/site/style.css +++ b/site/style.css @@ -1,11 +1,17 @@ * { - box-sizing: border-box; + box-sizing: border-box; } body { margin: 0; padding: 1em; - font-family: sans-serif; + font-family: Helvetica; + background: #f5f5f5; + color: #393939; +} + +a { + color: #c9543a; } .column { @@ -15,6 +21,7 @@ body { align-items: center; margin: 0 auto; width: 100%; + max-width: 1000px; } .search { @@ -28,33 +35,97 @@ body { margin-bottom: 1em; } +input { + border: 2px solid; + padding: 0.4em; + border-color: #ee907b; + accent-color: #c9543a; +} + input[type="number"] { - max-width: 3em; + max-width: 6em; + border: 2px solid; + padding: 0.4em; + border-color: #ee907b; +} + +h2 { + background-color: #c9543a; + color: white; + width: 100%; + text-transform: uppercase; + text-align: center; + padding: 0.5em; + margin-top: 0; } table { border-collapse: collapse; - border: 1px solid; + border: 0px; margin-top: 1em; } th { - border: 1px solid; padding: 0.2em; - background: #aaa; + text-align: left; + background: #c9543a; + color: white; + border: 0px; +} + +th:nth-child(1) { + text-align: center; +} + +th:nth-child(3) { + text-align: right; + padding: 0.7em; +} + +th:nth-child(4) { + text-align: center; + padding: 0.6em; } tr { border-collapse: collapse; border: 1px solid #ddd; + border-left: 0px; + border-right: 0px; } td { padding: 0.2em; } -th { - text-align: left; +td a { + color: #393939; + text-decoration: none; +} + +td:nth-child(1) { + text-align: center; + padding: 0.5em; +} + +td:nth-child(2) { + background-color: white; + padding: 0.4em; +} + +td:nth-child(3) { + background-color: white; + font-size: small; + text-align: right; + padding-right: 0.7em; +} + +td:nth-child(4) { + padding-left: 0.5em; +} + +td:nth-child(4):before { + content: "€ " } .itemname { @@ -88,51 +159,67 @@ th { @media screen and (max-width: 600px) { table { - border: 0; + border: 0; } table thead { - border: none; - clip: rect(0 0 0 0); - height: 1px; - margin: -1px; - overflow: hidden; - padding: 0; - position: absolute; - width: 1px; + border: none; + clip: rect(0 0 0 0); + height: 1px; + margin: -1px; + overflow: hidden; + padding: 0; + position: absolute; + width: 1px; } table tr { - display: block; - margin-bottom: .625em; + display: block; + margin-bottom: .625em; } table td { - border-bottom: 1px solid #ddd; - padding-left: calc(65px + 1.2em); - position: relative; - display: block; + border-bottom: 1px solid #ddd; + padding: 0 0 0 0 !important; + padding-left: calc(65px + 1.2em) !important; + position: relative; + display: block; } table td::before { - /* - * aria-label has no advantage, it won't be read inside a table - content: attr(aria-label); - */ - content: attr(data-label); - background-color: #aaa; - border: 1px solid; - position: absolute; - padding: .2em .4em; - width: 65px; - height: calc(100% - .4em); - left: -1px; - top: -1px; - text-align: right; - font-weight: bold; + /* + * aria-label has no advantage, it won't be read inside a table + content: attr(aria-label); + */ + content: attr(data-label); + background-color: #c9543a; + color: white; + border: 0px; + position: absolute; + padding: .1em .4em; + width: 65px; + height: calc(100%); + left: -1px; + top: -1px; + text-align: right; + font-weight: bold; + font-size: medium; + } + + td:nth-child(1) { + text-align: center; + font-size: medium; + padding: 0.5em; + } + + td:nth-child(3) { + background-color: white; + font-size: medium; + text-align: left; + padding-right: 0.7em; } table td:last-child { - border-bottom: 0; + border-bottom: 0; } - } \ No newline at end of file +} \ No newline at end of file diff --git a/site/utils.js b/site/utils.js index 0d60321..cf03b68 100644 --- a/site/utils.js +++ b/site/utils.js @@ -375,7 +375,15 @@ function showChart(canvasDom, items) { }, options: { responsive: true, - aspectRation: 16 / 9 + aspectRation: 16 / 9, + scales: { + y: { + title: { + display: true, + text: "EURO" + } + } + } } }); } diff --git a/stores/spar.js b/stores/spar.js index 469145a..cda8502 100644 --- a/stores/spar.js +++ b/stores/spar.js @@ -14,6 +14,7 @@ exports.getCanonical = function(item, today) { } return { id: item.masterValues["code-internal"], + sparId: item.masterValues["product-number"], name: item.masterValues.title + " " + item.masterValues["short-description"], price, priceHistory: [{ date: today, price }], diff --git a/stuff.js b/stuff.js index df963ef..a4c5043 100644 --- a/stuff.js +++ b/stuff.js @@ -150,4 +150,81 @@ function fixSparHistoricalData(dataDir) { } } -// momentumCartConversion(); \ No newline at end of file +const nReadlines = require('n-readlines'); + +function convertDossierData(dataDir, file) { + console.log(`Converting ${file}`); + const lookup = {}; + for (item of JSON.parse(fs.readFileSync(`${dataDir}/latest-canonical.json`))) { + lookup[item.store + item.id] = item; + if (item.sparId) + lookup[item.store + "-" + item.sparId] = item; + } + + const lines = new nReadlines(file); + + const itemsPerDate = {}; + let line = null; + const store = file.indexOf("spar") == 0 ? "spar" : "billa"; + lines.next() + let itemsTotal = 0; + let notFound = 0; + while(line = lines.next()) { + itemsTotal++; + const tokens = line.toString("utf-8").split(";"); + const dateTokens = tokens[0].split("."); + const date = "20" + dateTokens[2] + "-" + dateTokens[1] + "-" + dateTokens[0]; + const producer = tokens[5]; + const name = tokens[3]; + const unit = tokens[6]; + const price = Number.parseFloat(tokens[7].replace("€", "").trim().replace(",", ".")); + const id = tokens[4].replace("ARTIKELNUMMER: ", "").replace("Art. Nr.: ", ""); + let item = lookup[store + id]; + if (!item) + item = lookup[store + "-" + id] + if (!item) { + // console.log("Couldn't find item " + name); + notFound++; + continue; + } + let items = itemsPerDate[date]; + if (!items) itemsPerDate[date] = items = []; + if (store == "spar") { + items.push({ + masterValues: { + "code-internal": item.id, + "product-number": id, + price, + title: producer, + "short-description": name, + "short-description-3": unit, + bioLevel: "" + } + }); + } else { + items.push({ + data: { + articleId: id, + name: name, + price: { + final: price + }, + grammagePriceFactor: 1, + grammage: unit, + } + }) + } + } + console.log("total: " + itemsTotal); + console.log("not found: " + notFound); + + const dates = Object.keys(itemsPerDate).sort((a, b) => b.localeCompare(a)); + for (date of dates) { + fs.writeFileSync(`${dataDir}/${store}-${date}.json`, JSON.stringify(itemsPerDate[date], null, 2)); + } + console.log(`Wrote files for ${file}`); +} +// momentumCartConversion(); + +convertDossierData("data", "spar-2020.csv"); +convertDossierData("data", "billa-2020.csv"); \ No newline at end of file