diff options
| author | Rutger Broekhoff | 2026-09-22 22:28:10 +0200 |
|---|---|---|
| committer | Rutger Broekhoff | 2026-09-22 22:28:10 +0200 |
| commit | ecf1be3f834c7187312f8eee71f77e448c995388 (patch) | |
| tree | b7018285b815c356c49535c97ace67ac43deeb2d /web | |
| parent | ae652cab3cf0541173b31273bcec5746a9a81e68 (diff) | |
| download | routemon-ecf1be3f834c7187312f8eee71f77e448c995388.tar.gz routemon-ecf1be3f834c7187312f8eee71f77e448c995388.zip | |
Check period selection
Diffstat (limited to 'web')
| -rw-r--r-- | web/index.html | 55 | ||||
| -rw-r--r-- | web/index.js | 97 | ||||
| -rw-r--r-- | web/style.css | 21 |
3 files changed, 156 insertions, 17 deletions
diff --git a/web/index.html b/web/index.html index 877da14..42a61ff 100644 --- a/web/index.html +++ b/web/index.html | |||
| @@ -14,11 +14,57 @@ | |||
| 14 | 14 | ||
| 15 | <form name="gpx-upload-form" class="box"> | 15 | <form name="gpx-upload-form" class="box"> |
| 16 | <div> | 16 | <div> |
| 17 | <label for="gpx-file">Upload een GPX-bestand:</label> | 17 | <label for="gpx-file">GPX-bestand:</label> |
| 18 | <input type="file" id="gpx-file-input" name="gpx-file" accept="application/gpx+xml,.gpx" autofocus /> | 18 | <input type="file" id="gpx-file-input" name="gpx-file" accept="application/gpx+xml,.gpx" required autofocus /> |
| 19 | </div> | 19 | </div> |
| 20 | <fieldset> | ||
| 21 | <legend>Controleer werkzaamheden gepland in periode</legend> | ||
| 22 | <table class="borderless"> | ||
| 23 | <tr> | ||
| 24 | <td> | ||
| 25 | <label for="check-period-begin-preset">Vanaf (incl.):</label> | ||
| 26 | </td> | ||
| 27 | <td> | ||
| 28 | <select name="check-period-begin-preset" id="check-period-begin-preset-select"> | ||
| 29 | <option value="-1m">1 maand geleden</option> | ||
| 30 | <option value="-2w">2 weken geleden</option> | ||
| 31 | <option value="-1w" selected>1 week geleden</option> | ||
| 32 | <option value="0">nu</option> | ||
| 33 | <option value="+1w">over 1 week</option> | ||
| 34 | <option value="+2w">over 2 weken</option> | ||
| 35 | <option value="+1m">over 1 maand</option> | ||
| 36 | <option value="custom">een ander moment</option> | ||
| 37 | </select> | ||
| 38 | </td> | ||
| 39 | <td> | ||
| 40 | <input type="datetime-local" id="check-period-begin-input" name="check-period-begin" class="hidden" /> | ||
| 41 | </td> | ||
| 42 | </tr> | ||
| 43 | <tr> | ||
| 44 | <td> | ||
| 45 | <label for="check-period-end-preset">Tot (excl.):</label> | ||
| 46 | </td> | ||
| 47 | <td> | ||
| 48 | <select name="check-period-end-preset" id="check-period-end-preset-select"> | ||
| 49 | <option value="">aanpassen naar…</option> | ||
| 50 | <option value="-1m">1 maand geleden</option> | ||
| 51 | <option value="+2w">2 weken geleden</option> | ||
| 52 | <option value="-1w">1 week geleden</option> | ||
| 53 | <option value="0">nu</option> | ||
| 54 | <option value="+1w" selected>over 1 week</option> | ||
| 55 | <option value="+2w">over 2 weken</option> | ||
| 56 | <option value="+1m">over 1 maand</option> | ||
| 57 | <option value="custom">een ander moment</option> | ||
| 58 | </select> | ||
| 59 | </td> | ||
| 60 | <td> | ||
| 61 | <input type="datetime-local" id="check-period-end-input" name="check-period-end" class="hidden" /> | ||
| 62 | </td> | ||
| 63 | </tr> | ||
| 64 | </table> | ||
| 65 | </fieldset> | ||
| 20 | <div> | 66 | <div> |
| 21 | <input type="submit" value="Opsturen" /> | 67 | <input type="submit" value="Aanvraag versturen" /> |
| 22 | </div> | 68 | </div> |
| 23 | </form> | 69 | </form> |
| 24 | 70 | ||
| @@ -38,7 +84,7 @@ | |||
| 38 | <dialog class="box"> | 84 | <dialog class="box"> |
| 39 | <h3 id="dialog-title"></h3> | 85 | <h3 id="dialog-title"></h3> |
| 40 | <p id="dialog-message"></p> | 86 | <p id="dialog-message"></p> |
| 41 | <details> | 87 | <details id="dialog-more-info-container"> |
| 42 | <summary>Meer informatie</summary> | 88 | <summary>Meer informatie</summary> |
| 43 | <small id="dialog-more-info"></small> | 89 | <small id="dialog-more-info"></small> |
| 44 | </details> | 90 | </details> |
| @@ -46,6 +92,7 @@ | |||
| 46 | </dialog> | 92 | </dialog> |
| 47 | 93 | ||
| 48 | <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="anonymous" referrerpolicy="no-referrer"></script> | 94 | <script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.js" integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo=" crossorigin="anonymous" referrerpolicy="no-referrer"></script> |
| 95 | <script src="https://cdn.jsdelivr.net/npm/[email protected]/global.min.js"></script> | ||
| 49 | <script src="index.js"></script> | 96 | <script src="index.js"></script> |
| 50 | </body> | 97 | </body> |
| 51 | </html> | 98 | </html> |
diff --git a/web/index.js b/web/index.js index e8accf0..63292aa 100644 --- a/web/index.js +++ b/web/index.js | |||
| @@ -7,6 +7,50 @@ closeButton.addEventListener("click", () => { | |||
| 7 | dialog.close(); | 7 | dialog.close(); |
| 8 | }); | 8 | }); |
| 9 | 9 | ||
| 10 | function handlePeriodLimitPresetSelInput(selEl, inputEl) { | ||
| 11 | selEl.addEventListener("input", (event) => { | ||
| 12 | if (selEl.value === "custom") { | ||
| 13 | inputEl.classList.remove("hidden"); | ||
| 14 | } else { | ||
| 15 | inputEl.classList.add("hidden"); | ||
| 16 | } | ||
| 17 | }); | ||
| 18 | } | ||
| 19 | |||
| 20 | function getPeriodLimitPresetSelInput(selEl, inputEl) { | ||
| 21 | const relativeDateTime = (durationArgs) => Temporal.Now.plainDateTimeISO().add(durationArgs); | ||
| 22 | |||
| 23 | let durationArgs = {}; | ||
| 24 | switch (selEl.value) { | ||
| 25 | case "-1m": return relativeDateTime({ months: -1 }); | ||
| 26 | case "-2w": return relativeDateTime({ weeks: -2 }); | ||
| 27 | case "-1w": return relativeDateTime({ weeks: -1 }); | ||
| 28 | case "0": return relativeDateTime({ seconds: 0 }); | ||
| 29 | case "+1w": return relativeDateTime({ weeks: 1 }); | ||
| 30 | case "+2w": return relativeDateTime({ weeks: 2 }); | ||
| 31 | case "+1m": return relativeDateTime({ months: 1 }); | ||
| 32 | case "custom": return Temporal.PlainDateTime.from(inputEl.value); | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | handlePeriodLimitPresetSelInput( | ||
| 37 | document.getElementById("check-period-begin-preset-select"), | ||
| 38 | document.getElementById("check-period-begin-input")); | ||
| 39 | handlePeriodLimitPresetSelInput( | ||
| 40 | document.getElementById("check-period-end-preset-select"), | ||
| 41 | document.getElementById("check-period-end-input")); | ||
| 42 | |||
| 43 | window.addEventListener("load", (event) => { | ||
| 44 | const oneWeek = Temporal.Duration.from({ weeks: 1 }); | ||
| 45 | const now = Temporal.Now.plainDateTimeISO(); | ||
| 46 | document.getElementById("check-period-begin-input").value = now.subtract(oneWeek).toString({ | ||
| 47 | smallestUnit: "minute", | ||
| 48 | }); | ||
| 49 | document.getElementById("check-period-end-input").value = now.add(oneWeek).toString({ | ||
| 50 | smallestUnit: "minute", | ||
| 51 | }); | ||
| 52 | }); | ||
| 53 | |||
| 10 | let map = L.map("map"); | 54 | let map = L.map("map"); |
| 11 | 55 | ||
| 12 | L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { | 56 | L.tileLayer("https://tile.openstreetmap.org/{z}/{x}/{y}.png", { |
| @@ -19,10 +63,27 @@ document.forms["gpx-upload-form"].addEventListener("submit", (event) => { | |||
| 19 | event.preventDefault(); | 63 | event.preventDefault(); |
| 20 | const gpxFileInput = document.getElementById("gpx-file-input"); | 64 | const gpxFileInput = document.getElementById("gpx-file-input"); |
| 21 | if (gpxFileInput.files.length !== 1) { | 65 | if (gpxFileInput.files.length !== 1) { |
| 22 | alert("Voor de upload moet er exact één GPX-bestand zijn geselecteerd"); | 66 | showDialog({ |
| 67 | title: "Geen bestand geselecteerd", | ||
| 68 | detail: "Voor de upload moet er exact één GPX-bestand zijn geselecteerd", | ||
| 69 | moreInfoEl: null, | ||
| 70 | }); | ||
| 23 | return; | 71 | return; |
| 24 | } | 72 | } |
| 25 | load(gpxFileInput.files[0]); | 73 | |
| 74 | const tzidHere = Temporal.Now.timeZoneId(); | ||
| 75 | const cpBegin = getPeriodLimitPresetSelInput(document.getElementById("check-period-begin-preset-select"), | ||
| 76 | document.getElementById("check-period-begin-input")).toZonedDateTime(tzidHere).toInstant(); | ||
| 77 | const cpEnd = getPeriodLimitPresetSelInput(document.getElementById("check-period-end-preset-select"), | ||
| 78 | document.getElementById("check-period-end-input")).toZonedDateTime(tzidHere).toInstant(); | ||
| 79 | |||
| 80 | load({ | ||
| 81 | gpxFile: gpxFileInput.files[0], | ||
| 82 | checkPeriod: { | ||
| 83 | begin: cpBegin, | ||
| 84 | end: cpEnd, | ||
| 85 | }, | ||
| 86 | }); | ||
| 26 | }); | 87 | }); |
| 27 | 88 | ||
| 28 | function el(name, attrs, ...children) { | 89 | function el(name, attrs, ...children) { |
| @@ -47,15 +108,22 @@ function tbl(rowcols) { | |||
| 47 | )))); | 108 | )))); |
| 48 | } | 109 | } |
| 49 | 110 | ||
| 50 | async function showProblem(rsp) { | 111 | function showDialog({ title, detail, moreInfoEl }) { |
| 51 | const problem = await rsp.json(); | 112 | document.getElementById("dialog-title").innerText = title; |
| 113 | document.getElementById("dialog-message").innerText = detail !== null ? detail : ""; | ||
| 114 | if (moreInfoEl !== null) { | ||
| 115 | document.getElementById("dialog-more-info").replaceChildren(moreInfoEl); | ||
| 116 | document.getElementById("dialog-more-info-container").classList.remove("hidden"); | ||
| 117 | } else { | ||
| 118 | document.getElementById("dialog-more-info").replaceChildren(); | ||
| 119 | document.getElementById("dialog-more-info-container").classList.add("hidden"); | ||
| 120 | } | ||
| 52 | 121 | ||
| 53 | document.getElementById("dialog-title").innerText = problem.title; | ||
| 54 | if (problem.detail) | ||
| 55 | document.getElementById("dialog-message").innerText = problem.detail; | ||
| 56 | else | ||
| 57 | document.getElementById("dialog-message").innerText = ""; | ||
| 58 | dialog.showModal(); | 122 | dialog.showModal(); |
| 123 | } | ||
| 124 | |||
| 125 | async function showProblem(rsp) { | ||
| 126 | const problem = await rsp.json(); | ||
| 59 | 127 | ||
| 60 | let rows = [ | 128 | let rows = [ |
| 61 | [txt("HTTP-statuscode"), | 129 | [txt("HTTP-statuscode"), |
| @@ -70,9 +138,12 @@ async function showProblem(rsp) { | |||
| 70 | rows.push([txt("Instantie"), | 138 | rows.push([txt("Instantie"), |
| 71 | el("code", {}, txt(problem.instance))]); | 139 | el("code", {}, txt(problem.instance))]); |
| 72 | } | 140 | } |
| 73 | document.getElementById("dialog-more-info").replaceChildren(tbl(rows)); | ||
| 74 | 141 | ||
| 75 | return; | 142 | showDialog({ |
| 143 | title: problem.title, | ||
| 144 | detail: problem.detail ? problem.detail : null, | ||
| 145 | moreInfoEl: tbl(rows), | ||
| 146 | }); | ||
| 76 | } | 147 | } |
| 77 | 148 | ||
| 78 | async function getSysinfo() { | 149 | async function getSysinfo() { |
| @@ -101,7 +172,7 @@ async function getSysinfo() { | |||
| 101 | document.getElementById("sysinfo-details").replaceChildren(tbl(rows)); | 172 | document.getElementById("sysinfo-details").replaceChildren(tbl(rows)); |
| 102 | } | 173 | } |
| 103 | 174 | ||
| 104 | async function load(gpxFile) { | 175 | async function load({ gpxFile, checkPeriod }) { |
| 105 | let compressedParts = []; | 176 | let compressedParts = []; |
| 106 | const compressedReader = gpxFile.stream().pipeThrough(new CompressionStream("deflate-raw")).getReader(); | 177 | const compressedReader = gpxFile.stream().pipeThrough(new CompressionStream("deflate-raw")).getReader(); |
| 107 | while (true) { | 178 | while (true) { |
| @@ -113,7 +184,7 @@ async function load(gpxFile) { | |||
| 113 | } | 184 | } |
| 114 | } | 185 | } |
| 115 | 186 | ||
| 116 | const rsp = await fetch(apiBaseUrl + "/gpx", { | 187 | const rsp = await fetch(apiBaseUrl + `/gpx?cpbegin=${encodeURI(checkPeriod.begin.toString({ smallestUnit: "minute" }))}&cpend=${encodeURI(checkPeriod.end.toString({ smallestUnit: "minute" }))}`, { |
| 117 | method: "POST", | 188 | method: "POST", |
| 118 | headers: { | 189 | headers: { |
| 119 | "Accept-Language": "nl-NL", | 190 | "Accept-Language": "nl-NL", |
diff --git a/web/style.css b/web/style.css index a96354c..49cc502 100644 --- a/web/style.css +++ b/web/style.css | |||
| @@ -34,15 +34,32 @@ th, td { | |||
| 34 | padding: 5px 5px; | 34 | padding: 5px 5px; |
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | table.borderless, | ||
| 38 | table.borderless th, | ||
| 39 | table.borderless td { | ||
| 40 | border: none; | ||
| 41 | } | ||
| 42 | |||
| 37 | form[name="gpx-upload-form"] { | 43 | form[name="gpx-upload-form"] { |
| 38 | z-index: 1000; | 44 | z-index: 1000; |
| 39 | display: flex; | 45 | display: flex; |
| 46 | flex-direction: column; | ||
| 40 | gap: 16pt; | 47 | gap: 16pt; |
| 41 | position: absolute; | 48 | position: absolute; |
| 42 | top: 10px; | 49 | top: 10px; |
| 43 | right: 10px; | 50 | right: 10px; |
| 44 | } | 51 | } |
| 45 | 52 | ||
| 53 | form fieldset { | ||
| 54 | display: flex; | ||
| 55 | flex-direction: column; | ||
| 56 | gap: 5px; | ||
| 57 | } | ||
| 58 | |||
| 59 | form[name="gpx-upload-form"] input[type="submit"] { | ||
| 60 | float: right; | ||
| 61 | } | ||
| 62 | |||
| 46 | #sysinfo { | 63 | #sysinfo { |
| 47 | z-index: 1000; | 64 | z-index: 1000; |
| 48 | display: flex; | 65 | display: flex; |
| @@ -60,3 +77,7 @@ form[name="gpx-upload-form"] { | |||
| 60 | .help-cursor { | 77 | .help-cursor { |
| 61 | cursor: help; | 78 | cursor: help; |
| 62 | } | 79 | } |
| 80 | |||
| 81 | .hidden { | ||
| 82 | display: none; | ||
| 83 | } | ||