feat : migration des 5 écrans admin sur UiDataTable

- Filtres SearchFilter/BooleanFilter ajoutés sur User, Supplier, Customer, Carrier, BovineType
- Pagination activée sur l'opération admin/users
- UiTextInput et license-plate-input utilisent border-primary-700 pour la cohérence visuelle

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 15:12:58 +02:00
parent 86fd7e6b04
commit f945ae72a7
12 changed files with 304 additions and 198 deletions

View File

@@ -3,42 +3,52 @@
<h1 class="text-4xl font-bold uppercase text-primary-500">Liste des utilisateurs</h1>
</div>
<div v-if="auth.isAdmin" class="mt-7 border border-slate-200 mb-11">
<div class="grid grid-cols-3 text-primary-700 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
<div>Utilisateur</div>
<div>Role</div>
<div>Statut</div>
</div>
<div v-if="userList.length === 0" class="px-4 py-6 text-slate-400">
Aucun utilisateur.
</div>
<template v-else>
<div
v-for="user in userList"
:key="user.id"
class="grid grid-cols-3 text-primary-700 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200 items-center"
role="button"
tabindex="0"
@click="goToUser(user.id)"
@keydown.enter="goToUser(user.id)"
>
<div>{{ user.username }}</div>
<div>{{ getRoleLabels(user.roles) }}</div>
<div>
<span
v-if="user.isLocked"
class="inline-block px-2 py-0.5 text-xs font-semibold rounded bg-red-100 text-red-700"
>Verrouillé</span>
<span
v-else
class="inline-block px-2 py-0.5 text-xs font-semibold rounded bg-green-100 text-green-700"
>Actif</span>
</div>
</div>
</template>
<div v-if="auth.isAdmin" class="mt-7 mb-11">
<UiDataTable
v-model:page="page"
v-model:per-page="perPage"
:columns="columns"
:items="items"
:total-items="totalItems"
:loading="loading"
row-clickable
@row-click="goToUser"
>
<template #header-username>
<UiTextInput
v-model="filters.username"
placeholder="Utilisateur"
size="compact"
/>
</template>
<template #header-roles>
<UiTextInput :model-value="''" placeholder="Role" size="compact" disabled />
</template>
<template #header-isLocked>
<UiSelect
v-model="filters.isLocked"
placeholder="Statut"
:options="statusOptions"
size="compact"
/>
</template>
<template #cell-roles="{ item }">
{{ getRoleLabels(item.roles) }}
</template>
<template #cell-isLocked="{ item }">
<span
v-if="item.isLocked"
class="inline-block px-2 py-0.5 text-xs font-semibold rounded bg-red-100 text-red-700"
>Verrouillé</span>
<span
v-else
class="inline-block px-2 py-0.5 text-xs font-semibold rounded bg-green-100 text-green-700"
>Actif</span>
</template>
</UiDataTable>
</div>
<div v-else class="mt-7 border border-slate-200 mb-11 px-4 py-6 text-slate-400">
Acces reserve aux administrateurs.
Accès réservé aux administrateurs.
</div>
<div class="flex justify-center items-center">
@@ -55,19 +65,43 @@
</template>
<script setup lang="ts">
import type { UserData } from "~/services/dto/user-data"
import { getAdminUsers } from "~/services/auth"
import { ROLE } from "~/utils/constants"
import { useAuthStore } from "~/stores/auth"
import type { UserData } from '~/services/dto/user-data'
import { ROLE } from '~/utils/constants'
import { useAuthStore } from '~/stores/auth'
import { useDataTableServerState } from '~/composables/useDataTableServerState'
const userList = ref<UserData[]>([])
const router = useRouter()
const auth = useAuthStore()
const roleLabelByValue = new Map(ROLE.map((role) => [role.value, role.label]))
const roleLabelByValue = new Map(ROLE.map(role => [role.value, role.label]))
const goToUser = (id: number) => {
const { items, totalItems, page, perPage, filters, loading, reload } =
useDataTableServerState<UserData>(
'admin/users',
{
username: '',
isLocked: ''
}
)
const statusOptions = [
{ value: 'false', label: 'Actif' },
{ value: 'true', label: 'Verrouillé' }
]
const columns = [
{ key: 'username', label: 'Utilisateur' },
{ key: 'roles', label: 'Role' },
{ key: 'isLocked', label: 'Statut', width: '160px' }
]
const getRoleLabels = (roles?: string[]) => {
if (!roles || roles.length === 0) return '---'
return roles.map(role => roleLabelByValue.get(role) ?? role).join(', ')
}
const goToUser = (user: UserData) => {
if (!auth.isAdmin) return
router.push(`/admin/user/${id}`)
router.push(`/admin/user/${user.id}`)
}
const handleAddClick = (event: Event) => {
@@ -75,18 +109,7 @@ const handleAddClick = (event: Event) => {
event.preventDefault()
}
const getRoleLabels = (roles?: string[]) => {
if (!roles || roles.length === 0) {
return '---'
}
return roles
.map((role) => roleLabelByValue.get(role) ?? role)
.join(', ')
}
onMounted(async () => {
if (!auth.isAdmin) return
userList.value = await getAdminUsers()
onMounted(() => {
if (auth.isAdmin) reload()
})
</script>