From 01c30269c2ae5e56776f0efb60c7e7fae75f5505 Mon Sep 17 00:00:00 2001
From: Flatlogic Bot
Date: Tue, 9 Sep 2025 20:55:14 +0000
Subject: [PATCH] 1.1
---
.gitignore | 5 +
502.html | 2 +-
README.md | 2 +-
app-shell/src/_schema.json | 7 +-
backend/README.md | 6 +-
backend/package.json | 4 +-
backend/src/config.js | 4 +-
backend/src/db/api/bookings.js | 42 ++
backend/src/db/api/clients.js | 5 +
backend/src/db/api/powersportvehicles.js | 609 ++++++++++++++++++
backend/src/db/api/vehicles.js | 12 +
backend/src/db/db.config.js | 2 +-
backend/src/db/migrations/1757449078196.js | 51 ++
backend/src/db/migrations/1757450562030.js | 90 +++
backend/src/db/migrations/1757450589992.js | 49 ++
backend/src/db/migrations/1757450623285.js | 49 ++
backend/src/db/migrations/1757450719241.js | 36 ++
backend/src/db/migrations/1757450790548.js | 49 ++
backend/src/db/migrations/1757450827833.js | 49 ++
backend/src/db/migrations/1757450860278.js | 49 ++
backend/src/db/migrations/1757450889961.js | 51 ++
backend/src/db/migrations/1757450927629.js | 51 ++
backend/src/db/migrations/1757450953122.js | 51 ++
backend/src/db/migrations/1757450984490.js | 54 ++
backend/src/db/migrations/1757451017415.js | 51 ++
backend/src/db/migrations/1757451046441.js | 51 ++
backend/src/db/migrations/1757451083313.js | 51 ++
backend/src/db/models/bookings.js | 8 +
backend/src/db/models/clients.js | 8 +
backend/src/db/models/powersportvehicles.js | 117 ++++
backend/src/db/models/vehicles.js | 6 +
.../db/seeders/20200430130760-user-roles.js | 51 ++
.../db/seeders/20231127130745-sample-data.js | 336 +++++-----
backend/src/db/seeders/20250909204242.js | 87 +++
backend/src/index.js | 12 +-
backend/src/routes/powersportvehicles.js | 495 ++++++++++++++
backend/src/routes/vehicles.js | 1 +
backend/src/services/notifications/list.js | 2 +-
backend/src/services/powersportvehicles.js | 121 ++++
backend/src/services/search.js | 14 +
docker/docker-compose.yml | 2 +-
frontend/README.md | 2 +-
frontend/json/runtimeError.json | 1 +
frontend/src/components/AsideMenuLayer.tsx | 2 +-
.../src/components/Bookings/CardBookings.tsx | 13 +
.../src/components/Bookings/ListBookings.tsx | 11 +
.../Bookings/configureBookingsCols.tsx | 20 +
.../CardPowersportvehicles.tsx | 239 +++++++
.../ListPowersportvehicles.tsx | 176 +++++
.../TablePowersportvehicles.tsx | 489 ++++++++++++++
.../configurePowersportvehiclesCols.tsx | 235 +++++++
.../src/components/Vehicles/CardVehicles.tsx | 11 +
.../src/components/Vehicles/ListVehicles.tsx | 5 +
.../Vehicles/configureVehiclesCols.tsx | 15 +
.../components/WebPageComponents/Footer.tsx | 4 +-
.../components/WebPageComponents/Header.tsx | 4 +-
frontend/src/helpers/dataFormatter.js | 19 +
frontend/src/menuAside.ts | 8 +
frontend/src/pages/_app.tsx | 4 +-
frontend/src/pages/bookings/[bookingsId].tsx | 13 +
frontend/src/pages/bookings/bookings-edit.tsx | 13 +
frontend/src/pages/bookings/bookings-list.tsx | 2 +
frontend/src/pages/bookings/bookings-new.tsx | 12 +
.../src/pages/bookings/bookings-table.tsx | 2 +
frontend/src/pages/bookings/bookings-view.tsx | 6 +
frontend/src/pages/clients/clients-view.tsx | 95 +++
frontend/src/pages/dashboard.tsx | 36 ++
frontend/src/pages/index.tsx | 16 +-
frontend/src/pages/login.tsx | 2 +-
.../[powersportvehiclesId].tsx | 288 +++++++++
.../powersportvehicles-edit.tsx | 286 ++++++++
.../powersportvehicles-list.tsx | 185 ++++++
.../powersportvehicles-new.tsx | 222 +++++++
.../powersportvehicles-table.tsx | 184 ++++++
.../powersportvehicles-view.tsx | 249 +++++++
frontend/src/pages/privacy-policy.tsx | 2 +-
frontend/src/pages/terms-of-use.tsx | 2 +-
frontend/src/pages/users/users-view.tsx | 4 +
frontend/src/pages/vehicles/[vehiclesId].tsx | 10 +
frontend/src/pages/vehicles/vehicles-edit.tsx | 10 +
frontend/src/pages/vehicles/vehicles-list.tsx | 6 +
frontend/src/pages/vehicles/vehicles-new.tsx | 10 +
.../src/pages/vehicles/vehicles-table.tsx | 6 +
frontend/src/pages/vehicles/vehicles-view.tsx | 5 +
frontend/src/pages/web_pages/contact.tsx | 12 +-
frontend/src/pages/web_pages/faq.tsx | 10 +-
frontend/src/pages/web_pages/home.tsx | 16 +-
.../powersportvehiclesSlice.ts | 250 +++++++
frontend/src/stores/store.ts | 2 +
89 files changed, 5713 insertions(+), 243 deletions(-)
create mode 100644 backend/src/db/api/powersportvehicles.js
create mode 100644 backend/src/db/migrations/1757449078196.js
create mode 100644 backend/src/db/migrations/1757450562030.js
create mode 100644 backend/src/db/migrations/1757450589992.js
create mode 100644 backend/src/db/migrations/1757450623285.js
create mode 100644 backend/src/db/migrations/1757450719241.js
create mode 100644 backend/src/db/migrations/1757450790548.js
create mode 100644 backend/src/db/migrations/1757450827833.js
create mode 100644 backend/src/db/migrations/1757450860278.js
create mode 100644 backend/src/db/migrations/1757450889961.js
create mode 100644 backend/src/db/migrations/1757450927629.js
create mode 100644 backend/src/db/migrations/1757450953122.js
create mode 100644 backend/src/db/migrations/1757450984490.js
create mode 100644 backend/src/db/migrations/1757451017415.js
create mode 100644 backend/src/db/migrations/1757451046441.js
create mode 100644 backend/src/db/migrations/1757451083313.js
create mode 100644 backend/src/db/models/powersportvehicles.js
create mode 100644 backend/src/db/seeders/20250909204242.js
create mode 100644 backend/src/routes/powersportvehicles.js
create mode 100644 backend/src/services/powersportvehicles.js
create mode 100644 frontend/json/runtimeError.json
create mode 100644 frontend/src/components/Powersportvehicles/CardPowersportvehicles.tsx
create mode 100644 frontend/src/components/Powersportvehicles/ListPowersportvehicles.tsx
create mode 100644 frontend/src/components/Powersportvehicles/TablePowersportvehicles.tsx
create mode 100644 frontend/src/components/Powersportvehicles/configurePowersportvehiclesCols.tsx
create mode 100644 frontend/src/pages/powersportvehicles/[powersportvehiclesId].tsx
create mode 100644 frontend/src/pages/powersportvehicles/powersportvehicles-edit.tsx
create mode 100644 frontend/src/pages/powersportvehicles/powersportvehicles-list.tsx
create mode 100644 frontend/src/pages/powersportvehicles/powersportvehicles-new.tsx
create mode 100644 frontend/src/pages/powersportvehicles/powersportvehicles-table.tsx
create mode 100644 frontend/src/pages/powersportvehicles/powersportvehicles-view.tsx
create mode 100644 frontend/src/stores/powersportvehicles/powersportvehiclesSlice.ts
diff --git a/.gitignore b/.gitignore
index e427ff3..d0eb167 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,8 @@
node_modules/
*/node_modules/
*/build/
+
+**/node_modules/
+**/build/
+.DS_Store
+.env
\ No newline at end of file
diff --git a/502.html b/502.html
index 237b399..06f93db 100644
--- a/502.html
+++ b/502.html
@@ -129,7 +129,7 @@
The application is currently launching. The page will automatically refresh once site is
available.
-
Etherra
+
Powersport Rentals
A multitenant P2P RV rental marketplace with booking and search features.
diff --git a/README.md b/README.md
index 112c637..0a8c0ab 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,6 @@
-# Etherra
+# Powersport Rentals
## This project was generated by [Flatlogic Platform](https://flatlogic.com).
diff --git a/app-shell/src/_schema.json b/app-shell/src/_schema.json
index cd34342..94379ee 100644
--- a/app-shell/src/_schema.json
+++ b/app-shell/src/_schema.json
@@ -1,5 +1,4 @@
-
-
{
- "Initial version": "{\"iv\":\"6B24Z7uNdZXRI9iT\",\"encryptedData\":\"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\"}"
-}
+ "Initial version": "{\"iv\":\"6B24Z7uNdZXRI9iT\",\"encryptedData\":\"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\"}",
+ "1.1": "{\"iv\":\"+PGLW8HbB2oCs3XG\",\"encryptedData\":\"F7Yf7Tfjg13GisKlKMXX8+IXqqzK5O5oA6MriYV6aye0oHhin2I5VdqDmm4yuQrqV1qm/ZiW7lkGNAdL/qrojfrPvVDARcA4aY/okKzOjm35fuFkUhlTc0NTfx0LDf8MTEMf8EpntC9exez4R2PSwvGRkrSKkAJBdTeEpG5EQXy7qs7mOwyhFaRZ/ZEBn0lUGuQ+g0SBuM1RlqDfYuTAt+iS+ORxrGmuf2YB2MCceY+7HX13uGcxUDhwFrKHDKyVbkU7rfZ777Z71cRd8JuZ9yCi/LOLE+lZP4z7SUsgVCVeMacT2c6YxGIp1ezFIlB7ih93QwNZpR4OSNz6vP72yEuAnSZ8EhAim8Z3IKEgDsB6qSR/XKk837eXE0sK4jC8MLNwLPqcN676sQBNu5Mm7ncHtYxTyUzeixhmuJrivQ1CWtVJwk2PfMKiI9XEi8xo19uSQHm5oexyxPdoTlWIQCYr+JNb9uNK48Rz09dvf0E7ObXhtIOAheMVfEcQEHxfw8GoLGNkXLczVxg1nA+1Djf9h/CXmpC0oBKrBE+AE4Y4Aj796In0OSouSc01KQwPVp5QruSQ/0CbXHXa8AJoAxLQ3w/qFKEF4VnzZXXnwzMLe4WuzC2ULuHb6ql9q4TBZs76ET0f+Mtqxt8vFVm9+52tvmni94BrmIKOVSPHaoZoBZG4Uk+GMB/lnBY71TbNBDHMyiv7P7N4P26UBO5UylorviwE23SdZnXw39ma7ATvVvOf7QqyWmP7tPmXb4r2wRz/5jjRXZrgSHMkiDMV/372/gft3kKee9cNIgURNJF+Awyc2Vpm25UsrtHmSuHif7uwB/4VWnNAvZVaI3n/kGIJ0dUYhYbt2OoavmfY3AXJXvInAzug4HftH6tNALbXkoSfqKjHVulRhfs8ypSdJsHemGTtYYtXP2fm+MK+NUWOPe43M7okk0jPAFYGcfplFiW8a55tryUUAJY8CNHDLQhHOKXjPa86+Thev9Zrqk9DJ1rjs1no0kcMyzXQ4Q1VjQnL1WaKnJ1OryQUk7AAIrBeVkpdu2ORmAyjJiW1QWkntywurHdHlBBUHralY8eJyuOysN+D+Vp0Em+ysKOOKt7tPWSLeByoRoQSQ73+FO4/zQsZgWj6TS3epYmCyhe/ir4cQLZPPB5f8/W2EixK4+jFkX8BW7vfKxKStHg5sUIdkoC90OoODrReUkbs1f47BUNfQXQRwMMreuc530+ROawiP4QNQ+ar70sWSc+5YzXQ8iNdp28K2UgC1kvtkaIjiJCrqw6Z/Il2ROXA6tW3gstTLWs7IelUWBJYAJkLDyCIPL9mJfR9Wpfv4L2UAL7P9q2r1NpTDl4AcClsj5lbDaZomiAZaBVa2GETggu3QdTHQ/QQN49ifQliNBx3OQF7FLSoDf/Qw4bdQ6impaTgffjR8GOv/M/HNs6l63rtUEmG7/LgmoyJbhiT+4+VjbHLWHuV5tmG6bjpkdNBDsy+gOOgOmgnc9twqOcqI0xMVT8acPylM+zqV8m38APdfFV/3x/HT5IbSJpWP0dhOdZ7v+vNWam4ORm3P3lZhK7tu2FjJ64TSLCqdLZcL/ZcIvs+oXwZaXrG4ZNwpPBOVJ5ZgKgGjpikfUo8YgRAN64kwhpwGgf9NgA+E1OZjaLz2EHYgV9L4FgsG8JfoYDdgcUHurmP5TaJmXOwd7VHKhRIiucpYEDN8fAIIqVMNGfUEeJzwXb1IhYrRtE7i4KpbVeFrd0YSb81yV/oGbkRVRVg9Vz+66Qqy/+oKd2gfR4D1olr80bC/7NiSJXSFDjqBxjt2jLfCe2krnE0icrK8Dryzc7Wamvbd2rPlA8hBIx7qITwbDWJFjG7A0wgzXDnvdKaZCqMHiHwcFNcH6CacUqd9G4QXRBThIvVZt3nSH3IsE1YpfesxURh4Ue41WiyflBdyBmaRlyPnkE/pmTYR482HwliIYS89rEJrEgXoAK55OZTk/rlcdBakC0+CaXH4UMJjeyf7bYLaHVD8DfOR2/hczL/i/+acXaR0+xbVnuSODUCsC0RqmwpMUL9bsHR1/OwGa52AAxxEhr3gZczlarKBgHve0orFWQ53+JKLsxYpk9pDXWb/ChcKsT5qR//Vu9Scfw6J0tnPa7SnD7Nj0Sinz2TMZX6TnKFPZECrK25zqf6KpZ5yn/UcfMFmuc5x/WUIjFFaGmqaf4JWczYGW76DDrYVZI1hGONyrk8yEHxIABBsok2om+ZmmzTiVcymuvfeORHxHRNKp92qXJQJoaB56bIrMM48K2UnwG/LDUigM6GHunaIS4UohDK/6J4tK6UyjefFLIj5XMSKjKLUx9lnxNfWiHeJY9Ax98aindxvLAol5RNSsDzEceZzl3MoaCw2HQPXrxLjpze63G92Qu0uzDdFGJDteg6jd//1ffaYZKv3hpp8kmMYaWygoo8jUfj/OjPBKZ1rkzkbC7rmOr6kRYPW2Ci6zN3Zvw0VdLuEGj+8IQR4cC9zKVkSE9O1fo2RZe/UAleQ2ieLlO6SSb6K3o22P0qjz50HlXmlwMFIr4nwN1kEQ3i7W99Z71MVkoahy9vdrbqwnz6Hgmn+HRi/sRGRVVKvefhie9fMWtaZP/T/S/T5Jg4M579XeGOilnIs89e2eN3WURFGQSHhelYtpBi3zTxty3MLpA21PkzyCc4H9gC+xv3p0qEcU97Wex3+62RqemRa+jgmt2IfnbjhqdaIucyYiLczDlY3+trI/LgFeMUnhr1tACMrWDbbxSwqc+XkvCyH5sOmM/o78YPvFGaBKk72h6v+Z+F1aU7af5d1GwM1gcah04Sn2upt761VyIKm4/vzuCOrXkbJo3QJX5evG90oztDLU9cD6Sy1/Ly5jrz0XvGbWORbA5MvkduBvJCkvii6yPr4MnlsLJtOa8E8kcUpqYqKEVfgrin1Uqh2fxYjbeV9i5xJyP5uLrA9axR66UlYHpzafyHXe//vJXYkDrTRl2xRvXBGk5qkw0V6EGaeyGgOpiQfYQdOlU+oEWNbYldkYuVp4HFM6Ozq6y2OmTprn8vXJNkn7/tXRqf7IOp5p54NQr3PqIrbsJygZS3FEntq/VRFLuWuBLGIk9nRXXFMjx7eJnbwUBGOtYNTfoIrDD7DewbPPbTTad6wj4F0MYtUc0phAEoFE00JuFyWFRkEgll6msy9ItpdAqJYryZK8qupAxghXob8S/h8RxyqdMsX/bXnxHIyMPxjajy1bJtSGdiy+rgQSFIf8BWEvP9HgB8nscNKFqoClJ/VOwkgZITBUIth/QUS4BDSuQnE3c5JJ+03PgH4koEwYO9XHwI4xCN8LtiIj9dicMvNytUlWyPpgzon+kLppstxQagvnCkjNDA17kihdm/nbIhQOPMo2o2+0AAUmRqEjrdooMARfKsIBvRLH0MYvFqfxVbANrkZ2oNimSdMyJ5rDQqDuGZPKtOEONIq/NHYs/JdsSQLJAM1vcxxvzGZWwpzL0wPRwOkoJSUL37NB0oEAq8eoe9le8mzVKGuNJ3x7RVzb07O0aMawaRxIk87jnfxMppCfZ7iidnnbnATRHu4cWYSt2JtfeolA4S2a94GNB2HcLLsAUIUrepTGl+uOUzvUW1QE5y17n/e1p6ZsCFRTkGwGC79nEkdYGekwf0u0pM/5sqSFlGAnBof27wB/cV76kUFxM65A7qiIRjSHC1VFkJ+pF8AFsXpvunPxLTRWwONuopo2xMn48tksiigKudc4ikrXVGQ8a2y+PbzhWkhRpWbWiUUHvN8VfvQ18GjT8KY9qPqHX2ZN376axmn7IAWcXeOmxLf9fvqdsROTK7oUxzzaUdk9LRq5oORCjf9MMhzAKeiPVZjh7aVB2+dHSojnY4/2LAL2JwfJ0AMIPZKBEut8gLwLdW6zMu8d5ARBUiqMwMOtmrWOuU9LMQwogNixKPc6UxfOgSTTnO4T4I5XWjJR7BPsgzx03vAdMP2nVVbczIAP2aJzYa051M7iZLE/tRZnUMprAYYKZ1BHnOUR/Lerwqnw2H0INBowtDqOo0ZWo0M0Ph26zLOOpSAMLFrJk+GrUJKblsv3+ikvCu21P4WvQ5Rnwlv74hlYNxgmiOB3Xs2IyZQaUf2VF7xbF7EIE5dTNQtrXe7BSXr0rQbmFkHI8i7Mkii2ySaaciLhaJstuDR0n1TWYH+v4AR4JyXSUjrfiKbfkR2U/BxEHJ2IF0t8gwcxT/T5EjRdWYaJGmvnFXbEb7i3XiMNKA89fT4Dlg9MBBD1pIW1YM2TER/va3CVvRJPAHCG6+YPRu8cI2gho/4nIlqmcnG6cvS+HNTDaz/WAiK1VYXqt0jl/zuWlOZYUupkGGUg3MudA2C2Tjt99wbdD7cv4aSHbsEZ5cKqUXJo5CV5Qk+cI3X7hDnsMDMsxjFFfgdSzFeid833MF+nzRm7aonORZSe3vvlFdI6xIemVeWqn5KM1UlG8SfRLS+UDKOc3QCyZpt4WADJ5mw+AuK53Dm5wViuSCcn5pdBD5FOXXStBrbNf24TxkMdMJecDnl8xX6ISlxXlpeSfeiugmipySLuT/ZA3Pdqz2WbU18Xm+rc19H4M4h5DRJ0Wf8Le7xl2HJPiW2hF70B2hhUD8CcGwmU372/i+S9+Zh1NPXhoJrdcoFGIR3Ai5hJ0ZZOfd/Ma0rEsqkAIK7ToR1FP1RQ2kunVejr/wPFYSMMuF/jsKaIGmuTDaibMF61pRWmsZbS/4cueiVmC/hiq9t7Fzcr4tV3o5oIIUzi5twVtETECbv1mEXV+UIRu83JEHq27gZwHFAVAy2EyAuWQ5e9N4P3iL1C06PiRc3Tdc6iaV3FVQWTd+fNoQoQAqjgRaAALFsQ5HptRYpvmf9ItSt98MCDyCxHhGk5lzn2gAnDTPiv+jFxZmB/ZI43FbnoXhr2INcCmQUXEJzYxa48Xh091fjSIOokvifrDCy+QNOFF8mitidiQxVydn6pTfOl/NYIgG4G0oji/76Lf0OaJeJZbGZi9TgHOutItriG+9ipY/Ulo3+si9g15F0GQ8/3caxV/6chAEh7DNXVP80twFyjCJLqqzoxmk9iXSaRfOpk+0NvGxQv9iwTmhwsHY7aMN6leGCrn/7hrLcT1231HcK3Z6/WXRExqBnZ9bSc2dnDFV7j5j5ZKN9JvSCMobgPaR2ODGqLZeypWYAN6IGxzWHR1aVvrRkESpgOMNDTJnbFkV1+20Pkk3K6wK7UQV7FusVRxdXOETSsDQHpEgnIEjvkezxflqWtaQkFu331pdlCnPAS5CIzCgMVoDBThumWeh6EAoo8ouFZlOgQNjiZ8O2FXkZwdnDSQHfCIcmoFQ0jeZjknD+IkJsbVndd1Dx5+NKPWGHMmZj7e6lyQZBRgEIt3NUijy+Lb5WwjGYfvlVEEoK6mQgdGuTrtQRu0aXZ7ZbVqXvBYyUqK6cxoaBEPQYwABAuwfMQfVMn7FSniOez1BXoiHt0aqFzNsE9b7o3ZDnsk2n996LKvqo9dsCg/T2Ub9894xBF9lRnKGCkuOumk8ISD8ouI0wexzXs1uDQX0jIHWrBIHZw2Iye4ke01hsuVOVxsBJv/dsQ0pADCO7iYCyqosrEyHrHYaDyOJAcmprtRbhbh31EkJz4T1UOUUE78OEs79dQ5s21JfGSUp6S7t/FoM79kTczb3p2W3r4PvDGzqJ4cudoHrdjszjeXQ4pp1neo2x2aH4PS5EjDjc+O011uaeMKBWWf/LMXOH49hCV4Vi4yLoobUAbVGb2g+PblcQckS5dysw/HCTVdCfonOZOBGge3yARtDfNMVnAdYhRQrU3asZlVIarUMOTvrAgeLEpvwoi3WPI6HsKpVT/mnFpWT4J1LRxP4yuFXT+N+2S5+2Hqzr4z/u2vFYKxUjE00C3U0/xRD2BR4I9eZC3rsSUL3Cegl3fQBBzFixeaLoHN8gNqLA0qMBuiunw2SSFLp6RPVFzIY8UWKbBbw0XQPpIZE+Sm3xzxuvgojV540V79l5h+nLlh5upckUs/U4WKsK47QLs/ppWrLBYz6YqMfnuAQ85CkvxujwdfA6G0kAPCrMuB9cAVy4PE2qGha65AZES/S1z5ysPHFsiceU1ijcSxo0byw+EBW5g5gc6WUdqfdU4kf1b1JHXoe8Cu66rpi+gRzaiTsVUOPGRL7y1XM6XOut+ZWEHIwe6FCwUjQezvdbo0zzIQcPiekID8JSHUtezkzCl2A1IlMFoKvv4VIeJMGBPwD6J+Bw0jDau4ivEOIj0B5nT+59aOqTsBMF0d+gTlluYsHvPqQngknhc3fXSdkGYUQJ/62VFDfMk63op58lC2kMJjZaER87Yx7Mwmb5A3lyd+tc7XN98QdHrdBkdRE4MqDYbjW+3iPo0YuyDCB1e4WegbDsoBunGenwwFzH15qhp16R3DNnuTjwqfPkSlBw19+t6+nw7V1jpBbP9eHS86LoyY3uJU8F8G3q0Xdi98JT1iDDn+0PR+rbd9agaigAXbZyPsGipSyCOPSpQNSLNH/noXOUhuA5sr10Xg5bF91R6a1M7jNXJiVZVFVyQa9iKPwAYEnfKDtQemgUIXoQXv2AOfmZOCJ4PSjua5N1u8IH4/gkNGFTxKNvXt2blB5PlxyCL+s/VzBedppvqvjEohY7s7IZMaWoRHEN1k5SgTloEMKDiQUDqWoawhvuJO9I//tX0stlhV4k4tZkkUyVqAxvIVAj5YVhtBQAXQB5WxqTMnbMagBbA7rUNoRe6vkexoGSD40rPZdUZXMTnuIz5UtLf8I11yCBDhG0OkxjSh926iKJv9oS8GJrArj16w5I/17erjnQenyQNQ7oMDa9F749unHhip+1I9bo3XD8ILluKUZWc1coocW7V9MS4T/C6vms1adnt5U/L4yEQDEe72rMYa+nyop9LMjBGesTZfeQAeGfMdC8TjxWWjfr3eEVQf4c7SekntFk9TpCESauTAIRspdL7F30lWuAbVMOVSPe4F22BPg+uwcogonoOYJA7yuzG1JXLDNFO/NU1VNLZIcvkecsDm0HOgFxEAjPfcFu8OZ5jtE4bwFZRO8zaYwUe+nVCIhieN8ChnDRP4N2AnQ6atuORvOWAzli5Hixg0Rmn9PTx/7SuK/88ysjBiMRBdK8tfaCxbBUNo5k4RdoaZ38Rdg1uBJM5v0EXI6TUO4SMeRSH7jXsBa6+foXhSwOqxiSIBhXKWKLA1mSXutsMV6zZKraU0fwDkh10Eclezc1TyOyNuxutRzgIb8plNhMb3/cDcKSubsZuS3LtbS2GHe09yBUeLYOehtVkfpLh5jlyG2/tHLOqyrtcmf3us8C0CvkS1R/skH2rhAiU1dj84IZDXVcCFN1+vjR1is9FirZQGTksZRgxx6sdDnlMNF39xtK6Yx4JXTSsIflzocENgfRy1QPXuilb8wxNdOIuYO6z8OVVicuu2zVbjYvopzQMbU8FKtGGKowV97o3zDFIbD9NzabSe3d7ehedGX29aP2ZkpJfrCt4DthlgZZlXwQtvLVNtzbvCzg1iUcuUzyP7jYRw+mPUr/olfmBtwUODPpPgbnVi2XLHGjB/1vbSw5Wme/W37Lr2SnvUeUVGbPI7nuVp1I37+1Y2AOfFHxx2mX1zaUchpeIq/tQOTl7JWky9ew/Tn3e1myZpZn8/u8AXRozmacNes0rPs7YLsnjP0EYz/qKLOISUXGGym5uVP14jztLeFbv3gUY4jDwHl+sT1NnPdnnEZJDNqHlG2jnBtAJARv0lj7rcTL1BdPfABlQ0FlNPLaA2KCNhIpkB2DqB19NXPakiMyAj40dCu0sDYToYvPUJVDIdgy6+6tBEpxa2CACDzW1TnzUTjRG8DE/oJoH7IuIJTAdWwkpkdWj/hHvmAhYYp+/5qeQufbcmZIAlM2JHydZYKyg9AXVEIlZcrjdvb2OASLbQcvwb99l5CdIliDbNJbsNh0oo0ey/5i58WKspCoEIj5isL4MU9S83Qh2qbimTnRFm4RYCGmj/Mh5UeruICmqWgAHpJnj95G6MTiia+NfOthm0bO9AVgJhwJLrD3E7NYynzWfZ/78g61hckd+O2Dgb/vx1dU16WeUKB7ZhYTUTO+5XZyEhikAF7c4ChwJLAtWk9Dm+J6BqpbPOYiq2wmVAFi/E5jjdZe/iiFekK9pq9gKzWe0G4lFGRD/VOIHuxA3hNB8YV23Oxu3agb+2R/7u1fnceVbPDePaYRWPpnCBEICZBT7SqZ49+9PORzEwgsEZtr5XcHg/ncXj/37Ji2pB6ajyq9iVOb9Hk/zMhy2Qk7OkwCqM3bzifVK8NQsy6DbRI+oFi0j0uObMsGCPnYZDTqumh859yVBCnaGJ1SGKZmpMAjt5tMKiS33vlV2CDtzKUi14RNZgN16f3X+ChMYkVqjT0Ltos8Hz3TnZUC+m86ADMRI02aN0EIAsTcnxv3iAPuBmZRfWDHIjtkibuKQ+rmGCuCn5M8+HpsJbk7Mp4O4BNBMvUH+XgeLY5NctJM8AGZYfdHAtRI+mSkNRhX+GhrkHa/Ad2aIguk8m0hPxgt8JNjiKHsszhe8w9PnqDhvDz+SXIaN6XxZEdCNPwRmEa3W1bS0z5qWKs1oA+ZpU+dpASjUNUoeT9ktLF0ecePSyegq8irKumytVQbaNXRBPqivXOPc8d7Yck2vON6lM03wxLp+x0wSQY9JpBMmiFBcZnGZcMjUrW1IblWxfHzQGkBa+yvxznWehgT3wVTtBeIWTcCbAQe43GTaNBseOCxk2xhtWO2iSzWjri1qTZ9KNyAsGpiMNa3kZa731thpSrByBHvU0OEruVgQBrGvUEGyBGUlCCcQyL40FSaXBjr8RjJHJecxaVkVDqjfmQZ2XWo8x3emdHKgbd9FEn3SD3zMGIfwOH3Guot5yvOsD9GYmXXfshEhRCcqgEhS5uY0h+glZM4QUTKoV7xJ5Ch0m7UGgkM2viZxHTkhvxBYHS5IKPF9P9pQDXJN3qndzi9tfJtq/EOaSgSqfZferYDOIhvCUHVP4LQdS7NNj6ZhWuGLjPCqouCz2s4j1HKsMeoazTRvEgwdtNwkYEwF8s9Yq+mLwakO6lEsoAfVDYABolEVyfYyPXWOVMEplEJzC0Jq7yEoku76kdTGxGHEe0880mFgduOExs2K3Kket/eCBSF2aX+EWV4Dd7yuL8l0kGhmXxuKKn+TBgbm6nbnFMDfTK75Cv9Ld++22AgNsYyyVGyTxFdeyKbxD/m11T4DHLwrLtPXaMQFoid7LivTn3PJunJolG5EGIPT6Ox+t8NJ1JgSGumc864sKkw0HZjGZE6YyLvJUqnIFz9OzLIE658lQ1aVBvK/HJgr0fD6/0aAITX5fsJC62c/UlhE1QoZPS7m15RMnZP/BHtN27RS3AfDYKHc9DeOQSFlO7OhZdZVemlAhsd/iZjkF9HX/oh6OyTYIFxBlBKkZCsNYPh+JHFMgM31Uegmeq6zf/cE91Ric1rilmhmKOzHtQc+Ye1WJMRUvWC9vVkeAlbuxohiBG7dQoIj+CFcCVLs4dHFT9yD6AwM629cQjDyOUuKs7U+pytv6Nsn2H/udxiW1/CRnRlRpqRTVc8B8mbTCYr/El/BP9kbgR5+3iBWxkMMcdbYHC5/cnNtvHIfRlzVj3ASpoKyPXPbJUtLi27EFJXuTPmVdmuTzNb3KiuWCdKnvwrrmCnNBWNh8c58Jn4PQ5hR8JG2rcF8maqxfdYOcjy5uIZmvh29XOh5WdCkZlGejTD7tMxg/XJSDoNwWABmzw77mxf1fzlU/Yjz5AJA+4ypS6B+nJlrKpR9eAHA8RPqn9Gi9qPV0QD7yLfyFJxfAwEWmKl7r/tWEHEBwZWTMNzQ+Kpjn/B7dTXobw1HKxambrcHPDXcu580bJJIpZ69oKf7Ch7nbsec7Om6jpg66WrwgG3ceVokxYFTxIH6hRZryTmL0cz/Kxhbnq+uxROP8EAvQu0ErFC71StyedEc90sBoaAUjMtzDGrEDB5cltCnr0UHh9Di6QB6oAsIzA94noMQTKSuTCLXs/GUg6OIVW19G4vO24+2aHhnuF9dccSoQ/8Cu4VWdXdVxyVjNXakM6f7qP7JC11ajNeMtdxmPseMQXDt9YtSYiFL89/YoHGETMJzuKIjhycxtgfkGWSZ9G36pvh/tKkQT0WWqnswPrJTHY0UmbV3P/CIxdr1fLT4J+jBPK8/2+xvK+6JXb+22PVZmbiflQ+XeZQF9EM9lElFr0i5awXBct/dBxUxFtOllYkePH1bXcUWITX7jYZT/jMnXDamXigiedDP1zh1ossxXnUdxt+zMT4uIASGs9hOX7fQ9GuPHdRIKf4agJn78g1G1S045nFlCx1KUU0+UhLlesblvAZzmVrQRzScPqGQZ9cWyuVAcoANOxesMr9RDWMCbxgsKcBpJEsnHFy00cBELhNh5gXYctKjnmu9LztLdUyrJa1SOYmSj6JwgFyg2hPoVp0j5EZV+pKYcSZMMRHsCBtBssLSKTguECoo0CpwXbpLxtEyeZtLU3AgmbAzsPD3pbLw1p2Woezj52Er9jsxa/pxNiDvoMFAoGFYHVDmAWi46YLc56KKJhhJ/q6X3RwHwwdqMnCpPIwbotABdjXyTVM+f0M9HMSCB+33MKFLkZ58UEb71HTf5a6yoGjfAf7L3ZHOEAE6UfmaV//c5RIhFCAdbTc3GNoZInwVYldlKR84IvEONWLlKde1RHhLtr8o3F/zeAISV3K6Vg4+wOrKRTPANrjsB7CvPa/Ho/wx89F5aWhn5HFBls1k1paBQtg3hlZlF6cX7BfB2eO8BZ5idQeiogkf2GzLJHBmHnX1pNrakVqRC9GPNRFHmnElsCUKeI5v1sNDbD9PmOxuZmaFr8jkVNm9ZVth9o354c64qWoNLg4eIi9ide/dC2fKGHfAKuiCGV3VHCDoGOlRD3QOK8mOivfT0OJQUEyEOJQqT4hKQUD3kLoHcoODad4oR4j+FBGcdEhJ98JUtE+/0sE9odieP+GgVKmGAoTGZAD0AAvlpSIDNRxSs8rMn29t6f8vcpbbGaUVvRThbRPu8RHsD0bhkiRO7mhdZwMba4VwZ4IZjXUmPHaxoD/KkD6scqk2Wfty7fgjBi/geOwdww0VCewgFAJjouU/zbFS0kMxYulhcoZEzG1RbwV/bzqN1fquDR12E3ypRICIeOW0iRVE2hNTdtuWz1Aju8DGQRm9VXH6itph9LRFMMaoAtEuimk6DFZSsJNK8YKNUmbIl5HpKvwVQDb3v3pKgMuhmRpEIogQqIjWhe42SmAEjzt97Fch8T0d5nY4djGcE9+NwdZn9BXZUZbX0IORAVgChNT92ue8Y+yyS702o3leuBwbbU7iezJ9mJC5UoP2+HEGW1CMtCBFHlVWjZ2czKi8vdN6l8nmMts7x74d7Y+VZW9I2RsLJSRhRJw6Iylcrl0dFWnmPdOnw+ZQhc300Ap913xMvzWCf73OwQ/M5NiwdctCEj35cZ7S6rtGdEwlv8qyibT1Ldr9g8s7suHuJwfP27q7Rzq9kWtxHzvkqrVuISVfmCMhr3rQQ7Mltwmx0hq7frhZi+f+UsjpFak2yNi/zYYNsT3n5WJ01QEaoFWpk3w0OFkLD5N17juiaW7yFyQiTmny64FHrcs6LDwc2B38oNtE+Io3NiB4ONiCo2plQ7eMqWW/ZuJRtM7DvDjJS9Khlu8aCATg6OuDwFa0CD4+Mcqb8Vky0gDQxvQnJ2lN6WaBWOt/iXUpAG+AScEeLD2oGJ+QbJMdKyfduUwIIDSzXX3zw1UFwOl6CiIgm9jgYQRyw8WIs8Ec0iFq/rBqvdkr+C13yVs0LUStTz4LAyEzQ6GX7zj/WPCOdnlUsdwS22pXmQSTvaysmA+Iu6HnoE4ENiDA4vdlO0ij5V7f1QO3+kKyJmfJGKsikgdVIonGEmn8XLni9ru9qO6RxmVu23vswMSQ3RAs1/yq8AwhTZ4BeroChP1Vv8pIGcWCfsKK1IiIcPoTfasjWZUUyGjdncs0kH8A4sCncA/FZDUUjZaxxXKRdU1DcOgdgg/4kQV1XIqpAC8iZ7z46QkKvuYRIvYzFcORih1xSxY3oDUfqXOCcqGkQ72yC0DkqcgwvyZ1ylDSWeOcBDli2CswioUNcgfm6/X/zl++meGVkA7zifOsv2OrE8HSFJKeKW/l/zjrIV3v4NlwmnRagH6hBoPoe9weXfySLtEm1I0OcUS6n9LKAvPA9PxDDNjT4NjCxpBldTb5Owp1TsCZQFhOazQpRCzsiEFragb5D9EuWtpUjvZA6Yd27gY/UGDt2MGPRxDaODnQf10vuDTo+hFintkiA3dn1xm6G0W8hsk+xl402/g7n5j977XfGnmRfM+VcaXrILPvmxUXZbidwdRzAnAiDqSwmUNLjESH9pCC507lP0ZkakHLni5AH+DIlnUeK7JvwYDnWsx6HY13bLVfSLaddxSNy7kLP53QwjhmEc+umZVpXSFi0SYx4J6JgP4bWQ9ake1xA6xp7JMDII+S7d411QYdl4vYCU1L66tvM/eqbZNisrmcQNYCm9GUfOCkEZKiftftceRbEfgi0trop1tGz6r0/LJE1dAmlNzsJENATCsll+r9LXiLl8XhutTWaxp2M4FoGD0NDXX1xdG+5kRBV7uE4nC+dvRG3sTjLeEGpgwrEiVg7RPz+CscdzKmlYjmZ6+dNV93aW0w3eN/GvMkOOSrNdok5cM6brPYWJ27wPb8ZjcfBMojr5vmFuGw/KyaxjRvUXfOeCg6+XDRrSz/HCHXEhiu3agtqKTy0zOk1XcIONSkVZVNEgtrAJiR7yc590n3yml/mZOMwg0R9E/6myXjsF7nGcAV/+nkApEPJd0+UMBB7XvZ/Akld5DL8ibO16D+R32RaySo8x0h13rN3Prj6eUj6MEjlQNucMlCynmXTV43VERnzSkn9XTcyA7a//EP94KpbjtJWFI4PTzEA1gULZuBsRM3XscdhZNmT6lZFEDVUR13yNQe35lEr1vDDkQaQF8P+7SD6SaL1UunKsNdKA7X0L02+J4YYTfZl7vpn47nDTVtq9/MBp5xjRzw5reNrRQb2BO/sMQ0zaFhKkkXOFTk6acD92L67OORx98HS7veHQdMrw6HoGjkcc4j93FfbLoWMJ1BF79EB28hzyPer3OsPULA/rr2BC0vFQgFt7iDwXOpoPPcyU58YnK3LKmbHmEhqZnkeUZqOjMRUC2cX6dTKZcWsmyLEKjIB8z/lhOT3imRQIOqUOFW9vG663dLCKzCmh4+1hJK0NM+IAkrEIbZb6RWr8hDlYnEtMxtE4vFkFq7C0lP0oGWcY+yryFdewvtrZAls0Ij2uZsrKV1NhWxEoEjxncc8ljFMUMOO5vBtw77bBAfjeCikBjrDFsdnCV8c4IkTNV4CFiQLpeJ0AbGD5cpbTEajBQERlBhLNfo3O8OTla5qZOl6PFq91xx7KcbQ+v4rHuBukItsTo5z9SMf2B+GbRo/cbHA7/k0ZeAvlmK95yE7+sGuS8I9n+UUCnd6kF6/NQpHgHlHYpzULteuJcOdb5WVIrG5RdJQqh8JZbh0t1eplLNvhylfX2/fMeqKuXAUo+kWtWYCcNLh3PeOGJ5hCoeslOH2CxT+xNKHLhoq1Nc/bh+kxfVc2RBKtgCugHSwE4nUIwUp0FtjZ01xtlYa/WFfICTTL0HA1vvyu8vVTdF771rfUduHd2Ie1ZnJWBGXxdCnnCqrzhQIeOtu4KODUd/yS4GMXFqfUfEJOreUbp6xyfu8GL0VMAvYvYd7BHLwFwYNA6BKJPaZQRDZ4W+8s4BmrvnBfFh0Wt/CHJenUKc41EhlgS1DxxBw/3ikiceL44HDlygAXtSfXxjX3aks6mk/GOB2eTzuraVBujwJseF6Gg8hU087Gu9Gq5HFVQBliJAVsB+1QCjEcOx+L/hJXuoetaUgpvfUK/OTlSPleqZ9v1K9aNF11MPyY3tJvcklSygdGVveF7COyKQ5gTWbvftF59krAdbIc7dJz/wscJ9uhaTuoEN4g/HMhM1RhZdOCUyDZRrsoS9aN584hNDl925BQo6yc0C1BqrlO2AIfdHlONrxxmZX4kvgeKzjO0IecLd+lfF9LbLRKBdlQPZd4BwEtwDKYEyWI8y35j5pGowGs25Mrz4acPpRq/KVxuXruEwlB48rpqed1xeImLEc+6gUuNujPJfnwVqN2L2bb1T/G5OJMCJMCaBQOf+weS0OnjPi3L4id4FnSmBUv6vlauzI6nuBRdkmcFU0IPOinzatiathTBHEpJxmtR6Qkmuw4NT/g91O47V6TG+oE8xi44WBiRUg8L6YVS19WRZkLpdlGSTFumzd/Xp4xsgH+bJ2BfzmavfKQoD7BmFPNTyNaGt+0nnC2zNqqmp8wC09ZEslsW1VrD25eY45l9b8Rto4gacSVs0JKREijxgoc8121QS2cNujE53iMn0L+dMH1FkTMAXi0IFvDwoz7+/ftvN6OiFNAQYCdh0B5ZHx2v2CEZcGlylwH9578OqakbyheSq5fc7pn7m4+tdzsNiZzehUYb0DbaZXkAiAzOJvs4isYJ9F/7RlUnLXRB6UQ76wGtDBto4I80eUE3rpAvh4urnLISymcYaf/2YnzaHA4pSSz6+B4HkaqrbjHvTSW7HzicKM4tDM37MC5gdJOe5BcRgd8MycJHDF4Rq0a6V0P8gdtinPd+9UJBjAZcvgnpv5OBFXAn01SE+CYiWE7DbKCc4ay5L/4KO9SRPoHn6+7CD/i9a0sENXVPNYyJX3jmIQrZqEgUj+dBT0YCkEzdwviAfWS1VbwBp1QAGukGPJyFrgsb4hw08+Mki9IUxmkYA6v++pXW4uwbE6Z7x3kWKDlP04lTC5NbgilJgao7VQOG9CCFVu/PXsfbyR6KzxK4/qvTNu/eE0aToQsMndiW05w6MOEG0/tsZiROXigN84KYi1lOhUaZByuVg1CEBcMgOdHcyZa1ReYA8nA4fvwEwxz4nJPM/Vq0NeQ73+PNTaQesoR62MakRNrYCg9om1Q3Jxdtob8J8fMSPjh7kDMJDi0v3S7jn9Pk1v5CU/LEdcMoRDlDZhTeuBpiHzUks1SvGRB7E/Ni5jf+mJtmUVzYdTwahvRvTt0kSYbjUY09AOt4I1YB1T87L4akE6kCPA9PSpc98dt8ztd1zhtRrajdTBqGcxROyt3cYOTpRRwMistStNatWduaJy+rRj19DqFdoPxHsjfaQLlqKtCefVBCvwWZvm8LoI/rO/Yn3k9824SGTzs+BDio26wS9D45hYbrMZCQ1jp6fRmR5a5IWCIKy3ZBKiQR2nar9cMjI4KLssLc9xccNkzg2vqghnSjd+rcfidaJQuKhhZRlhUeZxQFgEP5I8lFBdFYxU12F1zKhGIgu6Riyr5MUAsHh2UhkElISNhtvcR+Hy/DKKojMdrYQNz82BcEUABfdDxToKkxsxlOU6pGsSq59Ihqn5cmMmU3hacs2XPSgTwOb2xAcn9S1IMiEVNbaD+F3kgaBxohwSEpp8rwlzvtkrktZX596cG8mqpGNpUzYBptXMLS7VnNUJjtdM2iM1Ln8hkN2ms9eGjmKqrBZzQhVjEj//xVJa37ZEAtHLuda1LYZraMFWLTcFcAoSMvuaWzkGT5ZGxhjdLxXtqVhYZmy+07lAMvjzyJWjVCyivCOjdOf/wMaHVkTiCBm3596juHlucXvnRD8EEEMp2RpPCap8RQEA/jx5DtKDKvnZ4Ykmaf+JTz/cTgjxXeQCdSu8FXfbgZ9Vpl2dA/DXI0Xt4GqxKIYf9wg+UV3zd2WqvjplXOUcgkHdASe+6WQG0aK9YjJevo+0Tn3SgwxhjKPVyV5uw9McUHkma7F5H361+E1IuhX56KnWph/8C2C7Su9UR6C5ETmwhLIgY2E5+4Xsuj5MIprntHCPp+/mzZC5/TXbxc0Mpj17BvQI9+EC6XXjtGaVyXg5KHHoRLVSi1TCxwRhc5Zw1DNg6G3q0wvOAD2HdZqg+CQfQfNF7snqJtMSU5AWC1iMQ13mwzttZN8xqU25zwYFuaSbARz7uFsjZCrvoqYFqSN60jDFquWY92WnLbJZ1XJNTCbv6jypKca+dLsv+H7Dkqd0KfFykd6lqrNqUsqqqazjgEqUKybbodtw8QTiKhl3igdF+Tr5maQd0xw3KfkGsgqRfRXzotcA/r4YKZmjSTNIwVt9ytjkhN61y1O12N0qtbyE4P64CqrjgokKMRkgJagSScBY6KBehZlw92YBrK37yJuOR9MxJhuOx2JbOdgaUTmZKsLXBjFuAyaKBsiKfjicRVV4AhE5MWNr9VcnNim4j7vpidt1HjNUiXX9WkTMQI1O0C6uQ2HAdRZkLcdlbY0AU17V9u5S/3UtceHammUqbyspP2fmjYk3XXwun3mS4NdbB6c9tdr+DQFOvXrzCWWZ3qH4FlLctlijKIkjA0aP/i4fH3IwGVRO/fYQtBlm6Y90wb/SpJ/+AnLSVS767u1wrOVPjI985MHXS68qaIwqZl9mVPtH8euhXoDjezZYKUWAi5BkolqjmYLLMBSXvQfdpH0d5JVVBOic2dSuJBl+e/ocWtH0LErd1VBbaoknjrm/RPcnO9vNb1kP9TMnV0p3hRWfyynVCB1HDtVFJYvh4nHqUvm+fKoLASBawWHIgOx1zxl62MS9zmDuh4GFmFAMFCqXU2igKvO7CU4MGQX3IUfAQ4RZUAEpc6KxXMIt1PowUEZX9bNuxkgHZ7MT+i26jo/JjhUNl7rCEfMNeAVLzia99TQtJz+SU6TbO+AK/xJ/UkCxMLhyYaw5/iGGZ+o2VKdQaEv1ApDUxiH/4C//ArTPIcXE10L/D2liIO4+ddA0xIT1jtPU1OxGFDHIIJetF0ZjyRUVvcoov5iZteYnbImgAchS2bwQsejU7lZ9CdD8ddJJGWOPIdEhS8rrkh9+uwhrZxi/B5cMwovCcqPMe8WSOzve41H5OdYv5pKrHje3FtMxsHErkmi2GN9txJWoAyHOcZIJx3qYqvpxt2BK3Ipmc4g5rRuV1f8/8+dfpcBb1/uzDQjJbK/0hENoSO2FLePzGAzKqdQtnyIJD+O2dtZNUOrQCjVMTMdDtpLIZzxgbm49743FL6qRTfPH/0hFvHRzc6tWL/4IBil+8Eb/5sShoi43luTl/o0w+bzcnK2gNsk6TMNqBCeAj/sMsnTMuc4kelDzw1wxVNNCe4Hz4nIgbZXZ4hE3ev3QuvAayOb2jAytKDTdAb7CTP5TBbWrG5s/sDxpss1e06RSqIHVVo3IGzPWYM6CLsrJMmStKxToqTbMQqT/5Fx8AwJG37T0bZYZM+elxPEjZjAvZYU/YlsW4ON4D+BSLNBbZg2ZO5eluaCo8169CCI9YkoOuUskIBHXuZxOF/92a9idMDTdMjCKvJxJ/wZdkxd58lDtGN1EDFnHr8/j7moWfxGpokutUWTt3kP1D1xfx42F3pRxlgwsdWxCrp1i2v8OYOIFYSenW3zaSWuQmOUkNBrx3XTMWd6HLrt/+NunLS+1LNXCxNfwKekWOxVH0VfSQzCVbDcGcYIlQsfCbuCIb2mABWhj9bcWixc3y8pf5oEIaI0xa+MGOdXaRp4hTVDPt8GCgUYBjExsuzsk1v5Lw5pIerQBPRlPDkN3OD1z9/M6ujMFpXVbq8UrejXQmORYaXzCGy4tI5SJurn54Xj8XAv5LQ6xYGlv2y4rGwrNGzwAzyCustpLByNKFEaMVjmBZsBcCWgBKL9BIkoyfqfTjw0Ctr9F1ze5J3lwowFPQZFKGlzovbtB3EL3amrf06wxkA5U/Ev81V4BY7y6h+NIuW9uFXZQcBI6qIqBl8XVGPCt80b8iG3E18Uiu50mMlMCqM06k/owTwogIvjTOclR1iMaub8a5l1uMmZYEb2/z9prOtfy8Q3DXbpxkYDZpt4rfObUhRNKWJ533lmX8lm+H6uMbBJe7R+MdLNFpAzzQoWYJPpnCkDatCiXok6OgHLFIzBASGNe8+dXXCD3pEmnsJt3MF5BooveLK2zdoR6kOkHo0fgUuB8chPVA4rj40TmuaUTQ0saANAby3MgmN+1F5185qrEg7ohHu4pmsgFW7uPYzu0WI4JWMQF5WE+CPBK2EilRYw8ZxxF7jee03ObB870h3uRvIoSXJEKYiRm/d7eF2M+dRvRY7ad6mt0iojhgwzMRh94G7clRzJewh1nrFeA48iDntSTA8DyFt1oqm2BGOErPw1/JsvmwGA1uwWDZ+Oq6NF/MVjRcdU9qMnmOHPh+nN9HFtUBL6NZ5mHD0m6sS5GBeuYc3Wuzo6AP0Kzyp11Jzu4KB6NJsJeJsxGX0A8JHYqpJckkyZLcYTIYQzf98Si4L5W746V6siP8f2U1uUSLSrq1i9N/Z0zmBvmJs8XTCFy6e1uTXkIOSVHGS0dP102pARzRHtA/87+ml14ibh9Yu4/tARMawuXdrN3v5JIDpRMTwo0FUUCxDLUN+s3lX0HrBbv5CTc2Jakj7YRvb8MG/1H5V5kA4Njpp6SCkFGJPOGcAb4fTqq1CU7Mi14iaiQ+AjfjjPilg6YNsS0EcdQBe2q5h8qrD1bXlq8z8A9CL769FuOqzL7JwFqhBeLZh7WHh0QdlPFsSgpPr7kSJj6R/rBnoBp1S2tjZgO7kUwyuakF3Nsodqw38/4hrEj0GpcUiFTsIrkwlPCx8vPlr820VT35EG/sqVUtRWk7l2oKfb0asV0QxsxH1akNFnapb449b2zib6FpgH4IDbOnTNWc/ozxHys82/EuiTSlf2ak2OIYnaH5jDhYdw6uqkjPHK7DMkj+9mVo2vgkb8InDzIgHq8+rx0Ri01YO/VcrLzM8Gy8/tXW0X5tKjBHOz3cog+OSDXUYbDg8LGf4O8GHeEuj5iScr/99Zl+GEYXhcvJ3wJUNP6ZefTcaiD0JTV4Bs4XqcX2WM7cJCOtuEZBO2LI6TemEGDZw7CZRXsevWJ4V1FOZS2QMGZ779r6lq2bASMeevDU0xZIeNyq9MrHs/e810zd8ZjhW4B0QNSNJr0HZ3CeNf4b412BydEP9cGO8mRJq9Lf0iMlJg+TWqypP37oXg7liBhl4i+gA2gylZcrI1NuvwjGxWJoY8VJETd8BVwR5Rcuj+m5K0IiyMUEYybToLAXbvaP2HzZkj2A7t/QhFODXmaSdBvOJviM9aKM4tuciiGcHbW/bznY0fUN0/Jl2Pz60VOqUZQA8lueHhg/ErlTtePDyMq4sJOFN/0QK9/3XNGrst8xoXKMmNRZVSDWc1VWbNPo7E1KWJMUW4kt0N78wWkbaLlQUX2JrjuKtpZEu+JVyNJ1bWeS29xcnZDGZoUL4VykGOJcGcUGtDloPHlhpsjZHROmUSZ7DwwHPdXFNohb7cp0CdiB1O+S+1RCca51JG4O2ij3RWpeLh14doUHwPp5WMUetSj6ovROgZRXZcM8L8hlBq0/NvB5x8IKCXEgHxpasZT1mDm/yO7YRMikSWbvCspp0vJLRoVahF6ClXVUbORmtfv9wNWKZ6t9Y6K8YBy67Jkp78oKC0Wi7rMKF1l2cNY619Upt/GdObRQ4vWwRfxgHGjngRujgOnl9zMWzgE1KDIL1WlgJFuMs2ZYxyRXSlzeL5wwBvFDfDiyUD+t6bVJU9hjtYU8+37hCqjJGzG17XdOYnKveV6QEFGcXiFPgEiVTp5z/wHrJes43tsHQaMdjcHqDIYoUUmH0k18+lRVwSWI74Nr9NGDo7DczPRhKWGBo8D6Psd/H63+vWegVc+JDmBodj3mXUxbPHRkPgOBMHdPSoY/T/JtvgDh2eX4hp8jdS1BoNpZ9Zw6WMT+4ac8O1jP34sK9iMBs2YD9O5y63wGdlUXQYmg9agLrZxLRytbhB4r9XtP9rNb4JrPLLX6nKcVawaUiJP96/S20HlzI4KrRYxDOuEH5GpnZf5BpLg3t2/LVChw+75RC8F/G5tzb680zjoVcsADs+p/wMj8e6wLVhO2DaazpuxxADNUd8BRa6i+EeoQUaVeThP9tzmvL/tdc/75miQTx+3yEuvbzY89cyO4waI4Y3NB0KsrPk96kIU6FN45uc2eo7sqnZxI6G8NYfGgVImRANmYSPNTYjdBHI7fzrRCzqYGLmb/VkHMAhndAXE5fhAb+v/apJVU3IQJt7MvkonhRnmghZdnsclgGuHdVRg3qDLCXdS7BMQvhbXYZwqCfPM3X3ZsVhTeYJcQNcBurpr8zOlQEs6+cFqQgbKQrYyM27RyCkF6QFJxjXVkLNsSpLKnEb5gweMWQH9gjIkiqMxe1KA1OIvlu8nqQyCto1R3XBM7VhJa8dJDtvYrYNF4Y/xolvpgMn8AHUOHxYzCl01I5lOL5H1o9s6aTa+Fnbb62wEfa+xs2MpQ9tHtg26uqMCVN00pqWhpZUFxCW46vRDYYMNaiizgTZU94W/e00/S4nrh4UzJElqVet2IlVFexKUt3Y8K0M3KI8OJWWoWvvBAxmy+6/Np/IA7tRTtREdbnsGFQntkc893MjLu+6vPTgSmIsWZh+DRTtxORbUrT5NBrFpD3InburvnXw1pwNiSwMSM++QOECc6kyr529fnimqn7yLcTc2uFlp+zptiTr7m3gybXySkBGusdoawWUlhI6qsasAlH7gENhqMquAUQxhnmAAu1ZNMp4503A7b4uW+5pnIomra3AjDLwr4SRMQT+TQ8fgA8gDkbcSPapqexTvr165FA7d//lJUniqpx5iZaKVKl81xQ+6IaEzgnk5DepLHkKcSPp3udB3HQJIPIbfyENncFCuF/ZET71Dxp6BihaIEWbX7Y2SX8JkuoXu+Epn6VLLJUWrhcmEQI5UdwKRPcmVUvETftkmjTwzRGeUFHFLdP2ce1F9p3N4dugcnjj+QvwVOrR/xbyyb1PpR3wWIjEX+ueXyT2G4pl3KYcVTVaRVRKL1wfbrpiJmEn9vb13h9SQlzJzQ65AxLRDuAytFcX0Iv83kezI5WZM11CzbpobYlDVZM4KOI6XvyJ1VUmSRO7khQnxfMXPEMYDy5imhF6B2C/PbXDAORIQG0EMib2nr1mlR2n3Pr+zhQZIDIzTz+m3zQxGSyGuYTGGeCM5FiIctdfnscZVgGP0u7CIB96Mz2KsqP3cUuepD58JEvpEiqh3UPRQ+HINHpLlqKLd72dta2+hQgbaK2xjUMyBEaPYMs3CU/MaPpFrcn42t052pmRChYDM6M4OVBwO6v37gdY99o5y0GjCc1cO1Bg7tMIQNsYylYDHH+D94i5yJqctL4xPBjV7kWbK8LLpt2X1HMKsiWi9+CvLT3PpcbztQkcB+Xfq3NLDzLa9exbqhXMop1LM6doLX5HfjLKUDzSTUwNTzFGqzJJVEOhPwyPZzbipQxTprqN6OfTzNhGF1oXki35BSkL2BgWo0pSZ2chP1t5JK5hp/mxR2jYtFNfAvtyoFCZGMnLqX+RFGo2+T3TGnbyXY52nFJL7dnzBnbHjMvYA+DSdRRKb9iPHjtHwW+wXbqCgS0DZtpNEyJLdUXoPIaD51QjcbAaPBPTgKcr8PobPtQv1OI4F3UQjtPQCDDHl65xZ6qqYOQjODGbIBSnNNLO8U1PXKIZXOBc+LlX6xPpYf7taYY1EIQBZEAej70sqeg064/bFM2itxXRYRYj/R6D8+PDGephSl+VebEsVKvV/9E0dg2E4eprUD0Yd5I7kjrSe/kLb3bh0F3vMfhnnhQm9byCzS9D64q+4dcyv6qEEVNStBHO93jneDqwJThZtbvOsJ8I3vkUH3Tt6VwJATyw/4fgxvK0UzJvv7owEQaUTfibT5jvlqPQr9WtT96Ii4VaHfxxTytujUSd4Z+68Ov5jFAwMkyEExxwHCJwKoBG8ovsFjIAeDqL26zx3IXl9Ohw0oAV7l9aKxVAB4wJlZfVJrg3rTGCeJW9pBgmEPQeO61mP6PcmDEKWMJx+7UlcJjFtIBw/yzcEd+fp6CRp8UUlfikIJgBDubwrN2PNQM79l+QCxH6aJvX6dlz+mbOUVubgM9UPJ7eq+dQ3K08SIMBy82JvFsyc6snEd6A5RUJcV/gwNRh9KSKZ+7HLecfbnjIRznfzt/jdjW0taz6JZPB0e0NwlvczepsmXsK52ZsICa0t88CfPnVYdQKfYoFOCQiJTn9fD7bGKSi3itXGfavD+Ps+dQzLkk+IIl+knnDv0dihrIyHoJJPD5W7SAAoTR4A83WBxJK3qM039AyFz4xmels8QiEMmsMs29hQhu1yVwELWu4lGUl8wFHyZ4d0xlrGUhBOcwFx9LtxYaHB7xttb1O+ggA9tzS38QMcwvyRFYw0+am/qbnTUiYpnYx4S9/GAPGR0PL4g+wbkRTsxGVZ7cvhxtSAIJIwb2U/l5uqQ/nbOiViLJZkKeGf6L3H8VF2m5GsgJJvojUiyGRzGCwYEf+rNZQ1LxCNkAT31qqj8kE69WRNzKtIgapCraZ9vuWy6mldelf1l5D+/BboNMum+besXt8VL+Jioeih64n+/P4mydEFReCu5mJtcdGkVCnW4f2+qeSyKkC8V5DSkiGUQAXiKS0wbMbtKavOU0XAosAC7bk/Zkx79SS9HkXwICr46KCH7oES1UDYR3a+QUWYiCpwjzYhFnpdORKOhmV7zjiM/mXrUwUYcWRIxeju1R8srJ0nuJIyRY2cPtairibvtshv1OeipWubxmqcgETL5UhWfGqXwUNsmGm3Tia5poA1oZtzRuT531fnwx8JGYWqmeNL3HrPCiGhGQ/oL2Yaux5ehr7Wkf10JBd37l/nbMdu0wt7k9xywcX/EMvO09Anx3GYDzExfa/7gjwWqaywpgUHByWYWQicJOuFMMb7oyPafWAEdCjMgzlk4JGCTC4yM1BviDT7HPDgo0jELQXO3jSVyoO4v1owXFrXFIrSnc4Wvl+lL6EMCspq7r+HsS2kjAUEQHPH3ExMIWhLpBAF/V8jCQmrPsk490qbd4sqUoEwCXKPiY4Nz8RXXz2T4KR2SajNvnofvApLvOdD1Er8bW20Dv45w4C2LgD3qf+v1Z/hrqwBkPkTKhtRVh1t5uDuosrGIqSXu7O3qXUnScsjVgL6xbNNYsL/4cb6BifFXD44Z8OOEYQ4NLu+o8Xp8i9rVYm/K3DtBVElpa86gt0CF6eVFIoFplmzJLtwPzZZSbEwFqFDNF5CZ+dCsjt6vyovK4Jrb2SLHZDHYsXNLnisd/1UWsFFj3TZRFbyM4/BNJpIFyz6Y9Yj+KghNyoLG9b5Ybh1wT9+yHqPK5E8hLeQGYSoR8KbqBm0dPxl3kcvDQySWlJNo5PMvs0KM3AzbpymnoFWk3odvOfu36n28FwN1s+yC3TiyYt57Z7QXIrm1U3uKvf9xt7I4rbN9iWMjSdX05vjy3kMwQHGzYQgy7URqTVITXBL2LMxmHdPCFNXgGa/zkCKeRPpzoajJna0QKBIsLvOpMYX1IQ4EoD+RFdVWwrw6PYC+GgXg1zLwMqNgNa3HSVDZFjgvAlnwi0Mj5GsdRi0fbFMUMUMNaT+Lb8IBs/uhUolvVPpSDYWJItHebyF7Sx8HPJXqIylXnLxP76Pn3fc+rFdKLm9U8MVOmXORJJhDbNmlI3lNjv2CKL6EcUwxkidzqy4gi76AwcjA/dNK3iT2vw7pXEapIdepDUbO+mYckfHtPtEZXr1XXOi4pUaC3FQzozMlLezKGpOh12NBqdY5rAHkOThf6KwcJk1dTM9ixTGkIe4x5yQ92bBJfjZQCva4sIUQfZiNxT1f8djxmV/m61LJ4Qz1ngOQ7RwqLyJLbASghFkxaEmrd/bkE3+ZYrCb1p0GeTX/y1LKUeXWXh/uXcu5Lw+3b8TVbjLfmaNTriu/4/9Mb/zs+RLfd7M46fN+k1Y/TkTm2xlr+Z4a3niBD68L+9XQ7IbDhU0JkP5FC7y/hZLMHwJr5sA0xookUVU2VHcko1RMPOmf8ZASrX5OyjRM+zCK50Ot6AMgeRAIYolywIRFhOk5HTwfAAN1n9xgr+kbmWRQLNEJ4FXOYohGb+/0nrt2dxvKdUQry16qBQt/AUUQhFJ/eRJOmfYbS/avTtPn/XafPaLqu6HnFJyJedxMLXmd+T2zdV2AQ7h1RLRQ0NxOpQpUO1SbpnDbsKMAju62R9Ic9zTx8vWxapeAOU/Ye901manX7GKp6HY3/riHIT00HZ6K5zbPX2565IJARAkhCoZRqAoFkLTyAnYTUaKCkOZduP6jJ9oGAWgIWycBUA1EzvtVRlPX8ZRxftMqkKKqtHsUSGLuqCy8SdNILNKVztSXeCN5YoGCZIL8XfT9P4GYfkT3Opoe/H6vJSl362JV0eQup9PSl5gVlfxyHkVo94ydfi7ruN4SiInz/o4pNt5g/TSX/arqnYY+yr9ri1W0w22V0oHHObC14NrKyje2oeBp9KPn3qq4RNslHfaC3HIzFE9F1M7dyyHq+scshrhrZOoFk0DZuhgFuBZtpCdywgNXQCAjZs494yvGOhmM4vSKhSMc/G9eRyU6G0lr+bkV3Q6GwLDeRPsd1N73Uz8R5243DPzwgBJ6fknkXVcCZWTbfxrXNfWIOMyL7xn/p/zf2T0W0IPfJgjl7Po9YxfeJTy8e+KgWMIfd7lbyJwXMsy/ZZfqKT4mpBEC2AA1+uuolWo75tv9ls0d83sSXxmLcEm2Ut8GnzmttosSuHHG8gcg2XJdNvyzoo94QlGvBfP9cpLMRN3ztsOO2wZ3tjmfkzuFPrgWn+Gu30LD+KmzDFkEV0r1rJET75cW3k+FIngzCYVNEAAYrl55UYiHpEjSWeKUy7j6XWdGwfLVwbr5a9+YeM5cUQVYuGjVnCzBz5tIuTvQ4yTP63JMCUP9zT7O7SuNrBQR24VaSUsvthMxQCg6qHA6MOVPn57AdJ1PEQYJzfg7iYcHvMu4oMHiNTEM4Z1hajxrqU1o7gx4ZPWZ04+MllfnxR7xyi3MgeUohmeOPM4MxbF8VVXdY9XLEb9hLl+K0O4xi27mzFkO4/krJsYakBSxKIWpqK+wMn34ZYdK8XZRd0VhUK4Tgt5iLKkWN1e+aZsoYvUAC3nKpeSK4u0AZ6922m66h/GVO3t+5r8LY5KHjs0m50k+DYasjCHge5yMlX8IWQViBB/e+RkJujH2cHnNLJ96DrC4oCbORjXOBXhUq1VWhgllGwk5pu6wBg6U/+nFpYIhX9tTYyAwRVaXdqF3l7rvj639UqcTkw3u43TrBFV0J/hp2QKvqrzbyviqrhl+6KEvoqpa0Tfu0H7OsBp8cMK3l62H6lGygrBS3Co5AanR96ymJi4QiKTInZXCl5bXsIgDH3j8omc5A+vtplE6La69k892O6VshRDDJ/eD/dynK8dYqi3H2rn8gjBF3wXT3vb7OBQUNaasMeOCwhbJuYH7ijW1jDIheFAkfajl38ApP+eJ0Jzfk21XQzm2p36hEgzJRJLvvk4CvjrtXrJ0TjVqM4ghYSgpe5nobpxZ/jIKzLQjPQsRGlk8BO+3wL1ALxQPgobtOpB1mUrdzt38XaGGzEtPXJnl4hZJw8iaVzR9T8kK/5Fk5s7MCyH59PIygaAHfu3N+383i+9nkhs43HPVbsJQLHvnPaIctxaegiGZMWzOAQlS76y76y1gxzMq8fmH5eMGhHu3srtZSDkb5J+4n3Hq1/HgrT5UMs0uzVvGi/VyTw3rN1hkzA/BRLS9X54bkbIyr2iyt/1e/jvzPrA0b3k5p0CPCYRnYbu/+ALFPinosUXtZJ/1CQCO/sCpwZmZfcSgpUb2mW/7MNNlmo/3DTyl3mqp0+xlQNyHOHeCbk6Tk0WYOfZcPa0MlU5qW9An+15PIx2s6D5zBS3RR5z7JiON0orsNpHOl2W6OfIgp9BAcbstKB1MOOACwdwnH9xoWx5ayzuHNUls/wTFjn/WaejT4E71pliu6G7DR8G9Pt3SS9tX7PMAIffqY7zJdBm/iMONVGuJq1CYH4czn5mv49rQJiYKWCwnsOgGkYYuqq0SVinfR4bExmwy8tlNssYm63Hr57U0iQK2r5xSTCqP+nu989OrPRrBd/3LhP1Lr9Bp8Qf2Jx5RIhKZy0HqfqsLjReSymfyFEKJ9wtHKSG36Mc64f1OgX69+lgKBQfvbt7txNgdRxoSIwEt+Kf/2L2+whEaWbbUDJV+9susWU+AF133LRIJ/Ms8CZ+45nMHT+Xe+CyIH3PLKBU+TwHU7o11+Ub5uY0llp904cIZRy1vFOqHVNxyUVn1kVZu88yyG/paAc5L7Nn8jnJ6oEC8W8jLJBAEzAdFFmYorJvVfnDGv9dT+bF9lYpkRJihIiS81r1X4uGYstcin27P4AGYk2rBppJFjIM/9Ts3yJp+msj4wbGq7O0Jgn+qb19IPxuNSmSl1CcB8j8GUXZY2lZXg94TqkkucTWEly5aVNox+hAZ34uFQeocajhTEExoBrCgx9L3OjtErbsHkTWJWMK7/sMxvoXO3GEzk86fnwu8mi7iNTYNKVnSKV6R8X3DzxPOVccONbm9NyToEX8j9yuT7TcqBLPoxG7tWOaHHT27ok/OlfanQFKwckipzp0+ZHMnsSI0y9JMmuO0eKyRiq0mundjpRyucoWx77sYZBkJ1J7PR5d5Pqk2vbVtqmYmLp6DCsyuhD4NBDGIQ1UX4nZQaZQjHoXykDvpzth3EENm5mAMNn/vwSuYYIe/q37o7/ZYWZ65MJU/XUgBLzv0sYVUA6n2KsV/2S7g+cil12QPfNczhKwSMnbr1eMKgP4gyHf1vhn5KWcE3I8x2z79e2nPq+RIe7XcjKWTYKGU89vtNfGVTYoKDE+0mhhjj94904ehHlHZFuApoNutvtDo5RYxbBYSmyhZNITCutVmx12ACNOIfr64uykwwwoUqY26+vXw851Uo+lTu5Xn9m+bhHwpMkyrErPGGVh02y5jOVyXvux002aoZm66c2N0OpEvh6nMpc7Wz5qdDJBrQhs3i/FQjWqNtjuFeXe0jF+nvus0Bv3A2eI0TPRBkH/EfYKXBSCPl+XOmZZposfOiY/jHMGCi5Wq6ffKqhxI3jGTjobYNBn8Krnfzzr5xVlyhG2f+nKupKXzq/zwq6Ui929t2EbVpq5usNOOt9uphWKr4ydXCOTpfneTrJ2LZaAKofbV/iAqEJS5olYLjiCEm2gGlDzfpkw3Xp1E0QBTTfJCivEyoZBIsSRDXXJdi04oB9Ut9eUQH6eDam/eW3T0+guZn+BqhtSwvCvqkCM60Pebgi3Pb5H/to4vMHjpHLy5JXsa1uOvcoFS7/hoEkBf/BDPudyBrg28zHfO+ozDfs8gaACe9nmLmzs81Pv814ZJQd93780Sr22HpitYYpt2ceQp49i31hxAqbpxMlkRthOZxxrKxvhpux849Q0LCmEtqKlLpIkCEBCxrdN5S65Dv73lczs1XtgPWiC4cnULg0KwScwt7p630p4v3tQPnm6RWOKHNl8v7yGl2/wfdasYe5H0I6RXYiooi7DARMCzp+QYsArQeZXrRzJCsU0ze3Tubr1r7tn6ReieOz9P3Clz1n7VBuRrzjw3Lskyv0cMUT3OaLoGn5GLjrsatgkQR3dV4GFU7cRJPH+42aidwcadp39PdbaWeCDaeZTLnaJOqREfyd912XyrzeN3C7b/WYZRoQIlSmXmNZ3G4ngRuSwdybdh9LMh3jcN5S0ILL9SHSr64X1P415CZJakpmSClpEEivwf3bJrWcIaqR0tCL8iKWmI/PxwQ5IWdlR/vt2VD/UTH/v0ooJmZFiyyh3nUmVykhP1roEj/R7z33zylHn3PEDEmy9SHbZRRT+A0/K2bWFInlCWgXZ4yVoLjl8Kev1C0hlxMjeoHWLitKTeK1uSG70Zk/lcWmhMxGhQWZusJtOta0Nm1BKqF5AO52Xj4b62h3CUf0+wWZUNar1+jtxCvohNYxNeJAClPkUrX3G3bZWRYDEI8mHgl77mT2hoht12+4qqlI1iShkHf+sICFXWsuSgnzkYG29JpSnSiRetzAwe3P4PsAppCh5cE5yWZNYBER5Sd7PsX0T5eHHZps8kKceoeibSiEnTOgoWXIkmehKQJy2WNRoCo4FX6db0gCROWcWn9dmH6XvN7VEM7Nanw/rtE4yzbHoAhvJo4m12z5ii4XAns66+k1yDJb0WEroJC0gCz2XHz710pI8erkFr3Uo6FSq5nyNZc8romn439Cp/+7i5GIXW4uYkY7UiQvQHIX8DWOzUPEaeKAhptBWIgmVHXlWauyPs+ONbKyWD+2A5s/Hs57t2GEZY5SzYJC8m4MUj/6MeQVE5HizmlWO662GS22CtNfmJMJIv6aaSyGOT+avMkIFnkoRsq0+KaUlGtZN76FORP4ChGNlzbqieb6GBaG7BU6F7D9tHBRahy4xvhFQtU1kbo5A6k9a4YC1Vvvf8z4hkgP6+KjWThq8hbCXZCqtRWGior9mMcu/VlGBcbvP5Tl3j3EyLvM/LhPvKDuLrmDy3jx7VKE0oPpF9gp8qaE+SQZvki2QPSitujVGciszFFiCGPDuAlpEUQHTVkXpOZCzCwEDt+m2gfsBbYr3VrsSCi/m0hH8z2x86u/Pj+dDoGBP7YRWD9KJB2+VDVc1oWh0/tCsTHhPjGllNb6U4JeSdlhgFIxH1XBfBba6I1sNtCE5DLH9iJxa9gnZhmxlrpIXortLk1lA/Exg1p3GBWvvRJg5OJ7otdPlYAtirchOCi+EN7sU6GzPnU3zuOck2bNGYHGZqnYxeFgdaqm+YFKALbp5i2R46NmdP2cgPQZAFqdyu+/sH026v1AfevEI5JOqrdp0MIbq2fJjgQtjQKyjxdX6WxXon9r3N+kGlGb1zoSzWsZsW7HoYl60dlBcW1vjdWB0YIm4ygLt6WXCciJu5TRAILZtCip7BtWPFujm8jzcYcqx/RSwpGwSqQexo5AzRDY86JyWuvhKQwbBF/9nfArh0eQk2+0ZRGDx8sEjXGjYzKv0sSeL65dJHMiiWwbg05JBkqUA1OsH5gE7CfktqFxsIwJ2WmePXJ7B4W68ZA/QzYvJ0lDjOk70v5Al+KTrLDUZdn9cL+sJraT52gRbPPWqwxWyDxOjJ0E/hk2MHx0XI7km5myMHUsfgfdweqfGzX6nluQMH1KJJDXQS8GjyJfbdsLFt0I7OhmCezNWIuON1LmiQCzdiCx2yUlNKrL7LP6n4qDDlA4h2WzLnOoy8OdVyTAecTwV4sQhEW8gVEX1geZzIpCr4GDexhmiXD2P1EUv86ZzZ8CA09QJzWiSBXyhuuHjIEsas5RWAcpbhlCkBAcsZF/KDu9COO7GEAwQQSPRtkxDJrffBLR/9W3cgnWacJSwyKn/HbH8ftOUy/AuJHb0Kgn6P37jybBG+JxkbEuDEQ4v9EmAdGh+lNlXRcm//jYcj7vavGWkJIlrlfemETU//QlU3G4cnlxCOZfXmM8G7HBBimx6WC0n78fgHrGLHXh+ZddGY+OeHRgrjh0pUfn+U2xvzx3sbXu1JVMYc7e5V01uDLME04uZOIulLFYr/4zc0dS181zopxkTnf6g442tvrOx1VhFWTovLbJyzjoToue8GtD4XnUl1wKIS5Y6HIsKhBf3+mS0n6ROVjAkNY0QnuDoZde9yeOSYi9Dgi9BnsSLtInAI4rgXYcxzH57Ktrn1+ZaKQcQXmWJCjQbvLz2/8xWphjUNdRjSJLgCWvr3T78ta3TQOSyfZpmyK11mrQfYyefSccF3TpbUVmbVcJsBTbx307bZ4nCqev5iiUKGpgbkiCdcIetVKKB6ka8kyUD+HOu0ddDM03EmItOKheXMTm6ll6n7Zv1R7LHEhKdQER6ev2286NnQDnvi9qW3MCxO6XKxM21zlLWFe98MhGKqFNN0kBKxmEeid5DW84O/BSeugF3dsPV6kTEL7bU/GMhZlwbxBH49Dv19RaBVgK7/xsXvDGd9aC5iFskv7Lps44U0qZk=\"}"
+}
\ No newline at end of file
diff --git a/backend/README.md b/backend/README.md
index 81f9be4..25137be 100644
--- a/backend/README.md
+++ b/backend/README.md
@@ -1,4 +1,4 @@
-#Etherra - template backend,
+#Powersport Rentals - template backend,
#### Run App on local machine:
@@ -38,10 +38,10 @@
- Type this command to creating a new database.
- - `postgres=> CREATE DATABASE db_etherra;`
+ - `postgres=> CREATE DATABASE db_powersport_rentals;`
- Then give that new user privileges to the new database then quit the `psql`.
- - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_etherra TO admin;`
+ - `postgres=> GRANT ALL PRIVILEGES ON DATABASE db_powersport_rentals TO admin;`
- `postgres=> \q`
---
diff --git a/backend/package.json b/backend/package.json
index f77dc40..397506a 100644
--- a/backend/package.json
+++ b/backend/package.json
@@ -1,6 +1,6 @@
{
- "name": "etherra",
- "description": "Etherra - template backend",
+ "name": "powersportrentals",
+ "description": "Powersport Rentals - template backend",
"scripts": {
"start": "npm run db:migrate && npm run db:seed && npm run watch",
"db:migrate": "sequelize-cli db:migrate",
diff --git a/backend/src/config.js b/backend/src/config.js
index ecf9953..39642f1 100644
--- a/backend/src/config.js
+++ b/backend/src/config.js
@@ -3,7 +3,7 @@ const os = require('os');
const config = {
gcloud: {
bucket: 'fldemo-files',
- hash: '7b2b237a7ca2d27371edcb8650f737ac',
+ hash: 'afeefb9d49f5b7977577876b99532ac7',
},
bcrypt: {
saltRounds: 12,
@@ -36,7 +36,7 @@ const config = {
},
uploadDir: os.tmpdir(),
email: {
- from: 'Etherra
',
+ from: 'Powersport Rentals ',
host: 'email-smtp.us-east-1.amazonaws.com',
port: 587,
auth: {
diff --git a/backend/src/db/api/bookings.js b/backend/src/db/api/bookings.js
index 8ee8e99..4de030b 100644
--- a/backend/src/db/api/bookings.js
+++ b/backend/src/db/api/bookings.js
@@ -38,6 +38,10 @@ module.exports = class BookingsDBApi {
transaction,
});
+ await bookings.setPowersportvehicle(data.powersportvehicle || null, {
+ transaction,
+ });
+
return bookings;
}
@@ -116,6 +120,14 @@ module.exports = class BookingsDBApi {
);
}
+ if (data.powersportvehicle !== undefined) {
+ await bookings.setPowersportvehicle(
+ data.powersportvehicle,
+
+ { transaction },
+ );
+ }
+
return bookings;
}
@@ -189,6 +201,10 @@ module.exports = class BookingsDBApi {
transaction,
});
+ output.powersportvehicle = await bookings.getPowersportvehicle({
+ transaction,
+ });
+
return output;
}
@@ -270,6 +286,32 @@ module.exports = class BookingsDBApi {
model: db.clients,
as: 'clients',
},
+
+ {
+ model: db.powersportvehicles,
+ as: 'powersportvehicle',
+
+ where: filter.powersportvehicle
+ ? {
+ [Op.or]: [
+ {
+ id: {
+ [Op.in]: filter.powersportvehicle
+ .split('|')
+ .map((term) => Utils.uuid(term)),
+ },
+ },
+ {
+ name: {
+ [Op.or]: filter.powersportvehicle
+ .split('|')
+ .map((term) => ({ [Op.iLike]: `%${term}%` })),
+ },
+ },
+ ],
+ }
+ : {},
+ },
];
if (filter) {
diff --git a/backend/src/db/api/clients.js b/backend/src/db/api/clients.js
index 80aad43..4b1cb4a 100644
--- a/backend/src/db/api/clients.js
+++ b/backend/src/db/api/clients.js
@@ -137,6 +137,11 @@ module.exports = class ClientsDBApi {
transaction,
});
+ output.powersportvehicles_clients =
+ await clients.getPowersportvehicles_clients({
+ transaction,
+ });
+
return output;
}
diff --git a/backend/src/db/api/powersportvehicles.js b/backend/src/db/api/powersportvehicles.js
new file mode 100644
index 0000000..d71627f
--- /dev/null
+++ b/backend/src/db/api/powersportvehicles.js
@@ -0,0 +1,609 @@
+const db = require('../models');
+const FileDBApi = require('./file');
+const crypto = require('crypto');
+const Utils = require('../utils');
+
+const Sequelize = db.Sequelize;
+const Op = Sequelize.Op;
+
+module.exports = class PowersportvehiclesDBApi {
+ static async create(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const powersportvehicles = await db.powersportvehicles.create(
+ {
+ id: data.id || undefined,
+
+ name: data.name || null,
+ description: data.description || null,
+ location: data.location || null,
+ pricehourly: data.pricehourly || null,
+ pricedaily: data.pricedaily || null,
+ securitydeposit: data.securitydeposit || null,
+ availabilitystart: data.availabilitystart || null,
+ availabilityend: data.availabilityend || null,
+ vehicletype: data.vehicletype || null,
+ preptimebefore: data.preptimebefore || null,
+ preptimebetween: data.preptimebetween || null,
+ importHash: data.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ },
+ { transaction },
+ );
+
+ await powersportvehicles.setClients(data.clients || null, {
+ transaction,
+ });
+
+ await FileDBApi.replaceRelationFiles(
+ {
+ belongsTo: db.powersportvehicles.getTableName(),
+ belongsToColumn: 'photos',
+ belongsToId: powersportvehicles.id,
+ },
+ data.photos,
+ options,
+ );
+
+ return powersportvehicles;
+ }
+
+ static async bulkImport(data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ // Prepare data - wrapping individual data transformations in a map() method
+ const powersportvehiclesData = data.map((item, index) => ({
+ id: item.id || undefined,
+
+ name: item.name || null,
+ description: item.description || null,
+ location: item.location || null,
+ pricehourly: item.pricehourly || null,
+ pricedaily: item.pricedaily || null,
+ securitydeposit: item.securitydeposit || null,
+ availabilitystart: item.availabilitystart || null,
+ availabilityend: item.availabilityend || null,
+ vehicletype: item.vehicletype || null,
+ preptimebefore: item.preptimebefore || null,
+ preptimebetween: item.preptimebetween || null,
+ importHash: item.importHash || null,
+ createdById: currentUser.id,
+ updatedById: currentUser.id,
+ createdAt: new Date(Date.now() + index * 1000),
+ }));
+
+ // Bulk create items
+ const powersportvehicles = await db.powersportvehicles.bulkCreate(
+ powersportvehiclesData,
+ { transaction },
+ );
+
+ // For each item created, replace relation files
+
+ for (let i = 0; i < powersportvehicles.length; i++) {
+ await FileDBApi.replaceRelationFiles(
+ {
+ belongsTo: db.powersportvehicles.getTableName(),
+ belongsToColumn: 'photos',
+ belongsToId: powersportvehicles[i].id,
+ },
+ data[i].photos,
+ options,
+ );
+ }
+
+ return powersportvehicles;
+ }
+
+ static async update(id, data, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+ const globalAccess = currentUser.app_role?.globalAccess;
+
+ const powersportvehicles = await db.powersportvehicles.findByPk(
+ id,
+ {},
+ { transaction },
+ );
+
+ const updatePayload = {};
+
+ if (data.name !== undefined) updatePayload.name = data.name;
+
+ if (data.description !== undefined)
+ updatePayload.description = data.description;
+
+ if (data.location !== undefined) updatePayload.location = data.location;
+
+ if (data.pricehourly !== undefined)
+ updatePayload.pricehourly = data.pricehourly;
+
+ if (data.pricedaily !== undefined)
+ updatePayload.pricedaily = data.pricedaily;
+
+ if (data.securitydeposit !== undefined)
+ updatePayload.securitydeposit = data.securitydeposit;
+
+ if (data.availabilitystart !== undefined)
+ updatePayload.availabilitystart = data.availabilitystart;
+
+ if (data.availabilityend !== undefined)
+ updatePayload.availabilityend = data.availabilityend;
+
+ if (data.vehicletype !== undefined)
+ updatePayload.vehicletype = data.vehicletype;
+
+ if (data.preptimebefore !== undefined)
+ updatePayload.preptimebefore = data.preptimebefore;
+
+ if (data.preptimebetween !== undefined)
+ updatePayload.preptimebetween = data.preptimebetween;
+
+ updatePayload.updatedById = currentUser.id;
+
+ await powersportvehicles.update(updatePayload, { transaction });
+
+ if (data.clients !== undefined) {
+ await powersportvehicles.setClients(
+ data.clients,
+
+ { transaction },
+ );
+ }
+
+ await FileDBApi.replaceRelationFiles(
+ {
+ belongsTo: db.powersportvehicles.getTableName(),
+ belongsToColumn: 'photos',
+ belongsToId: powersportvehicles.id,
+ },
+ data.photos,
+ options,
+ );
+
+ return powersportvehicles;
+ }
+
+ static async deleteByIds(ids, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const powersportvehicles = await db.powersportvehicles.findAll({
+ where: {
+ id: {
+ [Op.in]: ids,
+ },
+ },
+ transaction,
+ });
+
+ await db.sequelize.transaction(async (transaction) => {
+ for (const record of powersportvehicles) {
+ await record.update({ deletedBy: currentUser.id }, { transaction });
+ }
+ for (const record of powersportvehicles) {
+ await record.destroy({ transaction });
+ }
+ });
+
+ return powersportvehicles;
+ }
+
+ static async remove(id, options) {
+ const currentUser = (options && options.currentUser) || { id: null };
+ const transaction = (options && options.transaction) || undefined;
+
+ const powersportvehicles = await db.powersportvehicles.findByPk(
+ id,
+ options,
+ );
+
+ await powersportvehicles.update(
+ {
+ deletedBy: currentUser.id,
+ },
+ {
+ transaction,
+ },
+ );
+
+ await powersportvehicles.destroy({
+ transaction,
+ });
+
+ return powersportvehicles;
+ }
+
+ static async findBy(where, options) {
+ const transaction = (options && options.transaction) || undefined;
+
+ const powersportvehicles = await db.powersportvehicles.findOne(
+ { where },
+ { transaction },
+ );
+
+ if (!powersportvehicles) {
+ return powersportvehicles;
+ }
+
+ const output = powersportvehicles.get({ plain: true });
+
+ output.bookings_powersportvehicle =
+ await powersportvehicles.getBookings_powersportvehicle({
+ transaction,
+ });
+
+ output.clients = await powersportvehicles.getClients({
+ transaction,
+ });
+
+ output.photos = await powersportvehicles.getPhotos({
+ transaction,
+ });
+
+ return output;
+ }
+
+ static async findAll(filter, globalAccess, options) {
+ const limit = filter.limit || 0;
+ let offset = 0;
+ let where = {};
+ const currentPage = +filter.page;
+
+ const user = (options && options.currentUser) || null;
+ const userClients = (user && user.clients?.id) || null;
+
+ if (userClients) {
+ if (options?.currentUser?.clientsId) {
+ where.clientsId = options.currentUser.clientsId;
+ }
+ }
+
+ offset = currentPage * limit;
+
+ const orderBy = null;
+
+ const transaction = (options && options.transaction) || undefined;
+
+ let include = [
+ {
+ model: db.clients,
+ as: 'clients',
+ },
+
+ {
+ model: db.file,
+ as: 'photos',
+ },
+ ];
+
+ if (filter) {
+ if (filter.id) {
+ where = {
+ ...where,
+ ['id']: Utils.uuid(filter.id),
+ };
+ }
+
+ if (filter.name) {
+ where = {
+ ...where,
+ [Op.and]: Utils.ilike('powersportvehicles', 'name', filter.name),
+ };
+ }
+
+ if (filter.description) {
+ where = {
+ ...where,
+ [Op.and]: Utils.ilike(
+ 'powersportvehicles',
+ 'description',
+ filter.description,
+ ),
+ };
+ }
+
+ if (filter.location) {
+ where = {
+ ...where,
+ [Op.and]: Utils.ilike(
+ 'powersportvehicles',
+ 'location',
+ filter.location,
+ ),
+ };
+ }
+
+ if (filter.pricehourlyRange) {
+ const [start, end] = filter.pricehourlyRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ pricehourly: {
+ ...where.pricehourly,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ pricehourly: {
+ ...where.pricehourly,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.pricedailyRange) {
+ const [start, end] = filter.pricedailyRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ pricedaily: {
+ ...where.pricedaily,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ pricedaily: {
+ ...where.pricedaily,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.securitydepositRange) {
+ const [start, end] = filter.securitydepositRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ securitydeposit: {
+ ...where.securitydeposit,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ securitydeposit: {
+ ...where.securitydeposit,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.availabilitystartRange) {
+ const [start, end] = filter.availabilitystartRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ availabilitystart: {
+ ...where.availabilitystart,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ availabilitystart: {
+ ...where.availabilitystart,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.availabilityendRange) {
+ const [start, end] = filter.availabilityendRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ availabilityend: {
+ ...where.availabilityend,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ availabilityend: {
+ ...where.availabilityend,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.preptimebeforeRange) {
+ const [start, end] = filter.preptimebeforeRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ preptimebefore: {
+ ...where.preptimebefore,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ preptimebefore: {
+ ...where.preptimebefore,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.preptimebetweenRange) {
+ const [start, end] = filter.preptimebetweenRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ preptimebetween: {
+ ...where.preptimebetween,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ preptimebetween: {
+ ...where.preptimebetween,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+
+ if (filter.active !== undefined) {
+ where = {
+ ...where,
+ active: filter.active === true || filter.active === 'true',
+ };
+ }
+
+ if (filter.vehicletype) {
+ where = {
+ ...where,
+ vehicletype: filter.vehicletype,
+ };
+ }
+
+ if (filter.clients) {
+ const listItems = filter.clients.split('|').map((item) => {
+ return Utils.uuid(item);
+ });
+
+ where = {
+ ...where,
+ clientsId: { [Op.or]: listItems },
+ };
+ }
+
+ if (filter.createdAtRange) {
+ const [start, end] = filter.createdAtRange;
+
+ if (start !== undefined && start !== null && start !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.gte]: start,
+ },
+ };
+ }
+
+ if (end !== undefined && end !== null && end !== '') {
+ where = {
+ ...where,
+ ['createdAt']: {
+ ...where.createdAt,
+ [Op.lte]: end,
+ },
+ };
+ }
+ }
+ }
+
+ if (globalAccess) {
+ delete where.clientsId;
+ }
+
+ const queryOptions = {
+ where,
+ include,
+ distinct: true,
+ order:
+ filter.field && filter.sort
+ ? [[filter.field, filter.sort]]
+ : [['createdAt', 'desc']],
+ transaction: options?.transaction,
+ logging: console.log,
+ };
+
+ if (!options?.countOnly) {
+ queryOptions.limit = limit ? Number(limit) : undefined;
+ queryOptions.offset = offset ? Number(offset) : undefined;
+ }
+
+ try {
+ const { rows, count } = await db.powersportvehicles.findAndCountAll(
+ queryOptions,
+ );
+
+ return {
+ rows: options?.countOnly ? [] : rows,
+ count: count,
+ };
+ } catch (error) {
+ console.error('Error executing query:', error);
+ throw error;
+ }
+ }
+
+ static async findAllAutocomplete(
+ query,
+ limit,
+ offset,
+ globalAccess,
+ organizationId,
+ ) {
+ let where = {};
+
+ if (!globalAccess && organizationId) {
+ where.organizationId = organizationId;
+ }
+
+ if (query) {
+ where = {
+ [Op.or]: [
+ { ['id']: Utils.uuid(query) },
+ Utils.ilike('powersportvehicles', 'name', query),
+ ],
+ };
+ }
+
+ const records = await db.powersportvehicles.findAll({
+ attributes: ['id', 'name'],
+ where,
+ limit: limit ? Number(limit) : undefined,
+ offset: offset ? Number(offset) : undefined,
+ orderBy: [['name', 'ASC']],
+ });
+
+ return records.map((record) => ({
+ id: record.id,
+ label: record.name,
+ }));
+ }
+};
diff --git a/backend/src/db/api/vehicles.js b/backend/src/db/api/vehicles.js
index 936858e..81d1ff1 100644
--- a/backend/src/db/api/vehicles.js
+++ b/backend/src/db/api/vehicles.js
@@ -21,6 +21,7 @@ module.exports = class VehiclesDBApi {
price_hourly: data.price_hourly || null,
price_daily: data.price_daily || null,
security_deposit: data.security_deposit || null,
+ vehicletype: data.vehicletype || null,
importHash: data.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
@@ -67,6 +68,7 @@ module.exports = class VehiclesDBApi {
price_hourly: item.price_hourly || null,
price_daily: item.price_daily || null,
security_deposit: item.security_deposit || null,
+ vehicletype: item.vehicletype || null,
importHash: item.importHash || null,
createdById: currentUser.id,
updatedById: currentUser.id,
@@ -120,6 +122,9 @@ module.exports = class VehiclesDBApi {
if (data.security_deposit !== undefined)
updatePayload.security_deposit = data.security_deposit;
+ if (data.vehicletype !== undefined)
+ updatePayload.vehicletype = data.vehicletype;
+
updatePayload.updatedById = currentUser.id;
await vehicles.update(updatePayload, { transaction });
@@ -411,6 +416,13 @@ module.exports = class VehiclesDBApi {
};
}
+ if (filter.vehicletype) {
+ where = {
+ ...where,
+ vehicletype: filter.vehicletype,
+ };
+ }
+
if (filter.clients) {
const listItems = filter.clients.split('|').map((item) => {
return Utils.uuid(item);
diff --git a/backend/src/db/db.config.js b/backend/src/db/db.config.js
index d635487..9f1e2d9 100644
--- a/backend/src/db/db.config.js
+++ b/backend/src/db/db.config.js
@@ -13,7 +13,7 @@ module.exports = {
username: 'postgres',
dialect: 'postgres',
password: '',
- database: 'db_etherra',
+ database: 'db_powersport_rentals',
host: process.env.DB_HOST || 'localhost',
logging: console.log,
seederStorage: 'sequelize',
diff --git a/backend/src/db/migrations/1757449078196.js b/backend/src/db/migrations/1757449078196.js
new file mode 100644
index 0000000..a8a92a8
--- /dev/null
+++ b/backend/src/db/migrations/1757449078196.js
@@ -0,0 +1,51 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'vehicles',
+ 'vehicletype',
+ {
+ type: Sequelize.DataTypes.ENUM,
+
+ values: ['value'],
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('vehicles', 'vehicletype', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450562030.js b/backend/src/db/migrations/1757450562030.js
new file mode 100644
index 0000000..7f9a1f9
--- /dev/null
+++ b/backend/src/db/migrations/1757450562030.js
@@ -0,0 +1,90 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.createTable(
+ 'powersportvehicles',
+ {
+ id: {
+ type: Sequelize.DataTypes.UUID,
+ defaultValue: Sequelize.DataTypes.UUIDV4,
+ primaryKey: true,
+ },
+ createdById: {
+ type: Sequelize.DataTypes.UUID,
+ references: {
+ key: 'id',
+ model: 'users',
+ },
+ },
+ updatedById: {
+ type: Sequelize.DataTypes.UUID,
+ references: {
+ key: 'id',
+ model: 'users',
+ },
+ },
+ createdAt: { type: Sequelize.DataTypes.DATE },
+ updatedAt: { type: Sequelize.DataTypes.DATE },
+ deletedAt: { type: Sequelize.DataTypes.DATE },
+ importHash: {
+ type: Sequelize.DataTypes.STRING(255),
+ allowNull: true,
+ unique: true,
+ },
+ },
+ { transaction },
+ );
+
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'clientsId',
+ {
+ type: Sequelize.DataTypes.UUID,
+
+ references: {
+ model: 'clients',
+ key: 'id',
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('powersportvehicles', 'clientsId', {
+ transaction,
+ });
+
+ await queryInterface.dropTable('powersportvehicles', { transaction });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450589992.js b/backend/src/db/migrations/1757450589992.js
new file mode 100644
index 0000000..551f0d5
--- /dev/null
+++ b/backend/src/db/migrations/1757450589992.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'name',
+ {
+ type: Sequelize.DataTypes.TEXT,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('powersportvehicles', 'name', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450623285.js b/backend/src/db/migrations/1757450623285.js
new file mode 100644
index 0000000..0aa8f76
--- /dev/null
+++ b/backend/src/db/migrations/1757450623285.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'description',
+ {
+ type: Sequelize.DataTypes.TEXT,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('powersportvehicles', 'description', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450719241.js b/backend/src/db/migrations/1757450719241.js
new file mode 100644
index 0000000..e6bfba3
--- /dev/null
+++ b/backend/src/db/migrations/1757450719241.js
@@ -0,0 +1,36 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450790548.js b/backend/src/db/migrations/1757450790548.js
new file mode 100644
index 0000000..ccccb2c
--- /dev/null
+++ b/backend/src/db/migrations/1757450790548.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'location',
+ {
+ type: Sequelize.DataTypes.TEXT,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('powersportvehicles', 'location', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450827833.js b/backend/src/db/migrations/1757450827833.js
new file mode 100644
index 0000000..157f544
--- /dev/null
+++ b/backend/src/db/migrations/1757450827833.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'pricehourly',
+ {
+ type: Sequelize.DataTypes.DECIMAL,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('powersportvehicles', 'pricehourly', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450860278.js b/backend/src/db/migrations/1757450860278.js
new file mode 100644
index 0000000..fd99666
--- /dev/null
+++ b/backend/src/db/migrations/1757450860278.js
@@ -0,0 +1,49 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'pricedaily',
+ {
+ type: Sequelize.DataTypes.DECIMAL,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('powersportvehicles', 'pricedaily', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450889961.js b/backend/src/db/migrations/1757450889961.js
new file mode 100644
index 0000000..bf87e2f
--- /dev/null
+++ b/backend/src/db/migrations/1757450889961.js
@@ -0,0 +1,51 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'securitydeposit',
+ {
+ type: Sequelize.DataTypes.DECIMAL,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn(
+ 'powersportvehicles',
+ 'securitydeposit',
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450927629.js b/backend/src/db/migrations/1757450927629.js
new file mode 100644
index 0000000..fedf035
--- /dev/null
+++ b/backend/src/db/migrations/1757450927629.js
@@ -0,0 +1,51 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'availabilitystart',
+ {
+ type: Sequelize.DataTypes.DATE,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn(
+ 'powersportvehicles',
+ 'availabilitystart',
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450953122.js b/backend/src/db/migrations/1757450953122.js
new file mode 100644
index 0000000..d2819f6
--- /dev/null
+++ b/backend/src/db/migrations/1757450953122.js
@@ -0,0 +1,51 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'availabilityend',
+ {
+ type: Sequelize.DataTypes.DATE,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn(
+ 'powersportvehicles',
+ 'availabilityend',
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757450984490.js b/backend/src/db/migrations/1757450984490.js
new file mode 100644
index 0000000..05571e4
--- /dev/null
+++ b/backend/src/db/migrations/1757450984490.js
@@ -0,0 +1,54 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'bookings',
+ 'powersportvehicleId',
+ {
+ type: Sequelize.DataTypes.UUID,
+
+ references: {
+ model: 'powersportvehicles',
+ key: 'id',
+ },
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('bookings', 'powersportvehicleId', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757451017415.js b/backend/src/db/migrations/1757451017415.js
new file mode 100644
index 0000000..98ed1ad
--- /dev/null
+++ b/backend/src/db/migrations/1757451017415.js
@@ -0,0 +1,51 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'vehicletype',
+ {
+ type: Sequelize.DataTypes.ENUM,
+
+ values: ['value'],
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn('powersportvehicles', 'vehicletype', {
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757451046441.js b/backend/src/db/migrations/1757451046441.js
new file mode 100644
index 0000000..a46bf12
--- /dev/null
+++ b/backend/src/db/migrations/1757451046441.js
@@ -0,0 +1,51 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'preptimebefore',
+ {
+ type: Sequelize.DataTypes.INTEGER,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn(
+ 'powersportvehicles',
+ 'preptimebefore',
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/migrations/1757451083313.js b/backend/src/db/migrations/1757451083313.js
new file mode 100644
index 0000000..d76fd31
--- /dev/null
+++ b/backend/src/db/migrations/1757451083313.js
@@ -0,0 +1,51 @@
+module.exports = {
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async up(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.addColumn(
+ 'powersportvehicles',
+ 'preptimebetween',
+ {
+ type: Sequelize.DataTypes.INTEGER,
+ },
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+ /**
+ * @param {QueryInterface} queryInterface
+ * @param {Sequelize} Sequelize
+ * @returns {Promise}
+ */
+ async down(queryInterface, Sequelize) {
+ /**
+ * @type {Transaction}
+ */
+ const transaction = await queryInterface.sequelize.transaction();
+ try {
+ await queryInterface.removeColumn(
+ 'powersportvehicles',
+ 'preptimebetween',
+ { transaction },
+ );
+
+ await transaction.commit();
+ } catch (err) {
+ await transaction.rollback();
+ throw err;
+ }
+ },
+};
diff --git a/backend/src/db/models/bookings.js b/backend/src/db/models/bookings.js
index ba50c4c..ea0dbfc 100644
--- a/backend/src/db/models/bookings.js
+++ b/backend/src/db/models/bookings.js
@@ -74,6 +74,14 @@ module.exports = function (sequelize, DataTypes) {
constraints: false,
});
+ db.bookings.belongsTo(db.powersportvehicles, {
+ as: 'powersportvehicle',
+ foreignKey: {
+ name: 'powersportvehicleId',
+ },
+ constraints: false,
+ });
+
db.bookings.belongsTo(db.users, {
as: 'createdBy',
});
diff --git a/backend/src/db/models/clients.js b/backend/src/db/models/clients.js
index 266a6f9..7bcfc98 100644
--- a/backend/src/db/models/clients.js
+++ b/backend/src/db/models/clients.js
@@ -58,6 +58,14 @@ module.exports = function (sequelize, DataTypes) {
constraints: false,
});
+ db.clients.hasMany(db.powersportvehicles, {
+ as: 'powersportvehicles_clients',
+ foreignKey: {
+ name: 'clientsId',
+ },
+ constraints: false,
+ });
+
//end loop
db.clients.belongsTo(db.users, {
diff --git a/backend/src/db/models/powersportvehicles.js b/backend/src/db/models/powersportvehicles.js
new file mode 100644
index 0000000..59c003e
--- /dev/null
+++ b/backend/src/db/models/powersportvehicles.js
@@ -0,0 +1,117 @@
+const config = require('../../config');
+const providers = config.providers;
+const crypto = require('crypto');
+const bcrypt = require('bcrypt');
+const moment = require('moment');
+
+module.exports = function (sequelize, DataTypes) {
+ const powersportvehicles = sequelize.define(
+ 'powersportvehicles',
+ {
+ id: {
+ type: DataTypes.UUID,
+ defaultValue: DataTypes.UUIDV4,
+ primaryKey: true,
+ },
+
+ name: {
+ type: DataTypes.TEXT,
+ },
+
+ description: {
+ type: DataTypes.TEXT,
+ },
+
+ location: {
+ type: DataTypes.TEXT,
+ },
+
+ pricehourly: {
+ type: DataTypes.DECIMAL,
+ },
+
+ pricedaily: {
+ type: DataTypes.DECIMAL,
+ },
+
+ securitydeposit: {
+ type: DataTypes.DECIMAL,
+ },
+
+ availabilitystart: {
+ type: DataTypes.DATE,
+ },
+
+ availabilityend: {
+ type: DataTypes.DATE,
+ },
+
+ vehicletype: {
+ type: DataTypes.ENUM,
+
+ values: ['value'],
+ },
+
+ preptimebefore: {
+ type: DataTypes.INTEGER,
+ },
+
+ preptimebetween: {
+ type: DataTypes.INTEGER,
+ },
+
+ importHash: {
+ type: DataTypes.STRING(255),
+ allowNull: true,
+ unique: true,
+ },
+ },
+ {
+ timestamps: true,
+ paranoid: true,
+ freezeTableName: true,
+ },
+ );
+
+ powersportvehicles.associate = (db) => {
+ /// loop through entities and it's fields, and if ref === current e[name] and create relation has many on parent entity
+
+ db.powersportvehicles.hasMany(db.bookings, {
+ as: 'bookings_powersportvehicle',
+ foreignKey: {
+ name: 'powersportvehicleId',
+ },
+ constraints: false,
+ });
+
+ //end loop
+
+ db.powersportvehicles.belongsTo(db.clients, {
+ as: 'clients',
+ foreignKey: {
+ name: 'clientsId',
+ },
+ constraints: false,
+ });
+
+ db.powersportvehicles.hasMany(db.file, {
+ as: 'photos',
+ foreignKey: 'belongsToId',
+ constraints: false,
+ scope: {
+ belongsTo: db.powersportvehicles.getTableName(),
+ belongsToColumn: 'photos',
+ },
+ });
+
+ db.powersportvehicles.belongsTo(db.users, {
+ as: 'createdBy',
+ });
+
+ db.powersportvehicles.belongsTo(db.users, {
+ as: 'updatedBy',
+ });
+ };
+
+ return powersportvehicles;
+};
diff --git a/backend/src/db/models/vehicles.js b/backend/src/db/models/vehicles.js
index 09556e4..58a0093 100644
--- a/backend/src/db/models/vehicles.js
+++ b/backend/src/db/models/vehicles.js
@@ -38,6 +38,12 @@ module.exports = function (sequelize, DataTypes) {
type: DataTypes.DECIMAL,
},
+ vehicletype: {
+ type: DataTypes.ENUM,
+
+ values: ['value'],
+ },
+
importHash: {
type: DataTypes.STRING(255),
allowNull: true,
diff --git a/backend/src/db/seeders/20200430130760-user-roles.js b/backend/src/db/seeders/20200430130760-user-roles.js
index 7f62a4e..9e1ace0 100644
--- a/backend/src/db/seeders/20200430130760-user-roles.js
+++ b/backend/src/db/seeders/20200430130760-user-roles.js
@@ -107,6 +107,7 @@ module.exports = {
'roles',
'permissions',
'clients',
+ 'powersportvehicles',
,
];
await queryInterface.bulkInsert(
@@ -537,6 +538,31 @@ primary key ("roles_permissionsId", "permissionId")
permissionId: getId('DELETE_VEHICLES'),
},
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('CREATE_POWERSPORTVEHICLES'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('READ_POWERSPORTVEHICLES'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('UPDATE_POWERSPORTVEHICLES'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('Administrator'),
+ permissionId: getId('DELETE_POWERSPORTVEHICLES'),
+ },
+
{
createdAt,
updatedAt,
@@ -687,6 +713,31 @@ primary key ("roles_permissionsId", "permissionId")
permissionId: getId('DELETE_CLIENTS'),
},
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('SuperAdmin'),
+ permissionId: getId('CREATE_POWERSPORTVEHICLES'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('SuperAdmin'),
+ permissionId: getId('READ_POWERSPORTVEHICLES'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('SuperAdmin'),
+ permissionId: getId('UPDATE_POWERSPORTVEHICLES'),
+ },
+ {
+ createdAt,
+ updatedAt,
+ roles_permissionsId: getId('SuperAdmin'),
+ permissionId: getId('DELETE_POWERSPORTVEHICLES'),
+ },
+
{
createdAt,
updatedAt,
diff --git a/backend/src/db/seeders/20231127130745-sample-data.js b/backend/src/db/seeders/20231127130745-sample-data.js
index e430a85..dbf49f0 100644
--- a/backend/src/db/seeders/20231127130745-sample-data.js
+++ b/backend/src/db/seeders/20231127130745-sample-data.js
@@ -7,6 +7,8 @@ const Vehicles = db.vehicles;
const Clients = db.clients;
+const Powersportvehicles = db.powersportvehicles;
+
const BookingsData = [
{
// type code here for "relation_one" field
@@ -22,6 +24,8 @@ const BookingsData = [
total_price: 1200,
// type code here for "relation_one" field
+
+ // type code here for "relation_one" field
},
{
@@ -33,11 +37,13 @@ const BookingsData = [
end_date: new Date('2023-11-12T09:00:00Z'),
- status: 'completed',
+ status: 'cancelled',
total_price: 360,
// type code here for "relation_one" field
+
+ // type code here for "relation_one" field
},
{
@@ -49,41 +55,11 @@ const BookingsData = [
end_date: new Date('2023-11-20T08:00:00Z'),
- status: 'denied',
+ status: 'approved',
total_price: 1200,
// type code here for "relation_one" field
- },
-
- {
- // type code here for "relation_one" field
-
- // type code here for "relation_one" field
-
- start_date: new Date('2023-11-25T07:00:00Z'),
-
- end_date: new Date('2023-11-30T07:00:00Z'),
-
- status: 'completed',
-
- total_price: 1350,
-
- // type code here for "relation_one" field
- },
-
- {
- // type code here for "relation_one" field
-
- // type code here for "relation_one" field
-
- start_date: new Date('2023-12-01T06:00:00Z'),
-
- end_date: new Date('2023-12-05T06:00:00Z'),
-
- status: 'cancelled',
-
- total_price: 840,
// type code here for "relation_one" field
},
@@ -110,6 +86,8 @@ const VehiclesData = [
// type code here for "relation_many" field
// type code here for "relation_one" field
+
+ vehicletype: 'value',
},
{
@@ -132,6 +110,8 @@ const VehiclesData = [
// type code here for "relation_many" field
// type code here for "relation_one" field
+
+ vehicletype: 'value',
},
{
@@ -154,50 +134,8 @@ const VehiclesData = [
// type code here for "relation_many" field
// type code here for "relation_one" field
- },
- {
- title: 'Adventure Van',
-
- description: 'A rugged van equipped for off-road adventures.',
-
- // type code here for "images" field
-
- location: 'Denver, CO',
-
- price_hourly: 45,
-
- price_daily: 270,
-
- security_deposit: 450,
-
- // type code here for "relation_one" field
-
- // type code here for "relation_many" field
-
- // type code here for "relation_one" field
- },
-
- {
- title: 'Classic Trailer',
-
- description: 'A vintage trailer with a charming retro feel.',
-
- // type code here for "images" field
-
- location: 'Portland, OR',
-
- price_hourly: 35,
-
- price_daily: 210,
-
- security_deposit: 350,
-
- // type code here for "relation_one" field
-
- // type code here for "relation_many" field
-
- // type code here for "relation_one" field
+ vehicletype: 'value',
},
];
@@ -213,13 +151,91 @@ const ClientsData = [
{
name: 'Luxury Escapes',
},
+];
+const PowersportvehiclesData = [
{
- name: 'Budget Travels',
+ // type code here for "relation_one" field
+
+ name: 'Carl Linnaeus',
+
+ description: 'Albrecht von Haller',
+
+ // type code here for "images" field
+
+ location: 'Edwin Hubble',
+
+ pricehourly: 35.85,
+
+ pricedaily: 86.25,
+
+ securitydeposit: 32.01,
+
+ availabilitystart: new Date(Date.now()),
+
+ availabilityend: new Date(Date.now()),
+
+ vehicletype: 'value',
+
+ preptimebefore: 7,
+
+ preptimebetween: 4,
},
{
- name: 'Eco-Friendly Tours',
+ // type code here for "relation_one" field
+
+ name: 'Christiaan Huygens',
+
+ description: 'Lucretius',
+
+ // type code here for "images" field
+
+ location: 'Richard Feynman',
+
+ pricehourly: 73.21,
+
+ pricedaily: 91.22,
+
+ securitydeposit: 75.97,
+
+ availabilitystart: new Date(Date.now()),
+
+ availabilityend: new Date(Date.now()),
+
+ vehicletype: 'value',
+
+ preptimebefore: 3,
+
+ preptimebetween: 9,
+ },
+
+ {
+ // type code here for "relation_one" field
+
+ name: 'J. Robert Oppenheimer',
+
+ description: 'Richard Feynman',
+
+ // type code here for "images" field
+
+ location: 'Charles Lyell',
+
+ pricehourly: 89.83,
+
+ pricedaily: 34.17,
+
+ securitydeposit: 94.42,
+
+ availabilitystart: new Date(Date.now()),
+
+ availabilityend: new Date(Date.now()),
+
+ vehicletype: 'value',
+
+ preptimebefore: 8,
+
+ preptimebetween: 9,
},
];
@@ -258,28 +274,6 @@ async function associateUserWithClient() {
if (User2?.setClient) {
await User2.setClient(relatedClient2);
}
-
- const relatedClient3 = await Clients.findOne({
- offset: Math.floor(Math.random() * (await Clients.count())),
- });
- const User3 = await Users.findOne({
- order: [['id', 'ASC']],
- offset: 3,
- });
- if (User3?.setClient) {
- await User3.setClient(relatedClient3);
- }
-
- const relatedClient4 = await Clients.findOne({
- offset: Math.floor(Math.random() * (await Clients.count())),
- });
- const User4 = await Users.findOne({
- order: [['id', 'ASC']],
- offset: 4,
- });
- if (User4?.setClient) {
- await User4.setClient(relatedClient4);
- }
}
async function associateBookingWithVehicle() {
@@ -315,28 +309,6 @@ async function associateBookingWithVehicle() {
if (Booking2?.setVehicle) {
await Booking2.setVehicle(relatedVehicle2);
}
-
- const relatedVehicle3 = await Vehicles.findOne({
- offset: Math.floor(Math.random() * (await Vehicles.count())),
- });
- const Booking3 = await Bookings.findOne({
- order: [['id', 'ASC']],
- offset: 3,
- });
- if (Booking3?.setVehicle) {
- await Booking3.setVehicle(relatedVehicle3);
- }
-
- const relatedVehicle4 = await Vehicles.findOne({
- offset: Math.floor(Math.random() * (await Vehicles.count())),
- });
- const Booking4 = await Bookings.findOne({
- order: [['id', 'ASC']],
- offset: 4,
- });
- if (Booking4?.setVehicle) {
- await Booking4.setVehicle(relatedVehicle4);
- }
}
async function associateBookingWithRentee() {
@@ -372,28 +344,6 @@ async function associateBookingWithRentee() {
if (Booking2?.setRentee) {
await Booking2.setRentee(relatedRentee2);
}
-
- const relatedRentee3 = await Users.findOne({
- offset: Math.floor(Math.random() * (await Users.count())),
- });
- const Booking3 = await Bookings.findOne({
- order: [['id', 'ASC']],
- offset: 3,
- });
- if (Booking3?.setRentee) {
- await Booking3.setRentee(relatedRentee3);
- }
-
- const relatedRentee4 = await Users.findOne({
- offset: Math.floor(Math.random() * (await Users.count())),
- });
- const Booking4 = await Bookings.findOne({
- order: [['id', 'ASC']],
- offset: 4,
- });
- if (Booking4?.setRentee) {
- await Booking4.setRentee(relatedRentee4);
- }
}
async function associateBookingWithClient() {
@@ -429,27 +379,40 @@ async function associateBookingWithClient() {
if (Booking2?.setClient) {
await Booking2.setClient(relatedClient2);
}
+}
- const relatedClient3 = await Clients.findOne({
- offset: Math.floor(Math.random() * (await Clients.count())),
+async function associateBookingWithPowersportvehicle() {
+ const relatedPowersportvehicle0 = await Powersportvehicles.findOne({
+ offset: Math.floor(Math.random() * (await Powersportvehicles.count())),
});
- const Booking3 = await Bookings.findOne({
+ const Booking0 = await Bookings.findOne({
order: [['id', 'ASC']],
- offset: 3,
+ offset: 0,
});
- if (Booking3?.setClient) {
- await Booking3.setClient(relatedClient3);
+ if (Booking0?.setPowersportvehicle) {
+ await Booking0.setPowersportvehicle(relatedPowersportvehicle0);
}
- const relatedClient4 = await Clients.findOne({
- offset: Math.floor(Math.random() * (await Clients.count())),
+ const relatedPowersportvehicle1 = await Powersportvehicles.findOne({
+ offset: Math.floor(Math.random() * (await Powersportvehicles.count())),
});
- const Booking4 = await Bookings.findOne({
+ const Booking1 = await Bookings.findOne({
order: [['id', 'ASC']],
- offset: 4,
+ offset: 1,
});
- if (Booking4?.setClient) {
- await Booking4.setClient(relatedClient4);
+ if (Booking1?.setPowersportvehicle) {
+ await Booking1.setPowersportvehicle(relatedPowersportvehicle1);
+ }
+
+ const relatedPowersportvehicle2 = await Powersportvehicles.findOne({
+ offset: Math.floor(Math.random() * (await Powersportvehicles.count())),
+ });
+ const Booking2 = await Bookings.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Booking2?.setPowersportvehicle) {
+ await Booking2.setPowersportvehicle(relatedPowersportvehicle2);
}
}
@@ -486,28 +449,6 @@ async function associateVehicleWithOwner() {
if (Vehicle2?.setOwner) {
await Vehicle2.setOwner(relatedOwner2);
}
-
- const relatedOwner3 = await Users.findOne({
- offset: Math.floor(Math.random() * (await Users.count())),
- });
- const Vehicle3 = await Vehicles.findOne({
- order: [['id', 'ASC']],
- offset: 3,
- });
- if (Vehicle3?.setOwner) {
- await Vehicle3.setOwner(relatedOwner3);
- }
-
- const relatedOwner4 = await Users.findOne({
- offset: Math.floor(Math.random() * (await Users.count())),
- });
- const Vehicle4 = await Vehicles.findOne({
- order: [['id', 'ASC']],
- offset: 4,
- });
- if (Vehicle4?.setOwner) {
- await Vehicle4.setOwner(relatedOwner4);
- }
}
// Similar logic for "relation_many"
@@ -545,27 +486,40 @@ async function associateVehicleWithClient() {
if (Vehicle2?.setClient) {
await Vehicle2.setClient(relatedClient2);
}
+}
- const relatedClient3 = await Clients.findOne({
+async function associatePowersportvehicleWithClient() {
+ const relatedClient0 = await Clients.findOne({
offset: Math.floor(Math.random() * (await Clients.count())),
});
- const Vehicle3 = await Vehicles.findOne({
+ const Powersportvehicle0 = await Powersportvehicles.findOne({
order: [['id', 'ASC']],
- offset: 3,
+ offset: 0,
});
- if (Vehicle3?.setClient) {
- await Vehicle3.setClient(relatedClient3);
+ if (Powersportvehicle0?.setClient) {
+ await Powersportvehicle0.setClient(relatedClient0);
}
- const relatedClient4 = await Clients.findOne({
+ const relatedClient1 = await Clients.findOne({
offset: Math.floor(Math.random() * (await Clients.count())),
});
- const Vehicle4 = await Vehicles.findOne({
+ const Powersportvehicle1 = await Powersportvehicles.findOne({
order: [['id', 'ASC']],
- offset: 4,
+ offset: 1,
});
- if (Vehicle4?.setClient) {
- await Vehicle4.setClient(relatedClient4);
+ if (Powersportvehicle1?.setClient) {
+ await Powersportvehicle1.setClient(relatedClient1);
+ }
+
+ const relatedClient2 = await Clients.findOne({
+ offset: Math.floor(Math.random() * (await Clients.count())),
+ });
+ const Powersportvehicle2 = await Powersportvehicles.findOne({
+ order: [['id', 'ASC']],
+ offset: 2,
+ });
+ if (Powersportvehicle2?.setClient) {
+ await Powersportvehicle2.setClient(relatedClient2);
}
}
@@ -577,6 +531,8 @@ module.exports = {
await Clients.bulkCreate(ClientsData);
+ await Powersportvehicles.bulkCreate(PowersportvehiclesData);
+
await Promise.all([
// Similar logic for "relation_many"
@@ -588,11 +544,15 @@ module.exports = {
await associateBookingWithClient(),
+ await associateBookingWithPowersportvehicle(),
+
await associateVehicleWithOwner(),
// Similar logic for "relation_many"
await associateVehicleWithClient(),
+
+ await associatePowersportvehicleWithClient(),
]);
},
@@ -602,5 +562,7 @@ module.exports = {
await queryInterface.bulkDelete('vehicles', null, {});
await queryInterface.bulkDelete('clients', null, {});
+
+ await queryInterface.bulkDelete('powersportvehicles', null, {});
},
};
diff --git a/backend/src/db/seeders/20250909204242.js b/backend/src/db/seeders/20250909204242.js
new file mode 100644
index 0000000..07e0fc4
--- /dev/null
+++ b/backend/src/db/seeders/20250909204242.js
@@ -0,0 +1,87 @@
+const { v4: uuid } = require('uuid');
+const db = require('../models');
+const Sequelize = require('sequelize');
+const config = require('../../config');
+
+module.exports = {
+ /**
+ * @param{import("sequelize").QueryInterface} queryInterface
+ * @return {Promise}
+ */
+ async up(queryInterface) {
+ const createdAt = new Date();
+ const updatedAt = new Date();
+
+ /** @type {Map} */
+ const idMap = new Map();
+
+ /**
+ * @param {string} key
+ * @return {string}
+ */
+ function getId(key) {
+ if (idMap.has(key)) {
+ return idMap.get(key);
+ }
+ const id = uuid();
+ idMap.set(key, id);
+ return id;
+ }
+
+ /**
+ * @param {string} name
+ */
+ function createPermissions(name) {
+ return [
+ {
+ id: getId(`CREATE_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `CREATE_${name.toUpperCase()}`,
+ },
+ {
+ id: getId(`READ_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `READ_${name.toUpperCase()}`,
+ },
+ {
+ id: getId(`UPDATE_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `UPDATE_${name.toUpperCase()}`,
+ },
+ {
+ id: getId(`DELETE_${name.toUpperCase()}`),
+ createdAt,
+ updatedAt,
+ name: `DELETE_${name.toUpperCase()}`,
+ },
+ ];
+ }
+
+ const entities = ['powersportvehicles'];
+
+ const createdPermissions = entities.flatMap(createPermissions);
+
+ // Add permissions to database
+ await queryInterface.bulkInsert('permissions', createdPermissions);
+ // Get permissions ids
+ const permissionsIds = createdPermissions.map((p) => p.id);
+ // Get admin role
+ const adminRole = await db.roles.findOne({
+ where: { name: config.roles.super_admin },
+ });
+
+ if (adminRole) {
+ // Add permissions to admin role if it exists
+ await adminRole.addPermissions(permissionsIds);
+ }
+ },
+ down: async (queryInterface, Sequelize) => {
+ await queryInterface.bulkDelete(
+ 'permissions',
+ entities.flatMap(createPermissions),
+ );
+ },
+};
diff --git a/backend/src/index.js b/backend/src/index.js
index 34ab168..d877ada 100644
--- a/backend/src/index.js
+++ b/backend/src/index.js
@@ -33,6 +33,8 @@ const permissionsRoutes = require('./routes/permissions');
const clientsRoutes = require('./routes/clients');
+const powersportvehiclesRoutes = require('./routes/powersportvehicles');
+
const getBaseUrl = (url) => {
if (!url) return '';
return url.endsWith('/api') ? url.slice(0, -4) : url;
@@ -43,9 +45,9 @@ const options = {
openapi: '3.0.0',
info: {
version: '1.0.0',
- title: 'Etherra',
+ title: 'Powersport Rentals',
description:
- 'Etherra Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.',
+ 'Powersport Rentals Online REST API for Testing and Prototyping application. You can perform all major operations with your entities - create, delete and etc.',
},
servers: [
{
@@ -134,6 +136,12 @@ app.use(
clientsRoutes,
);
+app.use(
+ '/api/powersportvehicles',
+ passport.authenticate('jwt', { session: false }),
+ powersportvehiclesRoutes,
+);
+
app.use(
'/api/openai',
passport.authenticate('jwt', { session: false }),
diff --git a/backend/src/routes/powersportvehicles.js b/backend/src/routes/powersportvehicles.js
new file mode 100644
index 0000000..96a3111
--- /dev/null
+++ b/backend/src/routes/powersportvehicles.js
@@ -0,0 +1,495 @@
+const express = require('express');
+
+const PowersportvehiclesService = require('../services/powersportvehicles');
+const PowersportvehiclesDBApi = require('../db/api/powersportvehicles');
+const wrapAsync = require('../helpers').wrapAsync;
+
+const config = require('../config');
+
+const router = express.Router();
+
+const { parse } = require('json2csv');
+
+const { checkCrudPermissions } = require('../middlewares/check-permissions');
+
+router.use(checkCrudPermissions('powersportvehicles'));
+
+/**
+ * @swagger
+ * components:
+ * schemas:
+ * Powersportvehicles:
+ * type: object
+ * properties:
+
+ * name:
+ * type: string
+ * default: name
+ * description:
+ * type: string
+ * default: description
+ * location:
+ * type: string
+ * default: location
+
+ * preptimebefore:
+ * type: integer
+ * format: int64
+ * preptimebetween:
+ * type: integer
+ * format: int64
+
+ * pricehourly:
+ * type: integer
+ * format: int64
+ * pricedaily:
+ * type: integer
+ * format: int64
+ * securitydeposit:
+ * type: integer
+ * format: int64
+
+ *
+ */
+
+/**
+ * @swagger
+ * tags:
+ * name: Powersportvehicles
+ * description: The Powersportvehicles managing API
+ */
+
+/**
+ * @swagger
+ * /api/powersportvehicles:
+ * post:
+ * security:
+ * - bearerAuth: []
+ * tags: [Powersportvehicles]
+ * summary: Add new item
+ * description: Add new item
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * data:
+ * description: Data of the updated item
+ * type: object
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * responses:
+ * 200:
+ * description: The item was successfully added
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 405:
+ * description: Invalid input data
+ * 500:
+ * description: Some server error
+ */
+router.post(
+ '/',
+ wrapAsync(async (req, res) => {
+ const referer =
+ req.headers.referer ||
+ `${req.protocol}://${req.hostname}${req.originalUrl}`;
+ const link = new URL(referer);
+ await PowersportvehiclesService.create(
+ req.body.data,
+ req.currentUser,
+ true,
+ link.host,
+ );
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/budgets/bulk-import:
+ * post:
+ * security:
+ * - bearerAuth: []
+ * tags: [Powersportvehicles]
+ * summary: Bulk import items
+ * description: Bulk import items
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * data:
+ * description: Data of the updated items
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * responses:
+ * 200:
+ * description: The items were successfully imported
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 405:
+ * description: Invalid input data
+ * 500:
+ * description: Some server error
+ *
+ */
+router.post(
+ '/bulk-import',
+ wrapAsync(async (req, res) => {
+ const referer =
+ req.headers.referer ||
+ `${req.protocol}://${req.hostname}${req.originalUrl}`;
+ const link = new URL(referer);
+ await PowersportvehiclesService.bulkImport(req, res, true, link.host);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/powersportvehicles/{id}:
+ * put:
+ * security:
+ * - bearerAuth: []
+ * tags: [Powersportvehicles]
+ * summary: Update the data of the selected item
+ * description: Update the data of the selected item
+ * parameters:
+ * - in: path
+ * name: id
+ * description: Item ID to update
+ * required: true
+ * schema:
+ * type: string
+ * requestBody:
+ * description: Set new item data
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * id:
+ * description: ID of the updated item
+ * type: string
+ * data:
+ * description: Data of the updated item
+ * type: object
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * required:
+ * - id
+ * responses:
+ * 200:
+ * description: The item data was successfully updated
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * 400:
+ * description: Invalid ID supplied
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Item not found
+ * 500:
+ * description: Some server error
+ */
+router.put(
+ '/:id',
+ wrapAsync(async (req, res) => {
+ await PowersportvehiclesService.update(
+ req.body.data,
+ req.body.id,
+ req.currentUser,
+ );
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/powersportvehicles/{id}:
+ * delete:
+ * security:
+ * - bearerAuth: []
+ * tags: [Powersportvehicles]
+ * summary: Delete the selected item
+ * description: Delete the selected item
+ * parameters:
+ * - in: path
+ * name: id
+ * description: Item ID to delete
+ * required: true
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: The item was successfully deleted
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * 400:
+ * description: Invalid ID supplied
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Item not found
+ * 500:
+ * description: Some server error
+ */
+router.delete(
+ '/:id',
+ wrapAsync(async (req, res) => {
+ await PowersportvehiclesService.remove(req.params.id, req.currentUser);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/powersportvehicles/deleteByIds:
+ * post:
+ * security:
+ * - bearerAuth: []
+ * tags: [Powersportvehicles]
+ * summary: Delete the selected item list
+ * description: Delete the selected item list
+ * requestBody:
+ * required: true
+ * content:
+ * application/json:
+ * schema:
+ * properties:
+ * ids:
+ * description: IDs of the updated items
+ * type: array
+ * responses:
+ * 200:
+ * description: The items was successfully deleted
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Items not found
+ * 500:
+ * description: Some server error
+ */
+router.post(
+ '/deleteByIds',
+ wrapAsync(async (req, res) => {
+ await PowersportvehiclesService.deleteByIds(req.body.data, req.currentUser);
+ const payload = true;
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/powersportvehicles:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Powersportvehicles]
+ * summary: Get all powersportvehicles
+ * description: Get all powersportvehicles
+ * responses:
+ * 200:
+ * description: Powersportvehicles list successfully received
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Data not found
+ * 500:
+ * description: Some server error
+ */
+router.get(
+ '/',
+ wrapAsync(async (req, res) => {
+ const filetype = req.query.filetype;
+
+ const globalAccess = req.currentUser.app_role.globalAccess;
+
+ const currentUser = req.currentUser;
+ const payload = await PowersportvehiclesDBApi.findAll(
+ req.query,
+ globalAccess,
+ { currentUser },
+ );
+ if (filetype && filetype === 'csv') {
+ const fields = [
+ 'id',
+ 'name',
+ 'description',
+ 'location',
+ 'preptimebefore',
+ 'preptimebetween',
+ 'pricehourly',
+ 'pricedaily',
+ 'securitydeposit',
+ 'availabilitystart',
+ 'availabilityend',
+ ];
+ const opts = { fields };
+ try {
+ const csv = parse(payload.rows, opts);
+ res.status(200).attachment(csv);
+ res.send(csv);
+ } catch (err) {
+ console.error(err);
+ }
+ } else {
+ res.status(200).send(payload);
+ }
+ }),
+);
+
+/**
+ * @swagger
+ * /api/powersportvehicles/count:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Powersportvehicles]
+ * summary: Count all powersportvehicles
+ * description: Count all powersportvehicles
+ * responses:
+ * 200:
+ * description: Powersportvehicles count successfully received
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Data not found
+ * 500:
+ * description: Some server error
+ */
+router.get(
+ '/count',
+ wrapAsync(async (req, res) => {
+ const globalAccess = req.currentUser.app_role.globalAccess;
+
+ const currentUser = req.currentUser;
+ const payload = await PowersportvehiclesDBApi.findAll(
+ req.query,
+ globalAccess,
+ { countOnly: true, currentUser },
+ );
+
+ res.status(200).send(payload);
+ }),
+);
+
+/**
+ * @swagger
+ * /api/powersportvehicles/autocomplete:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Powersportvehicles]
+ * summary: Find all powersportvehicles that match search criteria
+ * description: Find all powersportvehicles that match search criteria
+ * responses:
+ * 200:
+ * description: Powersportvehicles list successfully received
+ * content:
+ * application/json:
+ * schema:
+ * type: array
+ * items:
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Data not found
+ * 500:
+ * description: Some server error
+ */
+router.get('/autocomplete', async (req, res) => {
+ const globalAccess = req.currentUser.app_role.globalAccess;
+
+ const organizationId = req.currentUser.organization?.id;
+
+ const payload = await PowersportvehiclesDBApi.findAllAutocomplete(
+ req.query.query,
+ req.query.limit,
+ req.query.offset,
+ globalAccess,
+ organizationId,
+ );
+
+ res.status(200).send(payload);
+});
+
+/**
+ * @swagger
+ * /api/powersportvehicles/{id}:
+ * get:
+ * security:
+ * - bearerAuth: []
+ * tags: [Powersportvehicles]
+ * summary: Get selected item
+ * description: Get selected item
+ * parameters:
+ * - in: path
+ * name: id
+ * description: ID of item to get
+ * required: true
+ * schema:
+ * type: string
+ * responses:
+ * 200:
+ * description: Selected item successfully received
+ * content:
+ * application/json:
+ * schema:
+ * $ref: "#/components/schemas/Powersportvehicles"
+ * 400:
+ * description: Invalid ID supplied
+ * 401:
+ * $ref: "#/components/responses/UnauthorizedError"
+ * 404:
+ * description: Item not found
+ * 500:
+ * description: Some server error
+ */
+router.get(
+ '/:id',
+ wrapAsync(async (req, res) => {
+ const payload = await PowersportvehiclesDBApi.findBy({ id: req.params.id });
+
+ res.status(200).send(payload);
+ }),
+);
+
+router.use('/', require('../helpers').commonErrorHandler);
+
+module.exports = router;
diff --git a/backend/src/routes/vehicles.js b/backend/src/routes/vehicles.js
index ecc9f15..77cf18c 100644
--- a/backend/src/routes/vehicles.js
+++ b/backend/src/routes/vehicles.js
@@ -42,6 +42,7 @@ router.use(checkCrudPermissions('vehicles'));
* type: integer
* format: int64
+ *
*/
/**
diff --git a/backend/src/services/notifications/list.js b/backend/src/services/notifications/list.js
index e108356..beac58e 100644
--- a/backend/src/services/notifications/list.js
+++ b/backend/src/services/notifications/list.js
@@ -1,6 +1,6 @@
const errors = {
app: {
- title: 'Etherra',
+ title: 'Powersport Rentals',
},
auth: {
diff --git a/backend/src/services/powersportvehicles.js b/backend/src/services/powersportvehicles.js
new file mode 100644
index 0000000..481f95c
--- /dev/null
+++ b/backend/src/services/powersportvehicles.js
@@ -0,0 +1,121 @@
+const db = require('../db/models');
+const PowersportvehiclesDBApi = require('../db/api/powersportvehicles');
+const processFile = require('../middlewares/upload');
+const ValidationError = require('./notifications/errors/validation');
+const csv = require('csv-parser');
+const axios = require('axios');
+const config = require('../config');
+const stream = require('stream');
+
+module.exports = class PowersportvehiclesService {
+ static async create(data, currentUser) {
+ const transaction = await db.sequelize.transaction();
+ try {
+ await PowersportvehiclesDBApi.create(data, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async bulkImport(req, res, sendInvitationEmails = true, host) {
+ const transaction = await db.sequelize.transaction();
+
+ try {
+ await processFile(req, res);
+ const bufferStream = new stream.PassThrough();
+ const results = [];
+
+ await bufferStream.end(Buffer.from(req.file.buffer, 'utf-8')); // convert Buffer to Stream
+
+ await new Promise((resolve, reject) => {
+ bufferStream
+ .pipe(csv())
+ .on('data', (data) => results.push(data))
+ .on('end', async () => {
+ console.log('CSV results', results);
+ resolve();
+ })
+ .on('error', (error) => reject(error));
+ });
+
+ await PowersportvehiclesDBApi.bulkImport(results, {
+ transaction,
+ ignoreDuplicates: true,
+ validate: true,
+ currentUser: req.currentUser,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async update(data, id, currentUser) {
+ const transaction = await db.sequelize.transaction();
+ try {
+ let powersportvehicles = await PowersportvehiclesDBApi.findBy(
+ { id },
+ { transaction },
+ );
+
+ if (!powersportvehicles) {
+ throw new ValidationError('powersportvehiclesNotFound');
+ }
+
+ const updatedPowersportvehicles = await PowersportvehiclesDBApi.update(
+ id,
+ data,
+ {
+ currentUser,
+ transaction,
+ },
+ );
+
+ await transaction.commit();
+ return updatedPowersportvehicles;
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async deleteByIds(ids, currentUser) {
+ const transaction = await db.sequelize.transaction();
+
+ try {
+ await PowersportvehiclesDBApi.deleteByIds(ids, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+
+ static async remove(id, currentUser) {
+ const transaction = await db.sequelize.transaction();
+
+ try {
+ await PowersportvehiclesDBApi.remove(id, {
+ currentUser,
+ transaction,
+ });
+
+ await transaction.commit();
+ } catch (error) {
+ await transaction.rollback();
+ throw error;
+ }
+ }
+};
diff --git a/backend/src/services/search.js b/backend/src/services/search.js
index 6a2be75..aa390ef 100644
--- a/backend/src/services/search.js
+++ b/backend/src/services/search.js
@@ -46,11 +46,25 @@ module.exports = class SearchService {
vehicles: ['title', 'description', 'location'],
clients: ['name'],
+
+ powersportvehicles: ['name', 'description', 'location'],
};
const columnsInt = {
bookings: ['total_price'],
vehicles: ['price_hourly', 'price_daily', 'security_deposit'],
+
+ powersportvehicles: [
+ 'pricehourly',
+
+ 'pricedaily',
+
+ 'securitydeposit',
+
+ 'preptimebefore',
+
+ 'preptimebetween',
+ ],
};
let allFoundRecords = [];
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 606bb2f..4e9f710 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -25,7 +25,7 @@ services:
- ./data/db:/var/lib/postgresql/data
environment:
- POSTGRES_HOST_AUTH_METHOD=trust
- - POSTGRES_DB=db_etherra
+ - POSTGRES_DB=db_powersport_rentals
ports:
- "5432:5432"
logging:
diff --git a/frontend/README.md b/frontend/README.md
index ec63cd6..9adeebb 100644
--- a/frontend/README.md
+++ b/frontend/README.md
@@ -1,4 +1,4 @@
-# Etherra
+# Powersport Rentals
## This project was generated by Flatlogic Platform.
diff --git a/frontend/json/runtimeError.json b/frontend/json/runtimeError.json
new file mode 100644
index 0000000..9e26dfe
--- /dev/null
+++ b/frontend/json/runtimeError.json
@@ -0,0 +1 @@
+{}
\ No newline at end of file
diff --git a/frontend/src/components/AsideMenuLayer.tsx b/frontend/src/components/AsideMenuLayer.tsx
index c4e300c..0aaf2d4 100644
--- a/frontend/src/components/AsideMenuLayer.tsx
+++ b/frontend/src/components/AsideMenuLayer.tsx
@@ -76,7 +76,7 @@ export default function AsideMenuLayer({
>
-
Etherra
+
Powersport Rentals
{organizationName &&
{organizationName}
}
diff --git a/frontend/src/components/Bookings/CardBookings.tsx b/frontend/src/components/Bookings/CardBookings.tsx
index 73312a3..8f70913 100644
--- a/frontend/src/components/Bookings/CardBookings.tsx
+++ b/frontend/src/components/Bookings/CardBookings.tsx
@@ -141,6 +141,19 @@ const CardBookings = ({
+
+
+
+ Powersportvehicle
+
+
+
+ {dataFormatter.powersportvehiclesOneListFormatter(
+ item.powersportvehicle,
+ )}
+
+
+
))}
diff --git a/frontend/src/components/Bookings/ListBookings.tsx b/frontend/src/components/Bookings/ListBookings.tsx
index 20fc161..af053d0 100644
--- a/frontend/src/components/Bookings/ListBookings.tsx
+++ b/frontend/src/components/Bookings/ListBookings.tsx
@@ -90,6 +90,17 @@ const ListBookings = ({
TotalPrice
{item.total_price}
+
+
+
+ Powersportvehicle
+
+
+ {dataFormatter.powersportvehiclesOneListFormatter(
+ item.powersportvehicle,
+ )}
+
+
value?.id,
+ getOptionLabel: (value: any) => value?.label,
+ valueOptions: await callOptionsApi('powersportvehicles'),
+ valueGetter: (params: GridValueGetterParams) =>
+ params?.value?.id ?? params?.value,
+ },
+
{
field: 'actions',
type: 'actions',
diff --git a/frontend/src/components/Powersportvehicles/CardPowersportvehicles.tsx b/frontend/src/components/Powersportvehicles/CardPowersportvehicles.tsx
new file mode 100644
index 0000000..a684610
--- /dev/null
+++ b/frontend/src/components/Powersportvehicles/CardPowersportvehicles.tsx
@@ -0,0 +1,239 @@
+import React from 'react';
+import ImageField from '../ImageField';
+import ListActionsPopover from '../ListActionsPopover';
+import { useAppSelector } from '../../stores/hooks';
+import dataFormatter from '../../helpers/dataFormatter';
+import { Pagination } from '../Pagination';
+import { saveFile } from '../../helpers/fileSaver';
+import LoadingSpinner from '../LoadingSpinner';
+import Link from 'next/link';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+type Props = {
+ powersportvehicles: any[];
+ loading: boolean;
+ onDelete: (id: string) => void;
+ currentPage: number;
+ numPages: number;
+ onPageChange: (page: number) => void;
+};
+
+const CardPowersportvehicles = ({
+ powersportvehicles,
+ loading,
+ onDelete,
+ currentPage,
+ numPages,
+ onPageChange,
+}: Props) => {
+ const asideScrollbarsStyle = useAppSelector(
+ (state) => state.style.asideScrollbarsStyle,
+ );
+ const bgColor = useAppSelector((state) => state.style.cardsColor);
+ const darkMode = useAppSelector((state) => state.style.darkMode);
+ const corners = useAppSelector((state) => state.style.corners);
+ const focusRing = useAppSelector((state) => state.style.focusRingColor);
+
+ const currentUser = useAppSelector((state) => state.auth.currentUser);
+ const hasUpdatePermission = hasPermission(
+ currentUser,
+ 'UPDATE_POWERSPORTVEHICLES',
+ );
+
+ return (
+
+ {loading &&
}
+
+ {!loading &&
+ powersportvehicles.map((item, index) => (
+ -
+
+
+
+
{item.name}
+
+
+
+
+
+
+
+
+
- Name
+
-
+
{item.name}
+
+
+
+
+
-
+ Description
+
+
-
+
+ {item.description}
+
+
+
+
+
+
+
+
-
+ Location
+
+
-
+
+ {item.location}
+
+
+
+
+
+
-
+ Pricehourly
+
+
-
+
+ {item.pricehourly}
+
+
+
+
+
+
-
+ Pricedaily
+
+
-
+
+ {item.pricedaily}
+
+
+
+
+
+
-
+ Securitydeposit
+
+
-
+
+ {item.securitydeposit}
+
+
+
+
+
+
-
+ Availabilitystart
+
+
-
+
+ {dataFormatter.dateTimeFormatter(item.availabilitystart)}
+
+
+
+
+
+
-
+ Availabilityend
+
+
-
+
+ {dataFormatter.dateTimeFormatter(item.availabilityend)}
+
+
+
+
+
+
-
+ Vehicletype
+
+
-
+
+ {item.vehicletype}
+
+
+
+
+
+
-
+ Preptimebefore
+
+
-
+
+ {item.preptimebefore}
+
+
+
+
+
+
-
+ Preptimebetween
+
+
-
+
+ {item.preptimebetween}
+
+
+
+
+
+ ))}
+ {!loading && powersportvehicles.length === 0 && (
+
+ )}
+
+
+
+ );
+};
+
+export default CardPowersportvehicles;
diff --git a/frontend/src/components/Powersportvehicles/ListPowersportvehicles.tsx b/frontend/src/components/Powersportvehicles/ListPowersportvehicles.tsx
new file mode 100644
index 0000000..d647558
--- /dev/null
+++ b/frontend/src/components/Powersportvehicles/ListPowersportvehicles.tsx
@@ -0,0 +1,176 @@
+import React from 'react';
+import CardBox from '../CardBox';
+import ImageField from '../ImageField';
+import dataFormatter from '../../helpers/dataFormatter';
+import { saveFile } from '../../helpers/fileSaver';
+import ListActionsPopover from '../ListActionsPopover';
+import { useAppSelector } from '../../stores/hooks';
+import { Pagination } from '../Pagination';
+import LoadingSpinner from '../LoadingSpinner';
+import Link from 'next/link';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+type Props = {
+ powersportvehicles: any[];
+ loading: boolean;
+ onDelete: (id: string) => void;
+ currentPage: number;
+ numPages: number;
+ onPageChange: (page: number) => void;
+};
+
+const ListPowersportvehicles = ({
+ powersportvehicles,
+ loading,
+ onDelete,
+ currentPage,
+ numPages,
+ onPageChange,
+}: Props) => {
+ const currentUser = useAppSelector((state) => state.auth.currentUser);
+ const hasUpdatePermission = hasPermission(
+ currentUser,
+ 'UPDATE_POWERSPORTVEHICLES',
+ );
+
+ const corners = useAppSelector((state) => state.style.corners);
+ const bgColor = useAppSelector((state) => state.style.cardsColor);
+
+ return (
+ <>
+
+ {loading &&
}
+ {!loading &&
+ powersportvehicles.map((item) => (
+
+
+
+
+
+
dark:divide-dark-700 overflow-x-auto'
+ }
+ >
+
+
+
+
Description
+
{item.description}
+
+
+
+
+
+
Location
+
{item.location}
+
+
+
+
Pricehourly
+
{item.pricehourly}
+
+
+
+
Pricedaily
+
{item.pricedaily}
+
+
+
+
+ Securitydeposit
+
+
{item.securitydeposit}
+
+
+
+
+ Availabilitystart
+
+
+ {dataFormatter.dateTimeFormatter(
+ item.availabilitystart,
+ )}
+
+
+
+
+
+ Availabilityend
+
+
+ {dataFormatter.dateTimeFormatter(item.availabilityend)}
+
+
+
+
+
Vehicletype
+
{item.vehicletype}
+
+
+
+
+ Preptimebefore
+
+
{item.preptimebefore}
+
+
+
+
+ Preptimebetween
+
+
{item.preptimebetween}
+
+
+
+
+
+
+ ))}
+ {!loading && powersportvehicles.length === 0 && (
+
+ )}
+
+
+ >
+ );
+};
+
+export default ListPowersportvehicles;
diff --git a/frontend/src/components/Powersportvehicles/TablePowersportvehicles.tsx b/frontend/src/components/Powersportvehicles/TablePowersportvehicles.tsx
new file mode 100644
index 0000000..3963f61
--- /dev/null
+++ b/frontend/src/components/Powersportvehicles/TablePowersportvehicles.tsx
@@ -0,0 +1,489 @@
+import React, { useEffect, useState, useMemo } from 'react';
+import { createPortal } from 'react-dom';
+import { ToastContainer, toast } from 'react-toastify';
+import BaseButton from '../BaseButton';
+import CardBoxModal from '../CardBoxModal';
+import CardBox from '../CardBox';
+import {
+ fetch,
+ update,
+ deleteItem,
+ setRefetch,
+ deleteItemsByIds,
+} from '../../stores/powersportvehicles/powersportvehiclesSlice';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import { useRouter } from 'next/router';
+import { Field, Form, Formik } from 'formik';
+import { DataGrid, GridColDef } from '@mui/x-data-grid';
+import { loadColumns } from './configurePowersportvehiclesCols';
+import _ from 'lodash';
+import dataFormatter from '../../helpers/dataFormatter';
+import { dataGridStyles } from '../../styles';
+
+const perPage = 10;
+
+const TableSamplePowersportvehicles = ({
+ filterItems,
+ setFilterItems,
+ filters,
+ showGrid,
+}) => {
+ const notify = (type, msg) => toast(msg, { type, position: 'bottom-center' });
+
+ const dispatch = useAppDispatch();
+ const router = useRouter();
+
+ const pagesList = [];
+ const [id, setId] = useState(null);
+ const [currentPage, setCurrentPage] = useState(0);
+ const [filterRequest, setFilterRequest] = React.useState('');
+ const [columns, setColumns] = useState([]);
+ const [selectedRows, setSelectedRows] = useState([]);
+ const [sortModel, setSortModel] = useState([
+ {
+ field: '',
+ sort: 'desc',
+ },
+ ]);
+
+ const {
+ powersportvehicles,
+ loading,
+ count,
+ notify: powersportvehiclesNotify,
+ refetch,
+ } = useAppSelector((state) => state.powersportvehicles);
+ const { currentUser } = useAppSelector((state) => state.auth);
+ const focusRing = useAppSelector((state) => state.style.focusRingColor);
+ const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
+ const corners = useAppSelector((state) => state.style.corners);
+ const numPages =
+ Math.floor(count / perPage) === 0 ? 1 : Math.ceil(count / perPage);
+ for (let i = 0; i < numPages; i++) {
+ pagesList.push(i);
+ }
+
+ const loadData = async (page = currentPage, request = filterRequest) => {
+ if (page !== currentPage) setCurrentPage(page);
+ if (request !== filterRequest) setFilterRequest(request);
+ const { sort, field } = sortModel[0];
+
+ const query = `?page=${page}&limit=${perPage}${request}&sort=${sort}&field=${field}`;
+ dispatch(fetch({ limit: perPage, page, query }));
+ };
+
+ useEffect(() => {
+ if (powersportvehiclesNotify.showNotification) {
+ notify(
+ powersportvehiclesNotify.typeNotification,
+ powersportvehiclesNotify.textNotification,
+ );
+ }
+ }, [powersportvehiclesNotify.showNotification]);
+
+ useEffect(() => {
+ if (!currentUser) return;
+ loadData();
+ }, [sortModel, currentUser]);
+
+ useEffect(() => {
+ if (refetch) {
+ loadData(0);
+ dispatch(setRefetch(false));
+ }
+ }, [refetch, dispatch]);
+
+ const [isModalInfoActive, setIsModalInfoActive] = useState(false);
+ const [isModalTrashActive, setIsModalTrashActive] = useState(false);
+
+ const handleModalAction = () => {
+ setIsModalInfoActive(false);
+ setIsModalTrashActive(false);
+ };
+
+ const handleDeleteModalAction = (id: string) => {
+ setId(id);
+ setIsModalTrashActive(true);
+ };
+ const handleDeleteAction = async () => {
+ if (id) {
+ await dispatch(deleteItem(id));
+ await loadData(0);
+ setIsModalTrashActive(false);
+ }
+ };
+
+ const generateFilterRequests = useMemo(() => {
+ let request = '&';
+ filterItems.forEach((item) => {
+ const isRangeFilter = filters.find(
+ (filter) =>
+ filter.title === item.fields.selectedField &&
+ (filter.number || filter.date),
+ );
+
+ if (isRangeFilter) {
+ const from = item.fields.filterValueFrom;
+ const to = item.fields.filterValueTo;
+ if (from) {
+ request += `${item.fields.selectedField}Range=${from}&`;
+ }
+ if (to) {
+ request += `${item.fields.selectedField}Range=${to}&`;
+ }
+ } else {
+ const value = item.fields.filterValue;
+ if (value) {
+ request += `${item.fields.selectedField}=${value}&`;
+ }
+ }
+ });
+ return request;
+ }, [filterItems, filters]);
+
+ const deleteFilter = (value) => {
+ const newItems = filterItems.filter((item) => item.id !== value);
+
+ if (newItems.length) {
+ setFilterItems(newItems);
+ } else {
+ loadData(0, '');
+
+ setFilterItems(newItems);
+ }
+ };
+
+ const handleSubmit = () => {
+ loadData(0, generateFilterRequests);
+ };
+
+ const handleChange = (id) => (e) => {
+ const value = e.target.value;
+ const name = e.target.name;
+
+ setFilterItems(
+ filterItems.map((item) => {
+ if (item.id !== id) return item;
+ if (name === 'selectedField') return { id, fields: { [name]: value } };
+
+ return { id, fields: { ...item.fields, [name]: value } };
+ }),
+ );
+ };
+
+ const handleReset = () => {
+ setFilterItems([]);
+ loadData(0, '');
+ };
+
+ const onPageChange = (page: number) => {
+ loadData(page);
+ setCurrentPage(page);
+ };
+
+ useEffect(() => {
+ if (!currentUser) return;
+
+ loadColumns(
+ handleDeleteModalAction,
+ `powersportvehicles`,
+ currentUser,
+ ).then((newCols) => setColumns(newCols));
+ }, [currentUser]);
+
+ const handleTableSubmit = async (id: string, data) => {
+ if (!_.isEmpty(data)) {
+ await dispatch(update({ id, data }))
+ .unwrap()
+ .then((res) => res)
+ .catch((err) => {
+ throw new Error(err);
+ });
+ }
+ };
+
+ const onDeleteRows = async (selectedRows) => {
+ await dispatch(deleteItemsByIds(selectedRows));
+ await loadData(0);
+ };
+
+ const controlClasses =
+ 'w-full py-2 px-2 my-2 rounded dark:placeholder-gray-400 ' +
+ ` ${bgColor} ${focusRing} ${corners} ` +
+ 'dark:bg-slate-800 border';
+
+ const dataGrid = (
+
+ `datagrid--row`}
+ rows={powersportvehicles ?? []}
+ columns={columns}
+ initialState={{
+ pagination: {
+ paginationModel: {
+ pageSize: 10,
+ },
+ },
+ }}
+ disableRowSelectionOnClick
+ onProcessRowUpdateError={(params) => {
+ console.log('Error', params);
+ }}
+ processRowUpdate={async (newRow, oldRow) => {
+ const data = dataFormatter.dataGridEditFormatter(newRow);
+
+ try {
+ await handleTableSubmit(newRow.id, data);
+ return newRow;
+ } catch {
+ return oldRow;
+ }
+ }}
+ sortingMode={'server'}
+ checkboxSelection
+ onRowSelectionModelChange={(ids) => {
+ setSelectedRows(ids);
+ }}
+ onSortModelChange={(params) => {
+ params.length
+ ? setSortModel(params)
+ : setSortModel([{ field: '', sort: 'desc' }]);
+ }}
+ rowCount={count}
+ pageSizeOptions={[10]}
+ paginationMode={'server'}
+ loading={loading}
+ onPaginationModelChange={(params) => {
+ onPageChange(params.page);
+ }}
+ />
+
+ );
+
+ return (
+ <>
+ {filterItems && Array.isArray(filterItems) && filterItems.length ? (
+
+ null}
+ >
+
+
+
+ ) : null}
+
+ Are you sure you want to delete this item?
+
+
+ {dataGrid}
+
+ {selectedRows.length > 0 &&
+ createPortal(
+ onDeleteRows(selectedRows)}
+ />,
+ document.getElementById('delete-rows-button'),
+ )}
+
+ >
+ );
+};
+
+export default TableSamplePowersportvehicles;
diff --git a/frontend/src/components/Powersportvehicles/configurePowersportvehiclesCols.tsx b/frontend/src/components/Powersportvehicles/configurePowersportvehiclesCols.tsx
new file mode 100644
index 0000000..f3188ac
--- /dev/null
+++ b/frontend/src/components/Powersportvehicles/configurePowersportvehiclesCols.tsx
@@ -0,0 +1,235 @@
+import React from 'react';
+import BaseIcon from '../BaseIcon';
+import { mdiEye, mdiTrashCan, mdiPencilOutline } from '@mdi/js';
+import axios from 'axios';
+import {
+ GridActionsCellItem,
+ GridRowParams,
+ GridValueGetterParams,
+} from '@mui/x-data-grid';
+import ImageField from '../ImageField';
+import { saveFile } from '../../helpers/fileSaver';
+import dataFormatter from '../../helpers/dataFormatter';
+import DataGridMultiSelect from '../DataGridMultiSelect';
+import ListActionsPopover from '../ListActionsPopover';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+type Params = (id: string) => void;
+
+export const loadColumns = async (
+ onDelete: Params,
+ entityName: string,
+
+ user,
+) => {
+ async function callOptionsApi(entityName: string) {
+ if (!hasPermission(user, 'READ_' + entityName.toUpperCase())) return [];
+
+ try {
+ const data = await axios(`/${entityName}/autocomplete?limit=100`);
+ return data.data;
+ } catch (error) {
+ console.log(error);
+ return [];
+ }
+ }
+
+ const hasUpdatePermission = hasPermission(user, 'UPDATE_POWERSPORTVEHICLES');
+
+ return [
+ {
+ field: 'name',
+ headerName: 'Name',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+ },
+
+ {
+ field: 'description',
+ headerName: 'Description',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+ },
+
+ {
+ field: 'photos',
+ headerName: 'Photos',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: false,
+ sortable: false,
+ renderCell: (params: GridValueGetterParams) => (
+
+ ),
+ },
+
+ {
+ field: 'location',
+ headerName: 'Location',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+ },
+
+ {
+ field: 'pricehourly',
+ headerName: 'Pricehourly',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'number',
+ },
+
+ {
+ field: 'pricedaily',
+ headerName: 'Pricedaily',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'number',
+ },
+
+ {
+ field: 'securitydeposit',
+ headerName: 'Securitydeposit',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'number',
+ },
+
+ {
+ field: 'availabilitystart',
+ headerName: 'Availabilitystart',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'dateTime',
+ valueGetter: (params: GridValueGetterParams) =>
+ new Date(params.row.availabilitystart),
+ },
+
+ {
+ field: 'availabilityend',
+ headerName: 'Availabilityend',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'dateTime',
+ valueGetter: (params: GridValueGetterParams) =>
+ new Date(params.row.availabilityend),
+ },
+
+ {
+ field: 'vehicletype',
+ headerName: 'Vehicletype',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'singleSelect',
+ valueOptions: ['value'],
+ },
+
+ {
+ field: 'preptimebefore',
+ headerName: 'Preptimebefore',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'number',
+ },
+
+ {
+ field: 'preptimebetween',
+ headerName: 'Preptimebetween',
+ flex: 1,
+ minWidth: 120,
+ filterable: false,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+
+ editable: hasUpdatePermission,
+
+ type: 'number',
+ },
+
+ {
+ field: 'actions',
+ type: 'actions',
+ minWidth: 30,
+ headerClassName: 'datagrid--header',
+ cellClassName: 'datagrid--cell',
+ getActions: (params: GridRowParams) => {
+ return [
+
+
+
,
+ ];
+ },
+ },
+ ];
+};
diff --git a/frontend/src/components/Vehicles/CardVehicles.tsx b/frontend/src/components/Vehicles/CardVehicles.tsx
index 786cf88..45d0b8d 100644
--- a/frontend/src/components/Vehicles/CardVehicles.tsx
+++ b/frontend/src/components/Vehicles/CardVehicles.tsx
@@ -180,6 +180,17 @@ const CardVehicles = ({
+
+
+
+ Vehicletype
+
+
+
+ {item.vehicletype}
+
+
+
))}
diff --git a/frontend/src/components/Vehicles/ListVehicles.tsx b/frontend/src/components/Vehicles/ListVehicles.tsx
index 974684c..66f91d5 100644
--- a/frontend/src/components/Vehicles/ListVehicles.tsx
+++ b/frontend/src/components/Vehicles/ListVehicles.tsx
@@ -118,6 +118,11 @@ const ListVehicles = ({
.join(', ')}
+
+
+
Vehicletype
+
{item.vehicletype}
+
state.style.borders);
const websiteHeder = useAppSelector((state) => state.style.websiteHeder);
- const style = FooterStyle.WITH_PAGES;
+ const style = FooterStyle.WITH_PROJECT_NAME;
- const design = FooterDesigns.DEFAULT_DESIGN;
+ const design = FooterDesigns.DESIGN_DIVERSITY;
return (
state.style.websiteHeder);
const borders = useAppSelector((state) => state.style.borders);
- const style = HeaderStyle.PAGES_RIGHT;
+ const style = HeaderStyle.PAGES_LEFT;
- const design = HeaderDesigns.DEFAULT_DESIGN;
+ const design = HeaderDesigns.DESIGN_DIVERSITY;
return (
)}
+
+ {hasPermission(currentUser, 'READ_POWERSPORTVEHICLES') && (
+
+
+
+
+
+ Powersportvehicles
+
+
+ {powersportvehicles}
+
+
+
+
+
+
+
+
+ )}
>
diff --git a/frontend/src/pages/index.tsx b/frontend/src/pages/index.tsx
index 3f7a725..300faee 100644
--- a/frontend/src/pages/index.tsx
+++ b/frontend/src/pages/index.tsx
@@ -24,7 +24,7 @@ import ContactFormSection from '../components/WebPageComponents/ContactFormCompo
export default function WebSite() {
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
- const projectName = 'Etherra';
+ const projectName = 'Powersport Rentals';
useEffect(() => {
const darkElement = document.querySelector('body .dark');
@@ -112,10 +112,10 @@ export default function WebSite() {
content={`Discover and rent recreational vehicles with ease on our peer-to-peer RV rental platform. List your RV or find the perfect one for your next adventure.`}
/>
-
+
-
+
);
}
diff --git a/frontend/src/pages/login.tsx b/frontend/src/pages/login.tsx
index ad612a3..215fd4e 100644
--- a/frontend/src/pages/login.tsx
+++ b/frontend/src/pages/login.tsx
@@ -52,7 +52,7 @@ export default function Login() {
remember: true,
});
- const title = 'Etherra';
+ const title = 'Powersport Rentals';
// Fetch Pexels image/video
useEffect(() => {
diff --git a/frontend/src/pages/powersportvehicles/[powersportvehiclesId].tsx b/frontend/src/pages/powersportvehicles/[powersportvehiclesId].tsx
new file mode 100644
index 0000000..4fe06b0
--- /dev/null
+++ b/frontend/src/pages/powersportvehicles/[powersportvehiclesId].tsx
@@ -0,0 +1,288 @@
+import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
+import Head from 'next/head';
+import React, { ReactElement, useEffect, useState } from 'react';
+import DatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css';
+import dayjs from 'dayjs';
+
+import CardBox from '../../components/CardBox';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import SectionMain from '../../components/SectionMain';
+import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
+import { getPageTitle } from '../../config';
+
+import { Field, Form, Formik } from 'formik';
+import FormField from '../../components/FormField';
+import BaseDivider from '../../components/BaseDivider';
+import BaseButtons from '../../components/BaseButtons';
+import BaseButton from '../../components/BaseButton';
+import FormCheckRadio from '../../components/FormCheckRadio';
+import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
+import FormFilePicker from '../../components/FormFilePicker';
+import FormImagePicker from '../../components/FormImagePicker';
+import { SelectField } from '../../components/SelectField';
+import { SelectFieldMany } from '../../components/SelectFieldMany';
+import { SwitchField } from '../../components/SwitchField';
+import { RichTextField } from '../../components/RichTextField';
+
+import {
+ update,
+ fetch,
+} from '../../stores/powersportvehicles/powersportvehiclesSlice';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import { useRouter } from 'next/router';
+import { saveFile } from '../../helpers/fileSaver';
+import dataFormatter from '../../helpers/dataFormatter';
+import ImageField from '../../components/ImageField';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+const EditPowersportvehicles = () => {
+ const router = useRouter();
+ const dispatch = useAppDispatch();
+ const initVals = {
+ clients: null,
+
+ name: '',
+
+ description: '',
+
+ photos: [],
+
+ location: '',
+
+ pricehourly: '',
+
+ pricedaily: '',
+
+ securitydeposit: '',
+
+ availabilitystart: new Date(),
+
+ availabilityend: new Date(),
+
+ vehicletype: '',
+
+ preptimebefore: '',
+
+ preptimebetween: '',
+ };
+ const [initialValues, setInitialValues] = useState(initVals);
+
+ const { powersportvehicles } = useAppSelector(
+ (state) => state.powersportvehicles,
+ );
+
+ const { currentUser } = useAppSelector((state) => state.auth);
+
+ const { powersportvehiclesId } = router.query;
+
+ useEffect(() => {
+ dispatch(fetch({ id: powersportvehiclesId }));
+ }, [powersportvehiclesId]);
+
+ useEffect(() => {
+ if (typeof powersportvehicles === 'object') {
+ setInitialValues(powersportvehicles);
+ }
+ }, [powersportvehicles]);
+
+ useEffect(() => {
+ if (typeof powersportvehicles === 'object') {
+ const newInitialVal = { ...initVals };
+
+ Object.keys(initVals).forEach(
+ (el) => (newInitialVal[el] = powersportvehicles[el]),
+ );
+
+ setInitialValues(newInitialVal);
+ }
+ }, [powersportvehicles]);
+
+ const handleSubmit = async (data) => {
+ await dispatch(update({ id: powersportvehiclesId, data }));
+ await router.push('/powersportvehicles/powersportvehicles-list');
+ };
+
+ return (
+ <>
+
+ {getPageTitle('Edit powersportvehicles')}
+
+
+
+ {''}
+
+
+ handleSubmit(values)}
+ >
+
+
+
+
+ >
+ );
+};
+
+EditPowersportvehicles.getLayout = function getLayout(page: ReactElement) {
+ return (
+
+ {page}
+
+ );
+};
+
+export default EditPowersportvehicles;
diff --git a/frontend/src/pages/powersportvehicles/powersportvehicles-edit.tsx b/frontend/src/pages/powersportvehicles/powersportvehicles-edit.tsx
new file mode 100644
index 0000000..bf04fbc
--- /dev/null
+++ b/frontend/src/pages/powersportvehicles/powersportvehicles-edit.tsx
@@ -0,0 +1,286 @@
+import { mdiChartTimelineVariant, mdiUpload } from '@mdi/js';
+import Head from 'next/head';
+import React, { ReactElement, useEffect, useState } from 'react';
+import DatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css';
+import dayjs from 'dayjs';
+
+import CardBox from '../../components/CardBox';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import SectionMain from '../../components/SectionMain';
+import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
+import { getPageTitle } from '../../config';
+
+import { Field, Form, Formik } from 'formik';
+import FormField from '../../components/FormField';
+import BaseDivider from '../../components/BaseDivider';
+import BaseButtons from '../../components/BaseButtons';
+import BaseButton from '../../components/BaseButton';
+import FormCheckRadio from '../../components/FormCheckRadio';
+import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
+import FormFilePicker from '../../components/FormFilePicker';
+import FormImagePicker from '../../components/FormImagePicker';
+import { SelectField } from '../../components/SelectField';
+import { SelectFieldMany } from '../../components/SelectFieldMany';
+import { SwitchField } from '../../components/SwitchField';
+import { RichTextField } from '../../components/RichTextField';
+
+import {
+ update,
+ fetch,
+} from '../../stores/powersportvehicles/powersportvehiclesSlice';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import { useRouter } from 'next/router';
+import { saveFile } from '../../helpers/fileSaver';
+import dataFormatter from '../../helpers/dataFormatter';
+import ImageField from '../../components/ImageField';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+const EditPowersportvehiclesPage = () => {
+ const router = useRouter();
+ const dispatch = useAppDispatch();
+ const initVals = {
+ clients: null,
+
+ name: '',
+
+ description: '',
+
+ photos: [],
+
+ location: '',
+
+ pricehourly: '',
+
+ pricedaily: '',
+
+ securitydeposit: '',
+
+ availabilitystart: new Date(),
+
+ availabilityend: new Date(),
+
+ vehicletype: '',
+
+ preptimebefore: '',
+
+ preptimebetween: '',
+ };
+ const [initialValues, setInitialValues] = useState(initVals);
+
+ const { powersportvehicles } = useAppSelector(
+ (state) => state.powersportvehicles,
+ );
+
+ const { currentUser } = useAppSelector((state) => state.auth);
+
+ const { id } = router.query;
+
+ useEffect(() => {
+ dispatch(fetch({ id: id }));
+ }, [id]);
+
+ useEffect(() => {
+ if (typeof powersportvehicles === 'object') {
+ setInitialValues(powersportvehicles);
+ }
+ }, [powersportvehicles]);
+
+ useEffect(() => {
+ if (typeof powersportvehicles === 'object') {
+ const newInitialVal = { ...initVals };
+ Object.keys(initVals).forEach(
+ (el) => (newInitialVal[el] = powersportvehicles[el]),
+ );
+ setInitialValues(newInitialVal);
+ }
+ }, [powersportvehicles]);
+
+ const handleSubmit = async (data) => {
+ await dispatch(update({ id: id, data }));
+ await router.push('/powersportvehicles/powersportvehicles-list');
+ };
+
+ return (
+ <>
+
+ {getPageTitle('Edit powersportvehicles')}
+
+
+
+ {''}
+
+
+ handleSubmit(values)}
+ >
+
+
+
+
+ >
+ );
+};
+
+EditPowersportvehiclesPage.getLayout = function getLayout(page: ReactElement) {
+ return (
+
+ {page}
+
+ );
+};
+
+export default EditPowersportvehiclesPage;
diff --git a/frontend/src/pages/powersportvehicles/powersportvehicles-list.tsx b/frontend/src/pages/powersportvehicles/powersportvehicles-list.tsx
new file mode 100644
index 0000000..d5a7f44
--- /dev/null
+++ b/frontend/src/pages/powersportvehicles/powersportvehicles-list.tsx
@@ -0,0 +1,185 @@
+import { mdiChartTimelineVariant } from '@mdi/js';
+import Head from 'next/head';
+import { uniqueId } from 'lodash';
+import React, { ReactElement, useState } from 'react';
+import CardBox from '../../components/CardBox';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import SectionMain from '../../components/SectionMain';
+import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
+import { getPageTitle } from '../../config';
+import TablePowersportvehicles from '../../components/Powersportvehicles/TablePowersportvehicles';
+import BaseButton from '../../components/BaseButton';
+import axios from 'axios';
+import Link from 'next/link';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import CardBoxModal from '../../components/CardBoxModal';
+import DragDropFilePicker from '../../components/DragDropFilePicker';
+import {
+ setRefetch,
+ uploadCsv,
+} from '../../stores/powersportvehicles/powersportvehiclesSlice';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+const PowersportvehiclesTablesPage = () => {
+ const [filterItems, setFilterItems] = useState([]);
+ const [csvFile, setCsvFile] = useState(null);
+ const [isModalActive, setIsModalActive] = useState(false);
+ const [showTableView, setShowTableView] = useState(false);
+
+ const { currentUser } = useAppSelector((state) => state.auth);
+
+ const dispatch = useAppDispatch();
+
+ const [filters] = useState([
+ { label: 'Name', title: 'name' },
+ { label: 'Description', title: 'description' },
+ { label: 'Location', title: 'location' },
+ { label: 'Preptimebefore', title: 'preptimebefore', number: 'true' },
+ { label: 'Preptimebetween', title: 'preptimebetween', number: 'true' },
+ { label: 'Pricehourly', title: 'pricehourly', number: 'true' },
+ { label: 'Pricedaily', title: 'pricedaily', number: 'true' },
+ { label: 'Securitydeposit', title: 'securitydeposit', number: 'true' },
+ { label: 'Availabilitystart', title: 'availabilitystart', date: 'true' },
+ { label: 'Availabilityend', title: 'availabilityend', date: 'true' },
+
+ {
+ label: 'Vehicletype',
+ title: 'vehicletype',
+ type: 'enum',
+ options: ['value'],
+ },
+ ]);
+
+ const hasCreatePermission =
+ currentUser && hasPermission(currentUser, 'CREATE_POWERSPORTVEHICLES');
+
+ const addFilter = () => {
+ const newItem = {
+ id: uniqueId(),
+ fields: {
+ filterValue: '',
+ filterValueFrom: '',
+ filterValueTo: '',
+ selectedField: '',
+ },
+ };
+ newItem.fields.selectedField = filters[0].title;
+ setFilterItems([...filterItems, newItem]);
+ };
+
+ const getPowersportvehiclesCSV = async () => {
+ const response = await axios({
+ url: '/powersportvehicles?filetype=csv',
+ method: 'GET',
+ responseType: 'blob',
+ });
+ const type = response.headers['content-type'];
+ const blob = new Blob([response.data], { type: type });
+ const link = document.createElement('a');
+ link.href = window.URL.createObjectURL(blob);
+ link.download = 'powersportvehiclesCSV.csv';
+ link.click();
+ };
+
+ const onModalConfirm = async () => {
+ if (!csvFile) return;
+ await dispatch(uploadCsv(csvFile));
+ dispatch(setRefetch(true));
+ setCsvFile(null);
+ setIsModalActive(false);
+ };
+
+ const onModalCancel = () => {
+ setCsvFile(null);
+ setIsModalActive(false);
+ };
+
+ return (
+ <>
+
+ {getPageTitle('Powersportvehicles')}
+
+
+
+ {''}
+
+
+ {hasCreatePermission && (
+
+ )}
+
+
+
+
+ {hasCreatePermission && (
+ setIsModalActive(true)}
+ />
+ )}
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+PowersportvehiclesTablesPage.getLayout = function getLayout(
+ page: ReactElement,
+) {
+ return (
+
+ {page}
+
+ );
+};
+
+export default PowersportvehiclesTablesPage;
diff --git a/frontend/src/pages/powersportvehicles/powersportvehicles-new.tsx b/frontend/src/pages/powersportvehicles/powersportvehicles-new.tsx
new file mode 100644
index 0000000..2c9f5fe
--- /dev/null
+++ b/frontend/src/pages/powersportvehicles/powersportvehicles-new.tsx
@@ -0,0 +1,222 @@
+import {
+ mdiAccount,
+ mdiChartTimelineVariant,
+ mdiMail,
+ mdiUpload,
+} from '@mdi/js';
+import Head from 'next/head';
+import React, { ReactElement } from 'react';
+import CardBox from '../../components/CardBox';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import SectionMain from '../../components/SectionMain';
+import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
+import { getPageTitle } from '../../config';
+
+import { Field, Form, Formik } from 'formik';
+import FormField from '../../components/FormField';
+import BaseDivider from '../../components/BaseDivider';
+import BaseButtons from '../../components/BaseButtons';
+import BaseButton from '../../components/BaseButton';
+import FormCheckRadio from '../../components/FormCheckRadio';
+import FormCheckRadioGroup from '../../components/FormCheckRadioGroup';
+import FormFilePicker from '../../components/FormFilePicker';
+import FormImagePicker from '../../components/FormImagePicker';
+import { SwitchField } from '../../components/SwitchField';
+
+import { SelectField } from '../../components/SelectField';
+import { SelectFieldMany } from '../../components/SelectFieldMany';
+import { RichTextField } from '../../components/RichTextField';
+
+import { create } from '../../stores/powersportvehicles/powersportvehiclesSlice';
+import { useAppDispatch } from '../../stores/hooks';
+import { useRouter } from 'next/router';
+import moment from 'moment';
+
+const initialValues = {
+ clients: '',
+
+ name: '',
+
+ description: '',
+
+ photos: [],
+
+ location: '',
+
+ pricehourly: '',
+
+ pricedaily: '',
+
+ securitydeposit: '',
+
+ availabilitystart: '',
+
+ availabilityend: '',
+
+ vehicletype: '',
+
+ preptimebefore: '',
+
+ preptimebetween: '',
+};
+
+const PowersportvehiclesNew = () => {
+ const router = useRouter();
+ const dispatch = useAppDispatch();
+
+ const handleSubmit = async (data) => {
+ await dispatch(create(data));
+ await router.push('/powersportvehicles/powersportvehicles-list');
+ };
+ return (
+ <>
+
+ {getPageTitle('New Item')}
+
+
+
+ {''}
+
+
+ handleSubmit(values)}
+ >
+
+
+
+
+ >
+ );
+};
+
+PowersportvehiclesNew.getLayout = function getLayout(page: ReactElement) {
+ return (
+
+ {page}
+
+ );
+};
+
+export default PowersportvehiclesNew;
diff --git a/frontend/src/pages/powersportvehicles/powersportvehicles-table.tsx b/frontend/src/pages/powersportvehicles/powersportvehicles-table.tsx
new file mode 100644
index 0000000..6d6d2a5
--- /dev/null
+++ b/frontend/src/pages/powersportvehicles/powersportvehicles-table.tsx
@@ -0,0 +1,184 @@
+import { mdiChartTimelineVariant } from '@mdi/js';
+import Head from 'next/head';
+import { uniqueId } from 'lodash';
+import React, { ReactElement, useState } from 'react';
+import CardBox from '../../components/CardBox';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import SectionMain from '../../components/SectionMain';
+import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
+import { getPageTitle } from '../../config';
+import TablePowersportvehicles from '../../components/Powersportvehicles/TablePowersportvehicles';
+import BaseButton from '../../components/BaseButton';
+import axios from 'axios';
+import Link from 'next/link';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import CardBoxModal from '../../components/CardBoxModal';
+import DragDropFilePicker from '../../components/DragDropFilePicker';
+import {
+ setRefetch,
+ uploadCsv,
+} from '../../stores/powersportvehicles/powersportvehiclesSlice';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+const PowersportvehiclesTablesPage = () => {
+ const [filterItems, setFilterItems] = useState([]);
+ const [csvFile, setCsvFile] = useState(null);
+ const [isModalActive, setIsModalActive] = useState(false);
+ const [showTableView, setShowTableView] = useState(false);
+
+ const { currentUser } = useAppSelector((state) => state.auth);
+
+ const dispatch = useAppDispatch();
+
+ const [filters] = useState([
+ { label: 'Name', title: 'name' },
+ { label: 'Description', title: 'description' },
+ { label: 'Location', title: 'location' },
+ { label: 'Preptimebefore', title: 'preptimebefore', number: 'true' },
+ { label: 'Preptimebetween', title: 'preptimebetween', number: 'true' },
+ { label: 'Pricehourly', title: 'pricehourly', number: 'true' },
+ { label: 'Pricedaily', title: 'pricedaily', number: 'true' },
+ { label: 'Securitydeposit', title: 'securitydeposit', number: 'true' },
+ { label: 'Availabilitystart', title: 'availabilitystart', date: 'true' },
+ { label: 'Availabilityend', title: 'availabilityend', date: 'true' },
+
+ {
+ label: 'Vehicletype',
+ title: 'vehicletype',
+ type: 'enum',
+ options: ['value'],
+ },
+ ]);
+
+ const hasCreatePermission =
+ currentUser && hasPermission(currentUser, 'CREATE_POWERSPORTVEHICLES');
+
+ const addFilter = () => {
+ const newItem = {
+ id: uniqueId(),
+ fields: {
+ filterValue: '',
+ filterValueFrom: '',
+ filterValueTo: '',
+ selectedField: '',
+ },
+ };
+ newItem.fields.selectedField = filters[0].title;
+ setFilterItems([...filterItems, newItem]);
+ };
+
+ const getPowersportvehiclesCSV = async () => {
+ const response = await axios({
+ url: '/powersportvehicles?filetype=csv',
+ method: 'GET',
+ responseType: 'blob',
+ });
+ const type = response.headers['content-type'];
+ const blob = new Blob([response.data], { type: type });
+ const link = document.createElement('a');
+ link.href = window.URL.createObjectURL(blob);
+ link.download = 'powersportvehiclesCSV.csv';
+ link.click();
+ };
+
+ const onModalConfirm = async () => {
+ if (!csvFile) return;
+ await dispatch(uploadCsv(csvFile));
+ dispatch(setRefetch(true));
+ setCsvFile(null);
+ setIsModalActive(false);
+ };
+
+ const onModalCancel = () => {
+ setCsvFile(null);
+ setIsModalActive(false);
+ };
+
+ return (
+ <>
+
+ {getPageTitle('Powersportvehicles')}
+
+
+
+ {''}
+
+
+ {hasCreatePermission && (
+
+ )}
+
+
+
+
+ {hasCreatePermission && (
+ setIsModalActive(true)}
+ />
+ )}
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+};
+
+PowersportvehiclesTablesPage.getLayout = function getLayout(
+ page: ReactElement,
+) {
+ return (
+
+ {page}
+
+ );
+};
+
+export default PowersportvehiclesTablesPage;
diff --git a/frontend/src/pages/powersportvehicles/powersportvehicles-view.tsx b/frontend/src/pages/powersportvehicles/powersportvehicles-view.tsx
new file mode 100644
index 0000000..08aee32
--- /dev/null
+++ b/frontend/src/pages/powersportvehicles/powersportvehicles-view.tsx
@@ -0,0 +1,249 @@
+import React, { ReactElement, useEffect } from 'react';
+import Head from 'next/head';
+import DatePicker from 'react-datepicker';
+import 'react-datepicker/dist/react-datepicker.css';
+import dayjs from 'dayjs';
+import { useAppDispatch, useAppSelector } from '../../stores/hooks';
+import { useRouter } from 'next/router';
+import { fetch } from '../../stores/powersportvehicles/powersportvehiclesSlice';
+import { saveFile } from '../../helpers/fileSaver';
+import dataFormatter from '../../helpers/dataFormatter';
+import ImageField from '../../components/ImageField';
+import LayoutAuthenticated from '../../layouts/Authenticated';
+import { getPageTitle } from '../../config';
+import SectionTitleLineWithButton from '../../components/SectionTitleLineWithButton';
+import SectionMain from '../../components/SectionMain';
+import CardBox from '../../components/CardBox';
+import BaseButton from '../../components/BaseButton';
+import BaseDivider from '../../components/BaseDivider';
+import { mdiChartTimelineVariant } from '@mdi/js';
+import { SwitchField } from '../../components/SwitchField';
+import FormField from '../../components/FormField';
+
+import { hasPermission } from '../../helpers/userPermissions';
+
+const PowersportvehiclesView = () => {
+ const router = useRouter();
+ const dispatch = useAppDispatch();
+ const { powersportvehicles } = useAppSelector(
+ (state) => state.powersportvehicles,
+ );
+
+ const { currentUser } = useAppSelector((state) => state.auth);
+
+ const { id } = router.query;
+
+ function removeLastCharacter(str) {
+ console.log(str, `str`);
+ return str.slice(0, -1);
+ }
+
+ useEffect(() => {
+ dispatch(fetch({ id }));
+ }, [dispatch, id]);
+
+ return (
+ <>
+
+ {getPageTitle('View powersportvehicles')}
+
+
+
+
+
+
+
+
clients
+
+
{powersportvehicles?.clients?.name ?? 'No data'}
+
+
+
+
Name
+
{powersportvehicles?.name}
+
+
+
+
Description
+
{powersportvehicles?.description}
+
+
+
+
Photos
+ {powersportvehicles?.photos?.length ? (
+
+ ) : (
+
No Photos
+ )}
+
+
+
+
Location
+
{powersportvehicles?.location}
+
+
+
+
Pricehourly
+
{powersportvehicles?.pricehourly || 'No data'}
+
+
+
+
Pricedaily
+
{powersportvehicles?.pricedaily || 'No data'}
+
+
+
+
Securitydeposit
+
{powersportvehicles?.securitydeposit || 'No data'}
+
+
+
+ {powersportvehicles.availabilitystart ? (
+
+ ) : (
+ No Availabilitystart
+ )}
+
+
+
+ {powersportvehicles.availabilityend ? (
+
+ ) : (
+ No Availabilityend
+ )}
+
+
+
+
Vehicletype
+
{powersportvehicles?.vehicletype ?? 'No data'}
+
+
+
+
Preptimebefore
+
{powersportvehicles?.preptimebefore || 'No data'}
+
+
+
+
Preptimebetween
+
{powersportvehicles?.preptimebetween || 'No data'}
+
+
+ <>
+ Bookings Powersportvehicle
+
+
+
+
+
+ | StartDate |
+
+ EndDate |
+
+ Status |
+
+ TotalPrice |
+
+
+
+ {powersportvehicles.bookings_powersportvehicle &&
+ Array.isArray(
+ powersportvehicles.bookings_powersportvehicle,
+ ) &&
+ powersportvehicles.bookings_powersportvehicle.map(
+ (item: any) => (
+
+ router.push(
+ `/bookings/bookings-view/?id=${item.id}`,
+ )
+ }
+ >
+ |
+ {dataFormatter.dateTimeFormatter(item.start_date)}
+ |
+
+
+ {dataFormatter.dateTimeFormatter(item.end_date)}
+ |
+
+ {item.status} |
+
+ {item.total_price} |
+
+ ),
+ )}
+
+
+
+ {!powersportvehicles?.bookings_powersportvehicle?.length && (
+ No data
+ )}
+
+ >
+
+
+
+
+ router.push('/powersportvehicles/powersportvehicles-list')
+ }
+ />
+
+
+ >
+ );
+};
+
+PowersportvehiclesView.getLayout = function getLayout(page: ReactElement) {
+ return (
+
+ {page}
+
+ );
+};
+
+export default PowersportvehiclesView;
diff --git a/frontend/src/pages/privacy-policy.tsx b/frontend/src/pages/privacy-policy.tsx
index 0925e13..6658113 100644
--- a/frontend/src/pages/privacy-policy.tsx
+++ b/frontend/src/pages/privacy-policy.tsx
@@ -5,7 +5,7 @@ import LayoutGuest from '../layouts/Guest';
import { getPageTitle } from '../config';
export default function PrivacyPolicy() {
- const title = 'Etherra';
+ const title = 'Powersport Rentals';
const [projectUrl, setProjectUrl] = useState('');
useEffect(() => {
diff --git a/frontend/src/pages/terms-of-use.tsx b/frontend/src/pages/terms-of-use.tsx
index 5b7c74b..ee14fac 100644
--- a/frontend/src/pages/terms-of-use.tsx
+++ b/frontend/src/pages/terms-of-use.tsx
@@ -5,7 +5,7 @@ import LayoutGuest from '../layouts/Guest';
import { getPageTitle } from '../config';
export default function PrivacyPolicy() {
- const title = 'Etherra';
+ const title = 'Powersport Rentals';
const [projectUrl, setProjectUrl] = useState('');
useEffect(() => {
diff --git a/frontend/src/pages/users/users-view.tsx b/frontend/src/pages/users/users-view.tsx
index 78ee0c7..13f3e2e 100644
--- a/frontend/src/pages/users/users-view.tsx
+++ b/frontend/src/pages/users/users-view.tsx
@@ -220,6 +220,8 @@ const UsersView = () => {
PriceDaily |
SecurityDeposit |
+
+ Vehicletype |
@@ -245,6 +247,8 @@ const UsersView = () => {
{item.security_deposit}
|
+
+ {item.vehicletype} |
))}
diff --git a/frontend/src/pages/vehicles/[vehiclesId].tsx b/frontend/src/pages/vehicles/[vehiclesId].tsx
index 803a921..9e02209 100644
--- a/frontend/src/pages/vehicles/[vehiclesId].tsx
+++ b/frontend/src/pages/vehicles/[vehiclesId].tsx
@@ -57,6 +57,8 @@ const EditVehicles = () => {
bookings: [],
clients: null,
+
+ vehicletype: '',
};
const [initialValues, setInitialValues] = useState(initVals);
@@ -200,6 +202,14 @@ const EditVehicles = () => {
>
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/vehicles/vehicles-edit.tsx b/frontend/src/pages/vehicles/vehicles-edit.tsx
index c60649d..d5e684b 100644
--- a/frontend/src/pages/vehicles/vehicles-edit.tsx
+++ b/frontend/src/pages/vehicles/vehicles-edit.tsx
@@ -57,6 +57,8 @@ const EditVehiclesPage = () => {
bookings: [],
clients: null,
+
+ vehicletype: '',
};
const [initialValues, setInitialValues] = useState(initVals);
@@ -198,6 +200,14 @@ const EditVehiclesPage = () => {
>
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/vehicles/vehicles-list.tsx b/frontend/src/pages/vehicles/vehicles-list.tsx
index 35b613e..d2624a5 100644
--- a/frontend/src/pages/vehicles/vehicles-list.tsx
+++ b/frontend/src/pages/vehicles/vehicles-list.tsx
@@ -40,6 +40,12 @@ const VehiclesTablesPage = () => {
{ label: 'Owner', title: 'owner' },
{ label: 'Bookings', title: 'bookings' },
+ {
+ label: 'Vehicletype',
+ title: 'vehicletype',
+ type: 'enum',
+ options: ['value'],
+ },
]);
const hasCreatePermission =
diff --git a/frontend/src/pages/vehicles/vehicles-new.tsx b/frontend/src/pages/vehicles/vehicles-new.tsx
index 1c7eb18..0100e12 100644
--- a/frontend/src/pages/vehicles/vehicles-new.tsx
+++ b/frontend/src/pages/vehicles/vehicles-new.tsx
@@ -52,6 +52,8 @@ const initialValues = {
bookings: [],
clients: '',
+
+ vehicletype: '',
};
const VehiclesNew = () => {
@@ -167,6 +169,14 @@ const VehiclesNew = () => {
>
+
+
+
+
+
+
+
+
diff --git a/frontend/src/pages/vehicles/vehicles-table.tsx b/frontend/src/pages/vehicles/vehicles-table.tsx
index 8c02eb9..cd8ab89 100644
--- a/frontend/src/pages/vehicles/vehicles-table.tsx
+++ b/frontend/src/pages/vehicles/vehicles-table.tsx
@@ -40,6 +40,12 @@ const VehiclesTablesPage = () => {
{ label: 'Owner', title: 'owner' },
{ label: 'Bookings', title: 'bookings' },
+ {
+ label: 'Vehicletype',
+ title: 'vehicletype',
+ type: 'enum',
+ options: ['value'],
+ },
]);
const hasCreatePermission =
diff --git a/frontend/src/pages/vehicles/vehicles-view.tsx b/frontend/src/pages/vehicles/vehicles-view.tsx
index 71b88be..76ba189 100644
--- a/frontend/src/pages/vehicles/vehicles-view.tsx
+++ b/frontend/src/pages/vehicles/vehicles-view.tsx
@@ -170,6 +170,11 @@ const VehiclesView = () => {
{vehicles?.clients?.name ?? 'No data'}
+
+
Vehicletype
+
{vehicles?.vehicletype ?? 'No data'}
+
+
<>
Bookings Vehicle
state.style.cardsStyle);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
- const projectName = 'Etherra';
+ const projectName = 'Powersport Rentals';
useEffect(() => {
const darkElement = document.querySelector('body .dark');
@@ -77,10 +77,10 @@ export default function WebSite() {
content={`Get in touch with our team for any inquiries or support related to our peer-to-peer RV rental platform. We're here to help you with your adventure needs.`}
/>
-
+
-
+
);
}
diff --git a/frontend/src/pages/web_pages/faq.tsx b/frontend/src/pages/web_pages/faq.tsx
index 075e9a7..b4e8f63 100644
--- a/frontend/src/pages/web_pages/faq.tsx
+++ b/frontend/src/pages/web_pages/faq.tsx
@@ -18,7 +18,7 @@ import FaqSection from '../../components/WebPageComponents/FaqComponent';
export default function WebSite() {
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
- const projectName = 'Etherra';
+ const projectName = 'Powersport Rentals';
useEffect(() => {
const darkElement = document.querySelector('body .dark');
@@ -69,10 +69,10 @@ export default function WebSite() {
content={`Find answers to common questions about our peer-to-peer RV rental platform. Learn about listing, booking, security, and more to enhance your rental experience.`}
/>
-
+
-
+
);
}
diff --git a/frontend/src/pages/web_pages/home.tsx b/frontend/src/pages/web_pages/home.tsx
index 8c95faf..a28a8e8 100644
--- a/frontend/src/pages/web_pages/home.tsx
+++ b/frontend/src/pages/web_pages/home.tsx
@@ -24,7 +24,7 @@ import ContactFormSection from '../../components/WebPageComponents/ContactFormCo
export default function WebSite() {
const cardsStyle = useAppSelector((state) => state.style.cardsStyle);
const bgColor = useAppSelector((state) => state.style.bgLayoutColor);
- const projectName = 'Etherra';
+ const projectName = 'Powersport Rentals';
useEffect(() => {
const darkElement = document.querySelector('body .dark');
@@ -96,10 +96,10 @@ export default function WebSite() {
content={`Discover and rent recreational vehicles with ease on our peer-to-peer RV rental platform. List your RV or find the perfect one for your next adventure.`}
/>
-
+
-
+
);
}
diff --git a/frontend/src/stores/powersportvehicles/powersportvehiclesSlice.ts b/frontend/src/stores/powersportvehicles/powersportvehiclesSlice.ts
new file mode 100644
index 0000000..73c16f1
--- /dev/null
+++ b/frontend/src/stores/powersportvehicles/powersportvehiclesSlice.ts
@@ -0,0 +1,250 @@
+import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
+import axios from 'axios';
+import {
+ fulfilledNotify,
+ rejectNotify,
+ resetNotify,
+} from '../../helpers/notifyStateHandler';
+
+interface MainState {
+ powersportvehicles: any;
+ loading: boolean;
+ count: number;
+ refetch: boolean;
+ rolesWidgets: any[];
+ notify: {
+ showNotification: boolean;
+ textNotification: string;
+ typeNotification: string;
+ };
+}
+
+const initialState: MainState = {
+ powersportvehicles: [],
+ loading: false,
+ count: 0,
+ refetch: false,
+ rolesWidgets: [],
+ notify: {
+ showNotification: false,
+ textNotification: '',
+ typeNotification: 'warn',
+ },
+};
+
+export const fetch = createAsyncThunk(
+ 'powersportvehicles/fetch',
+ async (data: any) => {
+ const { id, query } = data;
+ const result = await axios.get(
+ `powersportvehicles${query || (id ? `/${id}` : '')}`,
+ );
+ return id
+ ? result.data
+ : { rows: result.data.rows, count: result.data.count };
+ },
+);
+
+export const deleteItemsByIds = createAsyncThunk(
+ 'powersportvehicles/deleteByIds',
+ async (data: any, { rejectWithValue }) => {
+ try {
+ await axios.post('powersportvehicles/deleteByIds', { data });
+ } catch (error) {
+ if (!error.response) {
+ throw error;
+ }
+
+ return rejectWithValue(error.response.data);
+ }
+ },
+);
+
+export const deleteItem = createAsyncThunk(
+ 'powersportvehicles/deletePowersportvehicles',
+ async (id: string, { rejectWithValue }) => {
+ try {
+ await axios.delete(`powersportvehicles/${id}`);
+ } catch (error) {
+ if (!error.response) {
+ throw error;
+ }
+
+ return rejectWithValue(error.response.data);
+ }
+ },
+);
+
+export const create = createAsyncThunk(
+ 'powersportvehicles/createPowersportvehicles',
+ async (data: any, { rejectWithValue }) => {
+ try {
+ const result = await axios.post('powersportvehicles', { data });
+ return result.data;
+ } catch (error) {
+ if (!error.response) {
+ throw error;
+ }
+
+ return rejectWithValue(error.response.data);
+ }
+ },
+);
+
+export const uploadCsv = createAsyncThunk(
+ 'powersportvehicles/uploadCsv',
+ async (file: File, { rejectWithValue }) => {
+ try {
+ const data = new FormData();
+ data.append('file', file);
+ data.append('filename', file.name);
+
+ const result = await axios.post('powersportvehicles/bulk-import', data, {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ });
+
+ return result.data;
+ } catch (error) {
+ if (!error.response) {
+ throw error;
+ }
+
+ return rejectWithValue(error.response.data);
+ }
+ },
+);
+
+export const update = createAsyncThunk(
+ 'powersportvehicles/updatePowersportvehicles',
+ async (payload: any, { rejectWithValue }) => {
+ try {
+ const result = await axios.put(`powersportvehicles/${payload.id}`, {
+ id: payload.id,
+ data: payload.data,
+ });
+ return result.data;
+ } catch (error) {
+ if (!error.response) {
+ throw error;
+ }
+
+ return rejectWithValue(error.response.data);
+ }
+ },
+);
+
+export const powersportvehiclesSlice = createSlice({
+ name: 'powersportvehicles',
+ initialState,
+ reducers: {
+ setRefetch: (state, action: PayloadAction) => {
+ state.refetch = action.payload;
+ },
+ },
+ extraReducers: (builder) => {
+ builder.addCase(fetch.pending, (state) => {
+ state.loading = true;
+ resetNotify(state);
+ });
+ builder.addCase(fetch.rejected, (state, action) => {
+ state.loading = false;
+ rejectNotify(state, action);
+ });
+
+ builder.addCase(fetch.fulfilled, (state, action) => {
+ if (action.payload.rows && action.payload.count >= 0) {
+ state.powersportvehicles = action.payload.rows;
+ state.count = action.payload.count;
+ } else {
+ state.powersportvehicles = action.payload;
+ }
+ state.loading = false;
+ });
+
+ builder.addCase(deleteItemsByIds.pending, (state) => {
+ state.loading = true;
+ resetNotify(state);
+ });
+
+ builder.addCase(deleteItemsByIds.fulfilled, (state) => {
+ state.loading = false;
+ fulfilledNotify(state, 'Powersportvehicles has been deleted');
+ });
+
+ builder.addCase(deleteItemsByIds.rejected, (state, action) => {
+ state.loading = false;
+ rejectNotify(state, action);
+ });
+
+ builder.addCase(deleteItem.pending, (state) => {
+ state.loading = true;
+ resetNotify(state);
+ });
+
+ builder.addCase(deleteItem.fulfilled, (state) => {
+ state.loading = false;
+ fulfilledNotify(
+ state,
+ `${'Powersportvehicles'.slice(0, -1)} has been deleted`,
+ );
+ });
+
+ builder.addCase(deleteItem.rejected, (state, action) => {
+ state.loading = false;
+ rejectNotify(state, action);
+ });
+
+ builder.addCase(create.pending, (state) => {
+ state.loading = true;
+ resetNotify(state);
+ });
+ builder.addCase(create.rejected, (state, action) => {
+ state.loading = false;
+ rejectNotify(state, action);
+ });
+
+ builder.addCase(create.fulfilled, (state) => {
+ state.loading = false;
+ fulfilledNotify(
+ state,
+ `${'Powersportvehicles'.slice(0, -1)} has been created`,
+ );
+ });
+
+ builder.addCase(update.pending, (state) => {
+ state.loading = true;
+ resetNotify(state);
+ });
+ builder.addCase(update.fulfilled, (state) => {
+ state.loading = false;
+ fulfilledNotify(
+ state,
+ `${'Powersportvehicles'.slice(0, -1)} has been updated`,
+ );
+ });
+ builder.addCase(update.rejected, (state, action) => {
+ state.loading = false;
+ rejectNotify(state, action);
+ });
+
+ builder.addCase(uploadCsv.pending, (state) => {
+ state.loading = true;
+ resetNotify(state);
+ });
+ builder.addCase(uploadCsv.fulfilled, (state) => {
+ state.loading = false;
+ fulfilledNotify(state, 'Powersportvehicles has been uploaded');
+ });
+ builder.addCase(uploadCsv.rejected, (state, action) => {
+ state.loading = false;
+ rejectNotify(state, action);
+ });
+ },
+});
+
+// Action creators are generated for each case reducer function
+export const { setRefetch } = powersportvehiclesSlice.actions;
+
+export default powersportvehiclesSlice.reducer;
diff --git a/frontend/src/stores/store.ts b/frontend/src/stores/store.ts
index 8a42a7c..3357126 100644
--- a/frontend/src/stores/store.ts
+++ b/frontend/src/stores/store.ts
@@ -10,6 +10,7 @@ import vehiclesSlice from './vehicles/vehiclesSlice';
import rolesSlice from './roles/rolesSlice';
import permissionsSlice from './permissions/permissionsSlice';
import clientsSlice from './clients/clientsSlice';
+import powersportvehiclesSlice from './powersportvehicles/powersportvehiclesSlice';
export const store = configureStore({
reducer: {
@@ -24,6 +25,7 @@ export const store = configureStore({
roles: rolesSlice,
permissions: permissionsSlice,
clients: clientsSlice,
+ powersportvehicles: powersportvehiclesSlice,
},
});