feat: dashboard amélioré, exports budgets, alertes expiration et correctifs
## Dashboard - Refonte complète du tableau de bord avec widgets budgets, commandes, contrats - Intégration des données d'exécution budgétaire en temps réel ## Exports & Rapports - BudgetExecutionExport : export Excel de l'exécution budgétaire - Template PDF budgets (budgets_pdf.blade.php) - Routes d'export PDF et Excel ## Alertes & Notifications - Commande CheckExpirations : détection des contrats/assets arrivant à échéance - Mail ExpiringElementsMail avec template Blade - Planification via routes/console.php ## Correctifs - CommandePolicy et ContratPolicy : ajustements des règles d'autorisation - ContratController : corrections mineures - Commande model : ajustements relations/casts - AuthenticatedLayout : refonte navigation avec icônes budgets - Assets/Form.vue : corrections formulaire - Seeder rôles/permissions mis à jour - Dépendances composer mises à jour (barryvdh/laravel-dompdf, maatwebsite/excel) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -11,13 +11,14 @@ const userInitials = computed(() => {
|
||||
?.split(' ').map(n => n[0]).join('').toUpperCase().slice(0, 2) ?? '??'
|
||||
})
|
||||
|
||||
const roleLabels = { admin: 'Administrateur', responsable: 'Responsable', acheteur: 'Acheteur', lecteur: 'Lecteur' }
|
||||
const roleLabels = { admin: 'Administrateur', responsable: 'Responsable', acheteur: 'Acheteur', lecteur: 'Lecteur', raf: 'Responsable Administratif et Financier' }
|
||||
const userRole = computed(() => {
|
||||
const role = user.value?.roles?.[0]?.name
|
||||
return roleLabels[role] ?? role ?? ''
|
||||
})
|
||||
|
||||
const isAdmin = computed(() => user.value?.roles?.some(r => r.name === 'admin'))
|
||||
const isRAF = computed(() => user.value?.roles?.some(r => r.name === 'raf'))
|
||||
const canCreate = computed(() => user.value?.roles?.some(r => ['admin', 'responsable', 'acheteur'].includes(r.name)))
|
||||
|
||||
function isActive(...names) {
|
||||
@@ -42,6 +43,8 @@ function isActive(...names) {
|
||||
|
||||
<!-- Navigation -->
|
||||
<nav class="flex-1 overflow-y-auto px-3 py-4 space-y-6">
|
||||
|
||||
|
||||
<!-- Dashboard -->
|
||||
<div>
|
||||
<Link :href="route('dashboard')"
|
||||
@@ -55,6 +58,42 @@ function isActive(...names) {
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<!-- Calendrier -->
|
||||
<div>
|
||||
<Link :href="route('calendar.index')"
|
||||
:class="['flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive('calendar') ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-800 hover:text-white']">
|
||||
<svg class="h-5 w-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Calendrier
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<!-- Budgets -->
|
||||
<div>
|
||||
<p class="mb-1 px-3 text-xs font-semibold uppercase tracking-wider text-gray-500">Budgets</p>
|
||||
<div class="space-y-1">
|
||||
<Link :href="route('budgets.index')"
|
||||
:class="['flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
route().current('budgets.index') || route().current('budgets.show') ? 'bg-indigo-600 text-white' : 'text-gray-300 hover:bg-gray-800 hover:text-white']">
|
||||
<svg class="h-5 w-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
Planification
|
||||
</Link>
|
||||
<Link :href="route('budgets.execution')"
|
||||
:class="['flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
route().current('budgets.execution') ? 'bg-indigo-600 text-white' : 'text-gray-300 hover:bg-gray-800 hover:text-white']">
|
||||
<svg class="h-5 w-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
||||
</svg>
|
||||
Exécution réelle
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Commandes -->
|
||||
<div>
|
||||
<p class="mb-1 px-3 text-xs font-semibold uppercase tracking-wider text-gray-500">Commandes</p>
|
||||
@@ -66,15 +105,9 @@ function isActive(...names) {
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M4 6h16M4 10h16M4 14h16M4 18h16" />
|
||||
</svg>
|
||||
Toutes les commandes
|
||||
</Link>
|
||||
<Link v-if="canCreate" :href="route('commandes.create')"
|
||||
class="flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium text-gray-300 hover:bg-gray-800 hover:text-white transition-colors">
|
||||
<svg class="h-5 w-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
||||
</svg>
|
||||
Nouvelle commande
|
||||
Commandes
|
||||
</Link>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -88,13 +121,22 @@ function isActive(...names) {
|
||||
<svg class="h-5 w-5 flex-shrink-0" 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>
|
||||
Tous les contrats
|
||||
Contrats
|
||||
</Link>
|
||||
<Link :href="route('licences.index')"
|
||||
:class="['flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive('licences') ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-800 hover:text-white']">
|
||||
<svg class="h-5 w-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
||||
</svg>
|
||||
Licences
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Domaines -->
|
||||
<div>
|
||||
<div v-if="!isRAF">
|
||||
<p class="mb-1 px-3 text-xs font-semibold uppercase tracking-wider text-gray-500">Infrastructures</p>
|
||||
<div class="space-y-1">
|
||||
<Link :href="route('domaines.index')"
|
||||
@@ -111,13 +153,13 @@ function isActive(...names) {
|
||||
<svg class="h-5 w-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
|
||||
</svg>
|
||||
Assets (Matériels)
|
||||
Matériels
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Catalogue -->
|
||||
<div>
|
||||
<div v-if="!isRAF">
|
||||
<p class="mb-1 px-3 text-xs font-semibold uppercase tracking-wider text-gray-500">Catalogue</p>
|
||||
<div class="space-y-1">
|
||||
<Link :href="route('fournisseurs.index')"
|
||||
@@ -172,24 +214,7 @@ function isActive(...names) {
|
||||
</svg>
|
||||
Services
|
||||
</Link>
|
||||
<Link :href="route('licences.index')"
|
||||
:class="['flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive('licences') ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-800 hover:text-white']">
|
||||
<svg class="h-5 w-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
||||
</svg>
|
||||
Licences
|
||||
</Link>
|
||||
<Link :href="route('calendar.index')"
|
||||
:class="['flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive('calendar') ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-800 hover:text-white']">
|
||||
<svg class="h-5 w-5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
Calendrier
|
||||
</Link>
|
||||
|
||||
<Link :href="route('communes.index')"
|
||||
:class="['flex items-center gap-3 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive('communes') ? 'bg-blue-600 text-white' : 'text-gray-300 hover:bg-gray-800 hover:text-white']">
|
||||
|
||||
Reference in New Issue
Block a user