feat : page case sur UiDataTable server-side
- SearchFilter et DateFilter ajoutés sur l'entité Bovine - Filtres serveur sur numéro national, poids exact et date d'arrivée - Scope automatique via buildingCase IRI sur l'endpoint /bovines Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -33,42 +33,51 @@
|
|||||||
</NuxtLink>
|
</NuxtLink>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="mt-8 border border-slate-200 mb-16">
|
<div class="mt-8 mb-16">
|
||||||
<div
|
<UiDataTable
|
||||||
class="grid grid-cols-3 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"
|
v-model:page="page"
|
||||||
|
v-model:per-page="perPage"
|
||||||
|
:columns="columns"
|
||||||
|
:items="items"
|
||||||
|
:total-items="totalItems"
|
||||||
|
:loading="loading"
|
||||||
|
:row-clickable="auth.isAdmin"
|
||||||
|
empty-message="Aucun bovin dans cette case."
|
||||||
|
@row-click="goToBovine"
|
||||||
>
|
>
|
||||||
<div>Numéro national</div>
|
<template #header-nationalNumber>
|
||||||
<div>Poids à l'arrivée (kg)</div>
|
<UiTextInput
|
||||||
<div>Date d'arrivée</div>
|
v-model="filters.nationalNumber"
|
||||||
</div>
|
placeholder="Numéro national"
|
||||||
<template v-if="bovines.length > 0">
|
size="compact"
|
||||||
<div
|
/>
|
||||||
v-for="bovine in bovines"
|
|
||||||
:key="bovine.id"
|
|
||||||
class="grid grid-cols-3 gap-4 px-4 py-3 text-sm border-t border-slate-200"
|
|
||||||
:class="auth.isAdmin ? 'cursor-pointer hover:bg-slate-50' : ''"
|
|
||||||
:role="auth.isAdmin ? 'button' : undefined"
|
|
||||||
:tabindex="auth.isAdmin ? 0 : undefined"
|
|
||||||
@click="goToBovine(bovine.id)"
|
|
||||||
@keydown.enter="goToBovine(bovine.id)"
|
|
||||||
>
|
|
||||||
<div>{{ bovine.nationalNumber }}</div>
|
|
||||||
<div>{{ bovine.receivedWeight ?? '—' }}</div>
|
|
||||||
<div>{{ formatDate(bovine.arrivalDate) }}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
<div
|
<template #header-receivedWeight>
|
||||||
v-else
|
<UiTextInput
|
||||||
class="px-4 py-3 text-sm border-t border-slate-200 text-slate-500"
|
v-model="filters.receivedWeight"
|
||||||
>
|
placeholder="Poids (kg)"
|
||||||
Aucun bovin dans cette case.
|
size="compact"
|
||||||
</div>
|
/>
|
||||||
|
</template>
|
||||||
|
<template #header-arrivalDate>
|
||||||
|
<UiDateInput v-model="arrivalDateFilter" size="compact" />
|
||||||
|
</template>
|
||||||
|
<template #cell-arrivalDate="{ item }">
|
||||||
|
{{ formatDate(item.arrivalDate) }}
|
||||||
|
</template>
|
||||||
|
<template #cell-receivedWeight="{ item }">
|
||||||
|
{{ item.receivedWeight ?? '—' }}
|
||||||
|
</template>
|
||||||
|
</UiDataTable>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { BuildingCaseData } from '~/services/dto/building-case-data'
|
import type { BuildingCaseData } from '~/services/dto/building-case-data'
|
||||||
|
import type { BovineData } from '~/services/dto/bovine-data'
|
||||||
import { useAuthStore } from '~/stores/auth'
|
import { useAuthStore } from '~/stores/auth'
|
||||||
|
import { useDataTableServerState } from '~/composables/useDataTableServerState'
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -80,7 +89,44 @@ const caseId = computed(() => Number(route.query.id))
|
|||||||
const hasCaseId = computed(() => Number.isFinite(caseId.value) && caseId.value > 0)
|
const hasCaseId = computed(() => Number.isFinite(caseId.value) && caseId.value > 0)
|
||||||
|
|
||||||
const buildingCase = ref<BuildingCaseData | null>(null)
|
const buildingCase = ref<BuildingCaseData | null>(null)
|
||||||
const bovines = computed(() => buildingCase.value?.bovines ?? [])
|
|
||||||
|
const { items, totalItems, page, perPage, filters, loading, reload } =
|
||||||
|
useDataTableServerState<BovineData>(
|
||||||
|
'bovines',
|
||||||
|
{
|
||||||
|
buildingCase: '',
|
||||||
|
nationalNumber: '',
|
||||||
|
receivedWeight: '',
|
||||||
|
'arrivalDate[after]': '',
|
||||||
|
'arrivalDate[strictly_before]': ''
|
||||||
|
},
|
||||||
|
{ initialPerPage: 10 }
|
||||||
|
)
|
||||||
|
|
||||||
|
const addOneDay = (dateString: string): string => {
|
||||||
|
const [year, month, day] = dateString.split('-').map(Number)
|
||||||
|
const next = new Date(Date.UTC(year, month - 1, day + 1))
|
||||||
|
return next.toISOString().slice(0, 10)
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrivalDateFilter = computed<string>({
|
||||||
|
get: () => (filters.value['arrivalDate[after]'] as string) ?? '',
|
||||||
|
set: (value: string) => {
|
||||||
|
if (!value) {
|
||||||
|
filters.value['arrivalDate[after]'] = ''
|
||||||
|
filters.value['arrivalDate[strictly_before]'] = ''
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filters.value['arrivalDate[after]'] = value
|
||||||
|
filters.value['arrivalDate[strictly_before]'] = addOneDay(value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ key: 'nationalNumber', label: 'Numéro national' },
|
||||||
|
{ key: 'receivedWeight', label: "Poids à l'arrivée (kg)" },
|
||||||
|
{ key: 'arrivalDate', label: "Date d'arrivée" }
|
||||||
|
]
|
||||||
|
|
||||||
const title = computed(() => {
|
const title = computed(() => {
|
||||||
if (!buildingCase.value) return ''
|
if (!buildingCase.value) return ''
|
||||||
@@ -114,21 +160,27 @@ const loadCase = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const printCaseReport = async () => {
|
const printCaseReport = async () => {
|
||||||
if (!hasCaseId.value) {
|
if (!hasCaseId.value) return
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
const filename = `tableau_poids_case_${caseId.value}.pdf`
|
const filename = `tableau_poids_case_${caseId.value}.pdf`
|
||||||
await printPdf(`/building_cases/${caseId.value}/weights-report`, filename)
|
await printPdf(`/building_cases/${caseId.value}/weights-report`, filename)
|
||||||
}
|
}
|
||||||
|
|
||||||
const goToBovine = (id: number) => {
|
const goToBovine = (bovine: BovineData) => {
|
||||||
if (!auth.isAdmin) return
|
if (!auth.isAdmin) return
|
||||||
router.push({
|
router.push({
|
||||||
path: '/infrastructure/bovine',
|
path: '/infrastructure/bovine',
|
||||||
query: { id: String(id), caseId: String(caseId.value) }
|
query: { id: String(bovine.id), caseId: String(caseId.value) }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(caseId, loadCase, { immediate: true })
|
watch(caseId, (id) => {
|
||||||
|
if (!hasCaseId.value) {
|
||||||
|
filters.value.buildingCase = ''
|
||||||
|
buildingCase.value = null
|
||||||
|
return
|
||||||
|
}
|
||||||
|
filters.value.buildingCase = `/api/building_cases/${id}`
|
||||||
|
loadCase()
|
||||||
|
reload()
|
||||||
|
}, { immediate: true })
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Doctrine\Orm\Filter\DateFilter;
|
||||||
|
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;
|
||||||
@@ -19,6 +22,12 @@ use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
|||||||
#[ORM\Entity]
|
#[ORM\Entity]
|
||||||
#[ORM\Table(name: 'bovine')]
|
#[ORM\Table(name: 'bovine')]
|
||||||
#[ORM\UniqueConstraint(name: 'uniq_bovine_national_number', columns: ['national_number'])]
|
#[ORM\UniqueConstraint(name: 'uniq_bovine_national_number', columns: ['national_number'])]
|
||||||
|
#[ApiFilter(SearchFilter::class, properties: [
|
||||||
|
'nationalNumber' => 'ipartial',
|
||||||
|
'buildingCase' => 'exact',
|
||||||
|
'receivedWeight' => 'exact',
|
||||||
|
])]
|
||||||
|
#[ApiFilter(DateFilter::class, properties: ['arrivalDate'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new Get(
|
new Get(
|
||||||
|
|||||||
Reference in New Issue
Block a user