diff --git a/RELEASES.md b/RELEASES.md index 44c9d58..b5e8deb 100644 --- a/RELEASES.md +++ b/RELEASES.md @@ -1,5 +1,34 @@ # App updates VeiligStallen +## VeiligStallen 2024-04-02 + +**Algemeen** + +- Nieuwe "Stalling aanmaken" in app header, voor ingelogde gebruikers + +**Stallingen-beheer** + +- Meld een nieuwe stalling aan als gastgebruiker +- Bij aanmelden stalling als gast: Verberg Capaciteit, Abonnementen en Beheerder +- Na opslaan voorgestelde stalling: Toon dat deze stalling 'doorgestuurd' is aan gemeente, en mogelijk later online komt + +- Keur een aangemelde stalling goed als ingelogde gebruiker +- Knop: zet automatisch een marker op de kaart, op basis van adres +- Knop: vind automatisch adresgegevens op basis van de kaart-marker +- In bewerkmodus: geef de kaart 'vrij' voordat je de kaart-marker kunt verplaatsen +- Krijg validatie-meldingen voor stallingsvelden in bewerkdialoog (bijv: postcode) +- Zie notificatie na opslaan van een stalling + +- Sla op wanneer de stalling is aangemaakt, en wanneer voor het laatst gewijzigd + +**Stallingen-kaart** + +- Op desktop, open direct stalling bij klik op kaart-marker + +**Stallingen-filters** + +- Nieuw "Aangemelde stallingen" filter, dat alleen gesuggereerde stallingen toont + ## VeiligStallen 2024-03-03 **Algemeen** diff --git a/github/setup-actions.sh b/github/setup-actions.sh index ce9fab6..7b71246 100755 --- a/github/setup-actions.sh +++ b/github/setup-actions.sh @@ -10,6 +10,6 @@ gh variable set PROD_AZUREAPPSERVICE_PUBLISHPROFILE_VEILIGSTALLEN < ./github/pro gh variable set ACC_AZUREAPPSERVICE_PUBLISHPROFILE_VEILIGSTALLEN < ./github/acceptance.PublishSettings #enable the ci/cd action -gh workflow enable azure-webapps-node.yml +#gh workflow enable azure-webapps-node.yml gh variable list diff --git a/package-lock.json b/package-lock.json index a074789..dbf73c6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "next-redux-wrapper": "^8.1.0", "react": "18.2.0", "react-dom": "18.2.0", + "react-hot-toast": "^2.4.1", "react-icons": "^4.8.0", "react-redux": "^8.0.5", "superjson": "1.12.2", @@ -8341,6 +8342,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/goober": { + "version": "2.1.14", + "resolved": "https://registry.npmjs.org/goober/-/goober-2.1.14.tgz", + "integrity": "sha512-4UpC0NdGyAFqLNPnhCT2iHpza2q+RAY3GV85a/mRPdzyPQMsj0KmMMuetdIkzWRbJ+Hgau1EZztq8ImmiMGhsg==", + "peerDependencies": { + "csstype": "^3.0.10" + } + }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -11240,6 +11249,21 @@ "react": "^18.2.0" } }, + "node_modules/react-hot-toast": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/react-hot-toast/-/react-hot-toast-2.4.1.tgz", + "integrity": "sha512-j8z+cQbWIM5LY37pR6uZR6D4LfseplqnuAO4co4u8917hBUvXlEqyP1ZzqVLcqoyUesZZv/ImreoCeHVDpE5pQ==", + "dependencies": { + "goober": "^2.1.10" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, "node_modules/react-icons": { "version": "4.12.0", "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.12.0.tgz", diff --git a/package.json b/package.json index e3767ba..a916a97 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "docker-run": "docker run -p 3000:3000 nextjs-docker" }, "dependencies": { + "@auth/prisma-adapter": "^1.5.0", "@emotion/react": "^11.10.6", "@emotion/styled": "^11.10.6", "@fontsource/roboto": "^4.5.8", @@ -43,8 +44,10 @@ "next-auth": "^4.23.1", "next-pwa": "^5.6.0", "next-redux-wrapper": "^8.1.0", + "nodemailer": "^6.9.12", "react": "18.2.0", "react-dom": "18.2.0", + "react-hot-toast": "^2.4.1", "react-icons": "^4.8.0", "react-redux": "^8.0.5", "superjson": "1.12.2", @@ -52,7 +55,7 @@ }, "devDependencies": { "@prisma-korea/prisma-generator-proto": "^2.3.2", - "@prisma/client": "^5.1.1", + "@prisma/client": "^5.11.0", "@types/bcrypt": "^5.0.0", "@types/eslint": "^8.21.3", "@types/formidable": "^3.4.3", @@ -70,7 +73,7 @@ "postcss": "^8.4.21", "prettier": "^2.8.6", "prettier-plugin-tailwindcss": "^0.2.5", - "prisma": "^5.9.1", + "prisma": "^5.11.0", "tailwindcss": "^3.2.7", "typescript": "^5.0.2" }, diff --git a/prisma/schema.prisma b/prisma/schema.prisma index ffef650..aa3180f 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -2025,3 +2025,48 @@ enum accounts_account_type { // sync // reservation // } + +model Account { + id String @id @default(cuid()) + userId String + type String + provider String + providerAccountId String + refresh_token String? @db.Text + access_token String? @db.Text + expires_at Int? + token_type String? + scope String? + id_token String? @db.Text + session_state String? + + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerAccountId]) +} + +model Session { + id String @id @default(cuid()) + sessionToken String @unique + userId String + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} + +model User { + id String @id @default(cuid()) + name String? + email String? @unique + emailVerified DateTime? + image String? + accounts Account[] + sessions Session[] +} + +model VerificationToken { + identifier String + token String @unique + expires DateTime + + @@unique([identifier, token]) +} diff --git a/public/sw.js b/public/sw.js index 5c07aec..56b8bb3 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1 +1 @@ -if(!self.define){let e,s={};const i=(i,a)=>(i=new URL(i+".js",a).href,s[i]||new Promise((s=>{if("document"in self){const e=document.createElement("script");e.src=i,e.onload=s,document.head.appendChild(e)}else e=i,importScripts(i),s()})).then((()=>{let e=s[i];if(!e)throw new Error(`Module ${i} didn’t register its module`);return e})));self.define=(a,n)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(s[o])return;let c={};const r=e=>i(e,o),t={module:{uri:o},exports:c,require:r};s[o]=Promise.all(a.map((e=>t[e]||r(e)))).then((e=>(n(...e),c)))}}define(["./workbox-7c2a5a06"],(function(e){"use strict";importScripts("fallback-MXmK67eIRgmHH81JehWbj.js"),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/static/MXmK67eIRgmHH81JehWbj/_buildManifest.js",revision:"3cb29316328f35a929b174b57d998d19"},{url:"/_next/static/MXmK67eIRgmHH81JehWbj/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/215-ac45c62bc2f2aa0a.js",revision:"ac45c62bc2f2aa0a"},{url:"/_next/static/chunks/292-841d98abcd9ff4be.js",revision:"841d98abcd9ff4be"},{url:"/_next/static/chunks/344-bcf92b16e0822376.js",revision:"bcf92b16e0822376"},{url:"/_next/static/chunks/506-f27b3660c9e376d4.js",revision:"f27b3660c9e376d4"},{url:"/_next/static/chunks/71-5a67a2e9b71e0fbf.js",revision:"5a67a2e9b71e0fbf"},{url:"/_next/static/chunks/717-a876f1ddc5f9c556.js",revision:"a876f1ddc5f9c556"},{url:"/_next/static/chunks/742-2ecc794b6dd82d8a.js",revision:"2ecc794b6dd82d8a"},{url:"/_next/static/chunks/780-57edcdee954dbbf5.js",revision:"57edcdee954dbbf5"},{url:"/_next/static/chunks/785-163e46434781d32a.js",revision:"163e46434781d32a"},{url:"/_next/static/chunks/999-767df9b77f3eaad3.js",revision:"767df9b77f3eaad3"},{url:"/_next/static/chunks/d7eeaac4-a1e8e8e53989db51.js",revision:"a1e8e8e53989db51"},{url:"/_next/static/chunks/d94c0b71-2242b2fc86a4947a.js",revision:"2242b2fc86a4947a"},{url:"/_next/static/chunks/dd81a582-41786d5833b74cce.js",revision:"41786d5833b74cce"},{url:"/_next/static/chunks/framework-63157d71ad419e09.js",revision:"63157d71ad419e09"},{url:"/_next/static/chunks/main-99f5e3f92a717c61.js",revision:"99f5e3f92a717c61"},{url:"/_next/static/chunks/pages/%5BurlName%5D-764c9af1ec6004e8.js",revision:"764c9af1ec6004e8"},{url:"/_next/static/chunks/pages/%5BurlName%5D/%5BpageName%5D-fd0c7822e4f3c1af.js",revision:"fd0c7822e4f3c1af"},{url:"/_next/static/chunks/pages/_app-c7ae4465be87fcfb.js",revision:"c7ae4465be87fcfb"},{url:"/_next/static/chunks/pages/_error-54de1933a164a1ff.js",revision:"54de1933a164a1ff"},{url:"/_next/static/chunks/pages/_offline-c64183d4136009ce.js",revision:"c64183d4136009ce"},{url:"/_next/static/chunks/pages/content-3c8b4c5e89f528e2.js",revision:"3c8b4c5e89f528e2"},{url:"/_next/static/chunks/pages/filter-9c14b224bd0b2bd0.js",revision:"9c14b224bd0b2bd0"},{url:"/_next/static/chunks/pages/index-274d016be14ef1b5.js",revision:"274d016be14ef1b5"},{url:"/_next/static/chunks/pages/loading-0cbccf288848fdff.js",revision:"0cbccf288848fdff"},{url:"/_next/static/chunks/pages/login-e02cd7cfa6a70c0d.js",revision:"e02cd7cfa6a70c0d"},{url:"/_next/static/chunks/pages/login/error-b3931065be16e833.js",revision:"b3931065be16e833"},{url:"/_next/static/chunks/pages/parking-facilities-600206f2e1663e5b.js",revision:"600206f2e1663e5b"},{url:"/_next/static/chunks/pages/stalling-017711df207f5040.js",revision:"017711df207f5040"},{url:"/_next/static/chunks/pages/stalling-crud-e56732d4cf22d13b.js",revision:"e56732d4cf22d13b"},{url:"/_next/static/chunks/pages/stalling/%5Bid%5D-e673659d39eca499.js",revision:"e673659d39eca499"},{url:"/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js",revision:"79330112775102f91e1010318bae2bd3"},{url:"/_next/static/chunks/webpack-f1c64600e64f11be.js",revision:"f1c64600e64f11be"},{url:"/_next/static/css/00841263f4ca408b.css",revision:"00841263f4ca408b"},{url:"/_next/static/css/2ba05e4ff1f637dc.css",revision:"2ba05e4ff1f637dc"},{url:"/_next/static/css/448f8af27fbceac0.css",revision:"448f8af27fbceac0"},{url:"/_next/static/css/4b424eaa5ffd81a5.css",revision:"4b424eaa5ffd81a5"},{url:"/_next/static/css/5f83e2849d67ba57.css",revision:"5f83e2849d67ba57"},{url:"/_next/static/css/963c15b87a85cc54.css",revision:"963c15b87a85cc54"},{url:"/_next/static/css/be83c9cb059e752e.css",revision:"be83c9cb059e752e"},{url:"/_next/static/css/da7f0d6de06a7fc1.css",revision:"da7f0d6de06a7fc1"},{url:"/_next/static/css/dc12f666bb62a0e1.css",revision:"dc12f666bb62a0e1"},{url:"/_next/static/css/ee5279d39fad2a37.css",revision:"ee5279d39fad2a37"},{url:"/_next/static/media/roboto-all-300-normal.39add8fb.woff",revision:"39add8fb"},{url:"/_next/static/media/roboto-all-400-normal.2e9e9400.woff",revision:"2e9e9400"},{url:"/_next/static/media/roboto-all-500-normal.d96daa81.woff",revision:"d96daa81"},{url:"/_next/static/media/roboto-all-700-normal.ca3d0fdb.woff",revision:"ca3d0fdb"},{url:"/_next/static/media/roboto-cyrillic-300-normal.88798412.woff2",revision:"88798412"},{url:"/_next/static/media/roboto-cyrillic-400-normal.2d9c9d60.woff2",revision:"2d9c9d60"},{url:"/_next/static/media/roboto-cyrillic-500-normal.aa68ea54.woff2",revision:"aa68ea54"},{url:"/_next/static/media/roboto-cyrillic-700-normal.258a358e.woff2",revision:"258a358e"},{url:"/_next/static/media/roboto-cyrillic-ext-300-normal.cd7c5715.woff2",revision:"cd7c5715"},{url:"/_next/static/media/roboto-cyrillic-ext-400-normal.d7827ae3.woff2",revision:"d7827ae3"},{url:"/_next/static/media/roboto-cyrillic-ext-500-normal.a1b5c90d.woff2",revision:"a1b5c90d"},{url:"/_next/static/media/roboto-cyrillic-ext-700-normal.dd3651fb.woff2",revision:"dd3651fb"},{url:"/_next/static/media/roboto-greek-300-normal.25dc89b0.woff2",revision:"25dc89b0"},{url:"/_next/static/media/roboto-greek-400-normal.63e6dc18.woff2",revision:"63e6dc18"},{url:"/_next/static/media/roboto-greek-500-normal.533b03d2.woff2",revision:"533b03d2"},{url:"/_next/static/media/roboto-greek-700-normal.432b858b.woff2",revision:"432b858b"},{url:"/_next/static/media/roboto-greek-ext-300-normal.bc5ce703.woff2",revision:"bc5ce703"},{url:"/_next/static/media/roboto-greek-ext-400-normal.2b547ded.woff2",revision:"2b547ded"},{url:"/_next/static/media/roboto-greek-ext-500-normal.7ea6cffa.woff2",revision:"7ea6cffa"},{url:"/_next/static/media/roboto-greek-ext-700-normal.a8d16efd.woff2",revision:"a8d16efd"},{url:"/_next/static/media/roboto-latin-300-normal.a4eae32d.woff2",revision:"a4eae32d"},{url:"/_next/static/media/roboto-latin-400-normal.f2894edc.woff2",revision:"f2894edc"},{url:"/_next/static/media/roboto-latin-500-normal.3170fd9a.woff2",revision:"3170fd9a"},{url:"/_next/static/media/roboto-latin-700-normal.71b2beb8.woff2",revision:"71b2beb8"},{url:"/_next/static/media/roboto-latin-ext-300-normal.37d4965d.woff2",revision:"37d4965d"},{url:"/_next/static/media/roboto-latin-ext-400-normal.21abc8c8.woff2",revision:"21abc8c8"},{url:"/_next/static/media/roboto-latin-ext-500-normal.85ebfb55.woff2",revision:"85ebfb55"},{url:"/_next/static/media/roboto-latin-ext-700-normal.6af98c24.woff2",revision:"6af98c24"},{url:"/_next/static/media/roboto-vietnamese-300-normal.b3d3e960.woff2",revision:"b3d3e960"},{url:"/_next/static/media/roboto-vietnamese-400-normal.c95fc061.woff2",revision:"c95fc061"},{url:"/_next/static/media/roboto-vietnamese-500-normal.7f8c0554.woff2",revision:"7f8c0554"},{url:"/_next/static/media/roboto-vietnamese-700-normal.72bf832f.woff2",revision:"72bf832f"},{url:"/_offline",revision:"MXmK67eIRgmHH81JehWbj"},{url:"/favicon.ico",revision:"07e73dfd3bc26b7124b53257ba95fc96"},{url:"/font/poppins/generator_config.txt",revision:"4dce64e8848e18c45c2fdabbe045335d"},{url:"/font/poppins/poppins-bold-demo.html",revision:"fdd604cb0c616413d2e8b247709697bf"},{url:"/font/poppins/poppins-bold-webfont.woff",revision:"26994f9bf1a45cbd29fc49ab3ec468b5"},{url:"/font/poppins/poppins-bold-webfont.woff2",revision:"372bc341f0c4a256969b2360da7ccd48"},{url:"/font/poppins/poppins-bolditalic-demo.html",revision:"c490938b888cd890a0fd1940b1cae43b"},{url:"/font/poppins/poppins-bolditalic-webfont.woff",revision:"3194c595795ea137bd2b92f10b3a8527"},{url:"/font/poppins/poppins-bolditalic-webfont.woff2",revision:"03b130cacb4c3361816e0af8d2e2e2f0"},{url:"/font/poppins/poppins-medium-demo.html",revision:"513419a8db3123290caed074e68008a9"},{url:"/font/poppins/poppins-medium-webfont.woff",revision:"6e9d35e42eb6e7368f088be2b439122d"},{url:"/font/poppins/poppins-medium-webfont.woff2",revision:"7f3a510290336e19efd6d6329adcd318"},{url:"/font/poppins/poppins-regular-demo.html",revision:"fcc5a1e8b9ef2d3174be58ebe6f30460"},{url:"/font/poppins/poppins-regular-webfont.woff",revision:"cae665e0f4ba42dd79e01011062efd80"},{url:"/font/poppins/poppins-regular-webfont.woff2",revision:"47ed4fd3931fd63e6856fb7b60fe2563"},{url:"/font/poppins/poppins-semibold-demo.html",revision:"46fbd02bb3f3483006f95e3208aab348"},{url:"/font/poppins/poppins-semibold-webfont.woff",revision:"b5aabd34b8fc7970e925d6527749e002"},{url:"/font/poppins/poppins-semibold-webfont.woff2",revision:"82ba3cc0fcbc89006a00843228d146f3"},{url:"/font/poppins/specimen_files/grid_12-825-55-15.css",revision:"5fff298a4c23f5172c2856c4f2878a41"},{url:"/font/poppins/specimen_files/specimen_stylesheet.css",revision:"123174d5a39bdaa34a36871af61ce8ef"},{url:"/font/poppins/stylesheet.css",revision:"3f183bbe492d2dd2505087f7261e0385"},{url:"/icons/android-chrome-192x192.png",revision:"87895ca50fac59a1cd5b185366c4f172"},{url:"/icons/apple-touch-icon.png",revision:"4c3fb534390efaa18394fb6b21614a40"},{url:"/icons/favicon-16x16.png",revision:"89bdf20d01a34def5d147c6502066352"},{url:"/icons/favicon-32x32.png",revision:"9b9c6ba774d0a2ffbce04c8ce6809d87"},{url:"/icons/icon-192x192.png",revision:"60cf76038dd36878f52355f9226d182c"},{url:"/icons/icon-256x256.png",revision:"f0b3d37e6817e2c82f8d18539df4e391"},{url:"/icons/icon-384x384.png",revision:"9e4a2a170431010b6c48a42eedcad08b"},{url:"/icons/icon-512x512.png",revision:"d5499544101b8e9d0e8b17fb31748e9e"},{url:"/icons/preview-image.png",revision:"4ed6687b48e512348de6c2f3d1f6e0a8"},{url:"/icons/splashscreens-ios/ipad_splash.png",revision:"1de96ccea1da6e6154045ca5cd9f4147"},{url:"/icons/splashscreens-ios/ipadpro1_splash.png",revision:"e7b6ffff9cc4a94a778462b61aace55a"},{url:"/icons/splashscreens-ios/ipadpro2_splash.png",revision:"172076a271103aebe1102992e8757a84"},{url:"/icons/splashscreens-ios/ipadpro3_splash.png",revision:"f36b5a47f06ab2f2b8009cb30df407a4"},{url:"/icons/splashscreens-ios/iphone5_splash.png",revision:"f6e0d871ffec9f8bf43a03bbbf9b8572"},{url:"/icons/splashscreens-ios/iphone6_splash.png",revision:"0597fec2b3804c756fa43cd1f92f6d99"},{url:"/icons/splashscreens-ios/iphoneplus_splash.png",revision:"6553856a966842d555431e03c00001ff"},{url:"/icons/splashscreens-ios/iphonex_splash.png",revision:"257b5c5ba67b9b48fbe9b07870bfea63"},{url:"/icons/splashscreens-ios/iphonexr_splash.png",revision:"b9070c54467a42f99d6bddf76e50ba54"},{url:"/icons/splashscreens-ios/iphonexsmax_splash.png",revision:"7732043b5adee45544821104774f9c89"},{url:"/icons/touch-icon-ipad-retina.png",revision:"dce281626762e2bf6d0a73bd45a4780e"},{url:"/icons/touch-icon-ipad.png",revision:"231a193cee3cc49c1561cb9d4d60416d"},{url:"/icons/touch-icon-iphone-retina.png",revision:"9c8c0cc63c9b9870c56d4dce472d719f"},{url:"/icons/touch-icon-iphone.png",revision:"9586351acf5fc7a40865c7a414785cea"},{url:"/images/bike-blue-green.png",revision:"1bcb4c3a473bf51f89d5e15a0e6336ff"},{url:"/images/fietsenstalling-voorbeeld.jpg",revision:"5cb7adbd5d6953c9dac090a9eefe5e9b"},{url:"/images/icon-close-gray.png",revision:"b26bd27f9cfa12b75d5cc544c36b31d3"},{url:"/images/icon-close.png",revision:"b22e6b8e53cb259c967421f1792ab29b"},{url:"/images/icon-hamburger.png",revision:"68e958406881fd436473ee08380305f1"},{url:"/images/icon-list.png",revision:"a569ca340601ac9d50e14b791dd20229"},{url:"/images/icon-map.png",revision:"757eae24bc094e26ba4ed793c6abfb47"},{url:"/images/icon-route-white.png",revision:"d7ea65501359b6011b783b64637e93c8"},{url:"/images/icon-route.png",revision:"44d8094fc37d7bb7d9ca9a11bfbda237"},{url:"/images/kaart-voorbeeld.png",revision:"59a55cdd256c89403c37f01ee04e2bbb"},{url:"/images/logo-transparant.png",revision:"3b86c66ee4b4f9baca14f2c5d2187df2"},{url:"/images/logo-without-text.png",revision:"d44980958fadeb22800bbfbd2ea2a844"},{url:"/images/logo.png",revision:"d083244b6ff8e556199fd89d1233653e"},{url:"/manifest.json",revision:"b203f9a7549fdbb464ed2278dac40123"},{url:"/uploads/06-10-2023/media-1696587031479-756540006.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696587060294-238381396.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696587710713-534043846.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696587760325-765443889.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696587822982-56162933.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588017753-847052605.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588043664-212556406.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588052353-430698553.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588226767-587600497.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588241794-310975041.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588267445-471091990.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588343196-173624482.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696590610164-726407231.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696590732912-452327535.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696591654035-153487998.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696592235702-564845381.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696592269300-275608712.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696593037881-280251211.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696593592770-590832966.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:s,event:i,state:a})=>s&&"opaqueredirect"===s.type?new Response(s.body,{status:200,statusText:"OK",headers:s.headers}):s},{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute((({url:e})=>{if(!(self.origin===e.origin))return!1;const s=e.pathname;return!s.startsWith("/api/auth/")&&!!s.startsWith("/api/")}),new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute((({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")}),new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute((({url:e})=>!(self.origin===e.origin)),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET")})); +if(!self.define){let e,i={};const s=(s,a)=>(s=new URL(s+".js",a).href,i[s]||new Promise((i=>{if("document"in self){const e=document.createElement("script");e.src=s,e.onload=i,document.head.appendChild(e)}else e=s,importScripts(s),i()})).then((()=>{let e=i[s];if(!e)throw new Error(`Module ${s} didn’t register its module`);return e})));self.define=(a,n)=>{const o=e||("document"in self?document.currentScript.src:"")||location.href;if(i[o])return;let c={};const r=e=>s(e,o),t={module:{uri:o},exports:c,require:r};i[o]=Promise.all(a.map((e=>t[e]||r(e)))).then((e=>(n(...e),c)))}}define(["./workbox-7c2a5a06"],(function(e){"use strict";importScripts("fallback-0Rb3hooeW91Qk7Gxnykfw.js"),self.skipWaiting(),e.clientsClaim(),e.precacheAndRoute([{url:"/_next/static/0Rb3hooeW91Qk7Gxnykfw/_buildManifest.js",revision:"109d3ab9e86ff6149ed1dc2cc25c8a3b"},{url:"/_next/static/0Rb3hooeW91Qk7Gxnykfw/_ssgManifest.js",revision:"b6652df95db52feb4daf4eca35380933"},{url:"/_next/static/chunks/189-fb105318336e4380.js",revision:"fb105318336e4380"},{url:"/_next/static/chunks/265-3cec65231f171ca6.js",revision:"3cec65231f171ca6"},{url:"/_next/static/chunks/292-9f9446cee9311860.js",revision:"9f9446cee9311860"},{url:"/_next/static/chunks/714-203ccaf1187c60e1.js",revision:"203ccaf1187c60e1"},{url:"/_next/static/chunks/717-a876f1ddc5f9c556.js",revision:"a876f1ddc5f9c556"},{url:"/_next/static/chunks/748-c92bde025b91ba68.js",revision:"c92bde025b91ba68"},{url:"/_next/static/chunks/783-9e60b13972aa2555.js",revision:"9e60b13972aa2555"},{url:"/_next/static/chunks/88-bf0cd109eed7daf4.js",revision:"bf0cd109eed7daf4"},{url:"/_next/static/chunks/d7eeaac4-a1e8e8e53989db51.js",revision:"a1e8e8e53989db51"},{url:"/_next/static/chunks/d94c0b71-2242b2fc86a4947a.js",revision:"2242b2fc86a4947a"},{url:"/_next/static/chunks/dd81a582-41786d5833b74cce.js",revision:"41786d5833b74cce"},{url:"/_next/static/chunks/framework-63157d71ad419e09.js",revision:"63157d71ad419e09"},{url:"/_next/static/chunks/main-99f5e3f92a717c61.js",revision:"99f5e3f92a717c61"},{url:"/_next/static/chunks/pages/%5BurlName%5D-3cc12e80d56e8a36.js",revision:"3cc12e80d56e8a36"},{url:"/_next/static/chunks/pages/%5BurlName%5D/%5BpageName%5D-712342798917429f.js",revision:"712342798917429f"},{url:"/_next/static/chunks/pages/_app-c7ae4465be87fcfb.js",revision:"c7ae4465be87fcfb"},{url:"/_next/static/chunks/pages/_error-54de1933a164a1ff.js",revision:"54de1933a164a1ff"},{url:"/_next/static/chunks/pages/_offline-c64183d4136009ce.js",revision:"c64183d4136009ce"},{url:"/_next/static/chunks/pages/content-a13780494f18049e.js",revision:"a13780494f18049e"},{url:"/_next/static/chunks/pages/filter-9c14b224bd0b2bd0.js",revision:"9c14b224bd0b2bd0"},{url:"/_next/static/chunks/pages/index-9cd05a30dd9d09af.js",revision:"9cd05a30dd9d09af"},{url:"/_next/static/chunks/pages/loading-0cbccf288848fdff.js",revision:"0cbccf288848fdff"},{url:"/_next/static/chunks/pages/login-b97d46486c463fbb.js",revision:"b97d46486c463fbb"},{url:"/_next/static/chunks/pages/login/error-09487df76a55db67.js",revision:"09487df76a55db67"},{url:"/_next/static/chunks/pages/parking-facilities-6f6e5ac186bcabc8.js",revision:"6f6e5ac186bcabc8"},{url:"/_next/static/chunks/pages/stalling/%5Bid%5D-5ad02c7ec8515633.js",revision:"5ad02c7ec8515633"},{url:"/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js",revision:"79330112775102f91e1010318bae2bd3"},{url:"/_next/static/chunks/webpack-f1c64600e64f11be.js",revision:"f1c64600e64f11be"},{url:"/_next/static/css/00841263f4ca408b.css",revision:"00841263f4ca408b"},{url:"/_next/static/css/2ba05e4ff1f637dc.css",revision:"2ba05e4ff1f637dc"},{url:"/_next/static/css/448f8af27fbceac0.css",revision:"448f8af27fbceac0"},{url:"/_next/static/css/476c43bd2ea5ebeb.css",revision:"476c43bd2ea5ebeb"},{url:"/_next/static/css/4b424eaa5ffd81a5.css",revision:"4b424eaa5ffd81a5"},{url:"/_next/static/css/51fea7c636f88f12.css",revision:"51fea7c636f88f12"},{url:"/_next/static/css/963c15b87a85cc54.css",revision:"963c15b87a85cc54"},{url:"/_next/static/css/be83c9cb059e752e.css",revision:"be83c9cb059e752e"},{url:"/_next/static/css/ee5279d39fad2a37.css",revision:"ee5279d39fad2a37"},{url:"/_next/static/media/roboto-all-300-normal.39add8fb.woff",revision:"39add8fb"},{url:"/_next/static/media/roboto-all-400-normal.2e9e9400.woff",revision:"2e9e9400"},{url:"/_next/static/media/roboto-all-500-normal.d96daa81.woff",revision:"d96daa81"},{url:"/_next/static/media/roboto-all-700-normal.ca3d0fdb.woff",revision:"ca3d0fdb"},{url:"/_next/static/media/roboto-cyrillic-300-normal.88798412.woff2",revision:"88798412"},{url:"/_next/static/media/roboto-cyrillic-400-normal.2d9c9d60.woff2",revision:"2d9c9d60"},{url:"/_next/static/media/roboto-cyrillic-500-normal.aa68ea54.woff2",revision:"aa68ea54"},{url:"/_next/static/media/roboto-cyrillic-700-normal.258a358e.woff2",revision:"258a358e"},{url:"/_next/static/media/roboto-cyrillic-ext-300-normal.cd7c5715.woff2",revision:"cd7c5715"},{url:"/_next/static/media/roboto-cyrillic-ext-400-normal.d7827ae3.woff2",revision:"d7827ae3"},{url:"/_next/static/media/roboto-cyrillic-ext-500-normal.a1b5c90d.woff2",revision:"a1b5c90d"},{url:"/_next/static/media/roboto-cyrillic-ext-700-normal.dd3651fb.woff2",revision:"dd3651fb"},{url:"/_next/static/media/roboto-greek-300-normal.25dc89b0.woff2",revision:"25dc89b0"},{url:"/_next/static/media/roboto-greek-400-normal.63e6dc18.woff2",revision:"63e6dc18"},{url:"/_next/static/media/roboto-greek-500-normal.533b03d2.woff2",revision:"533b03d2"},{url:"/_next/static/media/roboto-greek-700-normal.432b858b.woff2",revision:"432b858b"},{url:"/_next/static/media/roboto-greek-ext-300-normal.bc5ce703.woff2",revision:"bc5ce703"},{url:"/_next/static/media/roboto-greek-ext-400-normal.2b547ded.woff2",revision:"2b547ded"},{url:"/_next/static/media/roboto-greek-ext-500-normal.7ea6cffa.woff2",revision:"7ea6cffa"},{url:"/_next/static/media/roboto-greek-ext-700-normal.a8d16efd.woff2",revision:"a8d16efd"},{url:"/_next/static/media/roboto-latin-300-normal.a4eae32d.woff2",revision:"a4eae32d"},{url:"/_next/static/media/roboto-latin-400-normal.f2894edc.woff2",revision:"f2894edc"},{url:"/_next/static/media/roboto-latin-500-normal.3170fd9a.woff2",revision:"3170fd9a"},{url:"/_next/static/media/roboto-latin-700-normal.71b2beb8.woff2",revision:"71b2beb8"},{url:"/_next/static/media/roboto-latin-ext-300-normal.37d4965d.woff2",revision:"37d4965d"},{url:"/_next/static/media/roboto-latin-ext-400-normal.21abc8c8.woff2",revision:"21abc8c8"},{url:"/_next/static/media/roboto-latin-ext-500-normal.85ebfb55.woff2",revision:"85ebfb55"},{url:"/_next/static/media/roboto-latin-ext-700-normal.6af98c24.woff2",revision:"6af98c24"},{url:"/_next/static/media/roboto-vietnamese-300-normal.b3d3e960.woff2",revision:"b3d3e960"},{url:"/_next/static/media/roboto-vietnamese-400-normal.c95fc061.woff2",revision:"c95fc061"},{url:"/_next/static/media/roboto-vietnamese-500-normal.7f8c0554.woff2",revision:"7f8c0554"},{url:"/_next/static/media/roboto-vietnamese-700-normal.72bf832f.woff2",revision:"72bf832f"},{url:"/_offline",revision:"0Rb3hooeW91Qk7Gxnykfw"},{url:"/favicon.ico",revision:"07e73dfd3bc26b7124b53257ba95fc96"},{url:"/font/poppins/generator_config.txt",revision:"4dce64e8848e18c45c2fdabbe045335d"},{url:"/font/poppins/poppins-bold-demo.html",revision:"fdd604cb0c616413d2e8b247709697bf"},{url:"/font/poppins/poppins-bold-webfont.woff",revision:"26994f9bf1a45cbd29fc49ab3ec468b5"},{url:"/font/poppins/poppins-bold-webfont.woff2",revision:"372bc341f0c4a256969b2360da7ccd48"},{url:"/font/poppins/poppins-bolditalic-demo.html",revision:"c490938b888cd890a0fd1940b1cae43b"},{url:"/font/poppins/poppins-bolditalic-webfont.woff",revision:"3194c595795ea137bd2b92f10b3a8527"},{url:"/font/poppins/poppins-bolditalic-webfont.woff2",revision:"03b130cacb4c3361816e0af8d2e2e2f0"},{url:"/font/poppins/poppins-medium-demo.html",revision:"513419a8db3123290caed074e68008a9"},{url:"/font/poppins/poppins-medium-webfont.woff",revision:"6e9d35e42eb6e7368f088be2b439122d"},{url:"/font/poppins/poppins-medium-webfont.woff2",revision:"7f3a510290336e19efd6d6329adcd318"},{url:"/font/poppins/poppins-regular-demo.html",revision:"fcc5a1e8b9ef2d3174be58ebe6f30460"},{url:"/font/poppins/poppins-regular-webfont.woff",revision:"cae665e0f4ba42dd79e01011062efd80"},{url:"/font/poppins/poppins-regular-webfont.woff2",revision:"47ed4fd3931fd63e6856fb7b60fe2563"},{url:"/font/poppins/poppins-semibold-demo.html",revision:"46fbd02bb3f3483006f95e3208aab348"},{url:"/font/poppins/poppins-semibold-webfont.woff",revision:"b5aabd34b8fc7970e925d6527749e002"},{url:"/font/poppins/poppins-semibold-webfont.woff2",revision:"82ba3cc0fcbc89006a00843228d146f3"},{url:"/font/poppins/specimen_files/grid_12-825-55-15.css",revision:"5fff298a4c23f5172c2856c4f2878a41"},{url:"/font/poppins/specimen_files/specimen_stylesheet.css",revision:"123174d5a39bdaa34a36871af61ce8ef"},{url:"/font/poppins/stylesheet.css",revision:"3f183bbe492d2dd2505087f7261e0385"},{url:"/icons/android-chrome-192x192.png",revision:"87895ca50fac59a1cd5b185366c4f172"},{url:"/icons/apple-touch-icon.png",revision:"4c3fb534390efaa18394fb6b21614a40"},{url:"/icons/favicon-16x16.png",revision:"89bdf20d01a34def5d147c6502066352"},{url:"/icons/favicon-32x32.png",revision:"9b9c6ba774d0a2ffbce04c8ce6809d87"},{url:"/icons/icon-192x192.png",revision:"60cf76038dd36878f52355f9226d182c"},{url:"/icons/icon-256x256.png",revision:"f0b3d37e6817e2c82f8d18539df4e391"},{url:"/icons/icon-384x384.png",revision:"9e4a2a170431010b6c48a42eedcad08b"},{url:"/icons/icon-512x512.png",revision:"d5499544101b8e9d0e8b17fb31748e9e"},{url:"/icons/preview-image.png",revision:"4ed6687b48e512348de6c2f3d1f6e0a8"},{url:"/icons/splashscreens-ios/ipad_splash.png",revision:"1de96ccea1da6e6154045ca5cd9f4147"},{url:"/icons/splashscreens-ios/ipadpro1_splash.png",revision:"e7b6ffff9cc4a94a778462b61aace55a"},{url:"/icons/splashscreens-ios/ipadpro2_splash.png",revision:"172076a271103aebe1102992e8757a84"},{url:"/icons/splashscreens-ios/ipadpro3_splash.png",revision:"f36b5a47f06ab2f2b8009cb30df407a4"},{url:"/icons/splashscreens-ios/iphone5_splash.png",revision:"f6e0d871ffec9f8bf43a03bbbf9b8572"},{url:"/icons/splashscreens-ios/iphone6_splash.png",revision:"0597fec2b3804c756fa43cd1f92f6d99"},{url:"/icons/splashscreens-ios/iphoneplus_splash.png",revision:"6553856a966842d555431e03c00001ff"},{url:"/icons/splashscreens-ios/iphonex_splash.png",revision:"257b5c5ba67b9b48fbe9b07870bfea63"},{url:"/icons/splashscreens-ios/iphonexr_splash.png",revision:"b9070c54467a42f99d6bddf76e50ba54"},{url:"/icons/splashscreens-ios/iphonexsmax_splash.png",revision:"7732043b5adee45544821104774f9c89"},{url:"/icons/touch-icon-ipad-retina.png",revision:"dce281626762e2bf6d0a73bd45a4780e"},{url:"/icons/touch-icon-ipad.png",revision:"231a193cee3cc49c1561cb9d4d60416d"},{url:"/icons/touch-icon-iphone-retina.png",revision:"9c8c0cc63c9b9870c56d4dce472d719f"},{url:"/icons/touch-icon-iphone.png",revision:"9586351acf5fc7a40865c7a414785cea"},{url:"/images/bike-blue-green.png",revision:"1bcb4c3a473bf51f89d5e15a0e6336ff"},{url:"/images/fietsenstalling-voorbeeld.jpg",revision:"5cb7adbd5d6953c9dac090a9eefe5e9b"},{url:"/images/icon-close-gray.png",revision:"b26bd27f9cfa12b75d5cc544c36b31d3"},{url:"/images/icon-close.png",revision:"b22e6b8e53cb259c967421f1792ab29b"},{url:"/images/icon-hamburger.png",revision:"68e958406881fd436473ee08380305f1"},{url:"/images/icon-list.png",revision:"a569ca340601ac9d50e14b791dd20229"},{url:"/images/icon-map.png",revision:"757eae24bc094e26ba4ed793c6abfb47"},{url:"/images/icon-route-white.png",revision:"d7ea65501359b6011b783b64637e93c8"},{url:"/images/icon-route.png",revision:"44d8094fc37d7bb7d9ca9a11bfbda237"},{url:"/images/kaart-voorbeeld.png",revision:"59a55cdd256c89403c37f01ee04e2bbb"},{url:"/images/logo-transparant.png",revision:"3b86c66ee4b4f9baca14f2c5d2187df2"},{url:"/images/logo-without-text.png",revision:"d44980958fadeb22800bbfbd2ea2a844"},{url:"/images/logo.png",revision:"d083244b6ff8e556199fd89d1233653e"},{url:"/manifest.json",revision:"b203f9a7549fdbb464ed2278dac40123"},{url:"/uploads/06-10-2023/media-1696587031479-756540006.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696587060294-238381396.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696587710713-534043846.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696587760325-765443889.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696587822982-56162933.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588017753-847052605.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588043664-212556406.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588052353-430698553.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588226767-587600497.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588241794-310975041.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588267445-471091990.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696588343196-173624482.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696590610164-726407231.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696590732912-452327535.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696591654035-153487998.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696592235702-564845381.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696592269300-275608712.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696593037881-280251211.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"},{url:"/uploads/06-10-2023/media-1696593592770-590832966.jpeg",revision:"186bea7a148312d4fdc4ff61e3e10013"}],{ignoreURLParametersMatching:[]}),e.cleanupOutdatedCaches(),e.registerRoute("/",new e.NetworkFirst({cacheName:"start-url",plugins:[{cacheWillUpdate:async({request:e,response:i,event:s,state:a})=>i&&"opaqueredirect"===i.type?new Response(i.body,{status:200,statusText:"OK",headers:i.headers}):i},{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:gstatic)\.com\/.*/i,new e.CacheFirst({cacheName:"google-fonts-webfonts",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:31536e3}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/^https:\/\/fonts\.(?:googleapis)\.com\/.*/i,new e.StaleWhileRevalidate({cacheName:"google-fonts-stylesheets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:eot|otf|ttc|ttf|woff|woff2|font.css)$/i,new e.StaleWhileRevalidate({cacheName:"static-font-assets",plugins:[new e.ExpirationPlugin({maxEntries:4,maxAgeSeconds:604800}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:jpg|jpeg|gif|png|svg|ico|webp)$/i,new e.StaleWhileRevalidate({cacheName:"static-image-assets",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\/_next\/image\?url=.+$/i,new e.StaleWhileRevalidate({cacheName:"next-image",plugins:[new e.ExpirationPlugin({maxEntries:64,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:mp3|wav|ogg)$/i,new e.CacheFirst({cacheName:"static-audio-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:mp4)$/i,new e.CacheFirst({cacheName:"static-video-assets",plugins:[new e.RangeRequestsPlugin,new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:js)$/i,new e.StaleWhileRevalidate({cacheName:"static-js-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:css|less)$/i,new e.StaleWhileRevalidate({cacheName:"static-style-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\/_next\/data\/.+\/.+\.json$/i,new e.StaleWhileRevalidate({cacheName:"next-data",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute(/\.(?:json|xml|csv)$/i,new e.NetworkFirst({cacheName:"static-data-assets",plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute((({url:e})=>{if(!(self.origin===e.origin))return!1;const i=e.pathname;return!i.startsWith("/api/auth/")&&!!i.startsWith("/api/")}),new e.NetworkFirst({cacheName:"apis",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:16,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute((({url:e})=>{if(!(self.origin===e.origin))return!1;return!e.pathname.startsWith("/api/")}),new e.NetworkFirst({cacheName:"others",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:86400}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET"),e.registerRoute((({url:e})=>!(self.origin===e.origin)),new e.NetworkFirst({cacheName:"cross-origin",networkTimeoutSeconds:10,plugins:[new e.ExpirationPlugin({maxEntries:32,maxAgeSeconds:3600}),{handlerDidError:async({request:e})=>self.fallback(e)}]}),"GET")})); diff --git a/scripts/create-test-user.js b/scripts/create-test-user.js new file mode 100644 index 0000000..f268d2c --- /dev/null +++ b/scripts/create-test-user.js @@ -0,0 +1,108 @@ +const fs = require('fs'); +const bcrypt = require('bcrypt'); + +const c_root_admin = 1; // Super Admin +const c_intern_admin = 2; // Admin (intern) +const c_intern_editor = 3; // Redacteur (intern) +const c_extern_admin = 4; // Admin (gemeente) +const c_extern_editor = 5; // Redacteur (gemeente) +const c_exploitant = 6; // Exploitant +const c_beheerder = 7; // Beheerder +// const c_exploitant_data_analyst: number = 8; // Exploitant data analyst -> disabled +const c_intern_data_analyst = 9; +// const c_extern_data_analyst: number = 10; // Extern data analyst -> disabled + +const saltRounds = 13; // 13 salt rounds used in the original code + +const stallingenIDs_utrecht = [ + 'E197BE1D-B9CC-9B59-D88D7A356D6FEEE8', + 'E197C7FD-C31E-050A-EAB81894CEB8C946', + 'E197C889-9EB9-5F9B-AEA4F4D7B993E795', + 'E197CD25-9F4F-A2D5-26007A22650A4DC0', + 'E197D9DC-C0B9-D9B2-9737C96AED1E68B0', + 'E197DD9B-C7B6-C306-07C6F96E7F42A79B', + 'E197EB66-A859-3EDA-D243C3789EEAE4F0', + 'E198D753-B00C-0F41-9A8C0F275D822E6D', + 'E199038B-DF30-10EB-93C7A52E7AA26808', + 'E1991A95-08EF-F11D-FF946CE1AA0578FB', + 'E199235E-A49E-734E-67A5836FA2358C14', + 'E1994219-9047-F340-067A1D64587BC21D', + 'E1994396-D16A-35F2-2D710CBCEC414338', +] + +// +++++++++++++++++++++++++++++++++++++++++++ +// Fill these fields + +const username = ""; +const email = ""; +const password = ""; +const roleID = c_root_admin; + +// +++++++++++++++++++++++++++++++++++++++++++ +function generateCustomId() { + // Function to generate a random hex string of a given length + const randomHex = (length) => { + let result = ''; + const characters = '0123456789ABCDEF'; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * characters.length)); + } + return result; + } + + // Generate segments of the custom ID + const part1 = randomHex(8); + const part2 = randomHex(4); + const part3 = randomHex(4); + const part4 = randomHex(16); + + // Combine segments into the custom ID format + return `${part1}-${part2}-${part3}-${part4}`; +} + +const hashedPassword = bcrypt.hashSync(password, saltRounds); +// const newUserUUID = generateCustomId(); +const newUserUUID = "D4351342-685D-D17A-B3617EEBBF39451C"; + + +const sqluser = `INSERT INTO 'security_users' ( + UserID, + Locale, + RoleID, + GroupID, + SiteID, + ParentID, + UserName, + EncryptedPassword, + EncryptedPassword2, + DisplayName, + LastLogin, + SendMailToMailAddress, + Theme, + Status) VALUES ( + '${newUserUUID}', + 'Dutch (Standard)', + ${roleID}, + 'intern', + NULL, + '', + '${email}', + '${hashedPassword}', + '', + '${username}', + '2021-04-06 22:58:13', + NULL, + 'default', + '1' + );`; + + const sql = []; + sql.push(sqluser); + +stallingenIDs_utrecht.forEach((stallingID, idx) => { + const sqlstalling = `INSERT INTO security_users_sites (ID,UserID,SiteID,IsContact) VALUES (${1000000+idx},'${newUserUUID}','${stallingID}','0');` + sql.push(sqlstalling); +}); + +fs.writeFileSync('create-test-user.sql', sql.join('\n')); +// console.log(sql); diff --git a/src/backend/handlers/crud-route-handler.ts b/src/backend/handlers/crud-route-handler.ts index 874754e..48fb71c 100644 --- a/src/backend/handlers/crud-route-handler.ts +++ b/src/backend/handlers/crud-route-handler.ts @@ -47,7 +47,7 @@ export const CrudRouteHandler = async ( if ((request.method as HttpMethod) === "DELETE") { const deleteResponse = await service.delete(id); - return response.status(20).json(deleteResponse); + return response.status(200).json(deleteResponse); } return response.status(405).send("Method not allowed"); diff --git a/src/components/AppHeader.tsx b/src/components/AppHeader.tsx index 0815b60..f481c2a 100644 --- a/src/components/AppHeader.tsx +++ b/src/components/AppHeader.tsx @@ -1,15 +1,11 @@ -// @ts-nocheck -import React, {useEffect, useState} from "react"; -// import { useDispatch, useSelector } from "react-redux"; -// import { useRouter } from "next/navigation"; -// import { usePathname } from 'next/navigation'; -// import Link from 'next/link' - +import React from "react"; import AppHeaderDesktop from "~/components/AppHeaderDesktop"; import AppHeaderMobile from "~/components/AppHeaderMobile"; function AppHeader({ + onStallingAanmelden }: { + onStallingAanmelden?: () => void }) { return ( @@ -21,7 +17,7 @@ function AppHeader({ sm:flex `} > - +
{ @@ -57,8 +56,10 @@ const SecundaryMenuItem = (props: any) => { } function AppHeaderDesktop({ + onStallingAanmelden, children }: { + onStallingAanmelden?: () => void, children?: any }) { const dispatch = useDispatch(); @@ -70,9 +71,9 @@ function AppHeaderDesktop({ const [fietsberaadArticles, setFietsberaadArticles] = useState([]); const [didNavOverflow, setDidNavOverflow] = useState(false); - const isAuthenticated = useSelector( - (state: AppState) => state.auth.authState - ); + // const isAuthenticated = useSelector( + // (state: AppState) => state.auth.authState + // ); const activeMunicipalityInfo = useSelector( (state: AppState) => state.map.activeMunicipalityInfo @@ -140,6 +141,13 @@ function AppHeaderDesktop({ setDidNavOverflow(navOverflow); }; + + const handleNieuweStallingClick = () => { + if (onStallingAanmelden) { + onStallingAanmelden(); + } + } + const handleLoginClick = () => { if (!session) { push('/login'); @@ -163,6 +171,8 @@ function AppHeaderDesktop({ const primaryMenuItems = getPrimary(allMenuItems) const secundaryMenuItems = getSecundary(allMenuItems); + const showStallingAanmaken = session && mapZoom >= 13 && activeMunicipalityInfo; + return ( <>
})} + {showStallingAanmaken ? + : null + } + {session && @@ -279,9 +309,6 @@ function AppHeaderDesktop({
- - {children} - ); } diff --git a/src/components/AppHeaderMobile.tsx b/src/components/AppHeaderMobile.tsx index 2425741..c23a3a1 100644 --- a/src/components/AppHeaderMobile.tsx +++ b/src/components/AppHeaderMobile.tsx @@ -45,7 +45,7 @@ function AppHeaderMobile({ children }: { title?: string, - handleCloseClick?: Function , + handleCloseClick?: Function, children?: any }) { const dispatch = useDispatch(); @@ -116,7 +116,7 @@ function AppHeaderMobile({ { // Custom set action - if(handleCloseClick) handleCloseClick(e); + if (handleCloseClick) handleCloseClick(e); // Or default action else dispatch(setIsMobileNavigationVisible(true)); }} className=" diff --git a/src/components/Button.tsx b/src/components/Button.tsx index 91ed193..30196a5 100644 --- a/src/components/Button.tsx +++ b/src/components/Button.tsx @@ -33,7 +33,7 @@ export const Button = ({ ${Styles[variant]} `} onClick={(e: MouseEvent) => { - if(onClick) onClick(e) + if (onClick) onClick(e) }} style={Object.assign({}, { userSelect: "none", // disable highlighting @@ -72,15 +72,14 @@ export const RadioButton = ({ transition-all bg-white ${className && className.indexOf('py-') > -1 ? '' : 'py-1'} - ${ - isActive - ? "border border-gray-700 shadow shadow-md" - : "border border-gray-200 bg-white text-gray-700 shadow shadow-sm" + ${isActive + ? "border border-gray-700 shadow shadow-md" + : "border border-gray-200 bg-white text-gray-700 shadow shadow-sm" } ${className} `} onClick={(e) => { - if(onClick) onClick(e) + if (onClick) onClick(e) }} style={{ userSelect: "none" }} // disable highlighting > @@ -99,7 +98,7 @@ export const IconButton = ({ style }: { onClick?: Function, - children: any + children?: any htmlBefore?: any, className?: string iconUrl?: string @@ -124,7 +123,7 @@ export const IconButton = ({ ${className} `} onClick={(e) => { - if(onClick) onClick(e) + if (onClick) onClick(e) }} style={Object.assign({}, { userSelect: "none", // disable highlighting diff --git a/src/components/CardList.tsx b/src/components/CardList.tsx index 64c1bc4..d656fef 100644 --- a/src/components/CardList.tsx +++ b/src/components/CardList.tsx @@ -13,16 +13,16 @@ import { import { findParkingIndex } from "~/utils/parkings"; interface Props { - cards: CardData[]; + fietsenstallingen: any; cardsPerSlide?: number; - onShowStallingDetails?: Function; + onShowStallingDetails?: (id: string) => void; } const CardList: React.FC = ({ fietsenstallingen, cardsPerSlide = 3, onShowStallingDetails -}) => { +}: Props) => { const dispatch = useDispatch(); const [visibleParkings, setVisibleParkings] = useState(fietsenstallingen); diff --git a/src/components/FilterBox.tsx b/src/components/FilterBox.tsx index a175780..0985e8b 100644 --- a/src/components/FilterBox.tsx +++ b/src/components/FilterBox.tsx @@ -2,6 +2,7 @@ import { useState } from "react"; import { FiFilter } from "react-icons/fi"; +import { useSession } from "next-auth/react"; import { AiFillMinusCircle, AiFillPlusCircle } from "react-icons/ai"; import FilterBoxList, { updateActiveTypeStates, @@ -10,7 +11,7 @@ import FilterBoxPrice, { updateActivePriceStates, } from "~/components/FilterBoxPrice"; import { useDispatch, useSelector } from "react-redux"; -import { toggleType } from "~/store/filterSlice"; +import { toggleType, toggleType2 } from "~/store/filterSlice"; import { AppState } from "~/store/store"; const OPTIONS_1 = [ @@ -67,12 +68,21 @@ const OPTIONS_PRICE = [ { id: "jaarabonnement", title: "Jaarabonnement", active: true }, ]; +const OPTIONS_SUBMISSIONS = [ + { + id: "show_submissions", + name: "show_submissions", + title: "Toon aanmeldingen", + active: false, + }, +]; + type FilterBoxProps = { children?: React.ReactNode; isOpen: boolean; - onOpen: () => void; - onClose: () => void; - onReset: () => void; + onOpen?: () => void; + onClose?: () => void; + onReset?: () => void; }; const FilterBox: React.FC = ({ @@ -89,6 +99,7 @@ const FilterBox: React.FC = ({ onReset: Function; }) => { const dispatch = useDispatch(); + const { data: session } = useSession() const activeTypes = useSelector( (state: AppState) => state.filter.activeTypes @@ -99,15 +110,24 @@ const FilterBox: React.FC = ({ dispatch(toggleType({ pfType: optionId })); }; + const activeTypes2 = useSelector( + (state: AppState) => state.filter.activeTypes2 + ); + + const toggleFilter2 = (optionId: string) => { + // ("toggleFilter", optionId); + dispatch(toggleType2({ pfType: optionId })); + }; + const options1_with_state = updateActiveTypeStates(OPTIONS_1, activeTypes); const options2_with_state = updateActiveTypeStates(OPTIONS_2, activeTypes); const options_price_with_state = updateActivePriceStates(OPTIONS_PRICE, []); + const options3_with_state = updateActiveTypeStates(OPTIONS_SUBMISSIONS, activeTypes2); return (
= ({ />
+ {session && session.user ? +
+ +
: null} + {children}
diff --git a/src/components/FooterNav.tsx b/src/components/FooterNav.tsx index 02452f3..e3f638d 100644 --- a/src/components/FooterNav.tsx +++ b/src/components/FooterNav.tsx @@ -1,6 +1,7 @@ // @ts-nocheck -import React, {useEffect, useState} from "react"; +import React, { useEffect, useState } from "react"; import { useRouter } from "next/navigation"; +import { useSession } from "next-auth/react" import { getNavigationItemsForMunicipality, @@ -22,26 +23,50 @@ const FooterNavItem = ({ ${className} mx-2 `} - onClick={(e) => { - e.preventDefault(); + onClick={(e) => { + e.preventDefault(); push(url); - }} + }} > {children} } -const FooterNav = () => { +const FooterNavItemClick = ({ + onClick, + children, + className +}: { + url?: string, + children: any, + className?: string, +}) => { + return
+ {children} +
+} + +const FooterNav = ({ onStallingAanmelden, children }: { + onStallingAanmelden?: () => void, + children?: any +}) => { + const { data: session } = useSession() + const [fietsberaadArticles, setFietsberaadArticles] = useState([]); - // Get menu items for siteId 1 (Fietsberaad) + // Get menu items for siteId 1 (Fietsberaad) useEffect(() => { (async () => { const response = await getNavigationItemsForMunicipality(1); setFietsberaadArticles(response); })(); - }, []); + }, []); const navItemsPrimary = [ // { title: 'Stalling toevoegen' }, @@ -63,22 +88,23 @@ const FooterNav = () => { text-xs z-10 "> - - Stalling Aanmelden - + {!session ? + { onStallingAanmelden && onStallingAanmelden() }} + className="cursor-pointer font-bold"> + Stalling aanmelden + : null} {navItemsPrimary.map(x => - {x.title} + {x.title} )} - {footerMenuItems ? footerMenuItems.map((x,idx) => {x.DisplayTitle ? x.DisplayTitle : (x.Title ? x.Title : '')} diff --git a/src/components/Form/FormCheckbox.tsx b/src/components/Form/FormCheckbox.tsx index f40a9eb..4632a06 100644 --- a/src/components/Form/FormCheckbox.tsx +++ b/src/components/Form/FormCheckbox.tsx @@ -13,7 +13,7 @@ function FormCheckbox({ classes?: string, checked?: boolean, defaultChecked?: boolean, - onChange?: Function, + onChange?: React.ChangeEventHandler | undefined, children?: any, }) { return ( @@ -25,7 +25,7 @@ function FormCheckbox({ ${classes} `} > - (onChange ? onChange(e) : () => {} )} + onChange={(e) => (onChange ? onChange(e) : () => { })} /> {children} diff --git a/src/components/Form/FormInput.tsx b/src/components/Form/FormInput.tsx index 1ba2ab3..1013bd8 100644 --- a/src/components/Form/FormInput.tsx +++ b/src/components/Form/FormInput.tsx @@ -12,7 +12,8 @@ function FormInput({ label, size, style, - defaultValue + defaultValue, + disabled }: { innerRef?: React.LegacyRef, type?: string, @@ -25,6 +26,7 @@ function FormInput({ label?: string, size?: number style?: object + disabled?: boolean }) { return ( <> @@ -32,8 +34,8 @@ function FormInput({ {label ?
{label}
: ''} - diff --git a/src/components/MapComponent.tsx b/src/components/MapComponent.tsx index 1afdf98..01d666b 100644 --- a/src/components/MapComponent.tsx +++ b/src/components/MapComponent.tsx @@ -4,32 +4,29 @@ import maplibregl from "maplibre-gl"; import * as turf from "@turf/turf"; import { useDispatch, useSelector } from "react-redux"; import { + setMapCurrentLatLong, setMapExtent, setMapZoom, setMapVisibleFeatures, setActiveMunicipality, setSelectedParkingId, + setActiveParkingId, } from "~/store/mapSlice"; import { AppState } from "~/store/store"; // Import utils -import { getParkingColor } from "~/utils/theme"; import { - // getParkingMarker, - // isPointInsidePolygon, convertCoordinatenToCoords, } from "~/utils/map/index"; import { getMunicipalityBasedOnLatLng } from "~/utils/map/active_municipality"; import { mapMoveEndEvents } from "~/utils/map/parkingsFilteringBasedOnExtent"; -// import { parkingTypes } from "~/utils/parkings"; +import useWindowDimensions from "~/hooks/useWindowDimensions"; // Import the mapbox-gl styles so that the map is displayed correctly import "maplibre-gl/dist/maplibre-gl.css"; // Import map styles import nine3030 from "../mapStyles/nine3030"; -// Import component styles, i.e. for the markers -import styles from "./MapComponent.module.css"; // Add custom markers // const addMarkerImages = (map: any) => { @@ -91,10 +88,15 @@ const createGeoJson = (input: GeoJsonFeature[]) => { }; }; -function MapboxMap({ fietsenstallingen = [] }: any) { +function MapboxMap({ fietsenstallingen = [] }: { fietsenstallingen: any[] }) { + // this is where the map instance will be stored after initialization const [stateMap, setStateMap] = React.useState(); + const { width } = useWindowDimensions(); + + const isMobile = width <= 640; // keep the same as the sm threshold in tailwind + // Connect to redux store const dispatch = useDispatch(); @@ -104,6 +106,8 @@ function MapboxMap({ fietsenstallingen = [] }: any) { const filterQuery = useSelector((state: AppState) => state.filter.query); + const filterTypes2 = useSelector((state: AppState) => state.filter.activeTypes2); + const mapExtent = useSelector((state: AppState) => state.map.extent); const mapZoom = useSelector((state: AppState) => state.map.zoom); @@ -178,6 +182,10 @@ function MapboxMap({ fietsenstallingen = [] }: any) { zoom: 7, }); + mapboxMap.on('styleimagemissing', (e) => { + mapboxMap.addImage(e.id, { width: 0, height: 0, data: new Uint8Array(0) }); + }); + mapboxMap.on("load", () => onMapLoaded(mapboxMap)); // Function that executes if component unloads: @@ -206,9 +214,11 @@ function MapboxMap({ fietsenstallingen = [] }: any) { if ( !stateMap || stateMap === undefined || - "getSource" in stateMap === false + "getSource" in stateMap === false || + "getLayer" in stateMap === false ) return; + if (!stateMap.getSource) return; // Create geojson @@ -216,6 +226,9 @@ function MapboxMap({ fietsenstallingen = [] }: any) { // Add or update fietsenstallingen data as sources const addOrUpdateSource = (sourceKey) => { + if (!stateMap || stateMap === undefined) { + return; + } const source: maplibregl.GeoJSONSource = stateMap.getSource( sourceKey ) as maplibregl.GeoJSONSource; @@ -334,12 +347,13 @@ function MapboxMap({ fietsenstallingen = [] }: any) { // Filter map markers if filterActiveTypes filter changes React.useEffect(() => { try { - if (!stateMap) return; + if (!stateMap || stateMap === undefined) return; let filter = [ "all", ["in", ["get", "type"], ["literal", filterActiveTypes]], ]; + if (filterQuery === "") { filter = [ "all", @@ -385,7 +399,7 @@ function MapboxMap({ fietsenstallingen = [] }: any) { } catch (ex) { console.warn("error in MapComponent layer setfilter useEffect call", ex); } - }, [stateMap, fietsenstallingen, filterActiveTypes, filterQuery]); + }, [stateMap, fietsenstallingen, filterActiveTypes, filterQuery, filterTypes2]); // Update visible features in state if filter changes React.useEffect(() => { @@ -427,16 +441,29 @@ function MapboxMap({ fietsenstallingen = [] }: any) { // This function is called when all rendering has been done // mapboxMap.on("idle", function () { onMoved(mapboxMap); + + if (!isMobile) { + mapboxMap.on('mouseenter', 'fietsenstallingen-markers', function () { + mapboxMap.getCanvas().style.cursor = 'pointer'; + }); + + // When the mouse leaves a feature in the 'markers' layer, change the cursor style back + mapboxMap.on('mouseleave', 'fietsenstallingen-markers', function () { + mapboxMap.getCanvas().style.cursor = ''; + }); + } // }); // Show parking info on click mapboxMap.on("click", "fietsenstallingen-markers", (e) => { + // Enlarge parking icon on click + highlighMarker(mapboxMap, e.features[0].properties.id); // Make clicked parking active dispatch(setSelectedParkingId(e.features[0].properties.id)); + if (!isMobile) { + dispatch(setActiveParkingId(e.features[0].properties.id)); + } }); - // Enlarge parking icon on click - mapboxMap.on("click", "fietsenstallingen-markers", (e) => { - highlighMarker(mapboxMap, e.features[0].properties.id); - }); + // Zoom in on cluster click mapboxMap.on("click", "fietsenstallingen-clusters", (e) => { // Zoom in @@ -490,6 +517,8 @@ function MapboxMap({ fietsenstallingen = [] }: any) { })(); // Set values in state + const coordinates = [center.geometry.coordinates[0].toString(), center.geometry.coordinates[1].toString()] + dispatch(setMapCurrentLatLong(coordinates)); dispatch(setMapExtent(extent)); dispatch(setMapZoom(theMap.getZoom())); }, []); diff --git a/src/components/Modals.tsx b/src/components/Modals.tsx new file mode 100644 index 0000000..badf713 --- /dev/null +++ b/src/components/Modals.tsx @@ -0,0 +1,14 @@ +export const SuccessModal = ({ message }) => { + return ( +
+

{message}

+
+ ) +} +export const ErrModal = ({ message }) => { + return ( +
+

{message}

+
+ ) +} \ No newline at end of file diff --git a/src/components/Parking.tsx b/src/components/Parking.tsx index 6ad43ba..9aa7afc 100644 --- a/src/components/Parking.tsx +++ b/src/components/Parking.tsx @@ -1,68 +1,95 @@ import React, { useState, useEffect } from "react"; +import { useSelector } from "react-redux"; import { useSession } from "next-auth/react"; +import { AppState } from "~/store/store"; import { type ParkingDetailsType } from "~/types/"; -import { getParkingDetails, generateRandomId } from "~/utils/parkings"; +import { getParkingDetails, getNewStallingDefaultRecord } from "~/utils/parkings"; +import Modal from "src/components/Modal"; import ParkingEdit from "~/components/parking/ParkingEdit"; import ParkingView from "~/components/parking/ParkingView"; -const Parking = ({ - parkingID, - startInEditMode = false -}: { - parkingID: string, - startInEditMode?: boolean -}) => { +const Parking = ({ id, stallingId, onStallingIdChanged, onClose }: { id: string, stallingId: string | undefined, onStallingIdChanged: (newId: string | undefined) => void, onClose: () => void }) => { const session = useSession(); + // const router = useRouter(); - const [currentStalling, setCurrentStalling] = useState(null); const [currentRevision, setCurrentRevision] = useState(0); + const [currentStallingId, setCurrentStallingId] = useState(stallingId); + const [currentStalling, setCurrentStalling] = useState(null); + const [editMode, setEditMode] = React.useState(false); + + const currentLatLong = useSelector( + (state: AppState) => state.map.currentLatLng + ); useEffect(() => { - const stallingId = parkingID; - if (stallingId === undefined || Array.isArray(stallingId)) { - console.warn('stallingId is undefined or array', stallingId); - return; - } + if (currentStallingId === "aanmelden") { - if (stallingId === "nieuw") { - console.warn('edit of stallingid "nieuw" is not allowed'); - return; + setCurrentStalling(getNewStallingDefaultRecord("", currentLatLong)); + setEditMode(true); + } else if (currentStallingId !== undefined) { + getParkingDetails(currentStallingId).then((stalling) => { + if (null !== stalling) { + setCurrentStalling(stalling); + } else { + setCurrentStalling(null); + } + }); + } else { + setCurrentStalling(null); } - - // console.log(`***** getParkingDetails ${stallingId} -R ${currentRevision} ******`); - getParkingDetails(stallingId).then((stalling) => { - setCurrentStalling(stalling); - }); }, [ - parkingID, + currentStallingId, currentRevision ]); - const handleCloseEdit = () => { - // console.log("handleCloseEdit"); + const handleCloseEdit = (newStallingId: string | false) => { setEditMode(false); + + if (false !== newStallingId) { + setCurrentStallingId(newStallingId); + if (newStallingId.substring(0, 8) === 'VOORSTEL' || newStallingId === '') { + onClose(); + } + } } const handleUpdateRevision = () => { setCurrentRevision(currentRevision + 1); } - const [editMode, setEditMode] = React.useState(startInEditMode); - - const allowEdit = session.status === "authenticated" || parkingID.substring(0, 8) === "VOORSTEL"; - if (null === currentStalling) { - return (null); + return null; } + let allowEdit = session.status === "authenticated" || currentStalling && currentStalling.ID === ""; + let content = undefined; if (allowEdit === true && (editMode === true)) { - return ( handleCloseEdit()} onChange={handleUpdateRevision} />); + content = (); } else { - return ( { setEditMode(true) } : undefined} />); + content = ( { setEditMode(true) } : undefined} />); } + + return ( + { + if (editMode) { + if (confirm('Wil je het bewerkformulier verlaten?')) { + setEditMode(false); + onStallingIdChanged(undefined) + } + } else { + onStallingIdChanged(undefined); + } + }} + clickOutsideClosesDialog={false} + > + {content} + + ) }; export default Parking; diff --git a/src/components/ParkingFacilities.tsx b/src/components/ParkingFacilities.tsx index 2f82ecf..6021026 100644 --- a/src/components/ParkingFacilities.tsx +++ b/src/components/ParkingFacilities.tsx @@ -10,21 +10,28 @@ import CardList from "~/components/CardList"; import { CardData } from "~/components/Card"; import FilterBox from "~/components/FilterBox"; import FooterNav from "~/components/FooterNav"; +import { ParkingDetailsType } from "~/types"; const ParkingFacilities = ({ fietsenstallingen, - initialLatLng -}: any) => { + onStallingAamelden +}: { fietsenstallingen: any, onStallingAamelden?: () => void }) => { const [mapmode, setMapmode] = useState(true); const [isFilterBoxOpen, setIsFilterBoxOpen] = useState(false); const activeTypes = useSelector( (state: AppState) => state.filter.activeTypes ); + const filterQuery = useSelector( + (state: AppState) => state.filter.query + ); + const activeTypes2 = useSelector( + (state: AppState) => state.filter.activeTypes2 + ); - const toggleParkingFacilitiesView = () => setMapmode(!mapmode); + // const toggleParkingFacilitiesView = () => setMapmode(!mapmode); const toggleFilterBox = () => setIsFilterBoxOpen(!isFilterBoxOpen); - const resetFilter = () => {}; + const resetFilter = () => { }; // let cards: CardData[] = []; let filteredFietsenstallingen: any[] = []; @@ -38,17 +45,22 @@ const ParkingFacilities = ({ // description: x.Description, // }; // }); - - filteredFietsenstallingen = fietsenstallingen.filter( - (x: any) => activeTypes.indexOf(x.Type) > -1 - ); + if (activeTypes2 && activeTypes2.includes("show_submissions")) { + filteredFietsenstallingen = fietsenstallingen.filter( + (x: any) => (x.ID.substring(0, 8) === "VOORSTEL") + ); + } else { + filteredFietsenstallingen = fietsenstallingen.filter( + (x: any) => (x.ID.substring(0, 8) !== "VOORSTEL") + ); + } } return (
{mapmode ? ( @@ -58,7 +70,7 @@ const ParkingFacilities = ({ ) : (
{filteredFietsenstallingen.map((x: any) => { - return ; + return ; })}
)} @@ -114,7 +126,7 @@ const ParkingFacilities = ({ onClose={toggleFilterBox} />
- +
); diff --git a/src/components/ParkingFacilityBlock.tsx b/src/components/ParkingFacilityBlock.tsx index c146661..6701b0d 100644 --- a/src/components/ParkingFacilityBlock.tsx +++ b/src/components/ParkingFacilityBlock.tsx @@ -30,19 +30,21 @@ type ParkingType = { Dicht_za?: string; Open_zo?: string; Dicht_zo?: string; + Image?: string; + ExtraServices?: string; } -const isOpen = (openingTime: Date, closingTime: Date): boolean => { +const isOpen = (openingTime: Date, closingTime: Date, isNS: boolean = false): boolean => { const now = new Date(); const currentTime = now.getHours() * 60 + now.getMinutes(); const opening = openingTime.getHours() * 60 + openingTime.getMinutes(); let closing = closingTime.getHours() * 60 + closingTime.getMinutes(); - // Exception for NS parkings: If NS parking AND open from 1am to 1am, - // then the parking is open 24 hours per day. // #TODO: Combine functions with /src/utils/parkings.tsx if (opening === closing && opening === 60 && closing === 60) { - return true; + // Exception for NS parkings: If NS parking AND open from 1am to 1am, + // then the parking is open 24 hours per day. + return isNS; } if (closing < opening) { @@ -73,7 +75,8 @@ const formatOpeningToday = (parkingdata: any): string => { const openinfo = new Date(openstr); const closeinfo = new Date(closestr); - if (isOpen(openinfo, closeinfo)) { + const isNS = parkingdata.EditorCreated === "NS-connector"; + if (isOpen(openinfo, closeinfo, isNS)) { let str = `open`; // Exception: If this is a 24/h a day @@ -100,7 +103,9 @@ function ParkingFacilityBlock({ }: { id?: any, parking: ParkingType, + compact: boolean openParkingHandler?: Function, + expandParkingHandler?: Function, showButtons?: false }) { const { push } = useRouter(); @@ -136,8 +141,8 @@ function ParkingFacilityBlock({ const openingDescription = formatOpeningToday(parking); - const detailsLine = `${costDescription}${costDescription && openingDescription ? "| " : "" - }${openingDescription}`; + // const detailsLine = `${costDescription}${costDescription && openingDescription ? "| " : "" + // }${openingDescription}`; if (parking.ExtraServices) { // console.log('parking', parking) @@ -174,7 +179,7 @@ function ParkingFacilityBlock({ } `} style={{ - backgroundColor: !compact ? 'rgba(31, 153, 210, 0.1)' : null + backgroundColor: !compact ? 'rgba(31, 153, 210, 0.1)' : undefined }} onClick={() => { // Expand parking if expandParkingHandler was given @@ -182,7 +187,7 @@ function ParkingFacilityBlock({ expandParkingHandler(parking.ID); } // Open parking details if ParkingBlock was already active - else if (expandParkingHandler && !compact) { + else if (expandParkingHandler && openParkingHandler && !compact) { openParkingHandler(parking.ID); } // Open parking if no expand handler was given diff --git a/src/components/ParkingFacilityBrowser.tsx b/src/components/ParkingFacilityBrowser.tsx index 430f562..bf67aee 100644 --- a/src/components/ParkingFacilityBrowser.tsx +++ b/src/components/ParkingFacilityBrowser.tsx @@ -4,8 +4,10 @@ import { useDispatch, useSelector } from "react-redux"; import { setSelectedParkingId, setInitialLatLng } from "~/store/mapSlice"; import { setQuery } from "~/store/filterSlice"; import { setMunicipalities } from "~/store/geoSlice"; +import { useSession } from "next-auth/react"; import { convertCoordinatenToCoords } from "~/utils/map/index"; +import type { fietsenstallingen } from "@prisma/client"; import { getMunicipalities @@ -13,14 +15,16 @@ import { import ParkingFacilityBrowserStyles from './ParkingFacilityBrowser.module.css'; -import Input from "@mui/material/TextField"; import SearchBar from "~/components/SearchBar"; import ParkingFacilityBlock from "~/components/ParkingFacilityBlock"; +import type { AppState } from "~/store/store"; + + const MunicipalityBlock = ({ title, onClick -}) => { +}: { title: string, onClick: React.MouseEventHandler }) => { return (
void; - showSearchBar?: false - customFilter?: Function + fietsenstallingen: fietsenstallingen[]; + onShowStallingDetails?: (id: string | undefined) => void; + showSearchBar?: boolean; + customFilter?: Function; }) { const dispatch = useDispatch(); + const session = useSession(); const [visibleParkings, setVisibleParkings] = useState(fietsenstallingen); const [visibleMunicipalities, setVisibleMunicipalities] = useState([]); @@ -76,6 +79,10 @@ function ParkingFacilityBrowser({ (state: AppState) => state.map.selectedParkingId ); + const filterTypes2 = useSelector( + (state: AppState) => state.filter.activeTypes2 + ); + const filterQuery = useSelector( (state: AppState) => state.filter.query ); @@ -92,9 +99,27 @@ function ParkingFacilityBrowser({ if (!filterQuery && !mapVisibleFeatures) return; // Create variable that represents all parkings - const allParkings = fietsenstallingen; + let allParkings = fietsenstallingen; + if (filterTypes2 && filterTypes2.includes('show_submissions')) { + allParkings = allParkings.filter((x) => { + return x.ID.substring(0, 8) === 'VOORSTEL' + }); + } else { + allParkings = allParkings.filter((x) => { + // console.log("filter-is", x.ID, x.ID.substring(0, 8), x.ID.substring(0, 7) !== 'VOORSTEL') + return x.ID.substring(0, 8) !== 'VOORSTEL' + }); + } + // Create variable that will have the filtered parkings let filtered = allParkings; + + // show all submissions and no other parkings + if (filterTypes2 && filterTypes2.includes('show_submissions')) { + setVisibleParkings(filtered); + return; + } + // If custom filter is given: Only apply custom filter if (customFilter) { filtered = filtered.filter((x) => { @@ -111,12 +136,15 @@ function ParkingFacilityBrowser({ (filterQuery && filterQuery.length > 0) ) { filtered = filtered.filter((p) => { + const titleIndex = (p.Title?.toLowerCase().indexOf(filterQuery.toLowerCase()) || -1) + const locationIndex = (p.Location?.toLowerCase().indexOf(filterQuery.toLowerCase()) || -1) + const plaatsIndex = (p.Plaats?.toLowerCase().indexOf(filterQuery.toLowerCase()) || -1) const inFilter = p.SiteID && ( filterQuery === "" || - p.Title?.toLowerCase().indexOf(filterQuery.toLowerCase()) > -1 || - p.Location?.toLowerCase().indexOf(filterQuery.toLowerCase()) > -1 || - p.Plaats?.toLowerCase().indexOf(filterQuery.toLowerCase()) > -1 + titleIndex > -1 || + locationIndex > -1 || + plaatsIndex > -1 ); // Decide if we want to show the parking @@ -131,12 +159,15 @@ function ParkingFacilityBrowser({ (filterQuery && filterQuery.length > 0) ) { filtered = filtered.filter((p) => { + const titleIndex = p.Title?.toLowerCase().indexOf(filterQuery.toLowerCase()) + const locationIndex = p.Location?.toLowerCase().indexOf(filterQuery.toLowerCase()) + const plaatsIndex = p.Plaats?.toLowerCase().indexOf(filterQuery.toLowerCase()) const inFilter = p.SiteID && ( filterQuery === "" || - p.Title?.toLowerCase().indexOf(filterQuery.toLowerCase()) > -1 || - p.Location?.toLowerCase().indexOf(filterQuery.toLowerCase()) > -1 || - p.Plaats?.toLowerCase().indexOf(filterQuery.toLowerCase()) > -1 + (titleIndex !== undefined && titleIndex > -1) || + (locationIndex !== undefined && locationIndex > -1) || + (plaatsIndex !== undefined && plaatsIndex > -1) ); // Decide if we want to show the parking @@ -153,7 +184,7 @@ function ParkingFacilityBrowser({ || p.Plaats === activeMunicipalityInfo.CompanyName;// Also show NS stallingen that have an other SiteID }); // Put the visible parkings on top - const visibleParkingIds = mapVisibleFeatures.map((x) => x.id); + const visibleParkingIds = mapVisibleFeatures.map((x: any) => x.id); parkingsInThisMunicipality.forEach((p) => { if (visibleParkingIds.indexOf(p.ID) > -1) { sorted.push(p); @@ -181,10 +212,10 @@ function ParkingFacilityBrowser({ return p.SiteID === activeMunicipalityInfo.ID || p.Plaats === activeMunicipalityInfo.CompanyName;// Also show NS stallingen that have an other SiteID }); - const parkingsInThisMunicipalityIds = mapVisibleFeatures.map((x) => x.id); + const parkingsInThisMunicipalityIds = mapVisibleFeatures.map((x: any) => x.id); // Put the visible parkings on top - const visibleParkingIds = mapVisibleFeatures.map((x) => x.id); + const visibleParkingIds = mapVisibleFeatures.map((x: any) => x.id); filtered = allParkings.filter((p) => { return visibleParkingIds.indexOf(p.ID) > -1; }); @@ -207,7 +238,8 @@ function ParkingFacilityBrowser({ mapVisibleFeatures, mapVisibleFeatures.length, filterQuery, - customFilter + customFilter, + filterTypes2, ]); useEffect(() => { @@ -229,7 +261,7 @@ function ParkingFacilityBrowser({ const elToScrollTo = document.getElementById('parking-facility-block-' + selectedParkingId); // Stop if no parking element was found if (!elToScrollTo) return; - container.scrollTo({ + container && container.scrollTo({ top: elToScrollTo.offsetTop - 250, behavior: "smooth" }); @@ -237,7 +269,7 @@ function ParkingFacilityBrowser({ // Filter municipalities based on search query useEffect(() => { - const filteredMunicipalities = municipalities.filter((x) => { + const filteredMunicipalities = municipalities.filter((x: any) => { if (!x.CompanyName) return false; if (filterQuery.length <= 1) return false; if (x.CompanyName === 'FIETSBERAAD') return false; @@ -282,9 +314,9 @@ function ParkingFacilityBrowser({ overflow: "auto", }} > - {showSearchBar ? { + filterChanged={(e: { target: { value: any; }; }) => { dispatch(setQuery(e.target.value)) }} /> : ''} diff --git a/src/components/ParkingOnTheMap.tsx b/src/components/ParkingOnTheMap.tsx index 74ba55e..e4fca66 100644 --- a/src/components/ParkingOnTheMap.tsx +++ b/src/components/ParkingOnTheMap.tsx @@ -80,9 +80,8 @@ function ParkingOnTheMap({ parking }) { ? parking.Coordinaten.split(",").map((coord: any) => Number(coord)) : null; // I.e.: 52.508011,5.473280; - if (coords[0] < -90 || coords[0] > 90 || coords[1] < -180 || coords[1] > 180) { + if (coords === null || (coords[0] < -90 || coords[0] > 90 || coords[1] < -180 || coords[1] > 180)) { console.log("***** invalid coordinates for parking", parking.Title, coords); - coords = [52.508011, 5.47328]; return; } @@ -97,6 +96,10 @@ function ParkingOnTheMap({ parking }) { }); mapboxMap.on("load", () => onMapLoaded(mapboxMap)); + mapboxMap.on('styleimagemissing', (e) => { + mapboxMap.addImage(e.id, { width: 0, height: 0, data: new Uint8Array(0) }); + }); + // Function that executes if component unloads: return () => { diff --git a/src/components/parking/ParkingEdit.tsx b/src/components/parking/ParkingEdit.tsx index c87a761..d61f599 100644 --- a/src/components/parking/ParkingEdit.tsx +++ b/src/components/parking/ParkingEdit.tsx @@ -1,128 +1,322 @@ -import React, { ReactEventHandler } from "react"; - -import { openRoute } from "~/utils/map/index"; -import { useRouter } from "next/navigation"; -import { useSession } from "next-auth/react"; +import React, { useState } from "react"; // Import components import PageTitle from "~/components/PageTitle"; -import ImageSlider from "~/components/ImageSlider"; import HorizontalDivider from "~/components/HorizontalDivider"; -import { Button, IconButton } from "~/components/Button"; +import { Button } from "~/components/Button"; import FormInput from "~/components/Form/FormInput"; -import FormCheckbox from "~/components/Form/FormCheckbox"; import SectionBlock from "~/components/SectionBlock"; import SectionBlockEdit from "~/components/SectionBlockEdit"; -import type { ParkingDetailsType } from "~/types/"; +import type { ParkingDetailsType, ParkingSections } from "~/types/"; import { - getAllServices + getAllServices, + generateRandomId, + getDefaultLocation, } from "~/utils/parkings"; -import { Tabs, Tab, FormHelperText, FormLabel, Typography } from "@mui/material"; +import { cbsCodeFromMunicipality, getMunicipalityBasedOnCbsCode } from "~/utils/municipality"; +import { Tabs, Tab, FormHelperText, Typography } from "@mui/material"; /* Use nicely formatted items for items that can not be changed yet */ import ParkingViewTarief from "~/components/parking/ParkingViewTarief"; -import ParkingViewCapaciteit from "~/components/parking/ParkingViewCapaciteit"; import ParkingViewAbonnementen from "~/components/parking/ParkingViewAbonnementen"; -import ParkingEditCapaciteit from "~/components/parking/ParkingEditCapaciteit"; +import ParkingEditCapaciteit, { type CapaciteitType } from "~/components/parking/ParkingEditCapaciteit"; import ParkingEditLocation from "~/components/parking/ParkingEditLocation"; import ParkingEditAfbeelding from "~/components/parking/ParkingEditAfbeelding"; -import ParkingEditOpening, {type OpeningChangedType} from "~/components/parking/ParkingEditOpening"; +import ParkingEditOpening, { type OpeningChangedType } from "~/components/parking/ParkingEditOpening"; +import { useSession } from "next-auth/react" +import ParkingViewBeheerder from "./ParkingViewBeheerder"; +import { type MunicipalityType, getMunicipalityBasedOnLatLng } from "~/utils/map/active_municipality"; +import { geocodeAddress, reverseGeocode } from "~/utils/nomatim"; +import toast from 'react-hot-toast'; + +export type ParkingEditUpdateStructure = { + ID?: string; + Title?: string; + Location?: string; + Postcode?: string; + Plaats?: string; + Coordinaten?: string; + DateCreated: Date; + DateModified: Date; + Type?: string; + SiteID?: string; + Beheerder?: string, + BeheerderContact?: string, + + // [key: string]: string | undefined; + Openingstijden?: any; // Replace with the actual type if different + fietsenstalling_secties?: ParkingSections; // Replace with the actual type if different +} -const ParkingEdit = ({ parkingdata, onClose, onChange }: { parkingdata: ParkingDetailsType, onClose: Function, onChange: Function }) => { - const router = useRouter(); - const session = useSession(); +type ServiceType = { ID: string, Name: string }; +type ChangedType = { ID: string, selected: boolean }; - const [selectedTab, setSelectedTab] = React.useState('tab-algemeen'); - // const [selectedTab, setSelectedTab] = React.useState('tab-capaciteit'); +const NoClickOverlay = () => { + const [didClick, setDidClick] = useState(false); - const [newTitle, setNewTitle ] = React.useState(undefined); - const [newLocation, setNewLocation ] = React.useState(undefined); - const [newPostcode, setNewPostcode ] = React.useState(undefined); - const [newPlaats, setNewPlaats ] = React.useState(undefined); - const [newCoordinaten, setNewCoordinaten ] = React.useState(undefined); + return ( +
setDidClick(true)} + > +
+ Klik om de kaart te bewegen +
+
+ ) +} + +const ParkingEdit = ({ parkingdata, onClose, onChange }: { parkingdata: ParkingDetailsType, onClose: (changeStallingID: string | false) => void, onChange: Function }) => { + + const [selectedTab, setSelectedTab] = React.useState('tab-algemeen'); + // const [waarschuwing, setWaarschuwing] = React.useState(''); + // const [allowSave, setAllowSave] = React.useState(true); + const allowSave = true; + + const [newSiteID, setNewSiteID] = React.useState(undefined); + const [newTitle, setNewTitle] = React.useState(undefined); + const [newLocation, setNewLocation] = React.useState(undefined); + const [newPostcode, setNewPostcode] = React.useState(undefined); + const [newPlaats, setNewPlaats] = React.useState(undefined); + const [newCoordinaten, setNewCoordinaten] = React.useState(undefined); // used for map recenter when coordinates are manually changed - const [centerCoords, setCenterCoords ] = React.useState(undefined); + const [centerCoords, setCenterCoords] = React.useState(undefined); - type FietsenstallingSectiesType = { [key: string]: Array[] } + // beheerder + const [newBeheerder, setNewBeheerder] = React.useState(undefined); + const [newBeheerderContact, setNewBeheerderContact] = React.useState(undefined); - type ServiceType = { ID: string, Name: string}; - type ChangedType = { ID: string, selected: boolean}; + // type FietsenstallingSectiesType = { [key: string]: Array[] } - const [allServices, setAllServices ] = React.useState([]); - const [newServices, setNewServices ] = React.useState([]); + const [allServices, setAllServices] = React.useState([]); + const [newServices, setNewServices] = React.useState([]); - const [newCapaciteit, setNewCapaciteit ] = React.useState([]); // capaciteitschema - const [newOpening, setNewOpening ] = React.useState(undefined); // openingstijdenschema - const [newOpeningstijden, setNewOpeningstijden ] = React.useState(undefined); // textveld afwijkende openingstijden + const [newCapaciteit, setNewCapaciteit] = React.useState([]); // capaciteitschema + const [newOpening, setNewOpening] = React.useState(undefined); // openingstijdenschema + const [newOpeningstijden, setNewOpeningstijden] = React.useState(undefined); // textveld afwijkende openingstijden - type StallingType = { id: string, name: string, sequence: number}; - const [allTypes, setAllTypes ] = React.useState([]); - const [newStallingType, setNewStallingType ] = React.useState(undefined); + type StallingType = { id: string, name: string, sequence: number }; + const [allTypes, setAllTypes] = React.useState([]); + const [newStallingType, setNewStallingType] = React.useState(undefined); + + const [currentMunicipality, setCurrentMunicipality] = React.useState(undefined); + + const { data: session } = useSession() // Set 'allServices' variable in local state React.useEffect(() => { (async () => { const result = await getAllServices(); setAllServices(result); - })(); - },[]) + })(); + }, []) React.useEffect(() => { (async () => { try { const response = await fetch( - `/api/fietsenstallingtypen/` - ); + `/api/fietsenstallingtypen/` + ); const json = await response.json(); - if(! json) return; + if (!json) return; setAllTypes(json); - } catch(err) { + } catch (err) { console.error("get all types error", err); } - })(); - },[]) + })(); + }, []) + + React.useEffect(() => { + updateSiteID(); + }, [parkingdata.Location, newCoordinaten]); const handleChange = (event: React.SyntheticEvent, newValue: string) => { setSelectedTab(newValue); } - - const getUpdate = (): any => { - let update: any = {}; - - if(newTitle !== undefined) { update.Title = newTitle; } - if(newLocation !== undefined) { update.Location = newLocation; } - if(newPostcode !== undefined) { update.Postcode = newPostcode; } - if(newPlaats !== undefined) { update.Plaats = newPlaats; } - if(newCoordinaten !== undefined) { update.Coordinaten = newCoordinaten; } - if(newStallingType !== undefined) { update.Type = newStallingType; } - - if(undefined!==newOpening) { - for(const keystr in newOpening) { - const key = keystr as keyof ParkingDetailsType; + + type checkInfo = { + type: "string" | "coordinaten", + text: string, + value: any, + newvalue: any + } + + // React.useEffect(() => { + // const code = cbsCodeFromMunicipality(currentMunicipality || false); + // if (code) { + // checkCanCreateSite(code); + // } + // }, [currentMunicipality]); + + // const checkCanCreateSite = (cbsCode: number | false | undefined): boolean => { + // return true; + + // if (session) { + // if (session?.user?.sites?.includes(cbsCode) === true) { + // console.log("user can save site"); + // setWaarschuwing(''); + // setAllowSave(true); + // } else { + // console.log("user cannot save site", session?.user?.sites, cbsCode); + // setWaarschuwing('U heeft geen rechten om een stalling aan te maken in deze gemeente.'); + // setAllowSave(false); + // } + // } else { + // // check if municipality has a contact + // return false; + // } + // } + + const updateSiteID = () => { + // console.log(session); + + const currentll = undefined !== newCoordinaten ? newCoordinaten : parkingdata.Coordinaten; + getMunicipalityBasedOnLatLng(currentll.split(",")).then(async (result) => { + if (result !== false) { + // console.log("*****", result); + + // Set municipality in state + setCurrentMunicipality(result); + + // Find CBS code of this municipality + const cbsCode = cbsCodeFromMunicipality(result); + // Reset newSiteID if no cbsCode was found + if (!cbsCode) { + setNewSiteID(undefined); + return; + } + + // Find municipality row in database based on cbsCode + const municipality = await getMunicipalityBasedOnCbsCode(cbsCode) + // Reset newSiteID if no municipality row was found + if (!municipality) { + setNewSiteID(undefined); + return; + } + + if (municipality.ID !== parkingdata.SiteID) { + setNewSiteID(municipality.ID); + } else { + setNewSiteID(undefined); + } + } else { + setCurrentMunicipality(undefined); + setNewSiteID(undefined); + } + }).catch((err) => { + console.error('municipality based on latlng error', err); + }) + } + + const validateParkingData = (): boolean => { + const checkStringType = (check: checkInfo): string => { + if ((check.value === "" || check.value === null) && check.newvalue === undefined) { + return `invoer van ${check.text} is verplicht`; + } else { + return ""; + } + } + + const checkCoordinatenType = (check: checkInfo): string => { + if (check.value === getDefaultLocation && check.newvalue === undefined) { + return `${check.text} is verplicht`; + } else { + return ""; + } + } + + let checks: checkInfo[] = [ + { type: "string", text: "invoer van de titel", value: parkingdata.Title, newvalue: newTitle }, + { type: "string", text: "invoer van de straat en huisnummer", value: parkingdata.Location, newvalue: newLocation }, + { type: "string", text: "invoer van de postcode", value: parkingdata.Postcode, newvalue: newPostcode }, + { type: "string", text: "invoer van de plaats", value: parkingdata.Plaats, newvalue: newPlaats }, + { type: "string", text: "selectie van de gemeente", value: parkingdata.SiteID, newvalue: newSiteID }, + { type: "coordinaten", text: "instellen van de locatie op de kaart", value: parkingdata.Coordinaten, newvalue: newCoordinaten }, + ] + + // FMS & ExploitantID cannot be changed for now, so no need to check those for changes + if (parkingdata.FMS !== true && parkingdata.ExploitantID === null) { + checks.push({ type: "string", text: "invoer van de contactgegevens van de beheerder", value: parkingdata.Beheerder, newvalue: newBeheerder }); + checks.push({ type: "string", text: "invoer van de contactgegevens van de beheerder", value: parkingdata.BeheerderContact, newvalue: newBeheerderContact }); + } + + // Not checked / check not required + // Type, Image, Openingstijden, Capacity, FMS, Beheerder, BeheerderContact, fietsenstalling_type, fietsenstalling_secties, abonnementsvorm_fietsenstalling, exploitant, fietsenstallingen_services + + let message = ""; + for (const check of checks) { + switch (check.type) { + case "string": + message = checkStringType(check); + break; + case "coordinaten": + message = checkCoordinatenType(check); + break; + default: + break; + } + + if (message !== "") { + alert(message); + return false; + } + }; + + return true; + } + + const getUpdate = () => { + let update: ParkingEditUpdateStructure = {}; + + if (newTitle !== undefined) { update.Title = newTitle; } + if (newLocation !== undefined) { update.Location = newLocation; } + if (newPostcode !== undefined) { update.Postcode = newPostcode; } + if (newPlaats !== undefined) { update.Plaats = newPlaats; } + if (newCoordinaten !== undefined) { update.Coordinaten = newCoordinaten; } + if (newStallingType !== undefined) { update.Type = newStallingType; } + if (newSiteID !== undefined) { update.SiteID = newSiteID; } + + if (newBeheerder !== undefined) { update.Beheerder = newBeheerder; } + if (newBeheerderContact !== undefined) { update.BeheerderContact = newBeheerderContact; } + + if (undefined !== newOpening) { + for (const keystr in newOpening) { + const key = keystr as keyof ParkingEditUpdateStructure; update[key] = new Date(newOpening[key]).toISOString(); } } // COULDDO: Save data in update object only, to update parkingdata as 1 full object - // console.log('>> parkingdata', parkingdata) // if(undefined!==newCapaciteit) { // update.fietsenstalling_sectie = newCapaciteit as FietsenstallingSectiesType // } - // console.log('>> update', update) - if(undefined!==newOpeningstijden) { - if(newOpeningstijden !== parkingdata.Openingstijden) { + if (undefined !== newOpeningstijden) { + if (newOpeningstijden !== parkingdata.Openingstijden) { update.Openingstijden = newOpeningstijden; } } - // console.log("got update", JSON.stringify(update,null,2)); + // Set DateCreated and DateModified + const today = new Date(); + if (!parkingdata.DateCreated) { + update.DateCreated = today; + } + update.DateModified = today; + return update; } - const updateServices = async (parkingdata, newServices) => { + const updateServices = async (parkingdata: ParkingDetailsType, newServices: ChangedType[]) => { try { // Delete existing services for this parking await fetch( @@ -132,20 +326,20 @@ const ParkingEdit = ({ parkingdata, onClose, onChange }: { parkingdata: ParkingD // Create servicesToSave object const servicesToSave: {}[] = []; // - First, add existing services - parkingdata.fietsenstallingen_services.forEach(x => { + parkingdata.fietsenstallingen_services && parkingdata.fietsenstallingen_services.forEach(x => { // To be removed? - const doRemove = newServices.filter(s => s.ID === x.services.ID && ! s.selected).pop(); - if(! doRemove) { + const doRemove = newServices.filter(s => s.ID === x.services.ID && !s.selected).pop(); + if (!doRemove) { servicesToSave.push({ ServiceID: x.services.ID, FietsenstallingID: parkingdata.ID, - }); + }); } }) // - Second, add new services newServices.forEach(s => { // Don't add if service is not selected - if(! s.selected) return; + if (!s.selected) return; servicesToSave.push({ ServiceID: s.ID, @@ -163,20 +357,20 @@ const ParkingEdit = ({ parkingdata, onClose, onChange }: { parkingdata: ParkingD }, } ); - } catch(err) { + } catch (err) { console.error(err); } } - const updateCapaciteit = async (parkingdata, newCapaciteit) => { - if(! newCapaciteit || newCapaciteit.length <= 0) return; - console.log('parkingdata, newCapaciteit', parkingdata, newCapaciteit) + const updateCapaciteit = async (parkingdata: ParkingDetailsType, newCapaciteit: ParkingSections) => { + if (!newCapaciteit || newCapaciteit.length <= 0) return; + try { // Get section to save - const sectionToSaveResponse = await fetch('/api/fietsenstalling_sectie/findFirstByFietsenstallingsId?ID='+parkingdata.ID); + const sectionToSaveResponse = await fetch('/api/fietsenstalling_sectie/findFirstByFietsenstallingsId?ID=' + parkingdata.ID); const sectionToSave = await sectionToSaveResponse.json(); const sectionId = sectionToSave.sectieId; - + // Save capaciteit const savedCapaciteit = await fetch('/api/fietsenstalling_sectie/saveManyFromFullObject', { method: 'POST', @@ -189,70 +383,229 @@ const ParkingEdit = ({ parkingdata, onClose, onChange }: { parkingdata: ParkingD "Content-Type": "application/json", } }) - } catch(err) { + } catch (err) { console.error(err); } } - const updateParking = async () => { - // Stop if no parking ID is available - if(! parkingdata || ! parkingdata.ID) return; + const parkingChanged = () => { + try { + const isChanged = + Object.keys(update).length !== 0 || + newServices.length > 0 || + newCapaciteit && newCapaciteit.length > 0 || + newOpening !== undefined || + newOpeningstijden !== undefined; + + return isChanged; + } catch (ex) { + console.error("ParkingEdit - unable to determine if parking data has changed"); + return false; + } + } - // Check if parking was changed - const update = getUpdate(); - const parkingChanged = Object.keys(update).length !== 0 || newServices.length > 0 || newCapaciteit.length > 0 || newOpening !==undefined || newOpeningstijden !== undefined; + const acceptParking = async (): Promise => { + try { + const newID = generateRandomId(); + + // create a new parking record with the given ID + + const storedata = Object.assign({}, parkingdata, { ID: newID }); + storedata.Open_ma = new Date(parkingdata.Open_ma); // Datetime + storedata.Dicht_ma = new Date(parkingdata.Dicht_ma); // Datetime + storedata.Open_di = new Date(parkingdata.Open_di); // Datetime + storedata.Dicht_di = new Date(parkingdata.Dicht_di); // Datetime + storedata.Open_wo = new Date(parkingdata.Open_wo); // Datetime + storedata.Dicht_wo = new Date(parkingdata.Dicht_wo); // Datetime + storedata.Open_do = new Date(parkingdata.Open_do); // Datetime + storedata.Dicht_do = new Date(parkingdata.Dicht_do); // Datetime + storedata.Open_vr = new Date(parkingdata.Open_vr); // Datetime + storedata.Dicht_vr = new Date(parkingdata.Dicht_vr); // Datetime + storedata.Open_za = new Date(parkingdata.Open_za); // Datetime + storedata.Dicht_za = new Date(parkingdata.Dicht_za); // Datetime + storedata.Open_zo = new Date(parkingdata.Open_zo); // Datetime + storedata.Dicht_zo = new Date(parkingdata.Dicht_zo); // Datetime - // If services are updated: Update services - if(newServices.length > 0) { - updateServices(parkingdata, newServices); - } + const result = await fetch( + "/api/fietsenstallingen?id=" + newID, + { + method: "POST", + body: JSON.stringify(storedata), + headers: { + "Content-Type": "application/json", + }, + } + ); + + if (!result.ok) { + toast("Accpeteren is mislukt. Probeer het later nog eens.") + + await fetch( + "/api/fietsenstallingen?id=" + newID, + { method: "DELETE" } + ); + throw Error('Er ging iets fout bij het verplaatsen') + } else { + await fetch( + "/api/fietsenstallingen?id=" + parkingdata.ID, + { method: "DELETE" } + ); + + toast("De stalling is geaccepteerd.") + + return newID; + } + + // store parking record + + // store fietsenstallingen_services records - // If capaciteit is updated: Update capaciteit - if(newCapaciteit.length > 0) { - updateCapaciteit(parkingdata, newCapaciteit); + // store fietsenstalling_secties records + + // store abonnementsvorm_fietsenstalling records + + // delete old fietsenstallingen_services records + + // delete old fietsenstalling_secties records + + // delete old abonnementsvorm_fietsenstalling records + + // delete old parking record + return ""; + } catch (ex: unknown) { + console.error("ParkingEdit - unable to move parking record to new ID"); + return ""; } + } + + const handleRemoveParking = async () => { + try { + if (parkingdata.ID.substring(0, 8) !== 'VOORSTEL') { + // Update logic for derived tables is not fully implemented! + throw Error('Het is niet toegestaan om een definitieve stalling te verwijderen.') + } + + + if (!confirm("Weet u zeker dat u deze stalling wilt verwijderen? Dit kan niet ongedaan worden gemaakt!")) return; + + const result1 = await fetch( + "/api/fietsenstallingen_services/deleteForParking?fietsenstallingId=" + parkingdata.ID, + { method: "DELETE" } + ); + + const result2 = await fetch( + "/api/fietsenstallingen?id=" + parkingdata.ID, + { method: "DELETE" } + ); + + if (false === result1.ok || false === result2.ok) { + toast("De stalling kon niet worden verwijderd.") + } else { + toast("De stalling is verwijderd.") + } - // If parking data didn't change: stop - if(!parkingChanged) { - // Go back to 'view' mode onChange(); - onClose(); - // Stop executing - return; + onClose(""); + } catch (ex) { + console.error("ParkingEdit - unable to remove parking record"); } + } + const handleUpdateParking = async () => { try { + // Stop if no parking ID is available + if (!parkingdata) return; + + const isNew = parkingdata.ID === "" + if (parkingdata.ID === "") { + // for public users, generate a recognizable ID + parkingdata.ID = generateRandomId(session === null ? "VOORSTEL" : ""); + } + + if (!validateParkingData()) { + console.warn("ParkingEdit - invalid data: update cancelled"); + return; + } + + // Check if parking was changed + const update = getUpdate(); + + const doUpdate = + parkingChanged() || + isNew || // default new parking data does not trigger parkingChanged > force update + !isNew && (parkingdata.ID.substring(0, 8) === 'VOORSTEL'); // force update if parkingdata.ID for existing parking starts with 'VOORSTEL' -> triggers update of ID to normal ID + + if (false === doUpdate) { + onChange(); + onClose(false); + return; + } + + const method = isNew ? "POST" : "PUT"; + const body = JSON.stringify(isNew ? Object.assign({}, parkingdata, update) : update); + const result = await fetch( "/api/fietsenstallingen?id=" + parkingdata.ID, { - method: "PUT", - body: JSON.stringify(update), + method, + body, headers: { "Content-Type": "application/json", }, } ); - if(! result.ok) { + if (!result.ok) { throw Error('Er ging iets fout bij het opslaan. Controleer of de gegevens kloppen. Is de postcode bijvoorbeeld juist, en niet te lang?') } - // Go back to 'view' mode - onChange(); - onClose(); - } catch(err) { - if(err.message) alert(err.message); + + // If services are updated: Update services + if (newServices.length > 0) { + await updateServices(parkingdata, newServices); + } + + // If capaciteit is updated: Update capaciteit + if (newCapaciteit && newCapaciteit.length > 0) { + await updateCapaciteit(parkingdata, newCapaciteit); + } + + let returnID: string | boolean = parkingdata.ID + if (session === null) { + toast(`Uw voorstel wordt aangemeld bij gemeente ${currentMunicipality?.name}.`, { duration: 15000, style: { minWidth: '40vw' } }) + + onChange(); + onClose(parkingdata.ID); + } else { + if (isNew) { // new parking created while logged in + toast(`De stallingsgegevens zijn opgeslagen`); + } else { // existing parking + if (parkingdata.ID.substring(0, 8) === 'VOORSTEL') { // accept proposed parking + // when updating a "VOORSTEL parking", it will be moved to a permanent record + returnID = await acceptParking() + if (returnID === "") { + alert(`Er ging iets mis bij het accepteren van dit voorstel. Probeer het later opnieuw.`); + return; + } + } else { + toast(`De stallingsgegevens zijn opgeslagen`); + } + } + + onChange(); + onClose(returnID); // show modal + } + } catch (err: any) { + if (err.message) alert(err.message); else alert(err); } }; - const update = getUpdate() - const parkingChanged = Object.keys(update).length !== 0 || newServices.length > 0 || newCapaciteit.length > 0 || newOpening !==undefined || newOpeningstijden !== undefined; - - // console.log("@@@ parkingdata", parkingdata); + const update: ParkingEditUpdateStructure = getUpdate() + const isVoorstel = parkingdata?.ID.substring(0, 8) === 'VOORSTEL' + const showUpdateButtons = isVoorstel || parkingChanged() const updateCoordinatesFromMap = (lat: number, lng: number) => { - // console.log("#### update from map") const latlngstring = `${lat},${lng}`; - if(latlngstring !== parkingdata.Coordinaten) { + if (latlngstring !== parkingdata.Coordinaten) { setNewCoordinaten(latlngstring); } else { setNewCoordinaten(undefined); @@ -261,48 +614,53 @@ const ParkingEdit = ({ parkingdata, onClose, onChange }: { parkingdata: ParkingD } const updateCoordinatesFromForm = (isLat: boolean) => (e: { target: { value: string; }; }) => { - // console.log("#### update from form") try { const latlng = parkingdata.Coordinaten.split(","); - if(isLat) { + if (isLat) { latlng[0] = e.target.value; } else { latlng[1] = e.target.value; } setNewCoordinaten(latlng.join(",")); setCenterCoords(latlng.join(",")); - } catch(ex) { - console.warn('ParkingEditLocation - unable to set coordinates from form: ', ex.message()); + } catch (ex: any) { + if (ex.message) { + console.warn('ParkingEditLocation - unable to set coordinates from form: ', ex.message()); + } else { + console.warn('ParkingEditLocation - unable to set coordinates from form'); + } } -} + } const getCoordinate = (isLat: boolean): string => { let coords = parkingdata.Coordinaten; - if(newCoordinaten !== undefined) { + if (newCoordinaten !== undefined) { coords = newCoordinaten; - } - if(coords === "") return ""; + } + if (coords === "") return ""; const latlng = coords.split(","); - if(isLat) { - return latlng[0]?.toString()||''; + if (isLat) { + return latlng[0]?.toString() || ''; } else { - return latlng[1]?.toString()||''; + return latlng[1]?.toString() || ''; } } - const renderTabAlgemeen = () => { + const renderTabAlgemeen = (visible: boolean = false) => { const serviceIsActive = (ID: string): boolean => { - const change = newServices.find(s=>(s.ID===ID)); - if(change!==undefined) { - // console.log(ID, "changed to ", change.selected); + const change = newServices.find(s => (s.ID === ID)); + if (change !== undefined) { return change.selected; } - for(const item of parkingdata.fietsenstallingen_services) { - if(item.services.ID===ID) { - // console.log("selected in parkingdata", ID, change); + if (undefined === parkingdata.fietsenstallingen_services) { + return false; + } + + for (const item of parkingdata.fietsenstallingen_services) { + if (item.services.ID === ID) { return true; } } @@ -311,29 +669,63 @@ const ParkingEdit = ({ parkingdata, onClose, onChange }: { parkingdata: ParkingD } const handleSelectService = (ID: string, checked: boolean) => { - const index = newServices.findIndex(s=>s.ID===ID); - if(index !== -1) { - newServices.splice(index, 1); + const index = newServices.findIndex(s => s.ID === ID); + if (index !== -1) { + newServices.splice(index, 1); } else { - newServices.push({ID: ID, selected: checked}); + newServices.push({ ID: ID, selected: checked }); } - // console.log('newservices - after', JSON.stringify(newServices,0,2)); - setNewServices([...newServices]); } + const addressValid = () => { + return ((parkingdata.Location !== "" || newLocation !== undefined) && (parkingdata.Plaats !== "" || newPlaats !== undefined)) || + (parkingdata.Postcode !== "" || newPostcode !== undefined) + } + + const handleAddressLookup = async () => { + let latlng = await geocodeAddress( + newLocation !== undefined ? newLocation : parkingdata.Location, + newPostcode !== undefined ? newPostcode : parkingdata.Postcode, + newPlaats !== undefined ? newPlaats : parkingdata.Plaats + ); + if (false !== latlng) { + setNewCoordinaten(latlng.lat + "," + latlng.lon); + setCenterCoords(latlng.lat + "," + latlng.lon); + } else { + alert("Er is geen locatie beschikbaar voor dit adres. U kunt de locatie handmatig aanpassen."); + } + } + + const handleCoordinatesLookup = async () => { + let address = await reverseGeocode(newCoordinaten !== undefined ? newCoordinaten : parkingdata.Coordinaten); + if (address && address.address) { + let location = ((address.address.road || "---") + " " + (address.address.house_number || "")).trim(); + setNewLocation(location); + setNewPostcode(address.address.postcode); + const plaats = address.address.city || address.address.town || address.address.village || address.address.quarter; + setNewPlaats(plaats); + + if (parkingdata.Title === "" && (newTitle === "" || newTitle === undefined)) { + setNewTitle("Nieuwe stalling " + (location + " " + plaats).trim()); + } + } else { + alert("Er is geen locatie beschikbaar voor dit adres. U kunt de locatie handmatig aanpassen."); + } + } + return ( -
+
- +
{ setNewTitle(e.target.value)}} value={newTitle!==undefined?newTitle:parkingdata.Title} + onChange={e => { setNewTitle(e.target.value) }} value={newTitle !== undefined ? newTitle : parkingdata.Title} />
{ setNewLocation(e.target.value)}} value={newLocation!==undefined?newLocation:parkingdata.Location} + onChange={e => { setNewLocation(e.target.value) }} value={newLocation !== undefined ? newLocation : parkingdata.Location} />
<> - { setNewPostcode(e.target.value)}} value={newPostcode!==undefined?newPostcode:parkingdata.Postcode} /> - { setNewPlaats(e.target.value)}} value={newPlaats!==undefined?newPlaats:parkingdata.Plaats} /> + { setNewPostcode(e.target.value) }} value={newPostcode !== undefined ? newPostcode : parkingdata.Postcode} /> + { setNewPlaats(e.target.value) }} value={newPlaats !== undefined ? newPlaats : parkingdata.Plaats} /> + {addressValid() && + }
@@ -377,10 +771,10 @@ const ParkingEdit = ({ parkingdata, onClose, onChange }: { parkingdata: ParkingD - { setNewStallingType(event.target.value) }}> {allTypes.map(type => ( ))} @@ -391,9 +785,10 @@ const ParkingEdit = ({ parkingdata, onClose, onChange }: { parkingdata: ParkingD {/**/}
-
+
- + +
Verschuif de kaart om de coordinaten aan te passen @@ -416,26 +811,38 @@ const ParkingEdit = ({ parkingdata, onClose, onChange }: { parkingdata: ParkingD onChange={updateCoordinatesFromForm(false)} value={getCoordinate(false)} /> + {(newCoordinaten || !addressValid()) && + } + + {}
); } - const renderTabAfbeelding = () => { - return ( -
- -
); - } + const renderTabAfbeelding = (visible: boolean = false) => { + return ( +
+ +
); + } - const renderTabOpeningstijden = () => { + const renderTabOpeningstijden = (visible: boolean = false) => { const handlerSetNewOpening = (tijden: OpeningChangedType, Openingstijden: string): void => { setNewOpening(tijden); setNewOpeningstijden(Openingstijden); return; } - return ( -
+ return ( +
); } - const renderTabTarieven = () => { - return ( -
+ const renderTabTarieven = (visible: boolean = false) => { + return ( +
); return (
- -
Fietsen
-
-
Eerste 24 uur:
-
gratis
-
Daarna per 24 uur:
-
€0,60
-
-
Bromfietsen
-
-
Eerste 24 uur:
-
€0,60
-
-
+ +
Fietsen
+
+
Eerste 24 uur:
+
gratis
+
Daarna per 24 uur:
+
€0,60
+
+
Bromfietsen
+
+
Eerste 24 uur:
+
€0,60
+
+
); } - - const renderTabCapaciteit = () => { - const handlerSetNewCapaciteit = (capaciteit: any): void => { + + const renderTabCapaciteit = (visible: boolean = false) => { + const handlerSetNewCapaciteit = (capaciteit: ParkingSections): void => { setNewCapaciteit(capaciteit); return; } + return ( -
- +
+ { + const renderTabAbonnementen = (visible: boolean = false) => { return ( -
+
); return (
- -
-
Jaarbonnement fiets
-
€80,90
-
Jaarabonnement bromfiets
-
€262.97
-
-
+ +
+
Jaarbonnement fiets
+
€80,90
+
Jaarabonnement bromfiets
+
€262.97
+
+
- {/**/} + {/**/}
); } - const renderTabBeheerder = () => { - let content; - - //console.log("#### got parkingdata", JSON.stringify(parkingdata, null, 2)); - - // console.log("### ParkingViewBeheerder", parkingdata, parkingdata.Exploitant, parkingdata.Beheerder, parkingdata.BeheerderContact); - if (parkingdata.FMS === true) { - content = FMS; - } else if(parkingdata?.exploitant) { - content = ( - - {parkingdata.exploitant.CompanyName} - + const renderTabBeheerder = (visible: boolean = false) => { + // TODO: uitzoeken & implementeren FMS / ExploitantID logica + if (parkingdata.FMS === true || !(parkingdata.ExploitantID === undefined || parkingdata.ExploitantID == null)) { + return ( +
+
+
+ +
+

Wijzigen van de beheerder is op dit moment niet mogelijk

+
+
) - } else if(parkingdata.BeheerderContact !== null) { - content = ( - - {parkingdata.Beheerder === null ? 'contact' : parkingdata.Beheerder} - - ); - } else { - content = null } return ( -
- {content} +
+
+ +
+ { setNewBeheerder(e.target.value) }} value={newBeheerder !== undefined ? newBeheerder : parkingdata.Beheerder} /> + { setNewBeheerderContact(e.target.value) }} value={newBeheerderContact !== undefined ? newBeheerderContact : parkingdata.BeheerderContact} /> +
+
+
- ); + ) } + let parkingTitle = parkingdata.Title; + if (parkingdata.ID.substring(0, 8) === 'VOORSTEL') { + parkingTitle += " (voorstel)"; + } + + const isLoggedIn = (session !== null); + const hasID = (parkingdata.ID !== ""); + return (
-
{parkingdata.Title}
-
+ {showUpdateButtons === true && allowSave && - {parkingChanged === true && } + {showUpdateButtons === true && } + {showUpdateButtons === false && }
- - - + + {hasID && } + + {/* */} - - - + {hasID && isLoggedIn && } + {hasID && isLoggedIn && } + {isLoggedIn && } - { selectedTab === 'tab-algemeen' ? renderTabAlgemeen() : null } - { selectedTab === 'tab-afbeelding' ? renderTabAfbeelding() : null } - { selectedTab === 'tab-openingstijden' ? renderTabOpeningstijden() : null } - { selectedTab === 'tab-tarieven' ? renderTabTarieven() : null } - { selectedTab === 'tab-capaciteit' ? renderTabCapaciteit() : null } - { selectedTab === 'tab-abonnementen' ? renderTabAbonnementen() : null } - { selectedTab === 'tab-beheerder' ? renderTabBeheerder() : null } + {renderTabAlgemeen(selectedTab === 'tab-algemeen')} + {renderTabAfbeelding(selectedTab === 'tab-afbeelding' && hasID)} + {renderTabOpeningstijden(selectedTab === 'tab-openingstijden')} + {renderTabTarieven(selectedTab === 'tab-tarieven')} + {renderTabCapaciteit(selectedTab === 'tab-capaciteit' && hasID && isLoggedIn)} + {renderTabAbonnementen(selectedTab === 'tab-abonnementen' && hasID && isLoggedIn)} + {renderTabBeheerder(selectedTab === 'tab-beheerder' && isLoggedIn)}
); }; diff --git a/src/components/parking/ParkingEditAfbeelding.tsx b/src/components/parking/ParkingEditAfbeelding.tsx index 635648d..ccc5ec2 100644 --- a/src/components/parking/ParkingEditAfbeelding.tsx +++ b/src/components/parking/ParkingEditAfbeelding.tsx @@ -5,31 +5,31 @@ import { Button } from "~/components/Button"; import ImageSlider from "~/components/ImageSlider"; // based on https://codersteps.com/articles/how-to-build-a-file-uploader-with-next-js-and-formidable -const ParkingEditAfbeelding = ({ parkingdata, onUpdateAfbeelding }: { parkingdata: ParkingDetailsType, onUpdateAfbeelding: Function }) => { +const ParkingEditAfbeelding = ({ parkingdata, onUpdateAfbeelding }: { parkingdata: ParkingDetailsType, onUpdateAfbeelding?: Function }) => { const [file, setFile] = useState(null); const [previewUrl, setPreviewUrl] = useState(null); - const onRemoveAfbeelding = async () => { - const update = { Image: '' } - try { - const result = await fetch( - "/api/fietsenstallingen?id=" + parkingdata.ID, - { - method: "PUT", - body: JSON.stringify(update), - headers: { - "Content-Type": "application/json", - }, - } - ); - if(! result.ok) { - throw Error('Er ging iets fout bij het verwijderen. Probeer je het later nog eens.') - } - - if(onUpdateAfbeelding) { - onUpdateAfbeelding('') + const onRemoveAfbeelding = async () => { + const update = { Image: '' } + try { + const result = await fetch( + "/api/fietsenstallingen?id=" + parkingdata.ID, + { + method: "PUT", + body: JSON.stringify(update), + headers: { + "Content-Type": "application/json", + }, + } + ); + if (!result.ok) { + throw Error('Er ging iets fout bij het verwijderen. Probeer je het later nog eens.') + } + + if (onUpdateAfbeelding) { + onUpdateAfbeelding('') }; - } catch(err) { + } catch (err) { console.error('onRemoveAfbeelding - error', err); } } @@ -37,13 +37,13 @@ const ParkingEditAfbeelding = ({ parkingdata, onUpdateAfbeelding }: { parkingdat const onFileUploadChange = (e: ChangeEvent) => { const fileInput = e.target; - if (!fileInput.files || fileInput.files.length === 0||!fileInput.files[0]) { + if (!fileInput.files || fileInput.files.length === 0 || !fileInput.files[0]) { console.warn("Files list is empty"); return; } const file = fileInput.files[0]; - + /** File validation */ if (!file.type.startsWith("image")) { alert("Ongeldig bestandstype geselecteerd. Alleen afbeeldingen zijn toegestaan."); @@ -71,7 +71,8 @@ const ParkingEditAfbeelding = ({ parkingdata, onUpdateAfbeelding }: { parkingdat const onUploadFile = async (e: MouseEvent) => { e.preventDefault(); - if(! parkingdata || ! parkingdata.ID || !file) { + if (!parkingdata || !parkingdata.ID || !file) { + console.warn("Missing parking data or file"); return; } @@ -112,16 +113,16 @@ const ParkingEditAfbeelding = ({ parkingdata, onUpdateAfbeelding }: { parkingdat }, } ); - if(! result.ok) { + if (!result.ok) { throw Error('Er ging iets fout bij het opslaan. Probeer je het later nog eens.') } - onUpdateAfbeelding(); - } catch(err: any) { - if(err.message) alert(err.message); + onUpdateAfbeelding && onUpdateAfbeelding(); + } catch (err: any) { + if (err.message) alert(err.message); else alert(err); } - + // onUpdateAfbeelding(data.url); } catch (error) { console.error(error); @@ -129,7 +130,7 @@ const ParkingEditAfbeelding = ({ parkingdata, onUpdateAfbeelding }: { parkingdat } }; - if(parkingdata.Image) { + if (parkingdata.Image) { // render current image return (
@@ -137,14 +138,14 @@ const ParkingEditAfbeelding = ({ parkingdata, onUpdateAfbeelding }: { parkingdat
+ className="mt-4 w-max-content mx-auto" + onClick={onRemoveAfbeelding} + > + Afbeelding verwijderen +
- ) - } else if(previewUrl) { + ) + } else if (previewUrl) { // render preview image return (
@@ -165,8 +166,8 @@ const ParkingEditAfbeelding = ({ parkingdata, onUpdateAfbeelding }: { parkingdat Afbeelding gebruiken
@@ -197,9 +198,9 @@ const ParkingEditAfbeelding = ({ parkingdata, onUpdateAfbeelding }: { parkingdat type="file" onChange={onFileUploadChange} /> - +
- ); + ); } }; diff --git a/src/components/parking/ParkingEditCapaciteit.tsx b/src/components/parking/ParkingEditCapaciteit.tsx index 16c2a91..56337ac 100644 --- a/src/components/parking/ParkingEditCapaciteit.tsx +++ b/src/components/parking/ParkingEditCapaciteit.tsx @@ -1,22 +1,25 @@ -import React, {useEffect} from "react"; -import HorizontalDivider from "~/components/HorizontalDivider"; - -import SectionBlock from "~/components/SectionBlock"; +import React, { useEffect } from "react"; import FormInput from "~/components/Form/FormInput"; import FormCheckbox from "~/components/Form/FormCheckbox"; -import { ParkingDetailsType } from '~/types'; - +import { ParkingDetailsType, ParkingSections } from '~/types'; +import { ParkingEditUpdateStructure } from './ParkingEdit'; + import { getAllFietstypen } from "~/utils/parkings"; +import { fietstypen } from "@prisma/client"; + +export type CapaciteitType = { ID: string, Name: string }; -type capacitydata = { +export type capacitydata = { unknown: boolean; total: number; - detailed: { [key: string]: { - Toegestaan: boolean; - Capaciteit: number; - } }; + detailed: { + [key: string]: { // fietstype + Toegestaan: boolean; + Capaciteit: number; + } + }; }; const calculateCapacityData = (parking: ParkingDetailsType): capacitydata | null => { @@ -42,9 +45,12 @@ const calculateCapacityData = (parking: ParkingDetailsType): capacitydata | null } } - capacity.detailed[name].Toegestaan = capacity.detailed[name].Toegestaan || data.Toegestaan!==null && data.Toegestaan; - capacity.detailed[name].Capaciteit += data.Capaciteit||0; - capacity.total += data.Capaciteit||0; + const item = capacity.detailed[name]; + if (item) { + item.Toegestaan = item.Toegestaan || data.Toegestaan !== null && data.Toegestaan; + item.Capaciteit += data.Capaciteit || 0; + capacity.total += data.Capaciteit || 0; + } }); }); } @@ -56,67 +62,65 @@ const calculateCapacityData = (parking: ParkingDetailsType): capacitydata | null } }; -const getCapacityForFietstype = (fietstypeName, capacitydata, localChanges) => { - if(! fietstypeName) return 0; - if(! capacitydata || ! capacitydata.detailed) return 0; +const getCapacityForFietstype = (fietstypeName: string, capacitydata: capacitydata | null, localChanges: ParkingSections | undefined) => { + if (!capacitydata) return 0; + + // console.log("**** GC", JSON.stringify(localChanges, null, 2)); // First, check if we have the value in our local changes - if(localChanges) { - const foundRelated = localChanges[0].secties_fietstype.filter((x) => { - return x.fietstype.Name === fietstypeName; - }).pop(); - if(foundRelated) { - return Number(foundRelated.Capaciteit); + if (localChanges) { + let foundRelated = undefined; + if (localChanges.length > 0 && localChanges[0] !== undefined) { + foundRelated = localChanges[0].secties_fietstype.filter((x) => { + return x.fietstype.Name === fietstypeName; + }).pop(); + if (foundRelated) { + return Number(foundRelated.Capaciteit); + } } } // If not, get the original value from the database const capacityForFietstype = capacitydata.detailed[fietstypeName]; - if(capacityForFietstype && capacityForFietstype.Capaciteit) { + if (capacityForFietstype && capacityForFietstype.Capaciteit) { return Number(capacityForFietstype.Capaciteit); } return 0; } -const getAllowedValueForFietstype = (fietstypeName, capacitydata, localChanges) => { - if(! fietstypeName) return 0; - if(! capacitydata || ! capacitydata.detailed) return 0; +const getAllowedValueForFietstype = (fietstypeName: string, capacitydata: capacitydata | null, localChanges: ParkingSections | undefined): boolean => { + if (!capacitydata) return false; // First, check if we have the value in our local changes - if(localChanges) { + if (localChanges && localChanges[0]) { const foundRelated = localChanges[0].secties_fietstype.filter((x) => { return x.fietstype.Name === fietstypeName; }).pop(); - if(foundRelated) { - return foundRelated.Toegestaan; + if (foundRelated) { + return foundRelated.Toegestaan || false; } } // If not, get the original value from the database const capacityForFietstype = capacitydata.detailed[fietstypeName]; - if(capacityForFietstype && capacityForFietstype.Toegestaan) { + if (capacityForFietstype && capacityForFietstype.Toegestaan) { return true; } return false; } -const toggleActive = (fietsenstalling_secties, fietstypeName, isActive): { - fietsenstalling_secties: any, - fietstypeName: String, // 'fietstype' - isActive: Boolean // 'Toegestaan' -} => { +const toggleActive = (fietsenstalling_secties: ParkingSections, fietstypeName: string, isActive: boolean): ParkingSections => { // It's mandatory to have at least 1 section - if(! fietsenstalling_secties) return fietsenstalling_secties; - if(! fietsenstalling_secties[0]) return fietsenstalling_secties; - if(! fietsenstalling_secties[0].secties_fietstype) return fietsenstalling_secties; + if (!fietsenstalling_secties || !fietsenstalling_secties[0]) return fietsenstalling_secties; + if (!fietsenstalling_secties[0].secties_fietstype) return fietsenstalling_secties; let didUpdateSomething = false; // Update the isActive/'Toegestaan' value for the 'fietstype' given fietsenstalling_secties[0].secties_fietstype = fietsenstalling_secties[0].secties_fietstype.map(x => { - if(x.fietstype.Name === fietstypeName) { + if (x.fietstype.Name === fietstypeName) { didUpdateSomething = true; } // Update 'Toegestaan' value if needed @@ -124,12 +128,12 @@ const toggleActive = (fietsenstalling_secties, fietstypeName, isActive): { Capaciteit: x.Capaciteit, fietstype: x.fietstype, Toegestaan: x.fietstype.Name === fietstypeName - ? isActive // Update - : x.Toegestaan // Or keep existing value + ? isActive // Update + : x.Toegestaan // Or keep existing value } }); - - if(! didUpdateSomething) { + + if (!didUpdateSomething) { // If above script didn't update a value, we should add this fietstypeName const newObject = { Toegestaan: isActive, @@ -145,21 +149,26 @@ const toggleActive = (fietsenstalling_secties, fietstypeName, isActive): { return fietsenstalling_secties; } -const handleCapacityChange = (fietsenstalling_secties, fietstypeName, amount): { - fietsenstalling_secties: any, - fietstypeName: String, // 'fietstype' - amount: Number // 'Toegestaan' -} => { +const handleCapacityChange = (fietsenstalling_secties: ParkingSections, fietstypeName: any, amountstr: string): ParkingSections => { // It's mandatory to have at least 1 section - if(! fietsenstalling_secties) return fietsenstalling_secties; - if(! fietsenstalling_secties[0]) return fietsenstalling_secties; - if(! fietsenstalling_secties[0].secties_fietstype) return fietsenstalling_secties; + if (!fietsenstalling_secties) return fietsenstalling_secties; + if (!fietsenstalling_secties[0]) return fietsenstalling_secties; + if (!fietsenstalling_secties[0].secties_fietstype) return fietsenstalling_secties; + + // check for valid amount + let amount = 0 + try { + amount = parseInt(amountstr); + } catch (e) { + console.error("handleCapacityChange - amount is not a number"); + return fietsenstalling_secties; + } let didUpdateSomething = false; // Update the isActive/'Toegestaan' value for the 'fietstype' given fietsenstalling_secties[0].secties_fietstype = fietsenstalling_secties[0].secties_fietstype.map(x => { - if(x.fietstype.Name === fietstypeName) { + if (x.fietstype.Name === fietstypeName) { didUpdateSomething = true; } // Update 'Toegestaan' value if needed @@ -169,8 +178,8 @@ const handleCapacityChange = (fietsenstalling_secties, fietstypeName, amount): { Toegestaan: x.Toegestaan } }); - - if(! didUpdateSomething) { + + if (!didUpdateSomething) { // If above script didn't update a value, we should add this fietstypeName const newObject = { Toegestaan: false, @@ -193,12 +202,10 @@ const ParkingEditCapaciteit = ({ }: { parkingdata: ParkingDetailsType, capaciteitChanged: Function, - update: any + update: ParkingEditUpdateStructure }) => { // Variable to store the 'alle fietstypen' response - const [allFietstypen, setAllFietstypen ] = React.useState([]); - // Variable to store changed values in - const [updated, setUpdated] = React.useState([]); + const [allFietstypen, setAllFietstypen] = React.useState([]); // Set 'allFietstypen' local state React.useEffect(() => { @@ -206,16 +213,15 @@ const ParkingEditCapaciteit = ({ const result = await getAllFietstypen(); setAllFietstypen(result); })(); - },[]) + }, []) - let content = null; const capacitydata = calculateCapacityData(parkingdata); - // console.log("#### capacitydata", capacitydata, '#### parkingdata', parkingdata); - if (capacitydata===null || capacitydata?.unknown) { + let content = null; + if (capacitydata === null || capacitydata?.unknown) { content = "Onbekend"; - } else if (capacitydata.detailed===null || Object.keys(capacitydata.detailed).length === 0) { + } else if (capacitydata.detailed === null || Object.keys(capacitydata.detailed).length === 0) { content = ( <>
{parkingdata.Capacity}
@@ -252,6 +258,11 @@ const ParkingEditCapaciteit = ({ <>
{allFietstypen.map(x => { + if (x.Name === null) { + console.warn('Fietstype has no name', x); + return null; + } + const capacity = getCapacityForFietstype(x.Name, capacitydata, update.fietsenstalling_secties); const isAllowed = getAllowedValueForFietstype(x.Name, capacitydata, update.fietsenstalling_secties); return @@ -262,8 +273,9 @@ const ParkingEditCapaciteit = ({ { + console.log("**** PDFS", JSON.stringify(parkingdata.fietsenstalling_secties, null, 2)); const newFietsenstallingSecties = handleCapacityChange(parkingdata.fietsenstalling_secties, x.Name, e.target.value); const getNewCapacity = () => { let newCapacity = parkingdata; @@ -272,13 +284,16 @@ const ParkingEditCapaciteit = ({ }; capaciteitChanged(getNewCapacity().fietsenstalling_secties) }} - style={{width: '100px'}} + style={{ width: '100px' }} />
{ + onChange={(e) => { + if (null === parkingdata.fietsenstalling_secties) return; + if (null === x.Name) return; + const newFietsenstallingSecties = toggleActive(parkingdata.fietsenstalling_secties, x.Name, e.target.checked); const getNewCapacity = () => { let newCapacity = parkingdata; diff --git a/src/components/parking/ParkingEditLocation.tsx b/src/components/parking/ParkingEditLocation.tsx index 1de7141..2e182f9 100644 --- a/src/components/parking/ParkingEditLocation.tsx +++ b/src/components/parking/ParkingEditLocation.tsx @@ -76,6 +76,10 @@ function ParkingEditLocation({ } // } }); + mapboxMap.on('styleimagemissing', (e) => { + mapboxMap.addImage(e.id, { width: 0, height: 0, data: new Uint8Array(0) }); + }); + // Function that executes if component unloads: return () => { diff --git a/src/components/parking/ParkingEditOpening.tsx b/src/components/parking/ParkingEditOpening.tsx index 3c1b30b..95c995a 100644 --- a/src/components/parking/ParkingEditOpening.tsx +++ b/src/components/parking/ParkingEditOpening.tsx @@ -1,4 +1,4 @@ -import React, {useState, useEffect} from "react"; +import React, { useState, useEffect } from "react"; import type { ParkingDetailsType, DayPrefix } from "~/types/"; import SectionBlock from "~/components/SectionBlock"; @@ -8,19 +8,19 @@ import FormTextArea from "~/components/Form/FormTextArea"; import FormCheckbox from "~/components/Form/FormCheckbox"; type OpeningDetailsType = { - Open_ma : Date, + Open_ma: Date, Dicht_ma: Date, - Open_di : Date, + Open_di: Date, Dicht_di: Date, - Open_wo : Date, + Open_wo: Date, Dicht_wo: Date, - Open_do : Date, + Open_do: Date, Dicht_do: Date, - Open_vr : Date, + Open_vr: Date, Dicht_vr: Date, - Open_za : Date, + Open_za: Date, Dicht_za: Date, - Open_zo : Date, + Open_zo: Date, Dicht_zo: Date, } @@ -30,11 +30,11 @@ export type OpeningChangedType = { const getOpenTimeKey = (day: DayPrefix): keyof OpeningDetailsType => { return ('Open_' + day) as keyof OpeningDetailsType; -} +} const getDichtTimeKey = (day: DayPrefix): keyof OpeningDetailsType => { return ('Dicht_' + day) as keyof OpeningDetailsType; -} +} const formatOpeningTimesForEdit = ( parkingdata: OpeningDetailsType, @@ -56,75 +56,75 @@ const formatOpeningTimesForEdit = ( let value = `${hoursopen}:${minutesopen} - ${hoursclose}:${minutesclose}`; let diff = Math.abs((tmpclose.getTime() - tmpopen.getTime()) / 1000); - if(diff>=86340) { + if (diff >= 86340) { value = '24h' - } else if(diff===0) { + } else if (diff === 0) { value = 'gesloten' } - const showtimes = diff>0 && diff<86340; + const showtimes = diff > 0 && diff < 86340; return ( {label} - =86340} onChange={handlerChangeChecks(day, true)}> + = 86340} onChange={handlerChangeChecks(day, true)}> 24h - + gesloten - { showtimes ? + {showtimes ?
+ />
- : - null } + : + null} - { showtimes ? 't/m': '' } + {showtimes ? 't/m' : ''} - { showtimes ? -
- + - + -
- : - null } - - [{value}] + /> +
+ : + null} + + [{value}] ); }; -const extractParkingFields = (parkingdata: ParkingDetailsType):OpeningDetailsType => { +const extractParkingFields = (parkingdata: ParkingDetailsType): OpeningDetailsType => { return { Open_ma: parkingdata.Open_ma, Dicht_ma: parkingdata.Dicht_ma, @@ -165,11 +165,11 @@ const setMinutesInDate = (date: Date, newMinutes: number): Date => { const ParkingEditOpening = ({ parkingdata, openingChanged }: { parkingdata: any, openingChanged: Function }) => { const startValues = extractParkingFields(parkingdata); - const [changes, setChanges ] = useState({}); - const [openingstijden, setOpeningstijden ] = useState(undefined); + const [changes, setChanges] = useState({}); + const [openingstijden, setOpeningstijden] = useState(undefined); - useEffect(()=>{ - if(Object.keys(changes).length>0) { + useEffect(() => { + if (Object.keys(changes).length > 0) { openingChanged(changes, openingstijden); } else { openingChanged(undefined, openingstijden); @@ -177,25 +177,25 @@ const ParkingEditOpening = ({ parkingdata, openingChanged }: { parkingdata: any, }, [changes, openingstijden]); // Function that runs if the capacity changes - const handleChange = (day: DayPrefix, isOpeningTime:boolean, isHoursField:boolean) => (e: React.ChangeEvent) => { + const handleChange = (day: DayPrefix, isOpeningTime: boolean, isHoursField: boolean) => (e: React.ChangeEvent) => { e.preventDefault(); const key = isOpeningTime ? getOpenTimeKey(day) : getDichtTimeKey(day); // determine new time - + // let oldtime: Date = new Date((key in currentValues) ? currentValues[key]: startValues[key]); - let oldtime: Date = new Date((key in changes) ? changes[key] as Date: startValues[key]); + let oldtime: Date = new Date((key in changes) ? changes[key] as Date : startValues[key]); let newtime = undefined; - const newval:number = Number(e.target.value); - if(isHoursField) { - if(newval<0 || newval>23) { + const newval: number = Number(e.target.value); + if (isHoursField) { + if (newval < 0 || newval > 23) { return; // invalid value } newtime = setHourInDate(oldtime, newval); } else { - if(newval<0 || newval>59) { + if (newval < 0 || newval > 59) { return; // invalid value } @@ -203,7 +203,7 @@ const ParkingEditOpening = ({ parkingdata, openingChanged }: { parkingdata: any, } // setCurrentValues({...currentValues, [key]: newtime.toString()}); - setChanges({...changes, [key]: newtime}); + setChanges({ ...changes, [key]: newtime }); } // Function that runs if the active state changes @@ -211,24 +211,24 @@ const ParkingEditOpening = ({ parkingdata, openingChanged }: { parkingdata: any, const openkey = getOpenTimeKey(day) const dichtkey = getDichtTimeKey(day); - if(e.target.checked) { + if (e.target.checked) { const newopen = new Date(0); // add 24 hours for full day open, otherwise 0 for full day closed - const newdicht = new Date(is24hourscheck ? (86340*1000): 0); + const newdicht = new Date(is24hourscheck ? (86340 * 1000) : 0); - setChanges({...changes, [openkey]: newopen, [dichtkey]: newdicht}); + setChanges({ ...changes, [openkey]: newopen, [dichtkey]: newdicht }); } else { const newopen = setHourInDate(new Date(0), 10); const newdicht = setHourInDate(new Date(0), 17); - setChanges({...changes, [openkey]: newopen, [dichtkey]: newdicht}); + setChanges({ ...changes, [openkey]: newopen, [dichtkey]: newdicht }); } } // Function that runs if extra description field changes const handleChangeOpeningstijden = () => (e: React.ChangeEvent) => { - if(e.target.value===parkingdata.Openingstijden) { + if (e.target.value === parkingdata.Openingstijden) { setOpeningstijden(undefined); } else { setOpeningstijden(e.target.value); @@ -236,8 +236,8 @@ const ParkingEditOpening = ({ parkingdata, openingChanged }: { parkingdata: any, } const data = Object.assign( - {...startValues}, - {...changes} + { ...startValues }, + { ...changes } ); return ( @@ -246,26 +246,28 @@ const ParkingEditOpening = ({ parkingdata, openingChanged }: { parkingdata: any, heading="Openingstijden" > - {formatOpeningTimesForEdit(data, "ma", "Maandag", handleChange, handleChangeChecks)} - {formatOpeningTimesForEdit(data, "di", "Dinsdag", handleChange, handleChangeChecks)} - {formatOpeningTimesForEdit(data, "wo", "Woensdag", handleChange, handleChangeChecks)} - {formatOpeningTimesForEdit(data, "do", "Donderdag", handleChange, handleChangeChecks)} - {formatOpeningTimesForEdit(data, "vr", "Vrijdag", handleChange, handleChangeChecks)} - {formatOpeningTimesForEdit(data, "za", "Zaterdag", handleChange, handleChangeChecks)} - {formatOpeningTimesForEdit(data, "zo", "Zondag", handleChange, handleChangeChecks)} + + {formatOpeningTimesForEdit(data, "ma", "Maandag", handleChange, handleChangeChecks)} + {formatOpeningTimesForEdit(data, "di", "Dinsdag", handleChange, handleChangeChecks)} + {formatOpeningTimesForEdit(data, "wo", "Woensdag", handleChange, handleChangeChecks)} + {formatOpeningTimesForEdit(data, "do", "Donderdag", handleChange, handleChangeChecks)} + {formatOpeningTimesForEdit(data, "vr", "Vrijdag", handleChange, handleChangeChecks)} + {formatOpeningTimesForEdit(data, "za", "Zaterdag", handleChange, handleChangeChecks)} + {formatOpeningTimesForEdit(data, "zo", "Zondag", handleChange, handleChangeChecks)} +
- ", "\n") : '') : openingstijden} - style={{width: '100%', borderRadius: '0 10px 10px 0'}} - className="w-full" - onChange={handleChangeOpeningstijden()} - rows={10} - /> + ", "\n") : '') : openingstijden} + style={{ width: '100%', borderRadius: '0 10px 10px 0' }} + className="w-full" + onChange={handleChangeOpeningstijden()} + rows={10} + />
); diff --git a/src/components/parking/ParkingView.tsx b/src/components/parking/ParkingView.tsx index fe02fca..c07c950 100644 --- a/src/components/parking/ParkingView.tsx +++ b/src/components/parking/ParkingView.tsx @@ -81,7 +81,7 @@ const ParkingView = ({