Compare commits

..

6 Commits

Author SHA1 Message Date
tristan f945ae72a7 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>
2026-04-22 15:12:58 +02:00
tristan 86fd7e6b04 feat : migration de la page expéditions en attente sur UiDataTable
- Filtres sur Date, Client, Type d'expédition, Transporteur, Immatriculation
- Slot header-actions avec input disabled pour cohérence visuelle
- Delete via reload() au lieu de filtrage local

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 14:52:44 +02:00
tristan ee2fb0fe8f feat : migration de la page expéditions finies sur UiDataTable
- Filtres SearchFilter et DateFilter ajoutés sur l'entité Shipment
- Colonnes typées, filtre date single input, placeholder disabled sur adresse et poids

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 14:50:16 +02:00
tristan 0008631099 feat : migration de la page réceptions en attente sur UiDataTable
- Filtres recherche case-insensitive (ipartial)
- Ajout carrier.name et licensePlate aux SearchFilter
- Slot header-actions pour customiser la colonne actions

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 14:47:13 +02:00
tristan a6be7fb6a4 feat : filtres tableau (texte, date, select) et largeurs de colonnes
- DateFilter + SearchFilter sur Reception (identificationNumber, supplier.name, receptionType.id, receptionDate)
- Prop width sur les colonnes du UiDataTable
- Prop size compact sur UiTextInput/UiSelect/UiDateInput
- Option placeholder re-sélectionnable sur UiSelect (clear du filtre)
- Loader inline quand no items, overlay quand refetch

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 11:17:10 +02:00
tristan ac3be7c94b feat : UiDataTable avec pagination server-side et loader
- Composant UiDataTable (pagination, slots header/cell/actions/empty)
- Composable useDataTableServerState (token anti-race, debounce filtres)
- Migration de la page réceptions finies sur le nouveau pattern
- pagination_client_items_per_page activé globalement

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 16:49:43 +02:00
3 changed files with 38 additions and 99 deletions
+1 -1
View File
@@ -1,2 +1,2 @@
parameters: parameters:
app.version: '0.0.86' app.version: '0.0.85'
+37 -89
View File
@@ -33,51 +33,42 @@
</NuxtLink> </NuxtLink>
</div> </div>
<div class="mt-8 mb-16"> <div class="mt-8 border border-slate-200 mb-16">
<UiDataTable <div
v-model:page="page" class="grid grid-cols-3 gap-4 bg-slate-100 px-4 py-3 text-sm font-semibold uppercase tracking-wide"
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"
> >
<template #header-nationalNumber> <div>Numéro national</div>
<UiTextInput <div>Poids à l'arrivée (kg)</div>
v-model="filters.nationalNumber" <div>Date d'arrivée</div>
placeholder="Numéro national" </div>
size="compact" <template v-if="bovines.length > 0">
/> <div
</template> v-for="bovine in bovines"
<template #header-receivedWeight> :key="bovine.id"
<UiTextInput class="grid grid-cols-3 gap-4 px-4 py-3 text-sm border-t border-slate-200"
v-model="filters.receivedWeight" :class="auth.isAdmin ? 'cursor-pointer hover:bg-slate-50' : ''"
placeholder="Poids (kg)" :role="auth.isAdmin ? 'button' : undefined"
size="compact" :tabindex="auth.isAdmin ? 0 : undefined"
/> @click="goToBovine(bovine.id)"
</template> @keydown.enter="goToBovine(bovine.id)"
<template #header-arrivalDate> >
<UiDateInput v-model="arrivalDateFilter" size="compact" /> <div>{{ bovine.nationalNumber }}</div>
</template> <div>{{ bovine.receivedWeight ?? '—' }}</div>
<template #cell-arrivalDate="{ item }"> <div>{{ formatDate(bovine.arrivalDate) }}</div>
{{ formatDate(item.arrivalDate) }} </div>
</template> </template>
<template #cell-receivedWeight="{ item }"> <div
{{ item.receivedWeight ?? '—' }} v-else
</template> class="px-4 py-3 text-sm border-t border-slate-200 text-slate-500"
</UiDataTable> >
Aucun bovin dans cette case.
</div>
</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()
@@ -89,44 +80,7 @@ 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 ''
@@ -160,27 +114,21 @@ const loadCase = async () => {
} }
const printCaseReport = async () => { const printCaseReport = async () => {
if (!hasCaseId.value) return if (!hasCaseId.value) {
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 = (bovine: BovineData) => { const goToBovine = (id: number) => {
if (!auth.isAdmin) return if (!auth.isAdmin) return
router.push({ router.push({
path: '/infrastructure/bovine', path: '/infrastructure/bovine',
query: { id: String(bovine.id), caseId: String(caseId.value) } query: { id: String(id), caseId: String(caseId.value) }
}) })
} }
watch(caseId, (id) => { watch(caseId, loadCase, { immediate: true })
if (!hasCaseId.value) {
filters.value.buildingCase = ''
buildingCase.value = null
return
}
filters.value.buildingCase = `/api/building_cases/${id}`
loadCase()
reload()
}, { immediate: true })
</script> </script>
-9
View File
@@ -4,9 +4,6 @@ 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;
@@ -22,12 +19,6 @@ 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(