205 lines
8.0 KiB
Vue
205 lines
8.0 KiB
Vue
<script setup lang="ts">
|
|
import { ref, computed } from 'vue'
|
|
|
|
// 1. API Fetching Logic
|
|
const { getOpenDataReports } = usePublic()
|
|
|
|
// Reactive pagination state
|
|
const currentPage = ref(1)
|
|
const perPage = ref(9)
|
|
|
|
// Fetch data automatically when currentPage changes
|
|
const { data: openDataResponse, pending } = await useAsyncData(
|
|
'open-data-reports',
|
|
() => getOpenDataReports({ page: currentPage.value, per_page: perPage.value }),
|
|
{ watch: [currentPage] } // Nuxt will re-fetch when currentPage changes
|
|
)
|
|
|
|
// Pagination logic: If we receive less items than `perPage`, we are on the last page.
|
|
const isLastPage = computed(() => {
|
|
if (!openDataResponse.value?.reports) return true
|
|
return openDataResponse.value.reports.length < perPage.value
|
|
})
|
|
|
|
const nextPage = () => {
|
|
if (!isLastPage.value) currentPage.value++
|
|
}
|
|
const prevPage = () => {
|
|
if (currentPage.value > 1) currentPage.value--
|
|
}
|
|
|
|
// 2. View Toggle (Grid vs Map)
|
|
const currentView = ref<'grid' | 'map'>('grid')
|
|
|
|
// 3. Helper to style status badges
|
|
const getStatusBadge = (status: string) => {
|
|
switch (status) {
|
|
case 'RESOLVED': return { class: 'bg-success', text: 'Risolto', icon: 'it-check-circle' }
|
|
case 'CLOSED': return { class: 'bg-secondary', text: 'Chiuso', icon: 'it-lock' }
|
|
default: return { class: 'bg-info', text: status, icon: 'it-info-circle' }
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div>
|
|
<!-- HERO SECTION (Small version for internal pages) -->
|
|
<div class="it-hero-wrapper it-primary">
|
|
<div class="container">
|
|
<div class="row">
|
|
<div class="col-12 text-center">
|
|
<div class="it-hero-text-wrapper p-4">
|
|
<h1 class="text-white font-weight-bold">Open Data Interventi</h1>
|
|
<p class="text-white mt-2 lead">
|
|
Esplora i dati pubblici degli interventi urbani risolti. La trasparenza al servizio del cittadino.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- MAIN CONTENT -->
|
|
<div class="container my-5">
|
|
|
|
<!-- TOOLBAR: View Toggles & Export -->
|
|
<div class="row align-items-center mb-5 pb-3 border-bottom">
|
|
<div class="col-12 col-md-6 mb-3 mb-md-0 d-flex gap-2">
|
|
<!-- View Toggle Buttons -->
|
|
<button
|
|
class="btn btn-sm"
|
|
:class="currentView === 'grid' ? 'btn-primary' : 'btn-outline-primary'"
|
|
@click="currentView = 'grid'"
|
|
>
|
|
<BIIcon name="it-grid" size="sm" class="me-1" :color="currentView === 'grid' ? 'white' : 'primary'" />
|
|
Griglia Dati
|
|
</button>
|
|
<button
|
|
class="btn btn-sm"
|
|
:class="currentView === 'map' ? 'btn-primary' : 'btn-outline-primary'"
|
|
@click="currentView = 'map'"
|
|
>
|
|
<BIIcon name="it-map-marker" size="sm" class="me-1" :color="currentView === 'map' ? 'white' : 'primary'" />
|
|
Mappa
|
|
</button>
|
|
</div>
|
|
<div class="col-12 col-md-6 text-md-end">
|
|
<!-- Fake Export CTA (Very common in Open Data portals) -->
|
|
<button class="btn btn-sm btn-outline-secondary" title="Scarica dataset in formato JSON">
|
|
<BIIcon name="it-download" size="sm" class="me-1" />
|
|
Scarica JSON
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- LOADER -->
|
|
<div v-if="pending" class="d-flex justify-content-center my-5 py-5">
|
|
<div class="progress-spinner progress-spinner-active" role="status">
|
|
<span class="visually-hidden">Caricamento in corso...</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- GRID VIEW -->
|
|
<div v-else-if="currentView === 'grid'">
|
|
<div class="row" v-if="openDataResponse?.reports && openDataResponse.reports.length > 0">
|
|
<div class="col-12 col-md-6 col-lg-4 mb-4" v-for="report in openDataResponse.reports" :key="report.id">
|
|
<!-- Strict BI Card -->
|
|
<div class="card-wrapper card-space h-100">
|
|
<div class="card card-bg h-100 shadow-sm">
|
|
<div class="card-body d-flex flex-column">
|
|
|
|
<div class="d-flex justify-content-between align-items-start mb-3">
|
|
<div class="category-top m-0">
|
|
<span class="badge rounded-pill text-white px-3 py-1" :class="getStatusBadge(report.status).class">
|
|
<BIIcon :name="getStatusBadge(report.status).icon" size="xs" color="white" class="me-1" />
|
|
{{ getStatusBadge(report.status).text }}
|
|
</span>
|
|
</div>
|
|
<span class="text-muted small">ID: #{{ report.id }}</span>
|
|
</div>
|
|
|
|
<h3 class="card-title h5 font-weight-bold">{{ report.category_name }}</h3>
|
|
|
|
<p class="card-text text-muted mb-4">
|
|
<BIIcon name="it-map-marker-circle" size="sm" class="me-1" />
|
|
{{ report.area_name || 'Area comunale non specificata' }}
|
|
</p>
|
|
|
|
<div class="mt-auto pt-3 border-top">
|
|
<div class="row text-muted small">
|
|
<div class="col-6">
|
|
<div class="fw-bold text-uppercase">Segnalato</div>
|
|
<div>{{ new Date(report.created_at).toLocaleDateString('it-IT') }}</div>
|
|
</div>
|
|
<div class="col-6 text-end">
|
|
<div class="fw-bold text-uppercase">Risolto</div>
|
|
<div>{{ new Date(report.updated_at).toLocaleDateString('it-IT') }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div v-else class="alert alert-warning text-center" role="alert">
|
|
Nessun dato pubblico disponibile al momento.
|
|
</div>
|
|
|
|
<!-- BOOTSTRAP ITALIA PAGINATION -->
|
|
<nav class="pagination-wrapper justify-content-center mt-5" aria-label="Navigazione pagine open data">
|
|
<ul class="pagination">
|
|
<li class="page-item" :class="{ disabled: currentPage === 1 }">
|
|
<a class="page-link" href="#" tabindex="-1" aria-disabled="true" @click.prevent="prevPage">
|
|
<BIIcon name="it-chevron-left" size="sm" />
|
|
<span class="visually-hidden">Pagina precedente</span>
|
|
</a>
|
|
</li>
|
|
<li class="page-item">
|
|
<span class="page-link font-weight-semibold">Pagina {{ currentPage }}</span>
|
|
</li>
|
|
<li class="page-item" :class="{ disabled: isLastPage }">
|
|
<a class="page-link" href="#" @click.prevent="nextPage">
|
|
<span class="visually-hidden">Pagina successiva</span>
|
|
<BIIcon name="it-chevron-right" size="sm" />
|
|
</a>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
|
|
<!-- MAP VIEW (Placeholder for Leaflet/Mapbox) -->
|
|
<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 {{ openDataResponse?.reports?.length || 0 }} punti geografici in corso...</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* Ensures the cards inside the grid stretch to equal heights */
|
|
.card-wrapper {
|
|
height: 100%;
|
|
}
|
|
.card-bg {
|
|
border: 1px solid rgba(0,0,0,0.05);
|
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
}
|
|
.card-bg:hover {
|
|
transform: translateY(-2px);
|
|
box-shadow: 0 4px 15px rgba(0,0,0,0.1) !important;
|
|
}
|
|
</style>
|