182 lines
5.7 KiB
Vue
182 lines
5.7 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 parseRoles = (roles?: string) => roles ? roles.split(',').map((r) => r.trim()).filter(Boolean) : []
|
|
|
|
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 handleToggleBan = async (id: number, currentBanStatus: boolean) => {
|
|
if (!confirm(`Sei sicuro di voler ${currentBanStatus ? 'sbloccare' : 'bannare'} questo utente?`)) return
|
|
|
|
try {
|
|
const res = await updateUserBan(id, { banned: !currentBanStatus })
|
|
if (res.ok) {
|
|
await refresh()
|
|
}
|
|
} catch (err) {
|
|
console.error('Failed to update user ban status', err)
|
|
alert('Errore durante l\'aggiornamento dello stato di ban.')
|
|
}
|
|
}
|
|
|
|
// Modal per i ruoli
|
|
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
|
|
|
|
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)
|
|
alert('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-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="handleToggleBan(user.id, !!user.banned)"
|
|
>
|
|
{{ 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>
|
|
</div>
|
|
</template>
|