Files
Inventory_frontend/app/pages/machines/index.vue
Matthieu 7e67b124f3 feat(machine) : allow site editing on machine detail page and align card buttons
- Add site select field in MachineInfoCard (edit mode)
- Include siteId in machine PATCH payload
- Align action buttons (Modifier/Supprimer/Détails) consistently at card bottom
- Use mt-auto + flex-col to push buttons to bottom across all machine cards

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 15:09:14 +01:00

190 lines
6.3 KiB
Vue

<template>
<main class="container mx-auto px-6 py-8">
<div class="my-8">
<div class="flex justify-between items-center mb-6">
<h2 class="text-2xl font-bold">
Parc Machines
</h2>
<NuxtLink to="/machines/new" class="btn btn-primary">
<IconLucidePlus class="w-5 h-5 mr-2" aria-hidden="true" />
Ajouter une machine
</NuxtLink>
</div>
<div class="card bg-base-100 shadow-sm mb-6">
<div class="card-body">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="form-control">
<label class="label">
<span class="label-text">Site</span>
</label>
<select v-model="selectedSite" class="select select-bordered">
<option value="">
Tous les sites
</option>
<option v-for="site in sites" :key="site.id" :value="site.id">
{{ site.name }}
</option>
</select>
</div>
<div class="form-control">
<label class="label">
<span class="label-text">Recherche</span>
</label>
<input
v-model="searchQuery"
type="text"
placeholder="Rechercher par nom ou référence..."
class="input input-bordered"
>
</div>
</div>
</div>
</div>
<div v-if="loading" class="flex justify-center items-center py-12">
<span class="loading loading-spinner loading-lg" />
</div>
<div v-else-if="filteredMachines.length === 0" class="text-center py-12">
<div class="max-w-md mx-auto">
<IconLucideFactory class="w-16 h-16 mx-auto text-gray-400 mb-4" aria-hidden="true" />
<h3 class="text-lg font-medium text-gray-900 mb-2">
Aucune machine trouvée
</h3>
<p class="text-gray-500 mb-4">
Commencez par ajouter votre première machine.
</p>
<NuxtLink to="/machines/new" class="btn btn-primary">
Ajouter une machine
</NuxtLink>
</div>
</div>
<div v-else class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<div
v-for="machine in filteredMachines"
:key="machine.id"
class="card bg-base-100 shadow-sm hover:shadow-xl transition-shadow cursor-pointer"
@click="viewMachineDetails(machine)"
>
<div class="card-body flex flex-col">
<div class="flex items-center justify-between mb-2">
<h3 class="card-title text-lg">
{{ machine.name }}
</h3>
</div>
<div class="space-y-2 text-sm">
<div class="flex items-center gap-2">
<IconLucideMapPin class="w-4 h-4 text-blue-500" aria-hidden="true" />
<span class="text-gray-600">{{ machine.site?.name || 'Site inconnu' }}</span>
</div>
<div v-if="machine.reference" class="flex items-center gap-2">
<IconLucideTag class="w-4 h-4 text-orange-500" aria-hidden="true" />
<span class="text-gray-600">{{ machine.reference }}</span>
</div>
</div>
<div class="mt-auto pt-3 flex items-center justify-end gap-2">
<button v-if="canEdit" class="btn btn-ghost btn-xs" @click.stop="editMachine(machine)">
Modifier
</button>
<button v-if="canEdit" class="btn btn-ghost btn-xs text-error" @click.stop="confirmDeleteMachine(machine)">
Supprimer
</button>
<NuxtLink :to="`/machine/${machine.id}`" class="btn btn-primary btn-xs">
Détails
</NuxtLink>
</div>
</div>
</div>
</div>
</div>
</main>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { useMachines } from '~/composables/useMachines'
import { useSites } from '~/composables/useSites'
import { useToast } from '~/composables/useToast'
import { humanizeError } from '~/shared/utils/errorMessages'
import IconLucidePlus from '~icons/lucide/plus'
import IconLucideFactory from '~icons/lucide/factory'
import IconLucideMapPin from '~icons/lucide/map-pin'
import IconLucideTag from '~icons/lucide/tag'
const { canEdit } = usePermissions()
const { machines, loading, loadMachines, deleteMachine } = useMachines()
const { sites, loadSites } = useSites()
const toast = useToast()
const selectedSite = ref('')
const searchQuery = ref('')
// Enrichir les machines avec les objets site complets
const enrichedMachines = computed(() => {
return machines.value.map((machine) => {
const site = sites.value.find(s => s.id === machine.siteId)
return {
...machine,
site: site || null,
}
})
})
const filteredMachines = computed(() => {
let filtered = enrichedMachines.value
if (selectedSite.value) {
filtered = filtered.filter(machine => machine.siteId === selectedSite.value)
}
if (searchQuery.value.trim()) {
const term = searchQuery.value.trim().toLowerCase()
filtered = filtered.filter(machine =>
machine.name?.toLowerCase().includes(term)
|| machine.reference?.toLowerCase().includes(term),
)
}
return filtered
})
const viewMachineDetails = (machine) => {
navigateTo(`/machine/${machine.id}`)
}
const editMachine = (machine) => {
navigateTo(`/machine/${machine.id}?edit=true`)
}
const { confirm: confirmDialog } = useConfirm()
const confirmDeleteMachine = async (machine) => {
const { showError, showSuccess } = toast
if (await confirmDialog({ message: `Êtes-vous sûr de vouloir supprimer la machine "${machine.name}" ? Cette action est irréversible.` })) {
try {
const result = await deleteMachine(machine.id)
if (result.success) {
showSuccess(`Machine "${machine.name}" supprimée avec succès`)
} else {
showError(`Impossible de supprimer la machine : ${result.error}`)
}
} catch (error) {
showError(`Impossible de supprimer la machine : ${humanizeError(error.message)}`)
}
}
}
onMounted(async () => {
await Promise.all([
loadMachines(),
loadSites(),
])
})
</script>