diff --git a/.gitignore b/.gitignore
index ad774cb..3f78594 100644
--- a/.gitignore
+++ b/.gitignore
@@ -24,4 +24,5 @@ logs
!.env.example
pnpm-*.yaml
+.pnpm-store/
AGENTS.md
\ No newline at end of file
diff --git a/app/assets/css/style.css b/app/assets/css/style.css
index 814b736..88bdbf1 100644
--- a/app/assets/css/style.css
+++ b/app/assets/css/style.css
@@ -296,6 +296,35 @@ html body .navbar .navbar-collapsable .navbar-nav li.nav-item a.nav-link[data-fo
line-height: 1;
}
+.page-shell {
+ padding-block: 3rem;
+}
+
+.page-shell-header {
+ align-items: flex-start;
+ display: flex;
+ flex-wrap: wrap;
+ gap: 1rem;
+ justify-content: space-between;
+ margin-bottom: 2rem;
+}
+
+.page-shell-title {
+ margin-bottom: 0.25rem;
+}
+
+.page-shell-subtitle {
+ color: var(--visione-text-soft);
+ margin-bottom: 0;
+ max-width: 48rem;
+}
+
+.surface-card {
+ background: #fff;
+ border: 1px solid #dfe4ea;
+ border-radius: 4px;
+}
+
.it-header-wrapper .header-slim-right-zone .btn .rounded-icon,
.it-header-wrapper .header-slim-right-zone .btn .rounded-icon .icon {
@@ -316,6 +345,10 @@ html body .navbar .navbar-collapsable .navbar-nav li.nav-item a.nav-link[data-fo
}
@media (max-width: 767.98px) {
+ .page-shell {
+ padding-block: 2rem;
+ }
+
.it-hero-wrapper .container {
min-height: 220px;
}
diff --git a/app/components/dashboard/AdminDashboard.vue b/app/components/dashboard/AdminDashboard.vue
index b60eacf..9598581 100644
--- a/app/components/dashboard/AdminDashboard.vue
+++ b/app/components/dashboard/AdminDashboard.vue
@@ -1,5 +1,4 @@
+
+
+
+
+
+
Categorie e Team
+
Gestione dati di riferimento per segnalazioni e interventi.
+
+
Torna alla Dashboard
+
+
+
+
+
+
+
+
Categorie
+ {{ categories.length }}
+
+
+
+ {{ categoryError }}
+
+
+
+
+
+
+
+
+ | ID |
+ Nome |
+ Stato |
+
+
+
+
+ | #{{ category.id }} |
+
+ {{ category.name }}
+ {{ category.description }}
+ |
+
+
+ {{ category.active === false ? 'Inattiva' : 'Attiva' }}
+
+ |
+
+
+ | Nessuna categoria configurata. |
+
+
+
+
+
+
+
+
+
+
+
+
+
Team di intervento
+ {{ teams.length }}
+
+
+
+ {{ teamError }}
+
+
+
+
+
+
+
+
+ | ID |
+ Team |
+ Categoria |
+ Area |
+ Stato |
+
+
+
+
+ | #{{ team.id }} |
+
+ {{ team.name }}
+ {{ team.description }}
+ |
+ {{ team.category_name || '-' }} |
+ {{ team.area_name || 'Tutte' }} |
+
+
+ {{ team.active === false ? 'Inattivo' : 'Attivo' }}
+
+ |
+
+
+ | Nessun team configurato. |
+
+
+
+
+
+
+
+
+
+
diff --git a/app/pages/admin/users.vue b/app/pages/admin/users.vue
index 96af45d..e1d8a4a 100644
--- a/app/pages/admin/users.vue
+++ b/app/pages/admin/users.vue
@@ -13,6 +13,7 @@ const { getUsers, updateUserBan, updateUserRoles } = useAdmin()
const { data: usersRes, pending, refresh } = useAsyncData('admin-users', () => getUsers())
const search = ref('')
+const parseRoles = (roles?: string) => roles ? roles.split(',').map((r) => r.trim()).filter(Boolean) : []
const filteredUsers = computed(() => {
if (!usersRes.value?.users) return []
@@ -47,7 +48,7 @@ const availableRoles = ['CITIZEN', 'OPERATOR', 'ADMIN']
const openRoleModal = (user: any) => {
editingUserId.value = user.id
- selectedRoles.value = user.roles ? user.roles.split(',').map((r: string) => r.trim()) : []
+ selectedRoles.value = parseRoles(user.roles)
}
const closeRoleModal = () => {
@@ -118,8 +119,8 @@ const saveRoles = async () => {
{{ user.fiscal_code }}
-
- {{ r.trim() }}
+
+ {{ r }}
|
{{ user.points || 0 }} |
diff --git a/app/pages/operator/reports/[id].vue b/app/pages/operator/reports/[id].vue
index d45ff33..af8c7c9 100644
--- a/app/pages/operator/reports/[id].vue
+++ b/app/pages/operator/reports/[id].vue
@@ -17,19 +17,22 @@ if (!reportId) {
}
const { getReportDetail, getMessages, addMessage, flagMessage } = useReports()
-const { getInterventionSummary, updateReportStatus, assignTeam } = useOperator()
+const { getInterventionSummary, updateReportStatus, assignTeam, getTeams } = useOperator()
const { user } = useAuth()
// Dati principali
const { data: reportData, refresh: refreshReport } = await useAsyncData(`op-report-${reportId}`, () => getReportDetail(reportId))
const { data: messagesData, refresh: refreshMessages } = await useAsyncData(`op-messages-${reportId}`, () => getMessages(reportId))
const { data: summaryData, refresh: refreshSummary } = await useAsyncData(`op-summary-${reportId}`, () => getInterventionSummary(reportId))
+const { data: teamsData } = await useAsyncData('operator-teams', () => getTeams())
const report = computed(() => (reportData.value as any)?.report)
const media = computed(() => (reportData.value as any)?.media || [])
const statusHistory = computed(() => (reportData.value as any)?.history || [])
const messages = computed(() => (messagesData.value as any)?.messages || [])
const summary = computed(() => (summaryData.value as any)?.summary)
+const summaryMedia = computed(() => (summaryData.value as any)?.media || [])
+const teams = computed(() => teamsData.value?.teams || [])
const statusMeta: Record = {
OPEN: 'Aperta',
UNDER_REVIEW: 'In revisione',
@@ -47,9 +50,7 @@ const isUpdatingStatus = ref(false)
// Form Assegna Squadra
const isAssignModalOpen = ref(false)
-const teamName = ref('')
-const assignNote = ref('')
-const estimatedDate = ref('')
+const selectedTeamId = ref(null)
const isAssigning = ref(false)
// Messaggi
@@ -90,19 +91,15 @@ const handleStatusUpdate = async () => {
}
const handleAssignTeam = async () => {
- if (!teamName.value) return
+ if (!selectedTeamId.value) return
isAssigning.value = true
try {
const res = await assignTeam(reportId, {
- assigned_to_team: teamName.value,
- estimated_resolution_date: estimatedDate.value || undefined,
- internal_notes: assignNote.value || undefined
+ team_id: selectedTeamId.value
})
if (res.ok) {
isAssignModalOpen.value = false
- teamName.value = ''
- assignNote.value = ''
- estimatedDate.value = ''
+ selectedTeamId.value = null
await Promise.all([refreshReport(), refreshSummary()])
}
} catch (e) {
@@ -152,6 +149,15 @@ const formatDate = (dateString: string) => {
hour: '2-digit', minute: '2-digit'
})
}
+
+const latestCitizenMessage = computed(() => {
+ const reportUserId = report.value?.user_id
+ if (!reportUserId) return null
+
+ return [...messages.value]
+ .reverse()
+ .find((message) => message.user_id === reportUserId) || null
+})
@@ -257,29 +263,31 @@ const formatDate = (dateString: string) => {