Implementazione delle modifiche schema backend nel frontend #4

Merged
reloia merged 4 commits from new-backend-schema into master 2026-06-15 12:44:12 +00:00
3 changed files with 124 additions and 15 deletions
Showing only changes of commit 645e6367e9 - Show all commits
+1 -1
View File
@@ -18,7 +18,7 @@ const navLinks = [
]
const { isAuthenticated, user, logout } = useAuth()
const panelLabel = computed(() => user.value?.role?.toUpperCase() === 'ADMIN' ? 'Pannello admin' : 'Dashboard')
const panelLabel = computed(() => user.value?.role?.toUpperCase() === 'ADMIN' ? 'Pannello' : 'Dashboard')
</script>
<template>
+2 -2
View File
@@ -21,7 +21,7 @@ export const useAdmin = () => {
useApiFetch<OkResponse>(`/api/v1/admin/users/${id}/roles`, { method: 'PATCH', body })
const getAreas = () =>
useApiFetch<AreaListResponse>('/api/v1/admin/areas', { method: 'GET' })
useApiFetch<AreaListResponse>('/api/v1/areas', { method: 'GET' })
const createArea = (body: CreateAreaRequest) =>
useApiFetch<OkResponse>('/api/v1/admin/areas', { method: 'POST', body })
@@ -58,4 +58,4 @@ export const useAdmin = () => {
getModerationFlags,
updateModerationFlag,
}
}
}
+121 -12
View File
@@ -1,6 +1,7 @@
<script setup lang="ts">
import { ref, computed } from 'vue'
import { isWithinBariBounds } from '~/utils/bari'
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import type { Map as LeafletMap, CircleMarker, LatLngBoundsExpression } from 'leaflet'
import { BARI_BOUNDS, BARI_CENTER, isWithinBariBounds } from '~/utils/bari'
// 1. API Fetching Logic
const { getOpenDataReports } = usePublic()
@@ -35,6 +36,10 @@ const prevPage = () => {
// 2. View Toggle (Grid vs Map)
const currentView = ref<'grid' | 'map'>('grid')
const mapContainer = ref<HTMLElement | null>(null)
let leaflet: typeof import('leaflet') | null = null
let map: LeafletMap | null = null
let markers: CircleMarker[] = []
// 3. Helper to style status badges
const getStatusBadge = (status: string) => {
@@ -44,6 +49,99 @@ const getStatusBadge = (status: string) => {
default: return { class: 'bg-info', text: status, icon: 'it-info-circle' }
}
}
const loadLeaflet = async () => {
if (leaflet) return leaflet
leaflet = await import('leaflet')
return leaflet
}
const clearMarkers = () => {
if (!map) return
markers.forEach((marker) => marker.removeFrom(map!))
markers = []
}
const renderMapMarkers = async () => {
if (!import.meta.client || !mapContainer.value) return
const L = await loadLeaflet()
if (!map) return
clearMarkers()
if (visibleReports.value.length === 0) {
map.setView(BARI_CENTER, 12)
return
}
visibleReports.value.forEach((report) => {
const marker = L.circleMarker([report.latitude, report.longitude], {
radius: 8,
weight: 2,
color: '#ffffff',
fillColor: getStatusBadge(report.status).class === 'bg-success' ? '#008758' : getStatusBadge(report.status).class === 'bg-secondary' ? '#5c6f82' : '#2f80ed',
fillOpacity: 0.95,
})
marker.bindPopup(`
<div class="open-data-popup">
<strong>${report.category_name}</strong><br/>
<span>ID #${report.id}</span><br/>
<span>${report.area_name || 'Area comunale non specificata'}</span><br/>
<span>Segnalato ${new Date(report.created_at).toLocaleDateString('it-IT')}</span>
</div>
`)
marker.addTo(map!)
markers.push(marker)
})
const fitBounds: LatLngBoundsExpression = visibleReports.value.map((report) => [report.latitude, report.longitude])
map.fitBounds(fitBounds, { padding: [32, 32], maxZoom: 15 })
}
const initMap = async () => {
if (!import.meta.client || !mapContainer.value || map) return
const L = await loadLeaflet()
const bariBounds = L.latLngBounds(
[BARI_BOUNDS.south, BARI_BOUNDS.west],
[BARI_BOUNDS.north, BARI_BOUNDS.east]
)
map = L.map(mapContainer.value, {
maxBounds: bariBounds,
maxBoundsViscosity: 1,
minZoom: 11,
}).setView(BARI_CENTER, 12)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; OpenStreetMap contributors'
}).addTo(map)
await renderMapMarkers()
}
watch(currentView, async (view) => {
if (view !== 'map') return
await nextTick()
await initMap()
map?.invalidateSize()
await renderMapMarkers()
})
watch(visibleReports, async () => {
if (currentView.value === 'map' && map) {
await renderMapMarkers()
}
})
onBeforeUnmount(() => {
clearMarkers()
map?.remove()
map = null
})
</script>
<template>
@@ -175,18 +273,15 @@ const getStatusBadge = (status: string) => {
</nav>
</div>
<!-- MAP VIEW (Placeholder for Leaflet/Mapbox) -->
<!-- MAP VIEW -->
<div v-else-if="currentView === 'map'" class="map-view-wrapper">
<div class="alert alert-info d-flex align-items-center" role="alert">
<span>La visualizzazione mappa è un modulo interattivo (ad es. Leaflet o Mapbox) che utilizzerà le coordinate <strong>latitudine</strong> e <strong>longitudine</strong> fornite dalle API.</span>
</div>
<div class="bg-light border rounded d-flex align-items-center justify-content-center" style="height: 500px;">
<div class="text-center text-muted">
<BIIcon name="it-map" size="xl" class="mb-3 opacity-50" />
<h4 class="h5">Mappa Interattiva</h4>
<p>Caricamento dei {{ visibleReports.length || 0 }} punti geografici in corso...</p>
</div>
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
<p class="text-muted mb-0">Mappa pubblica delle segnalazioni nell'area di Bari.</p>
<span class="badge bg-light text-dark border">{{ visibleReports.length }} punti</span>
</div>
<ClientOnly>
<div ref="mapContainer" class="open-data-map rounded border"></div>
</ClientOnly>
</div>
</div>
@@ -206,4 +301,18 @@ const getStatusBadge = (status: string) => {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
}
.open-data-map {
height: 520px;
width: 100%;
z-index: 1;
}
:global(.open-data-popup) {
color: #17324d;
font-family: "Titillium Web", Arial, sans-serif;
font-size: 0.875rem;
line-height: 1.4;
min-width: 12rem;
}
</style>