Files
OrderCheck/resources/js/Pages/Materiels/Show.vue
2026-06-15 08:13:42 +02:00

319 lines
18 KiB
Vue

<script setup>
import { computed } from 'vue';
import { Head, Link, router } from '@inertiajs/vue3';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
const props = defineProps({
hardware: Object,
});
// Supprimer le matériel de l'inventaire
const deleteHardware = () => {
if (confirm("Êtes-vous sûr de vouloir supprimer définitivement cet équipement de l'inventaire ? Cette action est irréversible.")) {
router.delete(route('materiels.destroy', { materiel: props.hardware.data.id }));
}
};
// Formater le type en français
const getTypeLabel = (type) => {
switch (type) {
case 'serveur': return 'Serveur';
case 'switch': return 'Switch';
case 'routeur': return 'Routeur';
case 'onduleur': return 'Onduleur';
case 'stockage': return 'Stockage (NAS/SAN)';
case 'pare-feu': return 'Pare-feu';
case 'poste_travail': return 'Poste de travail';
case 'autre': return 'Autre';
default: return type;
}
};
// Stylisation des badges de statut
const getStatusClasses = (status) => {
switch (status) {
case 'en_stock':
return 'bg-blue-100 text-blue-800 border-blue-200 dark:bg-blue-950/40 dark:text-blue-300 dark:border-blue-900';
case 'en_service':
return 'bg-emerald-100 text-emerald-800 border-emerald-200 dark:bg-emerald-950/40 dark:text-emerald-300 dark:border-emerald-900';
case 'en_panne':
return 'bg-rose-100 text-rose-800 border-rose-200 dark:bg-rose-950/40 dark:text-rose-300 dark:border-rose-900';
case 'au_rebut':
return 'bg-slate-100 text-slate-800 border-slate-200 dark:bg-slate-900/60 dark:text-slate-400 dark:border-slate-850';
default:
return 'bg-gray-100 text-gray-800 border-gray-200 dark:bg-gray-800 dark:text-gray-300';
}
};
const getStatusLabel = (status) => {
switch (status) {
case 'en_stock': return 'En stock / Rechange';
case 'en_service': return 'En service';
case 'en_panne': return 'En panne';
case 'au_rebut': return 'Au rebut';
default: return status;
}
};
// Calcul du pourcentage de garantie écoulé
const warrantyPercentage = computed(() => {
const hw = props.hardware.data;
if (!hw.purchase_date || !hw.warranty_expiration_date) {
return null;
}
const start = new Date(hw.purchase_date).getTime();
const end = new Date(hw.warranty_expiration_date).getTime();
const today = new Date().getTime();
if (today >= end) {
return 100; // Garantie entièrement expirée
}
if (today <= start) {
return 0; // Pas encore commencé
}
const total = end - start;
const elapsed = today - start;
return Math.round((elapsed / total) * 100);
});
// Calcul de l'âge de l'équipement
const ageLabel = computed(() => {
const hw = props.hardware.data;
if (!hw.purchase_date) return null;
const purchase = new Date(hw.purchase_date);
const today = new Date();
let diffYear = today.getFullYear() - purchase.getFullYear();
let diffMonth = today.getMonth() - purchase.getMonth();
if (diffMonth < 0) {
diffYear--;
diffMonth += 12;
}
if (diffYear === 0) {
return `${diffMonth} mois`;
}
return `${diffYear} an(s) et ${diffMonth} mois`;
});
</script>
<template>
<Head :title="`Équipement ${hardware.data.name}`" />
<AuthenticatedLayout>
<template #header>
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<div class="flex items-center gap-3">
<h2 class="text-xl font-bold leading-tight text-slate-800 dark:text-slate-200">
{{ hardware.data.name }}
</h2>
<span
:class="`inline-flex items-center px-2 py-0.5 rounded text-xs font-semibold border ${getStatusClasses(hardware.data.status)}`"
>
{{ getStatusLabel(hardware.data.status) }}
</span>
</div>
<div class="flex gap-2">
<Link
:href="route('materiels.index')"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-slate-700 bg-white border border-slate-300 rounded-lg hover:bg-slate-50 dark:bg-slate-900 dark:text-slate-300 dark:border-slate-850 dark:hover:bg-slate-800 transition-colors shadow-sm"
>
Retour à l'inventaire
</Link>
<Link
:href="route('materiels.edit', { materiel: hardware.data.id })"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-white bg-sky-600 rounded-lg hover:bg-sky-500 dark:bg-sky-500 dark:hover:bg-sky-400 transition-colors shadow-sm"
>
<svg class="w-4 h-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m16.862 4.487 1.687-1.688a1.875 1.875 0 1 1 2.652 2.652L6.83 18.75a4.48 4.48 0 0 1-1.897 1.13L6 18l.8-2.685a4.5 4.5 0 0 1 1.13-1.897l8.932-8.931Zm0 0L19.5 7.125M18 14v4.75A2.25 2.25 0 0 1 15.75 21H5.25A2.25 2.25 0 0 1 3 18.75V8.25A2.25 2.25 0 0 1 5.25 6H10" />
</svg>
Éditer
</Link>
<button
@click="deleteHardware"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-white bg-rose-600 rounded-lg hover:bg-rose-500 dark:bg-rose-650 dark:hover:bg-rose-550 transition-colors shadow-sm"
>
<svg class="w-4 h-4 mr-1.5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m14.74 9-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 0 1-2.244 2.077H8.084a2.25 2.25 0 0 1-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 0 0-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 0 1 3.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 0 0-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 0 0-7.5 0" />
</svg>
Supprimer
</button>
</div>
</div>
</template>
<div class="py-6">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 space-y-6">
<!-- Grille principale -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Fiche Technique (Col 1 & 2) -->
<div class="lg:col-span-2 space-y-6">
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl shadow-sm overflow-hidden">
<div class="border-b border-slate-100 dark:border-slate-850 px-6 py-4">
<h3 class="text-sm font-bold text-slate-850 dark:text-slate-200 uppercase tracking-wider">
Spécifications de l'équipement
</h3>
</div>
<dl class="grid grid-cols-1 md:grid-cols-2 divide-y divide-slate-100 dark:divide-slate-850 md:divide-y-0 text-sm">
<div class="px-6 py-4 space-y-1">
<dt class="text-xs font-semibold uppercase tracking-wider text-slate-400">Nom de l'Asset</dt>
<dd class="text-base font-bold text-slate-900 dark:text-white">{{ hardware.data.name }}</dd>
</div>
<div class="px-6 py-4 space-y-1">
<dt class="text-xs font-semibold uppercase tracking-wider text-slate-400">Catégorie</dt>
<dd class="text-slate-850 dark:text-slate-200 font-semibold">{{ getTypeLabel(hardware.data.type) }}</dd>
</div>
<div class="px-6 py-4 space-y-1 md:border-t border-slate-100 dark:border-slate-850">
<dt class="text-xs font-semibold uppercase tracking-wider text-slate-400">Constructeur / Marque</dt>
<dd class="text-slate-850 dark:text-slate-200 font-semibold">{{ hardware.data.brand }}</dd>
</div>
<div class="px-6 py-4 space-y-1 md:border-t border-slate-100 dark:border-slate-850">
<dt class="text-xs font-semibold uppercase tracking-wider text-slate-400">Modèle</dt>
<dd class="text-slate-850 dark:text-slate-200 font-medium">{{ hardware.data.model }}</dd>
</div>
<div class="px-6 py-4 space-y-1 border-t border-slate-100 dark:border-slate-850">
<dt class="text-xs font-semibold uppercase tracking-wider text-slate-400">Numéro de série physique</dt>
<dd class="text-slate-850 dark:text-slate-200 font-mono font-bold uppercase">{{ hardware.data.serial_number }}</dd>
</div>
<div class="px-6 py-4 space-y-1 border-t border-slate-100 dark:border-slate-850">
<dt class="text-xs font-semibold uppercase tracking-wider text-slate-400">IP de gestion réseau</dt>
<dd class="text-sky-600 dark:text-sky-400 font-mono font-semibold">
{{ hardware.data.ip_address || 'Non configurée' }}
</dd>
</div>
<div class="px-6 py-4 space-y-1 border-t border-slate-100 dark:border-slate-850">
<dt class="text-xs font-semibold uppercase tracking-wider text-slate-400">Emplacement géographique / Rack</dt>
<dd class="text-slate-850 dark:text-slate-200 font-semibold">{{ hardware.data.location }}</dd>
</div>
<div class="px-6 py-4 space-y-1 border-t border-slate-100 dark:border-slate-850 bg-slate-50/50 dark:bg-slate-950/20">
<dt class="text-xs font-semibold uppercase tracking-wider text-slate-400">Âge du matériel</dt>
<dd class="text-slate-800 dark:text-slate-200 font-medium">
{{ ageLabel || 'Date d\'achat non spécifiée' }}
</dd>
</div>
</dl>
</div>
<!-- Notes libres & Historique d'interventions -->
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl shadow-sm overflow-hidden">
<div class="border-b border-slate-100 dark:border-slate-850 px-6 py-4">
<h3 class="text-sm font-bold text-slate-850 dark:text-slate-200 uppercase tracking-wider">
Notes & Historique d'interventions
</h3>
</div>
<div class="p-6 text-sm text-slate-700 dark:text-slate-300 leading-relaxed whitespace-pre-line">
{{ hardware.data.notes || 'Aucun commentaire ou rapport d\'intervention sur cet équipement.' }}
</div>
</div>
</div>
<!-- Cycle de vie & Traçabilité financière (Col 3) -->
<div class="space-y-6">
<!-- Statut Garantie & Cycle -->
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl shadow-sm overflow-hidden">
<div class="border-b border-slate-100 dark:border-slate-850 px-6 py-4">
<h3 class="text-sm font-bold text-slate-850 dark:text-slate-200 uppercase tracking-wider">
Garantie & Cycle de vie
</h3>
</div>
<div class="p-6 space-y-6">
<!-- Indicateur de Garantie -->
<div class="space-y-2">
<div class="flex justify-between items-center text-xs">
<span class="font-semibold text-slate-400 uppercase">État de la Garantie</span>
<span :class="`font-bold ${hardware.data.is_under_warranty ? 'text-emerald-600 dark:text-emerald-400' : 'text-rose-600 dark:text-rose-450'}`">
{{ hardware.data.warranty_status_label }}
</span>
</div>
<!-- Barre de progression de la garantie -->
<div v-if="warrantyPercentage !== null" class="w-full bg-slate-100 dark:bg-slate-800 h-2.5 rounded-full overflow-hidden">
<div
class="h-full rounded-full transition-all duration-500"
:class="[
hardware.data.is_under_warranty
? (warrantyPercentage >= 80 ? 'bg-amber-500' : 'bg-emerald-500')
: 'bg-rose-500'
]"
:style="`width: ${warrantyPercentage}%`"
></div>
</div>
<div v-if="warrantyPercentage !== null" class="flex justify-between text-xxs text-slate-400">
<span>Achat : {{ hardware.data.purchase_date_formatted }}</span>
<span>{{ warrantyPercentage }}% écoulé</span>
<span>Fin : {{ hardware.data.warranty_expiration_date_formatted }}</span>
</div>
<div v-else class="text-xs text-slate-450 italic">
Dates de garantie non spécifiées.
</div>
</div>
<div class="border-t border-slate-100 dark:border-slate-850 pt-4 space-y-3 text-xs">
<div class="flex justify-between">
<span class="text-slate-400">Date d'achat :</span>
<span class="font-bold text-slate-800 dark:text-slate-200">{{ hardware.data.purchase_date_formatted }}</span>
</div>
<div class="flex justify-between">
<span class="text-slate-400">Mise en service :</span>
<span class="font-bold text-slate-800 dark:text-slate-200">{{ hardware.data.commissioning_date_formatted }}</span>
</div>
<div class="flex justify-between">
<span class="text-slate-400">Date fin garantie :</span>
<span class="font-bold text-slate-800 dark:text-slate-200">{{ hardware.data.warranty_expiration_date_formatted }}</span>
</div>
</div>
</div>
</div>
<!-- Traçabilité Commande -->
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl shadow-sm overflow-hidden">
<div class="border-b border-slate-100 dark:border-slate-850 px-6 py-4">
<h3 class="text-sm font-bold text-slate-850 dark:text-slate-200 uppercase tracking-wider">
Traçabilité Achat
</h3>
</div>
<div class="p-6">
<div v-if="hardware.data.order" class="space-y-3 text-sm">
<p class="text-xs text-slate-500 leading-relaxed">
Cet équipement fait partie de la commande suivante :
</p>
<div class="p-3 border border-slate-200 dark:border-slate-800 rounded-xl bg-slate-50/50 dark:bg-slate-950/20">
<Link
:href="route('commandes.show', { commande: hardware.data.order.id })"
class="block font-bold text-sky-600 dark:text-sky-400 hover:underline"
>
{{ hardware.data.order.number }}
</Link>
<span class="block text-xs text-slate-700 dark:text-slate-300 font-medium mt-1">
{{ hardware.data.order.label }}
</span>
</div>
</div>
<div v-else class="text-center py-4 text-xs text-slate-450 italic">
Aucune commande liée dans le système.
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
</template>