Intégration fonctionnalites V1

This commit is contained in:
bastien
2026-07-04 22:30:18 +02:00
parent 55a558b536
commit 10ee859602
54 changed files with 4802 additions and 1018 deletions
+148 -122
View File
@@ -1,13 +1,31 @@
<script setup>
import DataTable from '@/Components/Supervisor/DataTable.vue';
import Pagination from '@/Components/Supervisor/Pagination.vue';
import StatusBadge from '@/Components/Supervisor/StatusBadge.vue';
import TableHeaderCell from '@/Components/Supervisor/TableHeaderCell.vue';
import { useListFilters } from '@/Components/Supervisor/useListFilters.js';
import {
bookingStatusColors,
filterInputClass,
filterSelectClass,
getStatusColor,
linkClass,
rowClass,
tdClass,
} from '@/Components/Supervisor/ui.js';
import SupervisorLayout from '@/Layouts/SupervisorLayout.vue';
import { Head, Link, router } from '@inertiajs/vue3';
import { reactive, watch } from 'vue';
import { Head, Link } from '@inertiajs/vue3';
import { computed } from 'vue';
const props = defineProps({
bookings: {
type: Object,
required: true,
},
establishments: {
type: Array,
default: () => [],
},
filters: {
type: Object,
default: () => ({}),
@@ -18,17 +36,19 @@ const props = defineProps({
},
});
const localFilters = reactive({
const { localFilters, toggleSort } = useListFilters('supervisor.bookings.index', {
establishment_id: props.filters.establishment_id ?? '',
user: props.filters.user ?? '',
status: props.filters.status ?? '',
date: props.filters.date ?? '',
});
sort: props.filters.sort ?? 'slot_start',
direction: props.filters.direction ?? 'desc',
per_page: props.filters.per_page ?? 10,
}, { debounceKeys: ['user'] });
watch(localFilters, () => {
router.get(route('supervisor.bookings.index'), localFilters, {
preserveState: true,
replace: true,
});
});
const showEstablishmentFilter = computed(() => props.establishments.length > 1);
const handleSort = (key) => toggleSort(key, ['slot_start', 'booking_fee']);
const formatDate = (iso) => {
if (!iso) return '—';
@@ -40,124 +60,130 @@ const formatDate = (iso) => {
const formatCurrency = (value) =>
new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(value ?? 0);
const statusColor = (status) => {
const colors = {
pending: 'bg-gray-100 text-gray-800',
confirmed: 'bg-blue-100 text-blue-800',
expired: 'bg-orange-100 text-orange-800',
active: 'bg-green-100 text-green-800',
completed: 'bg-indigo-100 text-indigo-800',
cancelled: 'bg-yellow-100 text-yellow-800',
no_show: 'bg-red-100 text-red-800',
};
return colors[status] ?? 'bg-gray-100 text-gray-800';
};
</script>
<template>
<Head title="Réservations" />
<SupervisorLayout title="Réservations">
<div class="mb-6 flex flex-wrap gap-4 rounded-lg bg-white p-4 shadow">
<div>
<label class="block text-xs font-medium text-gray-500">Statut</label>
<select
v-model="localFilters.status"
class="mt-1 block rounded-md border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
>
<option value="">Tous</option>
<option v-for="(label, value) in statusOptions" :key="value" :value="value">
{{ label }}
</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-500">Date</label>
<input
v-model="localFilters.date"
type="date"
class="mt-1 block rounded-md border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
/>
</div>
</div>
<div class="overflow-hidden rounded-lg bg-white shadow">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Créneau</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Utilisateur</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Machine</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Statut</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Frais</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
<tr v-if="bookings.data.length === 0">
<td colspan="5" class="px-4 py-8 text-center text-sm text-gray-500">
Aucune réservation trouvée.
</td>
</tr>
<tr v-for="booking in bookings.data" :key="booking.uuid" class="hover:bg-gray-50">
<td class="whitespace-nowrap px-4 py-3 text-sm">
<div>{{ formatDate(booking.slot_start) }}</div>
<div class="text-xs text-gray-500"> {{ formatDate(booking.slot_end) }}</div>
</td>
<td class="px-4 py-3 text-sm">
<div class="font-medium text-gray-800">{{ booking.user?.name ?? '—' }}</div>
<div class="text-xs text-gray-500">{{ booking.user?.email }}</div>
</td>
<td class="px-4 py-3 text-sm">
<Link
v-if="booking.machine"
:href="route('supervisor.machines.show', booking.machine.uuid)"
class="text-indigo-600 hover:text-indigo-800"
>
{{ booking.machine.name }}
</Link>
<span v-else></span>
<div v-if="booking.machine?.establishment_name" class="text-xs text-gray-500">
{{ booking.machine.establishment_name }}
</div>
</td>
<td class="px-4 py-3">
<span
class="rounded-full px-2.5 py-0.5 text-xs font-medium"
:class="statusColor(booking.status)"
>
{{ booking.status_label }}
</span>
</td>
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-600">
{{ formatCurrency(booking.booking_fee) }}
</td>
</tr>
</tbody>
</table>
<div v-if="bookings.links.length > 3" class="flex items-center justify-between border-t px-4 py-3">
<p class="text-sm text-gray-600">
{{ bookings.from ?? 0 }}{{ bookings.to ?? 0 }} sur {{ bookings.total }}
</p>
<div class="flex gap-1">
<Link
v-for="link in bookings.links"
:key="link.label"
:href="link.url"
v-html="link.label"
class="rounded px-3 py-1 text-sm"
:class="
link.active
? 'bg-indigo-600 text-white'
: link.url
? 'text-gray-600 hover:bg-gray-100'
: 'cursor-not-allowed text-gray-300'
"
preserve-state
<DataTable>
<template #head>
<tr>
<TableHeaderCell
label="Créneau"
sortable
sort-key="slot_start"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
>
<input v-model="localFilters.date" type="date" :class="filterInputClass" />
</TableHeaderCell>
<TableHeaderCell
label="Utilisateur"
sortable
sort-key="user"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
>
<input
v-model="localFilters.user"
type="text"
placeholder="Nom ou e-mail…"
:class="filterInputClass"
/>
</TableHeaderCell>
<TableHeaderCell
label="Machine"
sortable
sort-key="machine"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
>
<select
v-if="showEstablishmentFilter"
v-model="localFilters.establishment_id"
:class="filterSelectClass"
>
<option value="">Toutes</option>
<option v-for="est in establishments" :key="est.id" :value="est.id">
{{ est.name }}
</option>
</select>
</TableHeaderCell>
<TableHeaderCell
label="Statut"
sortable
sort-key="status"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
>
<select v-model="localFilters.status" :class="filterSelectClass">
<option value="">Tous</option>
<option v-for="(label, value) in statusOptions" :key="value" :value="value">
{{ label }}
</option>
</select>
</TableHeaderCell>
<TableHeaderCell
label="Frais"
sortable
sort-key="booking_fee"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
/>
</div>
</div>
</div>
</tr>
</template>
<tr v-if="bookings.data.length === 0">
<td colspan="5" class="px-6 py-12 text-center text-sm text-slate-400">
Aucune réservation trouvée.
</td>
</tr>
<tr v-for="booking in bookings.data" :key="booking.uuid" :class="rowClass">
<td :class="tdClass">
<div class="font-medium text-slate-800">{{ formatDate(booking.slot_start) }}</div>
<div class="mt-0.5 text-xs text-slate-400"> {{ formatDate(booking.slot_end) }}</div>
</td>
<td :class="tdClass">
<div class="font-medium text-slate-800">{{ booking.user?.name ?? '—' }}</div>
<div class="text-xs text-slate-400">{{ booking.user?.email }}</div>
</td>
<td :class="tdClass">
<Link
v-if="booking.machine"
:href="route('supervisor.machines.show', booking.machine.uuid)"
:class="linkClass"
>
{{ booking.machine.name }}
</Link>
<span v-else></span>
<div v-if="booking.machine?.establishment_name" class="mt-0.5 text-xs text-slate-400">
{{ booking.machine.establishment_name }}
</div>
</td>
<td :class="tdClass">
<StatusBadge
:label="booking.status_label"
:color-class="getStatusColor(booking.status, bookingStatusColors)"
/>
</td>
<td :class="[tdClass, 'font-semibold text-slate-800']">
{{ formatCurrency(booking.booking_fee) }}
</td>
</tr>
<template #footer>
<Pagination
:paginator="bookings"
:per-page="localFilters.per_page"
@update:per-page="(value) => { localFilters.per_page = value; }"
/>
</template>
</DataTable>
</SupervisorLayout>
</template>