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

338 lines
12 KiB
Vue

<script setup>
import AlertBanner from '@/Components/Supervisor/AlertBanner.vue';
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 {
filterInputClass,
filterSelectClass,
getStatusColor,
inputClass,
linkClass,
machineStatusColors,
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, Link, useForm, usePage } from '@inertiajs/vue3';
import { ref, computed } from 'vue';
const props = defineProps({
machines: {
type: Object,
required: true,
},
establishments: {
type: Array,
default: () => [],
},
canManageMachines: {
type: Boolean,
default: false,
},
filters: {
type: Object,
default: () => ({}),
},
statusOptions: {
type: Object,
default: () => ({}),
},
typeOptions: {
type: Object,
default: () => ({}),
},
});
const page = usePage();
const editingUuid = ref(null);
const showForm = ref(false);
const deleteTarget = ref(null);
const deleteForm = useForm({});
const { localFilters, toggleSort } = useListFilters('supervisor.machines.index', {
establishment_id: props.filters.establishment_id ?? '',
search: props.filters.search ?? '',
status: props.filters.status ?? '',
type: props.filters.type ?? '',
sort: props.filters.sort ?? 'name',
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, ['last_heartbeat_at']);
const emptyForm = () => ({
establishment_id: props.establishments[0]?.id ?? '',
name: '',
type: '',
qr_code: '',
status: 'available',
});
const form = useForm(emptyForm());
const startCreate = () => {
editingUuid.value = null;
form.defaults(emptyForm());
form.reset();
showForm.value = true;
};
const startEdit = (machine) => {
editingUuid.value = machine.uuid;
form.defaults({
establishment_id: machine.establishment_id,
name: machine.name,
type: machine.type,
qr_code: machine.qr_code,
status: machine.status,
});
form.reset();
showForm.value = true;
};
const cancelForm = () => {
showForm.value = false;
editingUuid.value = null;
form.reset();
};
const submit = () => {
const payload = {
...form.data(),
qr_code: form.qr_code || null,
};
if (editingUuid.value) {
form.transform(() => payload).put(route('supervisor.machines.update', editingUuid.value), {
onSuccess: cancelForm,
});
} else {
form.transform(() => payload).post(route('supervisor.machines.store'), {
onSuccess: cancelForm,
});
}
};
const openDeleteModal = (machine) => {
deleteTarget.value = machine;
};
const closeDeleteModal = () => {
deleteTarget.value = null;
};
const confirmDelete = () => {
if (!deleteTarget.value) return;
deleteForm.delete(route('supervisor.machines.destroy', deleteTarget.value.uuid), {
onSuccess: closeDeleteModal,
});
};
const formatDate = (iso) => {
if (!iso) return '—';
return new Intl.DateTimeFormat('fr-FR', {
dateStyle: 'short',
timeStyle: 'short',
}).format(new Date(iso));
};
</script>
<template>
<Head title="Machines" />
<SupervisorLayout title="Machines">
<template #actions>
<PrimaryButton
v-if="canManageMachines && establishments.length > 0"
@click="startCreate"
>
Nouvelle machine
</PrimaryButton>
</template>
<AlertBanner v-if="page.props.errors?.delete">
{{ page.props.errors.delete }}
</AlertBanner>
<FormModal
:show="showForm"
:title="editingUuid ? 'Modifier la machine' : 'Nouvelle machine'"
:submit-label="editingUuid ? 'Enregistrer' : 'Créer'"
:processing="form.processing"
@close="cancelForm"
@submit="submit"
>
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<FormField label="Enseigne">
<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="Nom">
<input v-model="form.name" type="text" required :class="inputClass" />
</FormField>
<FormField label="Type">
<select v-model="form.type" required :class="selectClass">
<option value="" disabled>Sélectionner</option>
<option v-for="(label, value) in typeOptions" :key="value" :value="value">
{{ label }}
</option>
</select>
</FormField>
<FormField label="QR code" :hint="editingUuid ? '' : 'Généré automatiquement si vide'">
<input v-model="form.qr_code" type="text" :class="inputClass" />
</FormField>
<FormField label="Statut">
<select v-model="form.status" required :class="selectClass">
<option v-for="(label, value) in statusOptions" :key="value" :value="value">
{{ label }}
</option>
</select>
</FormField>
</div>
</FormModal>
<DataTable>
<template #head>
<tr>
<TableHeaderCell
label="Machine"
sortable
sort-key="name"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
>
<input
v-model="localFilters.search"
type="text"
placeholder="Nom ou QR…"
:class="filterInputClass"
/>
</TableHeaderCell>
<TableHeaderCell
label="Enseigne"
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="Type"
sortable
sort-key="type"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
>
<select v-model="localFilters.type" :class="filterSelectClass">
<option value="">Tous</option>
<option v-for="(label, value) in typeOptions" :key="value" :value="value">
{{ label }}
</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="Dernier signal"
sortable
sort-key="last_heartbeat_at"
:active-sort="localFilters.sort"
:sort-direction="localFilters.direction"
@sort="handleSort"
/>
<TableHeaderCell v-if="canManageMachines" label="Actions" align="right" />
</tr>
</template>
<tr v-if="machines.data.length === 0">
<td :colspan="canManageMachines ? 6 : 5" class="px-6 py-12 text-center text-sm text-slate-400">
Aucune machine trouvée.
</td>
</tr>
<tr v-for="machine in machines.data" :key="machine.uuid" :class="rowClass">
<td :class="tdClass">
<Link :href="route('supervisor.machines.show', machine.uuid)" :class="linkClass">
{{ machine.name }}
</Link>
<p class="mt-0.5 font-mono text-xs text-slate-400">{{ machine.qr_code }}</p>
</td>
<td :class="tdClass">{{ machine.establishment_name ?? '—' }}</td>
<td :class="tdClass">{{ machine.type_label }}</td>
<td :class="tdClass">
<StatusBadge
:label="machine.status_label"
:color-class="getStatusColor(machine.status, machineStatusColors)"
/>
</td>
<td :class="tdClass">{{ formatDate(machine.last_heartbeat_at) }}</td>
<td v-if="canManageMachines" :class="[tdClass, 'text-right']">
<button class="font-medium text-indigo-600 transition hover:text-indigo-800" @click="startEdit(machine)">
Modifier
</button>
<button class="ms-3 font-medium text-red-600 transition hover:text-red-800" @click="openDeleteModal(machine)">
Supprimer
</button>
</td>
</tr>
<template #footer>
<Pagination
:paginator="machines"
:per-page="localFilters.per_page"
@update:per-page="(value) => { localFilters.per_page = value; }"
/>
</template>
</DataTable>
<ConfirmDeleteModal
:show="deleteTarget !== null"
title="Supprimer la machine"
:message="deleteTarget ? `Voulez-vous supprimer « ${deleteTarget.name} » ?` : ''"
:processing="deleteForm.processing"
@close="closeDeleteModal"
@confirm="confirmDelete"
/>
</SupervisorLayout>
</template>