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

372 lines
25 KiB
Vue

<script setup>
import { Head, Link, router } from '@inertiajs/vue3';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import StatusBadge from '@/Components/StatusBadge.vue';
const props = defineProps({
order: Object,
});
// Lancer la transition de statut
const transitionTo = (status) => {
router.post(route('commandes.transition', { commande: props.order.data.id }), {
new_status: status,
}, {
preserveScroll: true,
});
};
// Supprimer la commande
const deleteOrder = () => {
if (confirm("Êtes-vous sûr de vouloir supprimer définitivement cette commande ainsi que ses pièces jointes ? Cette action est irréversible.")) {
router.delete(route('commandes.destroy', { commande: props.order.data.id }));
}
};
// Label français des statuts pour l'historique
const getStatusLabel = (status) => {
switch (status) {
case 'draft': return 'Brouillon';
case 'validated': return 'Validée';
case 'ordered': return 'Commandée';
case 'delivered': return 'Livrée';
case 'closed': return 'Clôturée';
default: return status || 'Création';
}
};
// Obtenir la couleur du point de la timeline
const getTimelineColor = (status) => {
switch (status) {
case 'draft': return 'bg-slate-400 dark:bg-slate-500';
case 'validated': return 'bg-sky-500 dark:bg-sky-400';
case 'ordered': return 'bg-amber-500 dark:bg-amber-400';
case 'delivered': return 'bg-emerald-500 dark:bg-emerald-400';
case 'closed': return 'bg-purple-500 dark:bg-purple-400';
default: return 'bg-gray-400';
}
};
</script>
<template>
<Head :title="`Commande ${order.data.number}`" />
<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">
Commande {{ order.data.number }}
</h2>
<StatusBadge :status="order.data.status" />
<span
v-if="order.data.is_overdue"
class="inline-flex items-center px-2 py-0.5 rounded text-xs font-bold bg-rose-100 text-rose-800 border border-rose-200 dark:bg-rose-950/40 dark:text-rose-300 dark:border-rose-900"
>
En Retard de Livraison
</span>
</div>
<div class="flex gap-2">
<Link
:href="route('commandes.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"
>
Retour à la liste
</Link>
<Link
v-if="order.data.can.update"
:href="route('commandes.edit', { commande: order.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
v-if="order.data.can.delete"
@click="deleteOrder"
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">
<!-- Notifications flash -->
<div v-if="$page.props.flash?.success" class="p-4 bg-emerald-50 border border-emerald-200 text-emerald-800 rounded-lg dark:bg-emerald-950/40 dark:border-emerald-900 dark:text-emerald-300 flex items-center shadow-sm">
<svg class="w-5 h-5 mr-3 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z" />
</svg>
<span>{{ $page.props.flash?.success }}</span>
</div>
<div v-if="$page.props.errors.error" class="p-4 bg-rose-50 border border-rose-200 text-rose-800 rounded-lg dark:bg-rose-950/40 dark:border-rose-900 dark:text-rose-300 flex items-center shadow-sm">
<svg class="w-5 h-5 mr-3 shrink-0" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z" />
</svg>
<span>{{ $page.props.errors.error }}</span>
</div>
<!-- Section des transitions contextuelles de statut -->
<div class="bg-gradient-to-r from-sky-50 to-indigo-50 border border-sky-100 dark:from-slate-900 dark:to-slate-900 dark:border-slate-800 rounded-xl p-6 shadow-sm flex flex-col md:flex-row md:items-center justify-between gap-4">
<div>
<h3 class="text-sm font-bold text-slate-850 dark:text-slate-200">
Actions sur le cycle de vie de la commande
</h3>
<p class="text-xs text-slate-500 dark:text-slate-400 mt-0.5">
Faites avancer cette demande à l'étape suivante en fonction de votre rôle.
</p>
</div>
<div class="flex flex-wrap gap-2">
<!-- Transition Draft -> Validated -->
<button
v-if="order.data.can_transition_to.validated"
@click="transitionTo('validated')"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-white bg-sky-600 rounded-lg hover:bg-sky-500 transition-colors shadow-sm"
>
<svg class="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
Valider la commande (Émettre BC)
</button>
<!-- Transition Validated -> Ordered -->
<button
v-if="order.data.can_transition_to.ordered"
@click="transitionTo('ordered')"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-white bg-amber-600 rounded-lg hover:bg-amber-500 transition-colors shadow-sm"
>
<svg class="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M6 12 3.269 3.125A59.769 59.769 0 0 1 21.485 12 59.768 59.768 0 0 1 3.27 20.875L5.999 12Zm0 0h7.5" />
</svg>
Marquer comme Commandée
</button>
<!-- Transition Ordered -> Delivered -->
<button
v-if="order.data.can_transition_to.delivered"
@click="transitionTo('delivered')"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-white bg-emerald-600 rounded-lg hover:bg-emerald-500 transition-colors shadow-sm"
>
<svg class="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 18.75a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m3 0h6m-9 0H3.375a1.125 1.125 0 0 1-1.125-1.125V14.25m17.25 4.5a1.5 1.5 0 0 1-3 0m3 0a1.5 1.5 0 0 0-3 0m3 0h1.125c.621 0 1.129-.504 1.129-1.125V11.25M3 14.25h16.5M5.25 14.25V4.5A2.25 2.25 0 0 1 7.5 2.25h6A2.25 2.25 0 0 1 15.75 4.5V14.25m-3-12h.008v.008H12.75V2.25Z" />
</svg>
Marquer comme Livrée (Réceptionnée)
</button>
<!-- Transition Delivered -> Closed -->
<button
v-if="order.data.can_transition_to.closed"
@click="transitionTo('closed')"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-white bg-purple-600 rounded-lg hover:bg-purple-500 transition-colors shadow-sm"
>
<svg class="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="m20.25 7.5-.625 10.632a2.25 2.25 0 0 1-2.247 2.118H6.622a2.25 2.25 0 0 1-2.247-2.118L3.75 7.5M10 11.25h4M3.375 7.5h17.25c.621 0 1.125-.504 1.125-1.125v-1.5c0-.621-.504-1.125-1.125-1.125H3.375c-.621 0-1.125.504-1.125 1.125v1.5c0 .621.504 1.125 1.125 1.125Z" />
</svg>
Clôturer & Archiver la commande
</button>
<!-- Aucune transition possible -->
<span
v-if="!order.data.can_transition_to.validated && !order.data.can_transition_to.ordered && !order.data.can_transition_to.delivered && !order.data.can_transition_to.closed"
class="text-xs font-semibold text-slate-500 bg-slate-100 dark:bg-slate-800 dark:text-slate-400 px-3 py-2 rounded-lg"
>
{{ order.data.status === 'closed' ? 'Cette commande est archivée.' : 'En attente d\'une action par un profil autorisé.' }}
</span>
</div>
</div>
<!-- Grille Principale (Fiche + Pièces Jointes / Historique) -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
<!-- Fiche Détails (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-800 dark:text-slate-200 uppercase tracking-wider">
Détails de la demande
</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">Libellé / Article</dt>
<dd class="text-base font-bold text-slate-900 dark:text-white">{{ order.data.label }}</dd>
</div>
<div class="px-6 py-4 space-y-1">
<dt class="text-xs font-semibold uppercase tracking-wider text-slate-400">Type de commande</dt>
<dd class="text-slate-800 dark:text-slate-200 font-medium">{{ order.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">Fournisseur</dt>
<dd class="text-slate-800 dark:text-slate-200 font-medium">{{ order.data.supplier }}</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">Numéro de Devis</dt>
<dd class="text-slate-800 dark:text-slate-200 font-mono">{{ order.data.quote_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">Demandeur</dt>
<dd class="text-slate-800 dark:text-slate-200 font-medium">{{ order.data.requested_by }}</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">Prescripteur / Service à l'origine</dt>
<dd class="text-slate-800 dark:text-slate-200 font-semibold">{{ order.data.prescriber || 'Non spécifié' }}</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">Livraison souhaitée</dt>
<dd class="text-slate-800 dark:text-slate-200 font-medium">{{ order.data.delivery_deadline_formatted }}</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">Montant HT</dt>
<dd class="text-slate-800 dark:text-slate-200 font-semibold">
{{ new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(order.data.amount_ht) }}
</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">
{{ order.data.exclude_vat ? 'Montant TTC (Sans TVA)' : 'Montant TTC (TVA 20%)' }}
</dt>
<dd class="text-lg font-bold text-sky-600 dark:text-sky-400">
{{ new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(order.data.amount_ttc) }}
</dd>
</div>
</dl>
</div>
<!-- Notes libres -->
<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-800 dark:text-slate-200 uppercase tracking-wider">
Notes & Commentaires
</h3>
</div>
<div class="p-6 text-sm text-slate-700 dark:text-slate-300 leading-relaxed whitespace-pre-line">
{{ order.data.notes || 'Aucun commentaire sur cette commande.' }}
</div>
</div>
<!-- Pièces Jointes -->
<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-800 dark:text-slate-200 uppercase tracking-wider">
Documents joints
</h3>
</div>
<div class="p-6">
<div v-if="order.data.attachments && order.data.attachments.length > 0" class="grid grid-cols-1 md:grid-cols-3 gap-4">
<div
v-for="file in order.data.attachments"
:key="file.id"
class="flex items-start p-3 border border-slate-200 dark:border-slate-800 hover:border-slate-300 dark:hover:border-slate-700 rounded-xl bg-slate-50/50 dark:bg-slate-950/20 hover:bg-slate-50 dark:hover:bg-slate-950/55 transition-colors"
>
<div class="p-2 bg-sky-50 dark:bg-slate-800 text-sky-600 dark:text-sky-400 rounded-lg mr-3">
<!-- Icône générique de fichier -->
<svg class="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m2.25 0H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" />
</svg>
</div>
<div class="flex-1 min-w-0">
<span class="block text-xxs font-semibold uppercase tracking-wider text-slate-400">
{{
file.file_type === 'quote' ? 'Devis' :
file.file_type === 'delivery_note' ? 'Bon de livraison' :
file.file_type === 'invoice' ? 'Facture' : file.file_type
}}
</span>
<a
:href="file.url"
target="_blank"
class="block text-sm font-bold text-slate-800 dark:text-slate-200 truncate hover:text-sky-600 dark:hover:text-sky-400 hover:underline mt-0.5"
>
{{ file.file_name }}
</a>
<span class="block text-xxs text-slate-400 mt-0.5">Ajouté le {{ file.created_at }}</span>
</div>
</div>
</div>
<div v-else class="text-center py-6 text-sm text-slate-500 dark:text-slate-400">
Aucune pièce jointe n'a été téléversée pour le moment.
</div>
</div>
</div>
</div>
<!-- Journal d'historique (Col 3) -->
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl shadow-sm overflow-hidden h-fit">
<div class="border-b border-slate-100 dark:border-slate-850 px-6 py-4">
<h3 class="text-sm font-bold text-slate-800 dark:text-slate-200 uppercase tracking-wider">
Historique des statuts
</h3>
</div>
<div class="p-6">
<ul v-if="order.data.status_logs && order.data.status_logs.length > 0" class="relative border-l border-slate-200 dark:border-slate-800 space-y-6 ml-2 pl-4">
<li
v-for="log in order.data.status_logs"
:key="log.id"
class="relative"
>
<!-- Point coloré -->
<div
:class="`absolute -left-6.5 top-1.5 h-3.5 w-3.5 rounded-full border-2 border-white dark:border-slate-900 ${getTimelineColor(log.new_status)} shadow-sm`"
></div>
<div class="space-y-1">
<div class="flex items-center justify-between gap-2">
<span class="text-sm font-bold text-slate-800 dark:text-slate-200">
{{ getStatusLabel(log.new_status) }}
</span>
<span class="text-xxs font-medium text-slate-400">
{{ log.changed_at }}
</span>
</div>
<p class="text-xs text-slate-500 dark:text-slate-400">
Auteur : <span class="font-semibold text-slate-600 dark:text-slate-300">{{ log.user.name }}</span>
<span class="text-slate-400 text-xxs font-normal">({{ log.user.role === 'chef_service' ? 'Chef de service' : 'Admin réseau' }})</span>
</p>
<p v-if="log.old_status" class="text-xxs text-slate-400">
Transition depuis : {{ getStatusLabel(log.old_status) }}
</p>
<p v-else class="text-xxs text-slate-400 font-semibold italic text-sky-600 dark:text-sky-400">
Création initiale de la demande
</p>
</div>
</li>
</ul>
<div v-else class="text-center py-6 text-sm text-slate-500 dark:text-slate-400">
Aucun journal de statut disponible.
</div>
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
</template>
<style scoped>
/* Ajustement de la position du point de timeline sur la bordure gauche */
.-left-6\.5 {
left: -23px;
}
</style>