Files
backend/resources/js/Pages/Supervisor/Pricing/Index.vue
T

356 lines
14 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<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 SupervisorLayout from '@/Layouts/SupervisorLayout.vue';
import { Head, useForm } from '@inertiajs/vue3';
import { computed, ref } from 'vue';
const props = defineProps({
rules: {
type: Object,
required: true,
},
establishments: {
type: Array,
default: () => [],
},
machineTypes: {
type: Object,
default: () => ({}),
},
dayTypes: {
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 ?? '',
machine_id: null,
machine_type: '',
day_type: 'all',
slot_start: '08:00',
slot_end: '22:00',
price: '',
label: '',
requires_app: false,
priority: 100,
is_active: true,
});
const form = useForm(emptyForm());
const startCreate = () => {
editingId.value = null;
form.defaults(emptyForm());
form.reset();
showForm.value = true;
};
const startEdit = (rule) => {
editingId.value = rule.id;
form.defaults({
establishment_id: rule.establishment_id,
machine_id: rule.machine_id,
machine_type: rule.machine_type ?? '',
day_type: rule.day_type,
slot_start: rule.slot_start,
slot_end: rule.slot_end,
price: rule.price,
label: rule.label ?? '',
requires_app: rule.requires_app,
priority: rule.priority,
is_active: rule.is_active,
});
form.reset();
showForm.value = true;
};
const cancelForm = () => {
showForm.value = false;
editingId.value = null;
form.reset();
};
const submit = () => {
const payload = {
...form.data(),
machine_id: form.machine_id || null,
machine_type: form.machine_type || null,
};
if (editingId.value) {
form.transform(() => payload).put(route('supervisor.pricing.update', editingId.value), {
onSuccess: cancelForm,
});
} else {
form.transform(() => payload).post(route('supervisor.pricing.store'), {
onSuccess: cancelForm,
});
}
};
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) =>
new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(value ?? 0);
</script>
<template>
<Head title="Tarifs" />
<SupervisorLayout title="Tarifs">
<template #actions>
<PrimaryButton @click="startCreate">
Nouvelle règle
</PrimaryButton>
</template>
<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>
</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>
</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>
</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 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>
</FormModal>
<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>