Files
dsi-commander/resources/js/Pages/Dashboard/Index.vue

213 lines
13 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout.vue'
import StatCard from '@/Components/StatCard.vue'
import StatutBadge from '@/Components/Commandes/StatutBadge.vue'
import PrioriteBadge from '@/Components/Commandes/PrioriteBadge.vue'
import { Link } from '@inertiajs/vue3'
import { Head } from '@inertiajs/vue3'
const props = defineProps({
stats: Object,
commandesRecentes: Array,
commandesEnRetard: Array,
commandesUrgentes: Array,
statsParStatut: Object,
statsParService: Array,
montantParMois: Array,
statsContrats: Object,
statsDomaines: Object,
})
function formatDate(d) {
if (!d) return '—'
return new Intl.DateTimeFormat('fr-FR').format(new Date(d))
}
function formatCurrency(v) {
if (v == null) return '—'
return new Intl.NumberFormat('fr-FR', { style: 'currency', currency: 'EUR' }).format(v)
}
</script>
<template>
<Head title="Tableau de bord" />
<AuthenticatedLayout>
<template #header>
<h1 class="text-xl font-semibold text-gray-900">Tableau de bord</h1>
</template>
<div class="space-y-6">
<!-- Stat cards -->
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
<StatCard label="Total commandes" :value="stats.total" color="blue" />
<StatCard label="En cours" :value="stats.en_cours" color="indigo" />
<StatCard label="En retard" :value="stats.en_retard" color="red" />
<StatCard label="Urgentes actives" :value="stats.commandees + stats.validees" color="amber" />
</div>
<!-- Contrats & Domaines Stats -->
<div class="grid grid-cols-1 gap-4 sm:grid-cols-2">
<!-- Contrats -->
<div class="rounded-xl bg-white p-5 shadow-sm border border-gray-100 flex items-center justify-between">
<div>
<h2 class="text-sm font-semibold uppercase tracking-wide text-gray-500 mb-2">Contrats en cours</h2>
<div class="flex gap-6">
<Link :href="route('contrats.index')" class="flex flex-col">
<span class="text-xs text-gray-400">Total</span>
<span class="font-bold text-gray-900">{{ statsContrats.total }}</span>
</Link>
<Link :href="route('contrats.index')" class="flex flex-col">
<span class="text-xs text-gray-400">À renouveler ()</span>
<span class="font-bold text-orange-600">{{ statsContrats.proches }}</span>
</Link>
<Link :href="route('contrats.index')" class="flex flex-col">
<span class="text-xs text-gray-400">Expirés ()</span>
<span class="font-bold text-red-600">{{ statsContrats.en_retard }}</span>
</Link>
</div>
</div>
<Link :href="route('contrats.index')" class="text-gray-400 hover:text-indigo-600 transition-colors">
<svg class="h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" /></svg>
</Link>
</div>
<!-- Domaines -->
<div class="rounded-xl bg-white p-5 shadow-sm border border-gray-100 flex items-center justify-between">
<div>
<h2 class="text-sm font-semibold uppercase tracking-wide text-gray-500 mb-2">Noms de domaine</h2>
<div class="flex gap-6">
<Link :href="route('domaines.index')" class="flex flex-col">
<span class="text-xs text-gray-400">Total</span>
<span class="font-bold text-gray-900">{{ statsDomaines.total }}</span>
</Link>
<Link :href="route('domaines.index')" class="flex flex-col">
<span class="text-xs text-gray-400">À renouveler ()</span>
<span class="font-bold text-orange-600">{{ statsDomaines.proches }}</span>
</Link>
<Link :href="route('domaines.index')" class="flex flex-col">
<span class="text-xs text-gray-400">Expirés ()</span>
<span class="font-bold text-red-600">{{ statsDomaines.en_retard }}</span>
</Link>
</div>
</div>
<Link :href="route('domaines.index')" class="text-gray-400 hover:text-indigo-600 transition-colors">
<svg class="h-8 w-8" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" /></svg>
</Link>
</div>
</div>
<!-- Statut breakdown -->
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
<div class="rounded-xl bg-white p-4 shadow-sm border border-gray-100">
<p class="text-xs font-medium text-gray-500 uppercase">Brouillons</p>
<p class="mt-1 text-2xl font-bold text-gray-700">{{ stats.brouillons }}</p>
</div>
<div class="rounded-xl bg-white p-4 shadow-sm border border-gray-100">
<p class="text-xs font-medium text-yellow-600 uppercase">En validation</p>
<p class="mt-1 text-2xl font-bold text-yellow-700">{{ stats.en_attente_validation }}</p>
</div>
<div class="rounded-xl bg-white p-4 shadow-sm border border-gray-100">
<p class="text-xs font-medium text-blue-600 uppercase">Validées</p>
<p class="mt-1 text-2xl font-bold text-blue-700">{{ stats.validees }}</p>
</div>
<div class="rounded-xl bg-white p-4 shadow-sm border border-gray-100">
<p class="text-xs font-medium text-indigo-600 uppercase">Commandées</p>
<p class="mt-1 text-2xl font-bold text-indigo-700">{{ stats.commandees }}</p>
</div>
</div>
<!-- Alertes retard + Services -->
<div class="grid gap-6 lg:grid-cols-2">
<!-- En retard -->
<div class="rounded-xl bg-white shadow-sm border border-gray-100">
<div class="flex items-center justify-between border-b border-gray-100 px-5 py-4">
<h2 class="font-semibold text-gray-900">Commandes en retard</h2>
<span class="rounded-full bg-red-100 px-2.5 py-1 text-xs font-medium text-red-700">{{ stats.en_retard }}</span>
</div>
<div class="divide-y divide-gray-50">
<div v-for="cmd in commandesEnRetard" :key="cmd.id"
class="flex items-center justify-between px-5 py-3 hover:bg-gray-50 transition-colors">
<div class="min-w-0">
<Link :href="route('commandes.show', cmd.id)"
class="truncate text-sm font-medium text-blue-600 hover:underline">
{{ cmd.numero_commande }}
</Link>
<p class="truncate text-xs text-gray-500">{{ cmd.objet }}</p>
</div>
<div class="ml-4 flex-shrink-0 text-right">
<p class="text-xs text-red-600 font-medium">{{ formatDate(cmd.date_souhaitee) }}</p>
<p class="text-xs text-gray-400">{{ cmd.service?.nom }}</p>
</div>
</div>
<p v-if="!commandesEnRetard.length" class="px-5 py-6 text-center text-sm text-gray-400">
Aucune commande en retard.
</p>
</div>
</div>
<!-- Par service -->
<div class="rounded-xl bg-white shadow-sm border border-gray-100">
<div class="border-b border-gray-100 px-5 py-4">
<h2 class="font-semibold text-gray-900">Par service</h2>
</div>
<div class="divide-y divide-gray-50">
<div v-for="service in statsParService" :key="service.id" class="px-5 py-4">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<div class="h-3 w-3 rounded-full" :style="{ backgroundColor: service.couleur || '#6B7280' }" />
<span class="text-sm font-medium text-gray-900">{{ service.nom }}</span>
</div>
<div class="text-right">
<p class="text-sm font-bold text-gray-900">{{ service.commandes_count }}</p>
<p class="text-xs text-gray-500">{{ service.commandes_en_cours_count }} en cours</p>
</div>
</div>
<div class="mt-2 h-1.5 rounded-full bg-gray-100 overflow-hidden">
<div class="h-full rounded-full transition-all"
:style="{ width: stats.total ? (service.commandes_count / stats.total * 100) + '%' : '0%', backgroundColor: service.couleur || '#6B7280' }" />
</div>
</div>
</div>
</div>
</div>
<!-- Commandes récentes -->
<div class="rounded-xl bg-white shadow-sm border border-gray-100">
<div class="flex items-center justify-between border-b border-gray-100 px-5 py-4">
<h2 class="font-semibold text-gray-900">Commandes récentes</h2>
<Link :href="route('commandes.index')" class="text-sm text-blue-600 hover:underline">Voir tout</Link>
</div>
<div class="overflow-x-auto">
<table class="min-w-full divide-y divide-gray-100 text-sm">
<thead class="bg-gray-50">
<tr>
<th class="px-5 py-3 text-left text-xs font-medium text-gray-500 uppercase">N°</th>
<th class="px-5 py-3 text-left text-xs font-medium text-gray-500 uppercase">Objet</th>
<th class="px-5 py-3 text-left text-xs font-medium text-gray-500 uppercase">Service</th>
<th class="px-5 py-3 text-left text-xs font-medium text-gray-500 uppercase">Statut</th>
<th class="px-5 py-3 text-left text-xs font-medium text-gray-500 uppercase">Priorité</th>
<th class="px-5 py-3 text-right text-xs font-medium text-gray-500 uppercase">Montant TTC</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-50">
<tr v-for="cmd in commandesRecentes" :key="cmd.id" class="hover:bg-gray-50 transition-colors">
<td class="px-5 py-3 font-medium">
<Link :href="route('commandes.show', cmd.id)" class="text-blue-600 hover:underline">
{{ cmd.numero_commande }}
</Link>
</td>
<td class="px-5 py-3 max-w-xs truncate text-gray-700">{{ cmd.objet }}</td>
<td class="px-5 py-3 text-gray-500">{{ cmd.service?.nom }}</td>
<td class="px-5 py-3"><StatutBadge :statut="cmd.statut" /></td>
<td class="px-5 py-3"><PrioriteBadge :priorite="cmd.priorite" /></td>
<td class="px-5 py-3 text-right font-medium text-gray-900">{{ formatCurrency(cmd.montant_ttc) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</AuthenticatedLayout>
</template>