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

@@ -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
]" ]"

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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(

View File

@@ -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']],