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:
@@ -16,10 +16,10 @@
|
|||||||
:maxlength="maxlength"
|
:maxlength="maxlength"
|
||||||
:disabled="disabled"
|
:disabled="disabled"
|
||||||
v-bind="attrs"
|
v-bind="attrs"
|
||||||
class="w-full min-w-0 border-b border-black bg-transparent text-primary-700"
|
class="w-full min-w-0 border-b border-primary-700 bg-transparent"
|
||||||
:class="[
|
:class="[
|
||||||
sizeClass,
|
sizeClass,
|
||||||
isEmpty ? 'text-neutral-400' : 'text-black',
|
isEmpty ? 'text-neutral-400' : 'text-primary-700',
|
||||||
disabled ? 'cursor-not-allowed' : 'cursor-text',
|
disabled ? 'cursor-not-allowed' : 'cursor-text',
|
||||||
inputClass
|
inputClass
|
||||||
]"
|
]"
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
:maxlength="maxLength"
|
:maxlength="maxLength"
|
||||||
:placeholder="placeholderText"
|
:placeholder="placeholderText"
|
||||||
:required="required"
|
:required="required"
|
||||||
class="border-b border-black flex-1 min-w-0 text-xl text-primary-500 uppercase h-[36px] py-[6px]"
|
class="border-b border-primary-700 flex-1 min-w-0 text-xl text-primary-500 uppercase h-[36px] py-[6px]"
|
||||||
@input="handleInput"
|
@input="handleInput"
|
||||||
/>
|
/>
|
||||||
<UiCheckbox
|
<UiCheckbox
|
||||||
|
|||||||
@@ -1,33 +1,31 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-between ">
|
<div class="flex items-center justify-between">
|
||||||
<h1 class="text-4xl font-bold uppercase text-primary-500">Liste des types bovins</h1>
|
<h1 class="text-4xl font-bold uppercase text-primary-500">Liste des types bovins</h1>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-7 border border-slate-200 mb-11 ">
|
|
||||||
<div class="grid grid-cols-2 gap-4 text-primary-700 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
<div v-if="auth.isAdmin" class="mt-7 mb-11">
|
||||||
<div>Nom</div>
|
<UiDataTable
|
||||||
<div>Code</div>
|
v-model:page="page"
|
||||||
</div>
|
v-model:per-page="perPage"
|
||||||
<div v-if="!auth.isAdmin" class="px-4 py-6 text-slate-400">
|
:columns="columns"
|
||||||
Accès réservé aux administrateurs.
|
:items="items"
|
||||||
</div>
|
:total-items="totalItems"
|
||||||
<div v-else-if="bovinList.length === 0" class="px-4 py-6 text-slate-400">
|
:loading="loading"
|
||||||
Aucun type de bovin.
|
row-clickable
|
||||||
</div>
|
@row-click="goToBovin"
|
||||||
<template v-else>
|
>
|
||||||
<div
|
<template #header-label>
|
||||||
v-for="bovin in bovinList"
|
<UiTextInput v-model="filters.label" placeholder="Nom" size="compact" />
|
||||||
:key="bovin.id"
|
</template>
|
||||||
class="grid grid-cols-2 text-primary-700 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
<template #header-code>
|
||||||
role="button"
|
<UiTextInput v-model="filters.code" placeholder="Code" size="compact" />
|
||||||
tabindex="0"
|
</template>
|
||||||
@click="goToBovin(bovin.id)"
|
</UiDataTable>
|
||||||
@keydown.enter="goToBovin(bovin.id)"
|
|
||||||
>
|
|
||||||
<div>{{ bovin.label }}</div>
|
|
||||||
<div>{{ bovin.code }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div v-else class="mt-7 border border-slate-200 mb-11 px-4 py-6 text-slate-400">
|
||||||
|
Accès réservé aux administrateurs.
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center items-center">
|
<div class="flex justify-center items-center">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/admin/bovin"
|
to="/admin/bovin"
|
||||||
@@ -35,24 +33,37 @@
|
|||||||
:class="auth.isAdmin ? '' : 'cursor-not-allowed opacity-60'"
|
:class="auth.isAdmin ? '' : 'cursor-not-allowed opacity-60'"
|
||||||
@click="handleAddClick"
|
@click="handleAddClick"
|
||||||
>
|
>
|
||||||
<Icon name="mdi:plus" size="28" />
|
<Icon name="mdi:plus" size="28" />
|
||||||
Ajouter
|
Ajouter
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getBovineTypeList } from "~/services/bovine-type"
|
import type { BovineTypeData } from '~/services/dto/bovine-type-data'
|
||||||
import type { BovineTypeData } from "~/services/dto/bovine-type-data"
|
import { useAuthStore } from '~/stores/auth'
|
||||||
import { useAuthStore } from "~/stores/auth"
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|
||||||
const bovinList = ref<BovineTypeData[]>([])
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const goToBovin = (id: number) => {
|
const { items, totalItems, page, perPage, filters, loading, reload } =
|
||||||
|
useDataTableServerState<BovineTypeData>(
|
||||||
|
'bovine_types',
|
||||||
|
{
|
||||||
|
label: '',
|
||||||
|
code: ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ key: 'label', label: 'Nom' },
|
||||||
|
{ key: 'code', label: 'Code' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const goToBovin = (bovin: BovineTypeData) => {
|
||||||
if (!auth.isAdmin) return
|
if (!auth.isAdmin) return
|
||||||
router.push(`/admin/bovin/${id}`)
|
router.push(`/admin/bovin/${bovin.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddClick = (event: Event) => {
|
const handleAddClick = (event: Event) => {
|
||||||
@@ -60,8 +71,7 @@ const handleAddClick = (event: Event) => {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(() => {
|
||||||
if (!auth.isAdmin) return
|
if (auth.isAdmin) reload()
|
||||||
bovinList.value = await getBovineTypeList()
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,51 +1,62 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center justify-between ">
|
<h1 class="text-4xl font-bold uppercase text-primary-500">listes des transporteurs</h1>
|
||||||
<h1 class="text-4xl font-bold uppercase text-primary-500">listes des transporteurs</h1>
|
</div>
|
||||||
|
|
||||||
|
<div 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="goToCarrier"
|
||||||
|
>
|
||||||
|
<template #header-name>
|
||||||
|
<UiTextInput v-model="filters.name" placeholder="Label" size="compact" />
|
||||||
|
</template>
|
||||||
|
<template #header-code>
|
||||||
|
<UiTextInput v-model="filters.code" placeholder="Code" size="compact" />
|
||||||
|
</template>
|
||||||
|
</UiDataTable>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-7 border border-slate-200 mb-11 ">
|
|
||||||
<div class="grid grid-cols-2 gap-4 text-primary-700 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide">
|
|
||||||
<div>Label</div>
|
|
||||||
<div>Code</div>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
v-for="carrier in carrierList"
|
|
||||||
:key="carrier.id"
|
|
||||||
class="grid grid-cols-2 text-primary-700 gap-4 px-4 py-3 text-sm hover:bg-slate-50 cursor-pointer border-t border-slate-200"
|
|
||||||
role="button"
|
|
||||||
tabindex="0"
|
|
||||||
@click="goToCarrier(carrier.id)"
|
|
||||||
@keydown.enter="goToCarrier(carrier.id)"
|
|
||||||
>
|
|
||||||
<div>{{ carrier.name}}</div>
|
|
||||||
<div>{{ carrier.code }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex justify-center items-center">
|
<div class="flex justify-center items-center">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/admin/carrier"
|
to="/admin/carrier"
|
||||||
class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
|
class="inline-flex items-center justify-center text-xl text-white uppercase bg-primary-500 h-[50px] px-8 rounded hover:opacity-80 gap-2"
|
||||||
>
|
>
|
||||||
<Icon name="mdi:plus" size="28" />
|
<Icon name="mdi:plus" size="28" />
|
||||||
Ajouter
|
Ajouter
|
||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type {CarrierData} from "~/services/dto/carrier-data";
|
import type { CarrierData } from '~/services/dto/carrier-data'
|
||||||
import {getCarrierList} from "~/services/carrier";
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|
||||||
const carrierList = ref<CarrierData[]>()
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const goToCarrier = (id: number) => {
|
const { items, totalItems, page, perPage, filters, loading, reload } =
|
||||||
router.push(`/admin/carrier/${id}`)
|
useDataTableServerState<CarrierData>(
|
||||||
|
'carriers',
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
code: ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ key: 'name', label: 'Label' },
|
||||||
|
{ key: 'code', label: 'Code' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const goToCarrier = (carrier: CarrierData) => {
|
||||||
|
router.push(`/admin/carrier/${carrier.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(reload)
|
||||||
carrierList.value = await getCarrierList(false)
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,37 +3,35 @@
|
|||||||
<h1 class="text-4xl font-bold uppercase text-primary-500">Liste des clients</h1>
|
<h1 class="text-4xl font-bold uppercase text-primary-500">Liste des clients</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="auth.isAdmin" class="mt-7 border border-slate-200 mb-11">
|
<div v-if="auth.isAdmin" class="mt-7 mb-11">
|
||||||
<div class="max-h-96 overflow-y-auto">
|
<UiDataTable
|
||||||
<div
|
v-model:page="page"
|
||||||
class="sticky text-primary-700 top-0 z-10 grid grid-cols-4 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"
|
v-model:per-page="perPage"
|
||||||
>
|
:columns="columns"
|
||||||
<div>Nom</div>
|
:items="items"
|
||||||
<div>Téléphone</div>
|
:total-items="totalItems"
|
||||||
<div>Mail</div>
|
:loading="loading"
|
||||||
<div>Créé par</div>
|
row-clickable
|
||||||
</div>
|
@row-click="goToCustomer"
|
||||||
|
>
|
||||||
<div v-if="customerList.length === 0" class="px-4 py-6 text-slate-400">
|
<template #header-name>
|
||||||
Aucun client.
|
<UiTextInput v-model="filters.name" placeholder="Nom" size="compact" />
|
||||||
</div>
|
</template>
|
||||||
|
<template #header-phone>
|
||||||
<div
|
<UiTextInput v-model="filters.phone" placeholder="Téléphone" size="compact" />
|
||||||
v-for="customer in customerList"
|
</template>
|
||||||
:key="customer.id"
|
<template #header-email>
|
||||||
class="grid grid-cols-4 text-primary-700 hover:bg-slate-50 border-t gap-4 px-4 py-2 cursor-pointer"
|
<UiTextInput v-model="filters.email" placeholder="Mail" size="compact" />
|
||||||
@click="goToCustomer(customer.id)"
|
</template>
|
||||||
>
|
<template #header-createdBy.username>
|
||||||
<div class="truncate">{{ customer.name || "—" }}</div>
|
<UiTextInput v-model="filters['createdBy.username']" placeholder="Créé par" size="compact" />
|
||||||
<div class="truncate">{{ customer.phone || "—" }}</div>
|
</template>
|
||||||
<div class="truncate">{{ customer.email || "—" }}</div>
|
</UiDataTable>
|
||||||
<div class="truncate">{{ customer.createdBy?.username || "—" }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mt-7 border border-slate-200 mb-11 px-4 py-6 text-slate-400">
|
<div v-else class="mt-7 border border-slate-200 mb-11 px-4 py-6 text-slate-400">
|
||||||
Accès réservé aux administrateurs.
|
Accès réservé aux administrateurs.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center items-center">
|
<div class="flex justify-center items-center">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/admin/customer"
|
to="/admin/customer"
|
||||||
@@ -48,17 +46,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getCustomerList } from "~/services/customer"
|
import type { CustomerData } from '~/services/dto/customer-data'
|
||||||
import type { CustomerData } from "~/services/dto/customer-data"
|
import { useAuthStore } from '~/stores/auth'
|
||||||
import { useAuthStore } from "~/stores/auth"
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|
||||||
const customerList = ref<CustomerData[]>([])
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const goToCustomer = (id: number) => {
|
const { items, totalItems, page, perPage, filters, loading, reload } =
|
||||||
|
useDataTableServerState<CustomerData>(
|
||||||
|
'customers',
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
email: '',
|
||||||
|
'createdBy.username': ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ key: 'name', label: 'Nom' },
|
||||||
|
{ key: 'phone', label: 'Téléphone' },
|
||||||
|
{ key: 'email', label: 'Mail' },
|
||||||
|
{ key: 'createdBy.username', label: 'Créé par' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const goToCustomer = (customer: CustomerData) => {
|
||||||
if (!auth.isAdmin) return
|
if (!auth.isAdmin) return
|
||||||
router.push(`/admin/customer/${id}`)
|
router.push(`/admin/customer/${customer.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddClick = (event: Event) => {
|
const handleAddClick = (event: Event) => {
|
||||||
@@ -66,8 +81,7 @@ const handleAddClick = (event: Event) => {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(() => {
|
||||||
if (!auth.isAdmin) return
|
if (auth.isAdmin) reload()
|
||||||
customerList.value = await getCustomerList()
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,37 +3,35 @@
|
|||||||
<h1 class="text-4xl font-bold uppercase text-primary-500">Liste des fournisseurs</h1>
|
<h1 class="text-4xl font-bold uppercase text-primary-500">Liste des fournisseurs</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="auth.isAdmin" class="mt-7 border border-slate-200 mb-11">
|
<div v-if="auth.isAdmin" class="mt-7 mb-11">
|
||||||
<div class="max-h-96 overflow-y-auto">
|
<UiDataTable
|
||||||
<div
|
v-model:page="page"
|
||||||
class="sticky text-primary-700 top-0 z-10 grid grid-cols-4 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"
|
v-model:per-page="perPage"
|
||||||
>
|
:columns="columns"
|
||||||
<div>Nom</div>
|
:items="items"
|
||||||
<div>Téléphone</div>
|
:total-items="totalItems"
|
||||||
<div>Mail</div>
|
:loading="loading"
|
||||||
<div>Créé par</div>
|
row-clickable
|
||||||
</div>
|
@row-click="goToSupplier"
|
||||||
|
>
|
||||||
<div v-if="supplierList.length === 0" class="px-4 py-6 text-slate-400">
|
<template #header-name>
|
||||||
Aucun fournisseur.
|
<UiTextInput v-model="filters.name" placeholder="Nom" size="compact" />
|
||||||
</div>
|
</template>
|
||||||
|
<template #header-phone>
|
||||||
<div
|
<UiTextInput v-model="filters.phone" placeholder="Téléphone" size="compact" />
|
||||||
v-for="supplier in supplierList"
|
</template>
|
||||||
:key="supplier.id"
|
<template #header-email>
|
||||||
class="grid grid-cols-4 text-primary-700 hover:bg-slate-50 border-t gap-4 px-4 py-2 cursor-pointer"
|
<UiTextInput v-model="filters.email" placeholder="Mail" size="compact" />
|
||||||
@click="goToSupplier(supplier.id)"
|
</template>
|
||||||
>
|
<template #header-createdBy.username>
|
||||||
<div class="truncate">{{ supplier.name || "—" }}</div>
|
<UiTextInput v-model="filters['createdBy.username']" placeholder="Créé par" size="compact" />
|
||||||
<div class="truncate">{{ supplier.phone || "—" }}</div>
|
</template>
|
||||||
<div class="truncate">{{ supplier.email || "—" }}</div>
|
</UiDataTable>
|
||||||
<div class="truncate">{{ supplier.createdBy?.username || "—" }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="mt-7 border border-slate-200 mb-11 px-4 py-6 text-slate-400">
|
<div v-else class="mt-7 border border-slate-200 mb-11 px-4 py-6 text-slate-400">
|
||||||
Accès réservé aux administrateurs.
|
Accès réservé aux administrateurs.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex justify-center items-center">
|
<div class="flex justify-center items-center">
|
||||||
<NuxtLink
|
<NuxtLink
|
||||||
to="/admin/supplier"
|
to="/admin/supplier"
|
||||||
@@ -48,17 +46,34 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { getSupplierList } from "~/services/supplier"
|
import type { SupplierData } from '~/services/dto/supplier-data'
|
||||||
import type { SupplierData } from "~/services/dto/supplier-data"
|
import { useAuthStore } from '~/stores/auth'
|
||||||
import { useAuthStore } from "~/stores/auth"
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|
||||||
const supplierList = ref<SupplierData[]>([])
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuthStore()
|
const auth = useAuthStore()
|
||||||
|
|
||||||
const goToSupplier = (id: number) => {
|
const { items, totalItems, page, perPage, filters, loading, reload } =
|
||||||
|
useDataTableServerState<SupplierData>(
|
||||||
|
'suppliers',
|
||||||
|
{
|
||||||
|
name: '',
|
||||||
|
phone: '',
|
||||||
|
email: '',
|
||||||
|
'createdBy.username': ''
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ key: 'name', label: 'Nom' },
|
||||||
|
{ key: 'phone', label: 'Téléphone' },
|
||||||
|
{ key: 'email', label: 'Mail' },
|
||||||
|
{ key: 'createdBy.username', label: 'Créé par' }
|
||||||
|
]
|
||||||
|
|
||||||
|
const goToSupplier = (supplier: SupplierData) => {
|
||||||
if (!auth.isAdmin) return
|
if (!auth.isAdmin) return
|
||||||
router.push(`/admin/supplier/${id}`)
|
router.push(`/admin/supplier/${supplier.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddClick = (event: Event) => {
|
const handleAddClick = (event: Event) => {
|
||||||
@@ -66,8 +81,7 @@ const handleAddClick = (event: Event) => {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(() => {
|
||||||
if (!auth.isAdmin) return
|
if (auth.isAdmin) reload()
|
||||||
supplierList.value = await getSupplierList()
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -3,42 +3,52 @@
|
|||||||
<h1 class="text-4xl font-bold uppercase text-primary-500">Liste des utilisateurs</h1>
|
<h1 class="text-4xl font-bold uppercase text-primary-500">Liste des utilisateurs</h1>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="auth.isAdmin" class="mt-7 border border-slate-200 mb-11">
|
<div v-if="auth.isAdmin" class="mt-7 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">
|
<UiDataTable
|
||||||
<div>Utilisateur</div>
|
v-model:page="page"
|
||||||
<div>Role</div>
|
v-model:per-page="perPage"
|
||||||
<div>Statut</div>
|
:columns="columns"
|
||||||
</div>
|
:items="items"
|
||||||
<div v-if="userList.length === 0" class="px-4 py-6 text-slate-400">
|
:total-items="totalItems"
|
||||||
Aucun utilisateur.
|
:loading="loading"
|
||||||
</div>
|
row-clickable
|
||||||
<template v-else>
|
@row-click="goToUser"
|
||||||
<div
|
>
|
||||||
v-for="user in userList"
|
<template #header-username>
|
||||||
:key="user.id"
|
<UiTextInput
|
||||||
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"
|
v-model="filters.username"
|
||||||
role="button"
|
placeholder="Utilisateur"
|
||||||
tabindex="0"
|
size="compact"
|
||||||
@click="goToUser(user.id)"
|
/>
|
||||||
@keydown.enter="goToUser(user.id)"
|
</template>
|
||||||
>
|
<template #header-roles>
|
||||||
<div>{{ user.username }}</div>
|
<UiTextInput :model-value="''" placeholder="Role" size="compact" disabled />
|
||||||
<div>{{ getRoleLabels(user.roles) }}</div>
|
</template>
|
||||||
<div>
|
<template #header-isLocked>
|
||||||
<span
|
<UiSelect
|
||||||
v-if="user.isLocked"
|
v-model="filters.isLocked"
|
||||||
class="inline-block px-2 py-0.5 text-xs font-semibold rounded bg-red-100 text-red-700"
|
placeholder="Statut"
|
||||||
>Verrouillé</span>
|
:options="statusOptions"
|
||||||
<span
|
size="compact"
|
||||||
v-else
|
/>
|
||||||
class="inline-block px-2 py-0.5 text-xs font-semibold rounded bg-green-100 text-green-700"
|
</template>
|
||||||
>Actif</span>
|
<template #cell-roles="{ item }">
|
||||||
</div>
|
{{ getRoleLabels(item.roles) }}
|
||||||
</div>
|
</template>
|
||||||
</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>
|
||||||
<div v-else class="mt-7 border border-slate-200 mb-11 px-4 py-6 text-slate-400">
|
<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>
|
||||||
|
|
||||||
<div class="flex justify-center items-center">
|
<div class="flex justify-center items-center">
|
||||||
@@ -55,19 +65,43 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { UserData } from "~/services/dto/user-data"
|
import type { UserData } from '~/services/dto/user-data'
|
||||||
import { getAdminUsers } from "~/services/auth"
|
import { ROLE } from '~/utils/constants'
|
||||||
import { ROLE } from "~/utils/constants"
|
import { useAuthStore } from '~/stores/auth'
|
||||||
import { useAuthStore } from "~/stores/auth"
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|
||||||
const userList = ref<UserData[]>([])
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const auth = useAuthStore()
|
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
|
if (!auth.isAdmin) return
|
||||||
router.push(`/admin/user/${id}`)
|
router.push(`/admin/user/${user.id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleAddClick = (event: Event) => {
|
const handleAddClick = (event: Event) => {
|
||||||
@@ -75,18 +109,7 @@ const handleAddClick = (event: Event) => {
|
|||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
}
|
}
|
||||||
|
|
||||||
const getRoleLabels = (roles?: string[]) => {
|
onMounted(() => {
|
||||||
if (!roles || roles.length === 0) {
|
if (auth.isAdmin) reload()
|
||||||
return '---'
|
|
||||||
}
|
|
||||||
|
|
||||||
return roles
|
|
||||||
.map((role) => roleLabelByValue.get(role) ?? role)
|
|
||||||
.join(', ')
|
|
||||||
}
|
|
||||||
|
|
||||||
onMounted(async () => {
|
|
||||||
if (!auth.isAdmin) return
|
|
||||||
userList.value = await getAdminUsers()
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
@@ -13,6 +15,10 @@ use Doctrine\ORM\Mapping as ORM;
|
|||||||
use Symfony\Component\Serializer\Attribute\Groups;
|
use Symfony\Component\Serializer\Attribute\Groups;
|
||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
|
#[ApiFilter(SearchFilter::class, properties: [
|
||||||
|
'label' => 'ipartial',
|
||||||
|
'code' => 'ipartial',
|
||||||
|
])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
@@ -14,6 +16,10 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table(name: 'carrier')]
|
#[ORM\Table(name: 'carrier')]
|
||||||
|
#[ApiFilter(SearchFilter::class, properties: [
|
||||||
|
'name' => 'ipartial',
|
||||||
|
'code' => 'ipartial',
|
||||||
|
])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
@@ -17,6 +19,12 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table(name: 'customer')]
|
#[ORM\Table(name: 'customer')]
|
||||||
|
#[ApiFilter(SearchFilter::class, properties: [
|
||||||
|
'name' => 'ipartial',
|
||||||
|
'email' => 'ipartial',
|
||||||
|
'phone' => 'ipartial',
|
||||||
|
'createdBy.username' => 'ipartial',
|
||||||
|
])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiProperty;
|
use ApiPlatform\Metadata\ApiProperty;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
@@ -17,6 +19,12 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table(name: 'supplier')]
|
#[ORM\Table(name: 'supplier')]
|
||||||
|
#[ApiFilter(SearchFilter::class, properties: [
|
||||||
|
'name' => 'ipartial',
|
||||||
|
'email' => 'ipartial',
|
||||||
|
'phone' => 'ipartial',
|
||||||
|
'createdBy.username' => 'ipartial',
|
||||||
|
])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\BooleanFilter;
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\SearchFilter;
|
||||||
|
use ApiPlatform\Metadata\ApiFilter;
|
||||||
use ApiPlatform\Metadata\ApiResource;
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
use ApiPlatform\Metadata\Get;
|
use ApiPlatform\Metadata\Get;
|
||||||
use ApiPlatform\Metadata\GetCollection;
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
@@ -20,6 +23,8 @@ use Symfony\Component\Serializer\Attribute\SerializedName;
|
|||||||
|
|
||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table(name: 'user', schema: 'public')]
|
#[ORM\Table(name: 'user', schema: 'public')]
|
||||||
|
#[ApiFilter(SearchFilter::class, properties: ['username' => 'ipartial'])]
|
||||||
|
#[ApiFilter(BooleanFilter::class, properties: ['isLocked'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
@@ -53,7 +58,8 @@ use Symfony\Component\Serializer\Attribute\SerializedName;
|
|||||||
new GetCollection(
|
new GetCollection(
|
||||||
uriTemplate: '/admin/users',
|
uriTemplate: '/admin/users',
|
||||||
normalizationContext: ['groups' => ['user:read']],
|
normalizationContext: ['groups' => ['user:read']],
|
||||||
security: "is_granted('ROLE_ADMIN')"
|
security: "is_granted('ROLE_ADMIN')",
|
||||||
|
paginationEnabled: true
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
normalizationContext: ['groups' => ['user:read']],
|
normalizationContext: ['groups' => ['user:read']],
|
||||||
|
|||||||
Reference in New Issue
Block a user