Selaa lähdekoodia

historicke grafy

marek 1 kuukausi sitten
vanhempi
sitoutus
8b613113ce

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 0 - 1461
assets/css/charts.css


+ 28 - 6
assets/css/style.css

@@ -76,7 +76,7 @@ body {
     margin: 0.5em 0;
 }
 
-p {
+p, a, h2 {
     display: flex;
     align-items: center;
     gap: 0.5em;
@@ -113,7 +113,7 @@ input, button {
     font-size: 1em;
 }
 
-[role="button"], button {
+[role="button"], button, select, input {
     background-color: rgba(0, 0, 0, 0.1);
     border-radius: 0.5em;
     padding: 0.5em 1em;
@@ -125,12 +125,11 @@ input, button {
     display: flex;
     align-items: center;
     gap: 0.5em;
+    font-size: 1em;
+    height: max-content;
 }
 
-input {
-    background-color: rgba(0, 0, 0, 0.1);
-    border-radius: 0.5em;
-    padding: 0.3em 1em;
+input, select {
     border: 2px solid rgba(0, 0, 0);
 }
 
@@ -138,4 +137,27 @@ form {
     display: flex;
     flex-direction: column;
     gap: 1em;
+}
+
+.historyLinks {
+    display: flex;
+    gap: 1em;
+    flex-wrap: wrap;
+    margin-block: 1em;
+    align-items: center;
+    justify-content: center;
+}
+
+#historyForm {
+    flex-direction: row;
+    flex-wrap: wrap;
+    align-items: flex-end;
+}
+
+.container-row {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 1em;
+    align-items: center;
+    margin-block: 1em;
 }

+ 67 - 0
lang/en.js

@@ -1,7 +1,74 @@
+const icons = {
+    tempIcon: () => `
+        <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0" />
+        </svg>
+    `,
+
+    pressureIcon: () => `
+        <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m12 14l4-4M3.34 19a10 10 0 1 1 17.32 0" />
+        </svg>
+    `,
+
+    humidityIcon: () => `
+        <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5S5 13 5 15a7 7 0 0 0 7 7" />
+        </svg>
+    `,
+
+    altitudeIcon: () => `
+        <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+            <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+                <circle cx="12" cy="12" r="10" />
+                <path d="m16 12l-4-4l-4 4m4 4V8" />
+            </g>
+        </svg>
+    `,
+
+    bluetoothConnectionIcon: () => `
+        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m7 7l10 10l-5 5V2l5 5L7 17m11-5h3M3 12h3" />
+        </svg>
+    `
+}
+
 export default {
     settings: {
         errors: {
             noCloudURL: () => `you need to provide a cloud url.`
         }
+    },
+
+    history: {
+        properties: {
+            indoorTemp: () => `${icons.tempIcon()} indoor temperature`,
+            indoorPressure: () => `${icons.pressureIcon()} indoor pressure`,
+            indoorHumidity: () => `${icons.humidityIcon()} indoor humidity`,
+            indoorAltitude: () => `${icons.altitudeIcon()} indoor altitude`,
+
+            outdoorConnected: () => `${icons.bluetoothConnectionIcon()} external unit connection`,
+            outdoorTemp: () => `${icons.tempIcon()} outdoor temperature`,
+            outdoorPressure: () => `${icons.pressureIcon()} outdoor pressure`,
+            outdoorHumidity: () => `${icons.humidityIcon()} outdoor humidity`,
+            outdoorAltitude: () => `${icons.altitudeIcon()} outdoor altitude`,
+        },
+
+        dateFormats: {            
+            months: {
+                [`01`]: () => `January`,
+                [`02`]: () => `February`,
+                [`03`]: () => `March`,
+                [`04`]: () => `April`,
+                [`05`]: () => `May`,
+                [`06`]: () => `June`,
+                [`07`]: () => `July`,
+                [`08`]: () => `August`,
+                [`09`]: () => `September`,
+                [`10`]: () => `October`,
+                [`11`]: () => `November`,
+                [`12`]: () => `December`,
+            }
+        }
     }
 }

+ 67 - 0
lang/sk.js

@@ -1,7 +1,74 @@
+const icons = {
+    tempIcon: () => `
+        <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 4v10.54a4 4 0 1 1-4 0V4a2 2 0 0 1 4 0" />
+        </svg>
+    `,
+
+    pressureIcon: () => `
+        <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m12 14l4-4M3.34 19a10 10 0 1 1 17.32 0" />
+        </svg>
+    `,
+
+    humidityIcon: () => `
+        <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 22a7 7 0 0 0 7-7c0-2-1-3.9-3-5.5s-3.5-4-4-6.5c-.5 2.5-2 4.9-4 6.5S5 13 5 15a7 7 0 0 0 7 7" />
+        </svg>
+    `,
+
+    altitudeIcon: () => `
+        <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+            <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+                <circle cx="12" cy="12" r="10" />
+                <path d="m16 12l-4-4l-4 4m4 4V8" />
+            </g>
+        </svg>
+    `,
+
+    bluetoothConnectionIcon: () => `
+        <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+            <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m7 7l10 10l-5 5V2l5 5L7 17m11-5h3M3 12h3" />
+        </svg>
+    `
+}
+
 export default {
     settings: {
         errors: {
             noCloudURL: () => `musíte zadať cloud url.`
         }
+    },
+
+    history: {
+        properties: {
+            indoorTemp: () => `${icons.tempIcon()} vnutorná teplota`,
+            indoorPressure: () => `${icons.pressureIcon()} vnutorný tlak`,
+            indoorHumidity: () => `${icons.humidityIcon()} vnutorná vlhkosť`,
+            indoorAltitude: () => `${icons.altitudeIcon()} vnutorná nadmorská výška`,
+
+            outdoorConnected: () => `${icons.bluetoothConnectionIcon()} pripojenie externej jednotky`,
+            outdoorTemp: () => `${icons.tempIcon()} vonkajšia teplota`,
+            outdoorPressure: () => `${icons.pressureIcon()} vonkajší tlak`,
+            outdoorHumidity: () => `${icons.humidityIcon()} vonkajšia vlhkosť`,
+            outdoorAltitude: () => `${icons.altitudeIcon()} vonkajšia nadmorská výška`,
+        },
+
+        dateFormats: {
+            months: {
+                [`01`]: () => `január`,
+                [`02`]: () => `február`,
+                [`03`]: () => `marec`,
+                [`04`]: () => `apríl`,
+                [`05`]: () => `máj`,
+                [`06`]: () => `jún`,
+                [`07`]: () => `júl`,
+                [`08`]: () => `august`,
+                [`09`]: () => `september`,
+                [`10`]: () => `október`,
+                [`11`]: () => `november`,
+                [`12`]: () => `december`,
+            }
+        }
     }
 }

+ 49 - 14
routes/include/history.js

@@ -7,10 +7,20 @@ const eta = new Eta({ views: "./templates" })
 export default (langName, lang) => new Elysia({ prefix: `/history` })
   .get('/', ({ set }) => {
     set.headers['content-type'] = 'text/html; charset=utf8'
-    return eta.render(`${langName}/history/index`, {  })
+    return eta.render(`${langName}/history/index`, { lang })
   })
-  .get(`/:property`, ({ params: { property }, set }) => {
-    const data = Meteostanica.getDataPropertyMonthly(property, `2026-03`)
+  .get(`/:property`, ({ params: { property }, query: { day, month, year }, set }) => {
+    const dateMap = Meteostanica.getDateMap()
+    
+    const years = Object.keys(dateMap)
+    const months = Object.keys(dateMap[years[years.length - 1]])
+    const days = dateMap[years[years.length - 1]][months[months.length - 1]]
+
+    const lastYear = years[years.length - 1]
+    const lastMonth = months[months.length - 1]
+    const lastDay = days[days.length - 1]
+
+    const data = Meteostanica.getDataPropertyDaily(property, `${year ?? lastYear}-${month ?? lastMonth}-${day ?? lastDay}`)
 
     if (!data) {
         set.headers['content-type'] = 'text/html; charset=utf8'
@@ -18,10 +28,20 @@ export default (langName, lang) => new Elysia({ prefix: `/history` })
     }
 
     set.headers['content-type'] = 'text/html; charset=utf8'
-    return eta.render(`${langName}/history/property`, { lang, property, data })
+    return eta.render(`${langName}/history/property`, { lang, dateMap: { years, months, days }, type: `daily`, property, data })
   })
-  .get(`/:property/daily`, ({ params: { property }, set }) => {
-    const data = Meteostanica.getDataPropertyDaily(property, `2026-03-01`)
+  .get(`/:property/daily`, ({ params: { property }, query: { day, month, year }, set }) => {
+    const dateMap = Meteostanica.getDateMap()
+    
+    const years = Object.keys(dateMap)
+    const months = Object.keys(dateMap[years[years.length - 1]])
+    const days = dateMap[years[years.length - 1]][months[months.length - 1]]
+
+    const lastYear = years[years.length - 1]
+    const lastMonth = months[months.length - 1]
+    const lastDay = days[days.length - 1]
+
+    const data = Meteostanica.getDataPropertyDaily(property, `${year ?? lastYear}-${month ?? lastMonth}-${day ?? lastDay}`)
 
     if (!data) {
         set.headers['content-type'] = 'text/html; charset=utf8'
@@ -29,10 +49,18 @@ export default (langName, lang) => new Elysia({ prefix: `/history` })
     }
 
     set.headers['content-type'] = 'text/html; charset=utf8'
-    return eta.render(`${langName}/history/property`, { lang, type: `daily`, property, data })
+    return eta.render(`${langName}/history/property`, { lang, dateMap: { years, months, days }, type: `daily`, property, data })
   })
-  .get(`/:property/monthly`, ({ params: { property }, set }) => {
-    const data = Meteostanica.getDataPropertyMonthly(property, `2026-03`)
+  .get(`/:property/monthly`, ({ params: { property }, query: { month, year }, set }) => {    
+    const dateMap = Meteostanica.getDateMap()
+    
+    const years = Object.keys(dateMap)
+    const months = Object.keys(dateMap[years[years.length - 1]])
+
+    const lastYear = years[years.length - 1]
+    const lastMonth = months[months.length - 1]
+
+    const data = Meteostanica.getDataPropertyMonthly(property, `${year ?? lastYear}-${month ?? lastMonth}`)
 
     if (!data) {
         set.headers['content-type'] = 'text/html; charset=utf8'
@@ -40,10 +68,16 @@ export default (langName, lang) => new Elysia({ prefix: `/history` })
     }
 
     set.headers['content-type'] = 'text/html; charset=utf8'
-    return eta.render(`${langName}/history/property`, { lang, type: `monthly`, property, data })
+    return eta.render(`${langName}/history/property`, { lang, dateMap: { years, months }, type: `monthly`, property, data })
   })
-  .get(`/:property/yearly`, ({ params: { property }, set }) => {
-    const data = Meteostanica.getDataPropertyYearly(property, `2026`)
+  .get(`/:property/yearly`, ({ params: { property }, query: { year }, set }) => {
+    const dateMap = Meteostanica.getDateMap()
+    
+    const years = Object.keys(dateMap)
+
+    const lastYear = years[years.length - 1]
+
+    const data = Meteostanica.getDataPropertyYearly(property, year ?? lastYear)
 
     if (!data) {
         set.headers['content-type'] = 'text/html; charset=utf8'
@@ -51,10 +85,11 @@ export default (langName, lang) => new Elysia({ prefix: `/history` })
     }
 
     set.headers['content-type'] = 'text/html; charset=utf8'
-    return eta.render(`${langName}/history/property`, { lang, type: `yearly`, property, data })
+    return eta.render(`${langName}/history/property`, { lang, dateMap: { years }, type: `yearly`, property, data })
   })
   .get(`/:property/allTime`, ({ params: { property }, set }) => {
     const data = Meteostanica.getDataPropertyAllTime(property)
+    const dateMap = Meteostanica.getDateMap()
 
     if (!data) {
         set.headers['content-type'] = 'text/html; charset=utf8'
@@ -62,5 +97,5 @@ export default (langName, lang) => new Elysia({ prefix: `/history` })
     }
 
     set.headers['content-type'] = 'text/html; charset=utf8'
-    return eta.render(`${langName}/history/property`, { lang, type: `allTime`, property, data })
+    return eta.render(`${langName}/history/property`, { lang, dateMap, type: `allTime`, property, data })
   })

+ 33 - 0
templates/en/history/index.eta

@@ -0,0 +1,33 @@
+<% layout("/en/history/layout", { title: `history` }) %>
+
+<%~ include("/en/partials/topbar") %>
+
+<% const historyIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+            <path d="M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8" />
+            <path d="M3 3v5h5m4-1v5l4 2" />
+        </g>
+    </svg>
+` %>
+
+<h2><%~ historyIcon %> history</h2>
+
+<h3>indoor</h3>
+
+<div class="container-row">
+    <a role="button" href="/en/history/indoorTemp"><%~ it?.lang?.history.properties?.indoorTemp() %></a>
+    <a role="button" href="/en/history/indoorPressure"><%~ it?.lang?.history.properties?.indoorPressure() %></a>
+    <a role="button" href="/en/history/indoorHumidity"><%~ it?.lang?.history.properties?.indoorHumidity() %></a>
+    <a role="button" href="/en/history/indoorAltitude"><%~ it?.lang?.history.properties?.indoorAltitude() %></a>
+</div>
+
+<h3>outdoor</h3>
+
+<div class="container-row">
+    <a role="button" href="/en/history/outdoorConnected"><%~ it?.lang?.history.properties?.outdoorConnected() %></a>
+    <a role="button" href="/en/history/outdoorTemp"><%~ it?.lang?.history.properties?.outdoorTemp() %></a>
+    <a role="button" href="/en/history/outdoorPressure"><%~ it?.lang?.history.properties?.outdoorPressure() %></a>
+    <a role="button" href="/en/history/outdoorHumidity"><%~ it?.lang?.history.properties?.outdoorHumidity() %></a>
+    <a role="button" href="/en/history/outdoorAltitude"><%~ it?.lang?.history.properties?.outdoorAltitude() %></a>
+</div>

+ 14 - 0
templates/en/history/layout.eta

@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title><%= it.title ? `${it.title} — meteostanica` : "meteostanica" %></title>
+    <link rel="stylesheet" href="/assets/css/style.css">
+</head>
+<body>
+    <script src="/assets/js/chart.umd.js"></script>
+    
+    <%~ it.body %>
+</body>
+</html>

+ 14 - 0
templates/en/history/notFound.eta

@@ -0,0 +1,14 @@
+<% layout("/en/history/layout", { title: `property not found` }) %>
+
+<%~ include("/en/partials/topbar") %>
+
+<% const backIcon = `
+    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m12 19l-7-7l7-7m7 7H5" />
+    </svg>
+` %>
+
+<div class="container-row">
+    <a role="button" href="/en/history"><%~ backIcon %> history</a>
+    <h2>property not found</h2>
+</div>

+ 99 - 0
templates/en/history/property.eta

@@ -0,0 +1,99 @@
+<% layout("/en/history/layout", { title: `history` }) %>
+
+<%~ include("/en/partials/topbar") %>
+
+<% const backIcon = `
+    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m12 19l-7-7l7-7m7 7H5" />
+    </svg>
+` %>
+
+<div class="container-row">
+    <a role="button" href="/en/history"><%~ backIcon %> history</a>
+    <h2><%~ it?.lang?.history.properties?.[it?.property]() %></h2>
+</div>
+
+<div class="historyLinks">
+    <a role="button" href="/en/history/<%= it.property %>/daily">day</a>
+    <a role="button" href="/en/history/<%= it.property %>/monthly">month</a>
+    <a role="button" href="/en/history/<%= it.property %>/yearly">year</a>
+    <a role="button" href="/en/history/<%= it.property %>/allTime">all time</a>
+</div>
+
+<% if (it?.dateMap?.years) { %>
+    <form id="historyForm">
+        <% if (it?.dateMap?.days) { %>
+            <div>
+            <label for="day">day</label>
+            <select id="day" name="day">
+                <% for (const item of it?.dateMap?.days) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.days[it?.dateMap?.days.length - 1] ? `selected` : `` %> ><%= item %></option>
+                <% } %>
+            </select>
+            </div>
+        <% } %>
+
+        <% if (it?.dateMap?.months) { %>
+            <div>
+            <label for="month">month</label>
+            <select id="month" name="month">
+                <% for (const item of it?.dateMap?.months) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.months[it?.dateMap?.months.length - 1] ? `selected` : `` %> > <%= it?.lang?.history.dateFormats.months?.[item]() %></option>
+                <% } %>
+            </select>
+            </div>
+        <% } %>
+
+        <div>
+            <label for="year">year</label>
+            <select id="year" name="year">
+                <% for (const item of it?.dateMap?.years) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.years[it?.dateMap?.years.length - 1] ? `selected` : `` %> > <%= item %></option>
+                <% } %>
+            </select>
+        </div>
+
+        <button type="submit">load</button>
+    </form>
+<% } %>
+
+<% const time = [] %>
+<% const data = [] %>
+
+<% for (const item of it.data) { %>
+    <% time.push(item.timeMark) %>
+    
+    <% if (it.property === "outdoorConnected") {%>
+        <% data.push(item.value) %>
+        <% continue %>
+    <% } %>
+
+    <% data.push(item.value / 100) %>
+<% } %>
+
+<div>
+    <canvas id="historyChart"></canvas>
+</div>
+
+<script>
+  const ctx = document.getElementById('historyChart');
+
+  new Chart(ctx, {
+    type: 'bar',
+    data: {
+      labels: <%~ JSON.stringify(time, null, 2) %>,
+      datasets: [{
+        label: `<%= it.property %>`,
+        data: <%~ JSON.stringify(data, null, 2) %>,
+        borderWidth: 1
+      }]
+    },
+    options: {
+      scales: {
+        y: {
+          beginAtZero: true
+        }
+      }
+    }
+  });
+</script>

+ 10 - 0
templates/en/partials/topbar.eta

@@ -37,6 +37,15 @@
     </svg>
 ` %>
 
+<% const historyIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+            <path d="M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8" />
+            <path d="M3 3v5h5m4-1v5l4 2" />
+        </g>
+    </svg>
+` %>
+
 <% const settingsIcon = `
     <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
         <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
@@ -73,6 +82,7 @@
     <div class="end">
         <a role="button" href="/"><%~ skFlag %></a>
         <a role="button" href="/en"><%~ homeIcon %></a>
+        <a role="button" href="/en/history"><%~ historyIcon %></a>
         <a role="button" href="/en/settings"><%~ settingsIcon %></a>
     </div>
 

+ 12 - 3
templates/en/settings.eta

@@ -1,7 +1,16 @@
-<% layout("/en/layout") %>
+<% layout("/en/layout", { title: `settings` }) %>
 
 <%~ include("/en/partials/topbar") %>
 
+<% const settingsIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+            <path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
+            <circle cx="12" cy="12" r="3" />
+        </g>
+    </svg>
+` %>
+
 <% const ipIcon = `
     <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
         <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
@@ -13,7 +22,7 @@
     </svg>
 ` %>
 
-<h2>settings</h2>
+<h2><%~ settingsIcon %> settings</h2>
 
 <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.settings?.errors)?.(it?.errorDetails) %>
 
@@ -26,7 +35,7 @@
 <p><%~ ipIcon %> <%= it.wifiIPv4 %></p>
 
 <form action="/en/settings" method="post">
-    <div>
+    <div class="container-row">
       <label for="postDataEnabled">post data</label>
       <input type="checkbox" id="postDataEnabled" name="postDataEnabled" <%= it.postDataEnabled !== "false" ? `checked` : `` %> value="true">
     </div>

+ 29 - 2
templates/sk/history/index.eta

@@ -1,6 +1,33 @@
-<% layout("/sk/history/layout") %>
+<% layout("/sk/history/layout", { title: `história` }) %>
 
 <%~ include("/sk/partials/topbar") %>
 
-<h2>história</h2>
+<% const historyIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+            <path d="M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8" />
+            <path d="M3 3v5h5m4-1v5l4 2" />
+        </g>
+    </svg>
+` %>
 
+<h2><%~ historyIcon %> história</h2>
+
+<h3>vnutorné</h3>
+
+<div class="container-row">
+    <a role="button" href="/history/indoorTemp"><%~ it?.lang?.history.properties?.indoorTemp() %></a>
+    <a role="button" href="/history/indoorPressure"><%~ it?.lang?.history.properties?.indoorPressure() %></a>
+    <a role="button" href="/history/indoorHumidity"><%~ it?.lang?.history.properties?.indoorHumidity() %></a>
+    <a role="button" href="/history/indoorAltitude"><%~ it?.lang?.history.properties?.indoorAltitude() %></a>
+</div>
+
+<h3>vonkajšie</h3>
+
+<div class="container-row">
+    <a role="button" href="/history/outdoorConnected"><%~ it?.lang?.history.properties?.outdoorConnected() %></a>
+    <a role="button" href="/history/outdoorTemp"><%~ it?.lang?.history.properties?.outdoorTemp() %></a>
+    <a role="button" href="/history/outdoorPressure"><%~ it?.lang?.history.properties?.outdoorPressure() %></a>
+    <a role="button" href="/history/outdoorHumidity"><%~ it?.lang?.history.properties?.outdoorHumidity() %></a>
+    <a role="button" href="/history/outdoorAltitude"><%~ it?.lang?.history.properties?.outdoorAltitude() %></a>
+</div>

+ 10 - 2
templates/sk/history/notFound.eta

@@ -1,6 +1,14 @@
-<% layout("/sk/history/layout") %>
+<% layout("/sk/history/layout", { title: `jednotka nenájdená` }) %>
 
 <%~ include("/sk/partials/topbar") %>
 
-<h2>história nenájdená</h2>
+<% const backIcon = `
+    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m12 19l-7-7l7-7m7 7H5" />
+    </svg>
+` %>
 
+<div class="container-row">
+    <a role="button" href="/history"><%~ backIcon %> história</a>
+    <h2>jednotka nenájdená</h2>
+</div>

+ 56 - 5
templates/sk/history/property.eta

@@ -1,10 +1,61 @@
-<% layout("/sk/history/layout") %>
+<% layout("/sk/history/layout", { title: `história` }) %>
 
 <%~ include("/sk/partials/topbar") %>
 
-<h2>história <%= it?.property %></h2>
+<% const backIcon = `
+    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m12 19l-7-7l7-7m7 7H5" />
+    </svg>
+` %>
 
-<%= JSON.stringify(it.data[0]) %>
+<div class="container-row">
+    <a role="button" href="/history"><%~ backIcon %> história</a>
+    <h2><%~ it?.lang?.history.properties?.[it?.property]() %></h2>
+</div>
+
+<div class="historyLinks">
+    <a role="button" href="/history/<%= it.property %>/daily">deň</a>
+    <a role="button" href="/history/<%= it.property %>/monthly">mesiac</a>
+    <a role="button" href="/history/<%= it.property %>/yearly">rok</a>
+    <a role="button" href="/history/<%= it.property %>/allTime">celý čas</a>
+</div>
+
+<% if (it?.dateMap?.years) { %>
+    <form id="historyForm">
+        <% if (it?.dateMap?.days) { %>
+            <div>
+            <label for="day">deň</label>
+            <select id="day" name="day">
+                <% for (const item of it?.dateMap?.days) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.days[it?.dateMap?.days.length - 1] ? `selected` : `` %> ><%= item %></option>
+                <% } %>
+            </select>
+            </div>
+        <% } %>
+
+        <% if (it?.dateMap?.months) { %>
+            <div>
+            <label for="month">mesiac</label>
+            <select id="month" name="month">
+                <% for (const item of it?.dateMap?.months) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.months[it?.dateMap?.months.length - 1] ? `selected` : `` %> > <%= it?.lang?.history.dateFormats.months?.[item]() %></option>
+                <% } %>
+            </select>
+            </div>
+        <% } %>
+
+        <div>
+            <label for="year">rok</label>
+            <select id="year" name="year">
+                <% for (const item of it?.dateMap?.years) { %>
+                    <option value="<%= item %>" <%= item === it?.dateMap?.years[it?.dateMap?.years.length - 1] ? `selected` : `` %> > <%= item %></option>
+                <% } %>
+            </select>
+        </div>
+
+        <button type="submit">načítať</button>
+    </form>
+<% } %>
 
 <% const time = [] %>
 <% const data = [] %>
@@ -30,10 +81,10 @@
   new Chart(ctx, {
     type: 'bar',
     data: {
-      labels: <%~ JSON.stringify(time) %>,
+      labels: <%~ JSON.stringify(time, null, 2) %>,
       datasets: [{
         label: `<%= it.property %>`,
-        data: <%~ JSON.stringify(data) %>,
+        data: <%~ JSON.stringify(data, null, 2) %>,
         borderWidth: 1
       }]
     },

+ 10 - 0
templates/sk/partials/topbar.eta

@@ -37,6 +37,15 @@
     </svg>
 ` %>
 
+<% const historyIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+            <path d="M3 12a9 9 0 1 0 9-9a9.75 9.75 0 0 0-6.74 2.74L3 8" />
+            <path d="M3 3v5h5m4-1v5l4 2" />
+        </g>
+    </svg>
+` %>
+
 <% const settingsIcon = `
     <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
         <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
@@ -70,6 +79,7 @@
     <div class="end">
         <a role="button" href="/en"><%~ usFlag %></a>
         <a role="button" href="/"><%~ homeIcon %></a>
+        <a role="button" href="/history"><%~ historyIcon %></a>
         <a role="button" href="/settings"><%~ settingsIcon %></a>
     </div>
     

+ 12 - 3
templates/sk/settings.eta

@@ -1,7 +1,16 @@
-<% layout("/sk/layout") %>
+<% layout("/sk/layout", { title: `nastavenia` }) %>
 
 <%~ include("/sk/partials/topbar") %>
 
+<% const settingsIcon = `
+    <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
+        <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
+            <path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915" />
+            <circle cx="12" cy="12" r="3" />
+        </g>
+    </svg>
+` %>
+
 <% const ipIcon = `
     <svg class="icon" xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
         <g fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2">
@@ -13,7 +22,7 @@
     </svg>
 ` %>
 
-<h2>nastavenia</h2>
+<h2><%~ settingsIcon %> nastavenia</h2>
 
 <% const errorValue = it.error?.split('.').reduce((a, b) => a[b], it.lang.settings?.errors)?.(it?.errorDetails) %>
 
@@ -26,7 +35,7 @@
 <p><%~ ipIcon %> <%= it.wifiIPv4 %></p>
 
 <form action="/settings" method="post">
-    <div>
+    <div class="container-row">
       <label for="postDataEnabled">odosielať dáta</label>
       <input type="checkbox" id="postDataEnabled" name="postDataEnabled" <%= it.postDataEnabled !== "false" ? `checked` : `` %> value="true">
     </div>

+ 16 - 0
utils/meteostanica.js

@@ -116,4 +116,20 @@ export default class Meteostanica {
 
         return result
     }
+
+    static getDateMap() {
+        const statement = meteostanicaDB.query("SELECT DISTINCT date(timestamp) as d FROM data ORDER BY d ASC");
+        const rows = statement.all();
+
+        return rows.reduce((acc, row) => {
+            const [year, month, day] = row.d.split("-");
+
+            if (!acc[year]) acc[year] = {};
+            if (!acc[year][month]) acc[year][month] = [];
+
+            acc[year][month].push(day);
+
+            return acc;
+        }, {});
+    }
 }

Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä