From e3a10dea688377e1833c35a8b0277a1b680256cf Mon Sep 17 00:00:00 2001 From: Amar Date: Tue, 11 Feb 2025 20:01:56 +0100 Subject: [PATCH] Added reports support + major implementation fixes --- backend/controllers/emergenciesController.js | 14 +- backend/controllers/reportsController.js | 3 +- .../components/Badge/BadgeStatoReports.vue | 18 ++ frontend/src/components/Mappa/Mappa.vue | 13 +- .../components/Tabelle/TabellaEmergenze.vue | 5 +- .../Tabelle/TabellaSegnalazioni.vue | 109 +++++++++++ .../src/components/Tabelle/TabellaUtenti.vue | 5 +- frontend/src/data/algolia.js | 24 --- frontend/src/data/emergencies.js | 8 +- frontend/src/data/reports.js | 170 ++++++++++++++++++ frontend/src/data/users.js | 5 + frontend/src/router/index.js | 18 +- frontend/src/states/loggedUser.js | 8 + frontend/src/views/ViewHome.vue | 7 +- frontend/src/views/ViewSegnalazione.vue | 81 --------- .../{ => dashboard}/ViewComunicazione.vue | 94 +++++----- .../views/{ => dashboard}/ViewDashboard.vue | 60 ++++++- .../{ => dashboard}/ViewEditComunicazione.vue | 6 +- .../views/dashboard/ViewEditSegnalazione.vue | 146 +++++++++++++++ .../views/{ => emergencies}/ViewDettagli.vue | 2 +- .../views/{ => emergencies}/ViewStorico.vue | 10 +- frontend/src/views/{ => user}/ViewAccedi.vue | 4 +- frontend/src/views/{ => user}/ViewProfilo.vue | 24 ++- .../src/views/{ => user}/ViewRegistrati.vue | 2 +- frontend/src/views/user/ViewSegnalazione.vue | 146 +++++++++++++++ 25 files changed, 772 insertions(+), 210 deletions(-) create mode 100644 frontend/src/components/Badge/BadgeStatoReports.vue create mode 100644 frontend/src/components/Tabelle/TabellaSegnalazioni.vue delete mode 100644 frontend/src/data/algolia.js create mode 100644 frontend/src/data/reports.js delete mode 100644 frontend/src/views/ViewSegnalazione.vue rename frontend/src/views/{ => dashboard}/ViewComunicazione.vue (72%) rename frontend/src/views/{ => dashboard}/ViewDashboard.vue (73%) rename frontend/src/views/{ => dashboard}/ViewEditComunicazione.vue (98%) create mode 100644 frontend/src/views/dashboard/ViewEditSegnalazione.vue rename frontend/src/views/{ => emergencies}/ViewDettagli.vue (97%) rename frontend/src/views/{ => emergencies}/ViewStorico.vue (96%) rename frontend/src/views/{ => user}/ViewAccedi.vue (98%) rename frontend/src/views/{ => user}/ViewProfilo.vue (90%) rename frontend/src/views/{ => user}/ViewRegistrati.vue (99%) create mode 100644 frontend/src/views/user/ViewSegnalazione.vue diff --git a/backend/controllers/emergenciesController.js b/backend/controllers/emergenciesController.js index d2424e7..2ec7c79 100644 --- a/backend/controllers/emergenciesController.js +++ b/backend/controllers/emergenciesController.js @@ -3,10 +3,12 @@ const pathApiEmergencies = "/api/emergencies/"; export const getEmergencies = async (req, res) => { try { - const state = req.query; let filter = {}; - if (state && (state === "In corso" || state === "Terminato")) { - filter.state = state; + const state = req.query.state; + + if (state && (state === "in_corso" || state === "terminato")) { + // Riformatto prima il parametro dello stato + filter.state = formatState(state); } let emergencies = await Emergency.find(filter); @@ -26,6 +28,7 @@ export const getEmergencies = async (req, res) => { res.status(200).json(emergencies); } catch (error) { res.status(500).json({ message: "Error in emergencies recovery" }); + console.log(error.message); } }; @@ -93,3 +96,8 @@ export const deleteEmergency = async (req, res) => { res.status(500).json({ message: "Error in emergency deletion" }); } }; + +function formatState(state) { + const words = state.split("_").join(" "); + return words.charAt(0).toUpperCase() + words.slice(1); +} diff --git a/backend/controllers/reportsController.js b/backend/controllers/reportsController.js index 6b8958e..04f41f0 100644 --- a/backend/controllers/reportsController.js +++ b/backend/controllers/reportsController.js @@ -52,7 +52,8 @@ export const getReportsByUser = async (req, res) => { coordinates: report.coordinates, state: report.state, description: report.description, - createdBy: report.createdBy + createdBy: report.createdBy, + createdAt: report.createdAt }; }); res.status(200).json(reports); diff --git a/frontend/src/components/Badge/BadgeStatoReports.vue b/frontend/src/components/Badge/BadgeStatoReports.vue new file mode 100644 index 0000000..5b433cf --- /dev/null +++ b/frontend/src/components/Badge/BadgeStatoReports.vue @@ -0,0 +1,18 @@ + + + + + diff --git a/frontend/src/components/Mappa/Mappa.vue b/frontend/src/components/Mappa/Mappa.vue index bfcb424..fb02d10 100644 --- a/frontend/src/components/Mappa/Mappa.vue +++ b/frontend/src/components/Mappa/Mappa.vue @@ -3,9 +3,7 @@ import { ref, onMounted } from 'vue' import { loggedUser } from '../../states/loggedUser.js' import { LMap, LTileLayer, LMarker, LPopup, LPolygon } from "@vue-leaflet/vue-leaflet" -import { ArrowsPointingOutIcon, ExclamationTriangleIcon } from '@heroicons/vue/24/solid' -import BadgeCategoria from '../Badge/BadgeCategoria.vue' -import BadgeStato from '../Badge/BadgeStato.vue' +import { ChevronDoubleRightIcon, ExclamationTriangleIcon } from '@heroicons/vue/24/solid' // Proprietà varie della mappa Leaflet const url = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png' @@ -46,14 +44,14 @@ onMounted(() => { -
- +
+

{{ marker.title }}

{{ marker.location }}

@@ -80,10 +78,11 @@ onMounted(() => { .marker-title { font-family: "Inter", sans-serif; margin-top: -3px; + line-height: 1.4; } .marker-location { - margin-top: 2px; + margin-top: 3px; } .btn-segnalazione { diff --git a/frontend/src/components/Tabelle/TabellaEmergenze.vue b/frontend/src/components/Tabelle/TabellaEmergenze.vue index f184e81..55d4564 100644 --- a/frontend/src/components/Tabelle/TabellaEmergenze.vue +++ b/frontend/src/components/Tabelle/TabellaEmergenze.vue @@ -57,7 +57,10 @@ function createToast(type, title, msg) {
- +
+ Nessun risultato disponibile +
+
diff --git a/frontend/src/components/Tabelle/TabellaSegnalazioni.vue b/frontend/src/components/Tabelle/TabellaSegnalazioni.vue new file mode 100644 index 0000000..cd47864 --- /dev/null +++ b/frontend/src/components/Tabelle/TabellaSegnalazioni.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/frontend/src/components/Tabelle/TabellaUtenti.vue b/frontend/src/components/Tabelle/TabellaUtenti.vue index 988b819..a544524 100644 --- a/frontend/src/components/Tabelle/TabellaUtenti.vue +++ b/frontend/src/components/Tabelle/TabellaUtenti.vue @@ -55,7 +55,10 @@ function createToast(type, title, msg) {
-
#
+
+ Nessun risultato disponibile +
+
diff --git a/frontend/src/data/algolia.js b/frontend/src/data/algolia.js deleted file mode 100644 index 6ec1e00..0000000 --- a/frontend/src/data/algolia.js +++ /dev/null @@ -1,24 +0,0 @@ -import { algoliasearch } from "algoliasearch"; - -// Aggiorna emergencies_index con i dati presi da apiEmergencies -export async function updateAlgoliaRecords() { - // Mi collego all'API di Algolia - const algolia = algoliasearch(import.meta.env.VITE_ALGOLIA_APP_ID, import.meta.env.VITE_ALGOLIA_WRITE_KEY); - - const processRecords = async () => { - // Svuoto prima emergencies_index - const response = await algolia.clearObjects({ indexName: 'emergencies_index' }); - - // Recupero poi le emergenze aggiornata dalla chiamata API - const apiEmergencies = import.meta.env.VITE_API_BASE_URL + "/emergencies/" - const datasetRequest = await fetch(apiEmergencies); - const emergenciesRecord = await datasetRequest.json(); - - // Inserisco infine le emergenze ottenuto in emergencies_index - return await algolia.saveObjects({ indexName: 'emergencies_index', objects: emergenciesRecord }); - }; - - processRecords() - .then(() => console.log('Algolia: emergencies successfully updated and indexed')) - .catch((err) => console.error(err)); -} \ No newline at end of file diff --git a/frontend/src/data/emergencies.js b/frontend/src/data/emergencies.js index 61a7dae..90a4e6e 100644 --- a/frontend/src/data/emergencies.js +++ b/frontend/src/data/emergencies.js @@ -23,6 +23,12 @@ function formattaData(date) { }); } +export function resetEmergencies() { + emergency.value = []; + emergencies.value = []; + emergenciesInProgress.value = []; +} + export async function nEmergencies() { try { const response = await fetch(apiEmergencies); @@ -70,7 +76,7 @@ export async function getEmergencyById(id) { } export function getEmergenciesInProgress() { - fetch(apiEmergencies + "/?state=in_progress") + fetch(apiEmergencies + "?state=in_corso") .then((response) => response.json()) .then((data) => { emergenciesInProgress.value = data.map((data) => { diff --git a/frontend/src/data/reports.js b/frontend/src/data/reports.js new file mode 100644 index 0000000..917e7ba --- /dev/null +++ b/frontend/src/data/reports.js @@ -0,0 +1,170 @@ +import { loggedUser } from "@/states/loggedUser"; +import { ref } from "vue"; + +const apiReports = import.meta.env.VITE_API_BASE_URL + "/reports/"; +const apiUsers = import.meta.env.VITE_API_BASE_URL + "/users/"; + +export const report = ref([]); +export const reports = ref([]); + +// Funzione per recuperare l'id delle segnalazioni +function recuperaId(self) { + return self.substring(self.lastIndexOf("/") + 1); +} + +// Funzione per formattare la data delle segnalazioni +function formattaData(date) { + return new Date(date).toLocaleString("it-IT", { + year: "numeric", + month: "long", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + }); +} + +export function resetReports() { + report.value = []; + reports.value = []; +} + +export async function nReports(role) { + try { + const response = await fetch(apiReports, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${loggedUser.token}`, + }, + }); + + const data = await response.json(); + return data.filter((user) => user.role === role).length; + } catch (error) { + console.error("Errore da nReports(): ", error); + return 0; + } +} + +export async function getReports() { + try { + const response = await fetch(apiReports, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${loggedUser.token}`, + }, + }); + const data = await response.json(); + + // Wait for all emails to resolve + const reportsData = await Promise.all(data.map(async (item) => { + const email = await getEmailByUserId(item.createdBy); + return { + ...item, + email: email, + startDate: formattaData(item.startDate), + createdAt: formattaData(item.createdAt), + id: recuperaId(item.self), + }; + })); + + reports.value = reportsData; + } catch (error) { + console.error("Errore da getReports(): ", error); + reports.value = null; + } +} + +export async function getMyReports() { + try { + const response = await fetch(apiReports + "myReports", { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${loggedUser.token}`, + }, + }); + + const data = await response.json(); + const reportsData = data.map((item) => ({ + ...item, + startDate: formattaData(item.startDate), + createdAt: formattaData(item.createdAt), + id: recuperaId(item.self), + })); + reports.value = reportsData; + } catch (error) { + console.error("Errore da getMyReports(): ", error); + reports.value = null; + } +} + + +export async function getReportById(id) { + fetch(apiReports + id, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${loggedUser.token}`, + }, + }) + .then((response) => response.json()) + .then((data) => { + emergency.value = { + ...data, + email: getEmailByUserId(data.createdBy), + startDate: formattaData(data.startDate), + createdAt: formattaData(data.createdAt), + id: recuperaId(data.self), + }; + }) + .catch((error) => { + console.error("Errore da getReportById(" + id + "): ", error); + emergency.value = null; + }); +} + +export async function getEmailByUserId(id) { + try { + const response = await fetch(apiUsers + id, { + method: "GET", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${loggedUser.token}`, + }, + }); + + const data = await response.json(); + return data.email; + } catch (error) { + console.error("Errore da getEmailByUserId(" + id + "): ", error); + return null; + } +} + +export async function createReport(data) { + try { + const response = await fetch(apiReports, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: `Bearer ${loggedUser.token}`, + }, + body: JSON.stringify(data), + }); + + if (!response.ok) { + throw new Error("Errore nella creazione del report"); + } + + if (response.status === 204 || response.headers.get("content-length") === "0") { + return null; + } + + return response.json(); + } catch (error) { + console.error("Errore da createReport(): ", error); + return null; + } +} diff --git a/frontend/src/data/users.js b/frontend/src/data/users.js index ae3327c..edb62c1 100644 --- a/frontend/src/data/users.js +++ b/frontend/src/data/users.js @@ -22,6 +22,11 @@ function formattaData(date) { }); } +export function resetUser() { + user.value = []; + users.value = []; +} + export async function nUsers(role) { try { const response = await fetch(apiUsers, { diff --git a/frontend/src/router/index.js b/frontend/src/router/index.js index dd98a23..e0e9ee6 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.js @@ -11,47 +11,47 @@ const router = createRouter({ { path: '/storico', name: 'storico', - component: () => import('../views/ViewStorico.vue'), + component: () => import('../views/emergencies/ViewStorico.vue'), }, { path: '/dettagli', name: 'dettagli', - component: () => import('../views/ViewDettagli.vue'), + component: () => import('../views/emergencies/ViewDettagli.vue'), }, { path: '/accedi', name: 'accedi', - component: () => import('../views/ViewAccedi.vue'), + component: () => import('../views/user/ViewAccedi.vue'), }, { path: '/registrati', name: 'registrati', - component: () => import('../views/ViewRegistrati.vue'), + component: () => import('../views/user/ViewRegistrati.vue'), }, { path: '/profilo', name: 'profilo', - component: () => import('../views/ViewProfilo.vue'), + component: () => import('../views/user/ViewProfilo.vue'), }, { path: '/dashboard', name: 'dashboard', - component: () => import('../views/ViewDashboard.vue'), + component: () => import('../views/dashboard/ViewDashboard.vue'), }, { path: '/invia_segnalazione', name: 'invia_segnalazione', - component: () => import('../views/ViewSegnalazione.vue'), + component: () => import('../views/user/ViewSegnalazione.vue'), }, { path: '/pubblica_comunicazione', name: 'pubblica_comunicazione', - component: () => import('../views/ViewComunicazione.vue'), + component: () => import('../views/dashboard/ViewComunicazione.vue'), }, { path: '/modifica_comunicazione', name: 'modifica_comunicazione', - component: () => import('../views/ViewEditComunicazione.vue'), + component: () => import('../views/dashboard/ViewEditComunicazione.vue'), }, { path: '/:pathMatch(.*)*', diff --git a/frontend/src/states/loggedUser.js b/frontend/src/states/loggedUser.js index 038240a..daf6b8b 100644 --- a/frontend/src/states/loggedUser.js +++ b/frontend/src/states/loggedUser.js @@ -1,5 +1,8 @@ import { reactive, watch } from 'vue'; import Cookies from 'js-cookie'; +import { resetUser } from '@/data/users'; +import { resetEmergencies } from '@/data/emergencies'; +import { resetReports } from '@/data/reports'; const loggedUser = reactive({ self: undefined, @@ -47,6 +50,11 @@ function clearLoggedUser() { // Remove from cookies Cookies.remove('loggedUser'); + + // Reset all data states + resetUser() + resetEmergencies() + resetReports() } export { loggedUser, setLoggedUser, clearLoggedUser, loadUserFromCookies }; diff --git a/frontend/src/views/ViewHome.vue b/frontend/src/views/ViewHome.vue index c024426..672c642 100644 --- a/frontend/src/views/ViewHome.vue +++ b/frontend/src/views/ViewHome.vue @@ -1,11 +1,10 @@ - - - - diff --git a/frontend/src/views/ViewComunicazione.vue b/frontend/src/views/dashboard/ViewComunicazione.vue similarity index 72% rename from frontend/src/views/ViewComunicazione.vue rename to frontend/src/views/dashboard/ViewComunicazione.vue index 97c57d2..cc1d3ff 100644 --- a/frontend/src/views/ViewComunicazione.vue +++ b/frontend/src/views/dashboard/ViewComunicazione.vue @@ -1,6 +1,7 @@
#