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
+230 -117
View File
@@ -1,14 +1,31 @@
<script setup>
import DataTable from '@/Components/Supervisor/DataTable.vue';
import FormField from '@/Components/Supervisor/FormField.vue';
import FormModal from '@/Components/Supervisor/FormModal.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 {
activeStatusColors,
filterInputClass,
filterSelectClass,
getStatusColor,
inputClass,
rowClass,
selectClass,
tdClass,
} from '@/Components/Supervisor/ui.js';
import ConfirmDeleteModal from '@/Components/ConfirmDeleteModal.vue';
import PrimaryButton from '@/Components/PrimaryButton.vue';
import SecondaryButton from '@/Components/SecondaryButton.vue';
import SupervisorLayout from '@/Layouts/SupervisorLayout.vue';
import { Head, useForm } from '@inertiajs/vue3';
import { ref } from 'vue';
import { computed, ref } from 'vue';
const props = defineProps({
rules: {
type: Array,
default: () => [],
type: Object,
required: true,
},
establishments: {
type: Array,
@@ -22,10 +39,30 @@ const props = defineProps({
type: Object,
default: () => ({}),
},
filters: {
type: Object,
default: () => ({}),
},
});
const { localFilters, toggleSort } = useListFilters('supervisor.pricing.index', {
establishment_id: props.filters.establishment_id ?? '',
machine_type: props.filters.machine_type ?? '',
day_type: props.filters.day_type ?? '',
is_active: props.filters.is_active ?? '',
search: props.filters.search ?? '',
sort: props.filters.sort ?? 'priority',
direction: props.filters.direction ?? 'asc',
per_page: props.filters.per_page ?? 10,
}, { debounceKeys: ['search'] });
const showEstablishmentFilter = computed(() => props.establishments.length > 1);
const handleSort = (key) => toggleSort(key);
const editingId = ref(null);
const showForm = ref(false);
const deleteTarget = ref(null);
const deleteForm = useForm({});
const emptyForm = () => ({
establishment_id: props.establishments[0]?.id ?? '',
@@ -93,10 +130,20 @@ const submit = () => {
}
};
const destroy = (id) => {
if (confirm('Supprimer cette règle tarifaire ?')) {
useForm({}).delete(route('supervisor.pricing.destroy', id));
}
const openDeleteModal = (rule) => {
deleteTarget.value = rule;
};
const closeDeleteModal = () => {
deleteTarget.value = null;
};
const confirmDelete = () => {
if (!deleteTarget.value) return;
deleteForm.delete(route('supervisor.pricing.destroy', deleteTarget.value.id), {
onSuccess: closeDeleteModal,
});
};
const formatCurrency = (value) =>
@@ -107,136 +154,202 @@ const formatCurrency = (value) =>
<Head title="Tarifs" />
<SupervisorLayout title="Tarifs">
<div class="mb-4 flex justify-end">
<PrimaryButton v-if="!showForm" @click="startCreate">
<template #actions>
<PrimaryButton @click="startCreate">
Nouvelle règle
</PrimaryButton>
</div>
</template>
<!-- Inline form -->
<div v-if="showForm" class="mb-6 rounded-lg border border-indigo-200 bg-white p-6 shadow">
<h2 class="mb-4 text-lg font-semibold text-gray-800">
{{ editingId ? 'Modifier la règle' : 'Nouvelle règle tarifaire' }}
</h2>
<form @submit.prevent="submit" class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<div>
<label class="block text-xs font-medium text-gray-500">Établissement</label>
<select
v-model="form.establishment_id"
required
class="mt-1 block w-full rounded-md border-gray-300 text-sm shadow-sm"
>
<FormModal
:show="showForm"
:title="editingId ? 'Modifier la règle' : 'Nouvelle règle tarifaire'"
:submit-label="editingId ? 'Enregistrer' : 'Créer'"
:processing="form.processing"
max-width="3xl"
@close="cancelForm"
@submit="submit"
>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3">
<FormField label="Établissement">
<select v-model="form.establishment_id" required :class="selectClass">
<option v-for="est in establishments" :key="est.id" :value="est.id">
{{ est.name }}
</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-500">Type de machine</label>
<select v-model="form.machine_type" class="mt-1 block w-full rounded-md border-gray-300 text-sm shadow-sm">
</FormField>
<FormField label="Type de machine">
<select v-model="form.machine_type" :class="selectClass">
<option value="">Tous types</option>
<option v-for="(label, value) in machineTypes" :key="value" :value="value">
{{ label }}
</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-500">Jour</label>
<select v-model="form.day_type" required class="mt-1 block w-full rounded-md border-gray-300 text-sm shadow-sm">
</FormField>
<FormField label="Jour">
<select v-model="form.day_type" required :class="selectClass">
<option v-for="(label, value) in dayTypes" :key="value" :value="value">
{{ label }}
</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-500">Début créneau</label>
<input v-model="form.slot_start" type="time" required class="mt-1 block w-full rounded-md border-gray-300 text-sm shadow-sm" />
</div>
<div>
<label class="block text-xs font-medium text-gray-500">Fin créneau</label>
<input v-model="form.slot_end" type="time" required class="mt-1 block w-full rounded-md border-gray-300 text-sm shadow-sm" />
</div>
<div>
<label class="block text-xs font-medium text-gray-500">Prix ()</label>
<input v-model="form.price" type="number" step="0.01" min="0" required class="mt-1 block w-full rounded-md border-gray-300 text-sm shadow-sm" />
</div>
<div>
<label class="block text-xs font-medium text-gray-500">Libellé</label>
<input v-model="form.label" type="text" class="mt-1 block w-full rounded-md border-gray-300 text-sm shadow-sm" />
</div>
<div>
<label class="block text-xs font-medium text-gray-500">Priorité</label>
<input v-model="form.priority" type="number" min="0" class="mt-1 block w-full rounded-md border-gray-300 text-sm shadow-sm" />
</div>
<div class="flex items-end gap-4">
<label class="flex items-center gap-2 text-sm">
<input v-model="form.requires_app" type="checkbox" class="rounded border-gray-300 text-indigo-600" />
</FormField>
<FormField label="Début créneau">
<input v-model="form.slot_start" type="time" required :class="inputClass" />
</FormField>
<FormField label="Fin créneau">
<input v-model="form.slot_end" type="time" required :class="inputClass" />
</FormField>
<FormField label="Prix (€)">
<input v-model="form.price" type="number" step="0.01" min="0" required :class="inputClass" />
</FormField>
<FormField label="Libellé">
<input v-model="form.label" type="text" :class="inputClass" />
</FormField>
<FormField label="Priorité">
<input v-model="form.priority" type="number" min="0" :class="inputClass" />
</FormField>
<div class="flex items-end gap-4 sm:col-span-2 lg:col-span-3">
<label class="flex items-center gap-2 text-sm text-slate-700">
<input v-model="form.requires_app" type="checkbox" class="rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20" />
App requise
</label>
<label class="flex items-center gap-2 text-sm">
<input v-model="form.is_active" type="checkbox" class="rounded border-gray-300 text-indigo-600" />
<label class="flex items-center gap-2 text-sm text-slate-700">
<input v-model="form.is_active" type="checkbox" class="rounded border-slate-300 text-indigo-600 focus:ring-indigo-500/20" />
Active
</label>
</div>
<div class="flex gap-2 sm:col-span-2 lg:col-span-3">
<PrimaryButton type="submit" :disabled="form.processing">
{{ editingId ? 'Enregistrer' : 'Créer' }}
</PrimaryButton>
<SecondaryButton type="button" @click="cancelForm">Annuler</SecondaryButton>
</div>
</form>
</div>
</div>
</FormModal>
<!-- Rules list -->
<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">Établissement</th>
<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">Type</th>
<th class="px-4 py-3 text-left text-xs font-medium uppercase text-gray-500">Prix</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-right text-xs font-medium uppercase text-gray-500">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
<tr v-if="rules.length === 0">
<td colspan="6" class="px-4 py-8 text-center text-sm text-gray-500">
Aucune règle tarifaire configurée.
</td>
</tr>
<tr v-for="rule in rules" :key="rule.id" class="hover:bg-gray-50">
<td class="px-4 py-3 text-sm text-gray-800">{{ rule.establishment_name }}</td>
<td class="px-4 py-3 text-sm text-gray-600">
{{ rule.slot_start }} {{ rule.slot_end }}
<span class="text-xs text-gray-400">({{ rule.day_type_label }})</span>
</td>
<td class="px-4 py-3 text-sm text-gray-600">
{{ rule.machine_type_label ?? 'Tous' }}
</td>
<td class="px-4 py-3 text-sm font-medium text-gray-800">
{{ formatCurrency(rule.price) }}
</td>
<td class="px-4 py-3">
<span
class="rounded-full px-2.5 py-0.5 text-xs font-medium"
:class="rule.is_active ? 'bg-green-100 text-green-800' : 'bg-gray-100 text-gray-600'"
>
{{ rule.is_active ? 'Active' : 'Inactive' }}
</span>
</td>
<td class="px-4 py-3 text-right text-sm">
<button @click="startEdit(rule)" class="text-indigo-600 hover:text-indigo-800">
Modifier
</button>
<button @click="destroy(rule.id)" class="ms-3 text-red-600 hover:text-red-800">
Supprimer
</button>
</td>
</tr>
</tbody>
</table>
</div>
<DataTable>
<template #head>
<tr>
<TableHeaderCell
label="Établissement"
sortable
sort-key="establishment"
: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="Créneau"
sortable
sort-key="slot_start"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
>
<select v-model="localFilters.day_type" :class="filterSelectClass">
<option value="">Tous</option>
<option v-for="(label, value) in dayTypes" :key="value" :value="value">
{{ label }}
</option>
</select>
</TableHeaderCell>
<TableHeaderCell
label="Type"
sortable
sort-key="machine_type"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
>
<select v-model="localFilters.machine_type" :class="filterSelectClass">
<option value="">Tous</option>
<option v-for="(label, value) in machineTypes" :key="value" :value="value">
{{ label }}
</option>
</select>
</TableHeaderCell>
<TableHeaderCell
label="Prix"
sortable
sort-key="price"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
>
<input
v-model="localFilters.search"
type="text"
placeholder="Libellé…"
:class="filterInputClass"
/>
</TableHeaderCell>
<TableHeaderCell
label="Statut"
sortable
sort-key="is_active"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
>
<select v-model="localFilters.is_active" :class="filterSelectClass">
<option value="">Tous</option>
<option value="1">Active</option>
<option value="0">Inactive</option>
</select>
</TableHeaderCell>
<TableHeaderCell label="Actions" align="right" />
</tr>
</template>
<tr v-if="rules.data.length === 0">
<td colspan="6" class="px-6 py-12 text-center text-sm text-slate-400">
Aucune règle tarifaire trouvée.
</td>
</tr>
<tr v-for="rule in rules.data" :key="rule.id" :class="rowClass">
<td :class="[tdClass, 'font-medium text-slate-800']">{{ rule.establishment_name }}</td>
<td :class="tdClass">
{{ rule.slot_start }} {{ rule.slot_end }}
<span class="text-xs text-slate-400">({{ rule.day_type_label }})</span>
</td>
<td :class="tdClass">{{ rule.machine_type_label ?? 'Tous' }}</td>
<td :class="[tdClass, 'font-semibold text-slate-800']">{{ formatCurrency(rule.price) }}</td>
<td :class="tdClass">
<StatusBadge
:label="rule.is_active ? 'Active' : 'Inactive'"
:color-class="getStatusColor(rule.is_active ? 'active' : 'inactive', activeStatusColors)"
/>
</td>
<td :class="[tdClass, 'text-right']">
<button class="font-medium text-indigo-600 transition hover:text-indigo-800" @click="startEdit(rule)">
Modifier
</button>
<button class="ms-3 font-medium text-red-600 transition hover:text-red-800" @click="openDeleteModal(rule)">
Supprimer
</button>
</td>
</tr>
<template #footer>
<Pagination
:paginator="rules"
:per-page="localFilters.per_page"
@update:per-page="(value) => { localFilters.per_page = value; }"
/>
</template>
</DataTable>
<ConfirmDeleteModal
:show="deleteTarget !== null"
title="Supprimer la règle tarifaire"
:message="deleteTarget ? `Voulez-vous supprimer la règle « ${deleteTarget.label || deleteTarget.establishment_name} » ?` : ''"
:processing="deleteForm.processing"
@close="closeDeleteModal"
@confirm="confirmDelete"
/>
</SupervisorLayout>
</template>