3 Commits
7 changed files with 85 additions and 20 deletions
+35 -4
View File
@@ -12,28 +12,59 @@ const { data: statsRes, pending } = useAsyncData('admin-stats', () => getOvervie
</div> </div>
<div class="row mb-5" v-if="!pending && statsRes?.ok"> <div class="row mb-5" v-if="!pending && statsRes?.ok">
<div class="col-6 col-md-3 mb-3"> <div class="col-6 col-md-3 mb-3">
<div class="card-wrapper card-space"> <div class="card-wrapper card-space">
<div class="card card-bg text-center"> <div class="card card-bg text-center">
<div class="card-body"> <div class="card-body">
<h2 class="text-primary">{{ statsRes.total_users || 0 }}</h2> <h2 class="text-primary">
<p class="card-text">Utenti Registrati</p> {{ statsRes.reports_by_status?.reduce((sum, item) => sum + Number(item.total), 0) || 0 }}
</h2>
<p class="card-text">Totale Segnalazioni</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-6 col-md-3 mb-3">
<div class="card-wrapper card-space">
<div class="card card-bg text-center">
<div class="card-body">
<h2 class="text-danger">
{{ statsRes.reports_by_status?.find(s => s.status === 'OPEN')?.total ?? 0 }}
</h2>
<p class="card-text">Aperte (Da Gestire)</p>
</div>
</div>
</div>
</div>
<div class="col-6 col-md-3 mb-3"> <div class="col-6 col-md-3 mb-3">
<div class="card-wrapper card-space"> <div class="card-wrapper card-space">
<div class="card card-bg text-center"> <div class="card card-bg text-center">
<div class="card-body"> <div class="card-body">
<h2 class="text-warning"> <h2 class="text-warning">
{{ statsRes.open_reports ?? statsRes.reports_by_status?.find(s => s.status === 'OPEN')?.total ?? 0 }} {{ statsRes.reports_by_status?.find(s => s.status === 'ASSIGNED')?.total ?? 0 }}
</h2> </h2>
<p class="card-text">Segn. Aperte</p> <p class="card-text">In Carico (Assegnate)</p>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="col-6 col-md-3 mb-3">
<div class="card-wrapper card-space">
<div class="card card-bg text-center">
<div class="card-body">
<h2 class="text-success">
{{ statsRes.reports_by_status?.find(s => s.status === 'CLOSED')?.total ?? 0 }}
</h2>
<p class="card-text">Risolte (Chiuse)</p>
</div>
</div>
</div>
</div>
</div> </div>
<h3 class="mb-3">Gestione Sistema</h3> <h3 class="mb-3">Gestione Sistema</h3>
+34 -9
View File
@@ -21,6 +21,7 @@ const formData = reactive({
}) })
const jsonError = ref('') const jsonError = ref('')
const actionError = ref('') const actionError = ref('')
const modalError = ref('')
const deletingAreaId = ref<number | null>(null) const deletingAreaId = ref<number | null>(null)
const areaPendingDelete = ref<any | null>(null) const areaPendingDelete = ref<any | null>(null)
@@ -121,13 +122,15 @@ const renderExistingAreasInModal = (L: any, currentMap: any) => {
}, },
interactive: false interactive: false
}).addTo(currentMap) }).addTo(currentMap)
} catch(e) {} } catch (e) {
}
} }
}) })
} }
const openCreateModal = () => { const openCreateModal = () => {
actionError.value = '' actionError.value = ''
modalError.value = ''
editingAreaId.value = null editingAreaId.value = null
formData.name = '' formData.name = ''
formData.description = '' formData.description = ''
@@ -171,6 +174,7 @@ const openCreateModal = () => {
} }
jsonError.value = '' jsonError.value = ''
modalError.value = ''
mapPoints.value.push([e.latlng.lat, e.latlng.lng]) mapPoints.value.push([e.latlng.lat, e.latlng.lng])
polygon.setLatLngs(mapPoints.value) polygon.setLatLngs(mapPoints.value)
updateGeojson() updateGeojson()
@@ -197,6 +201,7 @@ const loadPolygonFromGeoJson = (boundaryGeojson: any) => {
const openEditModal = (area: any) => { const openEditModal = (area: any) => {
actionError.value = '' actionError.value = ''
modalError.value = ''
editingAreaId.value = area.id editingAreaId.value = area.id
formData.name = area.name || '' formData.name = area.name || ''
formData.description = area.description || '' formData.description = area.description || ''
@@ -250,6 +255,7 @@ const openEditModal = (area: any) => {
} }
jsonError.value = '' jsonError.value = ''
modalError.value = ''
mapPoints.value.push([e.latlng.lat, e.latlng.lng]) mapPoints.value.push([e.latlng.lat, e.latlng.lng])
polygon.setLatLngs(mapPoints.value) polygon.setLatLngs(mapPoints.value)
updateGeojson() updateGeojson()
@@ -263,6 +269,7 @@ const clearMap = () => {
polygon.setLatLngs([]) polygon.setLatLngs([])
} }
formData.geojson = '' formData.geojson = ''
modalError.value = ''
} }
const updateGeojson = () => { const updateGeojson = () => {
@@ -270,7 +277,7 @@ const updateGeojson = () => {
formData.geojson = '' formData.geojson = ''
return return
} }
const coords = mapPoints.value.map(p => [p[1], p[0]] as [number, number]) // Lng, Lat const coords = mapPoints.value.map(p => [p[1], p[0]] as [number, number])
if (coords[0]) { if (coords[0]) {
coords.push([...coords[0]]) coords.push([...coords[0]])
} }
@@ -297,6 +304,7 @@ onBeforeUnmount(() => {
const handleCreate = async () => { const handleCreate = async () => {
jsonError.value = '' jsonError.value = ''
modalError.value = ''
let parsedGeojson = null let parsedGeojson = null
try { try {
@@ -331,9 +339,16 @@ const handleCreate = async () => {
closeCreateModal() closeCreateModal()
actionError.value = '' actionError.value = ''
} }
} catch (err) { } catch (err: any) {
console.error('Failed to save area', err) console.error('Failed to save area', err)
actionError.value = 'Errore durante il salvataggio dell\'area.'
const backendError = err?.data?.error || err?.response?.data?.error || err?.response?._data?.error;
if (backendError === 'area_outside_allowed_boundary') {
modalError.value = "Attenzione: l'area disegnata esce dai confini consentiti per il Comune di Bari.";
} else {
modalError.value = err?.data?.message || err?.message || "Errore durante il salvataggio dell'area.";
}
} }
} }
@@ -379,7 +394,7 @@ const handleDeleteArea = async () => {
<div> <div>
<NuxtLink to="/dashboard" class="btn btn-outline-secondary btn-sm me-2">Torna al pannello admin</NuxtLink> <NuxtLink to="/dashboard" class="btn btn-outline-secondary btn-sm me-2">Torna al pannello admin</NuxtLink>
<button class="btn btn-primary btn-sm" @click="openCreateModal"> <button class="btn btn-primary btn-sm" @click="openCreateModal">
<BIIcon name="it-plus" size="xs" class="me-1" /> <BIIcon class="me-1" name="it-plus" size="xs"/>
Nuova Area Nuova Area
</button> </button>
</div> </div>
@@ -441,7 +456,8 @@ const handleDeleteArea = async () => {
</div> </div>
</template> </template>
<div v-if="isCreating" class="modal-backdrop fade show d-block" style="background: rgba(0,0,0,0.5); z-index: 1040;"></div> <div v-if="isCreating" class="modal-backdrop fade show d-block"
style="background: rgba(0,0,0,0.5); z-index: 1040;"></div>
<div v-if="isCreating" class="modal fade show d-block" tabindex="-1" style="z-index: 1050;"> <div v-if="isCreating" class="modal fade show d-block" tabindex="-1" style="z-index: 1050;">
<div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable"> <div class="modal-dialog modal-xl modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content"> <div class="modal-content">
@@ -450,12 +466,19 @@ const handleDeleteArea = async () => {
<button type="button" class="btn-close" @click="closeCreateModal"></button> <button type="button" class="btn-close" @click="closeCreateModal"></button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<!-- ALERT ERRORE DEL BACKEND -->
<div v-if="modalError" class="alert alert-danger mb-4 d-flex align-items-center" role="alert">
<strong>Errore:</strong>&nbsp;{{ modalError }}
</div>
<form @submit.prevent="handleCreate"> <form @submit.prevent="handleCreate">
<div class="row"> <div class="row">
<div class="col-md-5"> <div class="col-md-5">
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Nome Area</label> <label class="form-label">Nome Area</label>
<input type="text" class="form-control" v-model="formData.name" required placeholder="Es. Centro Storico" /> <input v-model="formData.name" class="form-control" placeholder="Es. Centro Storico" required
type="text"/>
</div> </div>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Descrizione (opzionale)</label> <label class="form-label">Descrizione (opzionale)</label>
@@ -483,7 +506,8 @@ const handleDeleteArea = async () => {
</button> </button>
</div> </div>
<div class="alert alert-light border small mb-2 p-2"> <div class="alert alert-light border small mb-2 p-2">
Clicca sulla mappa per aggiungere vertici al poligono. Sono necessari almeno 3 punti. Sono ammessi solo punti nel Comune di Bari. Clicca sulla mappa per aggiungere vertici al poligono. Sono necessari almeno 3 punti. Sono ammessi
solo punti nel Comune di Bari.
</div> </div>
<div id="areaMap" class="rounded border" style="height: 400px; width: 100%; z-index: 1;"></div> <div id="areaMap" class="rounded border" style="height: 400px; width: 100%; z-index: 1;"></div>
</div> </div>
@@ -492,7 +516,8 @@ const handleDeleteArea = async () => {
</div> </div>
<div class="modal-footer d-flex justify-content-end gap-2"> <div class="modal-footer d-flex justify-content-end gap-2">
<button type="button" class="btn btn-outline-secondary" @click="closeCreateModal">Annulla</button> <button type="button" class="btn btn-outline-secondary" @click="closeCreateModal">Annulla</button>
<button type="button" class="btn btn-primary" :disabled="!formData.name || !formData.geojson" @click="handleCreate"> <button :disabled="!formData.name || !formData.geojson" class="btn btn-primary" type="button"
@click="handleCreate">
{{ submitLabel }} {{ submitLabel }}
</button> </button>
</div> </div>
+9 -2
View File
@@ -80,12 +80,11 @@ const handleCieLogin = async () => {
<button <button
class="btn btn-primary w-100 mb-4 d-flex align-items-center justify-content-center gap-2" class="btn btn-primary w-100 mb-4 d-flex align-items-center justify-content-center gap-2 entraconcie"
:disabled="isCieLoading || isLoading" :disabled="isCieLoading || isLoading"
@click="handleCieLogin" @click="handleCieLogin"
> >
<span v-if="isCieLoading" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span> <span v-if="isCieLoading" class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>
<BIIcon name="it-card" size="sm" color="white" />
Entra con CIE Entra con CIE
</button> </button>
@@ -147,4 +146,12 @@ const handleCieLogin = async () => {
.pe-5 { .pe-5 {
padding-right: 3rem !important; padding-right: 3rem !important;
} }
button.entraconcie {
background-image: url('/entraconcie.png');
background-repeat: no-repeat;
background-position: center center;
background-size: 100%;
color: transparent;
}
</style> </style>
+3 -1
View File
@@ -3,6 +3,8 @@ import BIIcon from "~/components/BIIcon.vue";
const { getOpenDataReports } = usePublic() const { getOpenDataReports } = usePublic()
const { user } = useAuth();
const stats = { const stats = {
total_reports: 14523, total_reports: 14523,
resolved_reports: 13890, resolved_reports: 13890,
@@ -38,7 +40,7 @@ const getStatusBadge = (status: string) => {
e monitora l'intervento in totale trasparenza. e monitora l'intervento in totale trasparenza.
</p> </p>
<div class="it-btn-container d-flex justify-content-center gap-3"> <div class="it-btn-container d-flex justify-content-center gap-3">
<NuxtLink class="btn btn-primary" to="/auth/login"> <NuxtLink v-if="isCitizen(user.value?.role)" class="btn btn-primary" to="/auth/login">
Fai una segnalazione Fai una segnalazione
</NuxtLink> </NuxtLink>
<NuxtLink class="btn btn-outline-primary" to="/open-data"> <NuxtLink class="btn btn-outline-primary" to="/open-data">
-1
View File
@@ -122,7 +122,6 @@ const initMap = async () => {
watch(currentView, async (view) => { watch(currentView, async (view) => {
if (view !== 'map') return if (view !== 'map') return
await nextTick() await nextTick()
// Inizializza la mappa se non esiste, altrimenti aggiornane solo dimensioni e dati
if (!map) { if (!map) {
await initMap() await initMap()
} else { } else {
+2 -1
View File
@@ -130,7 +130,8 @@ const formatDate = (dateString: string) =>
<div class="card-body"> <div class="card-body">
<h2 class="h6 card-title mb-3">Azioni account</h2> <h2 class="h6 card-title mb-3">Azioni account</h2>
<div class="d-grid gap-2"> <div class="d-grid gap-2">
<NuxtLink to="/reports/new" class="btn btn-primary">Nuova segnalazione</NuxtLink> <NuxtLink v-if="isCitizen(user?.role)" class="btn btn-primary" to="/reports/new">Nuova segnalazione
</NuxtLink>
<NuxtLink to="/notifications" class="btn btn-outline-primary">Notifiche</NuxtLink> <NuxtLink to="/notifications" class="btn btn-outline-primary">Notifiche</NuxtLink>
<button type="button" class="btn btn-outline-danger" @click="logout"> <button type="button" class="btn btn-outline-danger" @click="logout">
Logout Logout
Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB