feat: update admin panel labels and adjust API endpoints for areas
This commit is contained in:
@@ -18,7 +18,7 @@ const navLinks = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
const { isAuthenticated, user, logout } = useAuth()
|
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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ export const useAdmin = () => {
|
|||||||
useApiFetch<OkResponse>(`/api/v1/admin/users/${id}/roles`, { method: 'PATCH', body })
|
useApiFetch<OkResponse>(`/api/v1/admin/users/${id}/roles`, { method: 'PATCH', body })
|
||||||
|
|
||||||
const getAreas = () =>
|
const getAreas = () =>
|
||||||
useApiFetch<AreaListResponse>('/api/v1/admin/areas', { method: 'GET' })
|
useApiFetch<AreaListResponse>('/api/v1/areas', { method: 'GET' })
|
||||||
|
|
||||||
const createArea = (body: CreateAreaRequest) =>
|
const createArea = (body: CreateAreaRequest) =>
|
||||||
useApiFetch<OkResponse>('/api/v1/admin/areas', { method: 'POST', body })
|
useApiFetch<OkResponse>('/api/v1/admin/areas', { method: 'POST', body })
|
||||||
@@ -58,4 +58,4 @@ export const useAdmin = () => {
|
|||||||
getModerationFlags,
|
getModerationFlags,
|
||||||
updateModerationFlag,
|
updateModerationFlag,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+121
-12
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from 'vue'
|
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
|
||||||
import { isWithinBariBounds } from '~/utils/bari'
|
import type { Map as LeafletMap, CircleMarker, LatLngBoundsExpression } from 'leaflet'
|
||||||
|
import { BARI_BOUNDS, BARI_CENTER, isWithinBariBounds } from '~/utils/bari'
|
||||||
|
|
||||||
// 1. API Fetching Logic
|
// 1. API Fetching Logic
|
||||||
const { getOpenDataReports } = usePublic()
|
const { getOpenDataReports } = usePublic()
|
||||||
@@ -35,6 +36,10 @@ const prevPage = () => {
|
|||||||
|
|
||||||
// 2. View Toggle (Grid vs Map)
|
// 2. View Toggle (Grid vs Map)
|
||||||
const currentView = ref<'grid' | 'map'>('grid')
|
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
|
// 3. Helper to style status badges
|
||||||
const getStatusBadge = (status: string) => {
|
const getStatusBadge = (status: string) => {
|
||||||
@@ -44,6 +49,99 @@ const getStatusBadge = (status: string) => {
|
|||||||
default: return { class: 'bg-info', text: status, icon: 'it-info-circle' }
|
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: '© 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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@@ -175,18 +273,15 @@ const getStatusBadge = (status: string) => {
|
|||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- MAP VIEW (Placeholder for Leaflet/Mapbox) -->
|
<!-- MAP VIEW -->
|
||||||
<div v-else-if="currentView === 'map'" class="map-view-wrapper">
|
<div v-else-if="currentView === 'map'" class="map-view-wrapper">
|
||||||
<div class="alert alert-info d-flex align-items-center" role="alert">
|
<div class="d-flex justify-content-between align-items-center flex-wrap gap-2 mb-3">
|
||||||
<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>
|
<p class="text-muted mb-0">Mappa pubblica delle segnalazioni nell'area di Bari.</p>
|
||||||
</div>
|
<span class="badge bg-light text-dark border">{{ visibleReports.length }} punti</span>
|
||||||
<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>
|
</div>
|
||||||
|
<ClientOnly>
|
||||||
|
<div ref="mapContainer" class="open-data-map rounded border"></div>
|
||||||
|
</ClientOnly>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -206,4 +301,18 @@ const getStatusBadge = (status: string) => {
|
|||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
|
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>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user