Files
frontend/app/pages/admin/users.vue
T
reloia f32fa8620b style: remove unnecessary inline comments
Removed Italian and redundant inline comments across multiple files.
2026-06-16 18:30:40 +02:00

228 lines
7.0 KiB
Vue

<script setup lang="ts">
import { ref } from 'vue'
definePageMeta({
middleware: 'auth',
auth: { role: 'ADMIN' }
})
useHead({ title: 'Gestione Utenti - Admin' })
const { getUsers, updateUserBan, updateUserRoles } = useAdmin()
const { data: usersRes, pending, refresh } = useAsyncData('admin-users', () => getUsers())
const search = ref('')
const banError = ref('')
const roleError = ref('')
const pendingBanUser = ref<any | null>(null)
const isUpdatingBan = ref(false)
const parseRoles = (roles?: string) => roles ? roles.split(',').map(normalizeRole).filter(Boolean) : []
const banConfirmMessage = computed(() =>
pendingBanUser.value
? `Vuoi ${pendingBanUser.value.banned ? 'sbloccare' : 'bannare'} utente ${pendingBanUser.value.first_name} ${pendingBanUser.value.last_name}?`
: ''
)
const banConfirmLabel = computed(() => pendingBanUser.value?.banned ? 'Sblocca utente' : 'Banna utente')
const banConfirmClass = computed(() => pendingBanUser.value?.banned ? 'btn-success' : 'btn-danger')
const filteredUsers = computed(() => {
if (!usersRes.value?.users) return []
if (!search.value) return usersRes.value.users
const s = search.value.toLowerCase()
return usersRes.value.users.filter(u =>
u.first_name.toLowerCase().includes(s) ||
u.last_name.toLowerCase().includes(s) ||
u.email.toLowerCase().includes(s) ||
u.fiscal_code?.toLowerCase().includes(s)
)
})
const askToggleBan = (user: any) => {
pendingBanUser.value = user
}
const closeBanModal = () => {
if (isUpdatingBan.value) return
pendingBanUser.value = null
}
const handleToggleBan = async () => {
if (!pendingBanUser.value) return
const user = pendingBanUser.value
isUpdatingBan.value = true
banError.value = ''
try {
const res = await updateUserBan(user.id, { banned: !user.banned })
if (res.ok) {
await refresh()
pendingBanUser.value = null
}
} catch (err) {
console.error('Failed to update user ban status', err)
banError.value = 'Errore durante l\'aggiornamento dello stato di ban.'
} finally {
isUpdatingBan.value = false
}
}
const editingUserId = ref<number | null>(null)
const selectedRoles = ref<string[]>([])
const availableRoles = ['CITIZEN', 'OPERATOR', 'ADMIN']
const openRoleModal = (user: any) => {
editingUserId.value = user.id
selectedRoles.value = parseRoles(user.roles)
}
const closeRoleModal = () => {
editingUserId.value = null
selectedRoles.value = []
}
const saveRoles = async () => {
if (!editingUserId.value) return
roleError.value = ''
try {
const res = await updateUserRoles(editingUserId.value, { roles: selectedRoles.value })
if (res.ok) {
await refresh()
closeRoleModal()
}
} catch (err) {
console.error('Failed to update roles', err)
roleError.value = 'Errore durante l\'aggiornamento dei ruoli.'
}
}
</script>
<template>
<div class="container my-5">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1 class="h3 font-weight-bold mb-0">Gestione Utenti</h1>
<NuxtLink to="/dashboard" class="btn btn-outline-secondary btn-sm">Torna al pannello admin</NuxtLink>
</div>
<div class="row mb-4">
<div class="col-md-6">
<div class="input-group">
<input type="text" class="form-control" placeholder="Cerca per nome, email o CF..." v-model="search">
</div>
</div>
</div>
<div v-if="pending" class="text-center py-5">
<div class="spinner-border text-primary" role="status"></div>
</div>
<div v-else-if="!usersRes?.users || usersRes.users.length === 0" class="alert alert-info">
Nessun utente trovato.
</div>
<div v-if="banError" class="alert alert-danger">
{{ banError }}
</div>
<div v-if="roleError" class="alert alert-danger">
{{ roleError }}
</div>
<div v-else class="table-responsive">
<table class="table table-hover align-middle">
<thead>
<tr>
<th>ID</th>
<th>Nome</th>
<th>Email / CF</th>
<th>Ruoli</th>
<th>Punti</th>
<th>Stato</th>
<th class="text-end">Azioni</th>
</tr>
</thead>
<tbody>
<tr v-for="user in filteredUsers" :key="user.id">
<td>{{ user.id }}</td>
<td>
<strong>{{ user.first_name }} {{ user.last_name }}</strong>
</td>
<td>
<div>{{ user.email }}</div>
<small class="text-muted">{{ user.fiscal_code }}</small>
</td>
<td>
<span v-for="r in parseRoles(user.roles)" :key="r" class="badge bg-primary me-1">
{{ r }}
</span>
</td>
<td>{{ user.points || 0 }}</td>
<td>
<span v-if="user.banned" class="badge bg-danger">Bannato</span>
<span v-else class="badge bg-success">Attivo</span>
</td>
<td class="text-end">
<button
class="btn btn-sm btn-outline-primary me-2"
@click="openRoleModal(user)"
>
Ruoli
</button>
<button
class="btn btn-sm"
:class="user.banned ? 'btn-outline-success' : 'btn-outline-danger'"
@click="askToggleBan(user)"
>
{{ user.banned ? 'Sblocca' : 'Banna' }}
</button>
</td>
</tr>
</tbody>
</table>
</div>
<div v-if="editingUserId !== null" class="modal d-block" tabindex="-1" style="background: rgba(0,0,0,0.5)">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Modifica Ruoli Utente</h5>
<button type="button" class="btn-close" aria-label="Close" @click="closeRoleModal"></button>
</div>
<div class="modal-body">
<div class="form-check mb-2" v-for="role in availableRoles" :key="role">
<input
class="form-check-input"
type="checkbox"
:value="role"
:id="'role-'+role"
:checked="selectedRoles.includes(role)"
v-model="selectedRoles"
>
<label class="form-check-label" :for="'role-'+role">
{{ role }}
</label>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="closeRoleModal">Annulla</button>
<button type="button" class="btn btn-primary" @click="saveRoles">Salva</button>
</div>
</div>
</div>
</div>
<ConfirmModal
:open="pendingBanUser !== null"
title="Conferma stato utente"
:message="banConfirmMessage"
:confirm-label="banConfirmLabel"
:confirm-class="banConfirmClass"
:pending="isUpdatingBan"
@cancel="closeBanModal"
@confirm="handleToggleBan"
/>
</div>
</template>