189 lines
7.4 KiB
Vue
189 lines
7.4 KiB
Vue
<script setup>
|
||
import SupervisorLayout from '@/Layouts/SupervisorLayout.vue';
|
||
import { Head, Link, router } from '@inertiajs/vue3';
|
||
import { reactive, watch } from 'vue';
|
||
|
||
const props = defineProps({
|
||
machines: {
|
||
type: Object,
|
||
required: true,
|
||
},
|
||
filters: {
|
||
type: Object,
|
||
default: () => ({}),
|
||
},
|
||
statusOptions: {
|
||
type: Object,
|
||
default: () => ({}),
|
||
},
|
||
typeOptions: {
|
||
type: Object,
|
||
default: () => ({}),
|
||
},
|
||
});
|
||
|
||
const localFilters = reactive({
|
||
search: props.filters.search ?? '',
|
||
status: props.filters.status ?? '',
|
||
type: props.filters.type ?? '',
|
||
});
|
||
|
||
let debounceTimer = null;
|
||
|
||
watch(localFilters, () => {
|
||
clearTimeout(debounceTimer);
|
||
debounceTimer = setTimeout(() => {
|
||
router.get(route('supervisor.machines.index'), localFilters, {
|
||
preserveState: true,
|
||
replace: true,
|
||
});
|
||
}, 300);
|
||
});
|
||
|
||
const statusColor = (status) => {
|
||
const colors = {
|
||
available: 'bg-green-100 text-green-800',
|
||
reserved: 'bg-yellow-100 text-yellow-800',
|
||
running: 'bg-blue-100 text-blue-800',
|
||
maintenance: 'bg-orange-100 text-orange-800',
|
||
offline: 'bg-gray-100 text-gray-800',
|
||
error: 'bg-red-100 text-red-800',
|
||
};
|
||
return colors[status] ?? 'bg-gray-100 text-gray-800';
|
||
};
|
||
|
||
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">
|
||
<!-- Filters -->
|
||
<div class="mb-6 flex flex-wrap gap-4 rounded-lg bg-white p-4 shadow">
|
||
<div class="min-w-[200px] flex-1">
|
||
<label class="block text-xs font-medium text-gray-500">Recherche</label>
|
||
<input
|
||
v-model="localFilters.search"
|
||
type="text"
|
||
placeholder="Nom ou QR code…"
|
||
class="mt-1 block w-full rounded-md border-gray-300 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label class="block text-xs font-medium text-gray-500">Statut</label>
|
||
<select
|
||
v-model="localFilters.status"
|
||
class="mt-1 block w-full 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">Type</label>
|
||
<select
|
||
v-model="localFilters.type"
|
||
class="mt-1 block w-full 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 typeOptions" :key="value" :value="value">
|
||
{{ label }}
|
||
</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Table -->
|
||
<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 tracking-wider text-gray-500">
|
||
Machine
|
||
</th>
|
||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||
Établissement
|
||
</th>
|
||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||
Type
|
||
</th>
|
||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||
Statut
|
||
</th>
|
||
<th class="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider text-gray-500">
|
||
Dernier signal
|
||
</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-gray-100 bg-white">
|
||
<tr v-if="machines.data.length === 0">
|
||
<td colspan="5" class="px-4 py-8 text-center text-sm text-gray-500">
|
||
Aucune machine trouvée.
|
||
</td>
|
||
</tr>
|
||
<tr v-for="machine in machines.data" :key="machine.uuid" class="hover:bg-gray-50">
|
||
<td class="whitespace-nowrap px-4 py-3">
|
||
<Link
|
||
:href="route('supervisor.machines.show', machine.uuid)"
|
||
class="font-medium text-indigo-600 hover:text-indigo-800"
|
||
>
|
||
{{ machine.name }}
|
||
</Link>
|
||
</td>
|
||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-600">
|
||
{{ machine.establishment_name ?? '—' }}
|
||
</td>
|
||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-600">
|
||
{{ machine.type_label }}
|
||
</td>
|
||
<td class="whitespace-nowrap px-4 py-3">
|
||
<span
|
||
class="rounded-full px-2.5 py-0.5 text-xs font-medium"
|
||
:class="statusColor(machine.status)"
|
||
>
|
||
{{ machine.status_label }}
|
||
</span>
|
||
</td>
|
||
<td class="whitespace-nowrap px-4 py-3 text-sm text-gray-500">
|
||
{{ formatDate(machine.last_heartbeat_at) }}
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
|
||
<!-- Pagination -->
|
||
<div v-if="machines.links.length > 3" class="flex items-center justify-between border-t px-4 py-3">
|
||
<p class="text-sm text-gray-600">
|
||
{{ machines.from ?? 0 }}–{{ machines.to ?? 0 }} sur {{ machines.total }}
|
||
</p>
|
||
<div class="flex gap-1">
|
||
<Link
|
||
v-for="link in machines.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
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</SupervisorLayout>
|
||
</template>
|