Initial commit: Import existing Laravel project

This commit is contained in:
jeremy bayse
2026-06-15 08:12:33 +02:00
parent 7420d1b466
commit 030d76af53
143 changed files with 21885 additions and 1 deletions

View File

@@ -0,0 +1,368 @@
<script setup>
import { ref, reactive } from 'vue';
import { Head, Link, router } from '@inertiajs/vue3';
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue';
import StatusBadge from '@/Components/StatusBadge.vue';
const props = defineProps({
orders: Object,
filters: Object,
});
// État des filtres réactifs
const form = reactive({
search: props.filters?.search || '',
status: props.filters?.status || '',
requested_by: props.filters?.requested_by || '',
type: props.filters?.type || '',
date_start: props.filters?.date_start || '',
date_end: props.filters?.date_end || '',
});
// Appliquer les filtres
const applyFilters = () => {
router.get(route('commandes.index'), form, {
preserveState: true,
replace: true,
});
};
// Réinitialiser les filtres
const resetFilters = () => {
form.search = '';
form.status = '';
form.requested_by = '';
form.type = '';
form.date_start = '';
form.date_end = '';
applyFilters();
};
// Exporter en CSV
const exportCsv = () => {
// Génère l'URL d'export avec les filtres courants
const params = new URLSearchParams(form).toString();
window.location.href = `${route('commandes.index')}?export=1&${params}`;
};
// Types de commandes pour le filtre
const orderTypes = [
'Matériel réseau / serveur',
'Licences logicielles',
'Consommables / câblage',
'Prestations / services',
];
// Demandeur pour le filtre
const demandeurs = ['Jérémy', 'Sylvain', 'Kévin'];
</script>
<template>
<Head title="Suivi des Commandes" />
<AuthenticatedLayout>
<template #header>
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
<h2 class="text-xl font-bold leading-tight text-slate-800 dark:text-slate-200">
Suivi des Commandes
</h2>
<div class="flex gap-2">
<button
@click="exportCsv"
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 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500 dark:bg-slate-900 dark:text-slate-300 dark:border-slate-755 dark:hover:bg-slate-800 transition-colors"
>
<svg class="w-4 h-4 mr-2" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M3 16.5v2.25A2.25 2.25 0 0 0 5.25 21h13.5A2.25 2.25 0 0 0 21 18.75V16.5M16.5 12 12 16.5m0 0L7.5 12m4.5 4.5V3" />
</svg>
Exporter en CSV
</button>
<Link
:href="route('commandes.create')"
class="inline-flex items-center px-4 py-2 text-sm font-semibold text-white bg-sky-600 rounded-lg hover:bg-sky-500 shadow-sm focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-sky-500 dark:bg-sky-500 dark:hover:bg-sky-400 transition-colors"
>
<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="M12 4.5v15m7.5-7.5h-15" />
</svg>
Créer une demande
</Link>
</div>
</div>
</template>
<div class="py-6">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
<!-- Session Flash Messages -->
<div v-if="$page.props.flash?.success" class="mb-6 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>
<!-- Filtres -->
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl p-5 shadow-sm mb-6">
<h3 class="text-sm font-bold text-slate-800 dark:text-slate-200 mb-4 flex items-center uppercase tracking-wider">
<svg class="w-4 h-4 mr-2 text-slate-400" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3c2.755 0 5.455.232 8.083.678.533.09.917.556.917 1.096v1.044a2.25 2.25 0 0 1-.659 1.591l-5.432 5.432a2.25 2.25 0 0 0-.659 1.591v2.927a2.25 2.25 0 0 1-1.244 2.013L9.75 21v-6.562a2.25 2.25 0 0 0-.659-1.591L3.659 7.409A2.25 2.25 0 0 1 3 5.818V4.774c0-.54.384-1.006.917-1.096A48.32 48.32 0 0 1 12 3Z" />
</svg>
Filtres de recherche
</h3>
<div class="grid grid-cols-1 md:grid-cols-3 lg:grid-cols-4 gap-4">
<!-- Recherche textuelle -->
<div class="md:col-span-2 lg:col-span-1">
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 mb-1">Recherche</label>
<input
type="text"
v-model="form.search"
placeholder="Numéro, libellé, fournisseur..."
@keydown.enter="applyFilters"
class="w-full text-sm rounded-lg border-slate-300 shadow-sm focus:border-sky-500 focus:ring-sky-500 dark:bg-slate-950 dark:border-slate-800 dark:text-slate-200"
/>
</div>
<!-- Statut -->
<div>
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 mb-1">Statut</label>
<select
v-model="form.status"
@change="applyFilters"
class="w-full text-sm rounded-lg border-slate-300 shadow-sm focus:border-sky-500 focus:ring-sky-500 dark:bg-slate-950 dark:border-slate-800 dark:text-slate-200"
>
<option value="">Tous les statuts</option>
<option value="draft">Brouillon</option>
<option value="validated">Validée</option>
<option value="ordered">Commandée</option>
<option value="delivered">Livrée</option>
<option value="closed">Clôturée</option>
</select>
</div>
<!-- Demandeur -->
<div>
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 mb-1">Demandeur</label>
<select
v-model="form.requested_by"
@change="applyFilters"
class="w-full text-sm rounded-lg border-slate-300 shadow-sm focus:border-sky-500 focus:ring-sky-500 dark:bg-slate-950 dark:border-slate-800 dark:text-slate-200"
>
<option value="">Tous les demandeurs</option>
<option v-for="d in demandeurs" :key="d" :value="d">{{ d }}</option>
</select>
</div>
<!-- Type -->
<div>
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 mb-1">Type de commande</label>
<select
v-model="form.type"
@change="applyFilters"
class="w-full text-sm rounded-lg border-slate-300 shadow-sm focus:border-sky-500 focus:ring-sky-500 dark:bg-slate-950 dark:border-slate-800 dark:text-slate-200"
>
<option value="">Tous les types</option>
<option v-for="t in orderTypes" :key="t" :value="t">{{ t }}</option>
</select>
</div>
<!-- Période début -->
<div>
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 mb-1">Livraison souhaitée du</label>
<input
type="date"
v-model="form.date_start"
@change="applyFilters"
class="w-full text-sm rounded-lg border-slate-300 shadow-sm focus:border-sky-500 focus:ring-sky-500 dark:bg-slate-950 dark:border-slate-800 dark:text-slate-200"
/>
</div>
<!-- Période fin -->
<div>
<label class="block text-xs font-semibold text-slate-500 dark:text-slate-400 mb-1">Au</label>
<input
type="date"
v-model="form.date_end"
@change="applyFilters"
class="w-full text-sm rounded-lg border-slate-300 shadow-sm focus:border-sky-500 focus:ring-sky-500 dark:bg-slate-950 dark:border-slate-800 dark:text-slate-200"
/>
</div>
<!-- Actions filtres -->
<div class="flex items-end gap-2 md:col-span-3 lg:col-span-2">
<button
@click="applyFilters"
class="inline-flex items-center justify-center px-4 py-2 border border-transparent text-sm font-semibold rounded-lg text-white bg-slate-800 hover:bg-slate-700 dark:bg-slate-800 dark:hover:bg-slate-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 transition-colors w-full sm:w-auto"
>
Filtrer
</button>
<button
@click="resetFilters"
class="inline-flex items-center justify-center px-4 py-2 border border-slate-300 text-sm font-semibold rounded-lg text-slate-700 bg-white hover:bg-slate-50 dark:bg-slate-900 dark:text-slate-300 dark:border-slate-800 dark:hover:bg-slate-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-slate-500 transition-colors w-full sm:w-auto"
>
Réinitialiser
</button>
</div>
</div>
</div>
<!-- Tableau des commandes -->
<div class="bg-white dark:bg-slate-900 border border-slate-200 dark:border-slate-800 rounded-xl shadow-sm overflow-hidden">
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-slate-200 dark:divide-slate-800 text-left">
<thead class="bg-slate-50 dark:bg-slate-950">
<tr>
<th scope="col" class="px-6 py-4 text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400">Numéro</th>
<th scope="col" class="px-6 py-4 text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400">Libellé / Article</th>
<th scope="col" class="px-6 py-4 text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400">Type</th>
<th scope="col" class="px-6 py-4 text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400">Fournisseur</th>
<th scope="col" class="px-6 py-4 text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400">Demandeur / Service</th>
<th scope="col" class="px-6 py-4 text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400">Date souhaitée</th>
<th scope="col" class="px-6 py-4 text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400 text-right">Montant TTC</th>
<th scope="col" class="px-6 py-4 text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400">Statut</th>
<th scope="col" class="px-6 py-4 text-xs font-bold uppercase tracking-wider text-slate-500 dark:text-slate-400 text-right">Actions</th>
</tr>
</thead>
<tbody class="divide-y divide-slate-200 dark:divide-slate-850">
<tr
v-for="order in orders?.data || []"
:key="order.id"
class="hover:bg-slate-50/50 dark:hover:bg-slate-900/40 transition-colors"
>
<!-- Numéro -->
<td class="px-6 py-4 whitespace-nowrap text-sm font-semibold text-sky-600 dark:text-sky-400">
<Link :href="route('commandes.show', { commande: order.id })" class="hover:underline">
{{ order.number }}
</Link>
</td>
<!-- Libellé -->
<td class="px-6 py-4 text-sm text-slate-900 dark:text-slate-200 max-w-xs truncate">
<span class="font-medium">{{ order.label }}</span>
</td>
<!-- Type -->
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-slate-400">
{{ order.type }}
</td>
<!-- Fournisseur -->
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900 dark:text-slate-300 font-medium">
{{ order.supplier }}
</td>
<!-- Demandeur / Service -->
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900 dark:text-slate-350">
<div class="font-medium text-slate-800 dark:text-slate-200">{{ order.requested_by }}</div>
<div class="text-xs text-slate-450 dark:text-slate-400 font-semibold">{{ order.prescriber }}</div>
</td>
<!-- Date -->
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-500 dark:text-slate-400">
<div class="flex items-center gap-1.5">
<span>{{ order.delivery_deadline_formatted }}</span>
<!-- Alerte de retard -->
<span
v-if="order.is_overdue"
class="inline-flex items-center px-1.5 py-0.5 rounded text-xxs 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"
title="Date souhaitée de livraison dépassée"
>
RETARD
</span>
</div>
</td>
<!-- Montant TTC -->
<td class="px-6 py-4 whitespace-nowrap text-sm text-slate-900 dark:text-slate-100 text-right font-bold">
{{ new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(order.amount_ttc) }}
</td>
<!-- Statut -->
<td class="px-6 py-4 whitespace-nowrap text-sm">
<StatusBadge :status="order.status" />
</td>
<!-- Actions -->
<td class="px-6 py-4 whitespace-nowrap text-sm font-semibold text-right space-x-3">
<Link
:href="route('commandes.show', { commande: order.id })"
class="text-slate-600 hover:text-slate-900 dark:text-slate-400 dark:hover:text-slate-200"
>
Voir
</Link>
<Link
v-if="order.can.update"
:href="route('commandes.edit', { commande: order.id })"
class="text-sky-600 hover:text-sky-900 dark:text-sky-400 dark:hover:text-sky-300"
>
Éditer
</Link>
</td>
</tr>
<tr v-if="!orders?.data || orders.data.length === 0">
<td colspan="9" class="px-6 py-8 text-center text-sm text-slate-500 dark:text-slate-400">
Aucune commande ne correspond aux critères de recherche.
</td>
</tr>
</tbody>
</table>
</div>
<!-- Pagination -->
<div
v-if="orders.meta && orders.meta.links && orders.meta.links.length > 3"
class="bg-slate-50 dark:bg-slate-950 border-t border-slate-200 dark:border-slate-800 px-6 py-4 flex items-center justify-between"
>
<div class="flex-1 flex justify-between sm:hidden">
<Link
:href="orders.links.prev || '#'"
:disabled="!orders.links.prev"
class="relative inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-semibold rounded-md text-slate-700 bg-white hover:bg-slate-50 disabled:opacity-50"
>
Précédent
</Link>
<Link
:href="orders.links.next || '#'"
:disabled="!orders.links.next"
class="relative inline-flex items-center px-4 py-2 border border-slate-300 text-sm font-semibold rounded-md text-slate-700 bg-white hover:bg-slate-50 disabled:opacity-50"
>
Suivant
</Link>
</div>
<div class="hidden sm:flex-1 sm:flex sm:items-center sm:justify-between">
<div>
<p class="text-sm text-slate-700 dark:text-slate-400">
Affichage de la page
<span class="font-bold">{{ orders.meta.current_page }}</span>
sur
<span class="font-bold">{{ orders.meta.last_page }}</span>
</p>
</div>
<div>
<nav class="relative z-0 inline-flex rounded-md shadow-sm -space-x-px" aria-label="Pagination">
<template v-for="(link, key) in orders.meta.links" :key="key">
<div
v-if="link.url === null"
class="relative inline-flex items-center px-3 py-2 border border-slate-300 dark:border-slate-800 text-xs font-semibold text-slate-400 bg-white dark:bg-slate-900 rounded-md cursor-default select-none"
v-html="link.label"
/>
<Link
v-else
:href="link.url"
class="relative inline-flex items-center px-3 py-2 border text-xs font-semibold transition-colors"
:class="[
link.active
? 'z-10 bg-sky-50 border-sky-500 text-sky-600 dark:bg-sky-950/40 dark:border-sky-500 dark:text-sky-300'
: 'bg-white border-slate-300 text-slate-500 hover:bg-slate-50 dark:bg-slate-900 dark:border-slate-800 dark:text-slate-400 dark:hover:bg-slate-800'
]"
v-html="link.label"
/>
</template>
</nav>
</div>
</div>
</div>
</div>
</div>
</div>
</AuthenticatedLayout>
</template>