Files
ludikevent_crm/templates/dashboard/base.twig
Serreau Jovann 1896f83107 ```
 feat(reservation/flow): Améliore le flux de réservation et ajoute des options.

Cette commit améliore le flux de réservation, ajoute une estimation des
frais de livraison et gère les options de produit et les paiements.
```
2026-02-05 08:18:29 +01:00

220 lines
17 KiB
Twig

<!DOCTYPE html>
<html lang="fr" class="dark" style="color-scheme: dark;">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Administration{% endblock %} — Intranet Ludikevent</title>
{{ vite_asset('admin.js', {}) }}
{% if app.environment != 'dev' %}
{{ pwa() }}
{% endif %}
</head>
<body class="bg-[#0f172a] text-slate-200 antialiased overflow-hidden font-sans">
<div class="flex h-screen overflow-hidden">
{# SIDEBAR #}
<aside id="sidebar" class="fixed inset-y-0 left-0 z-40 w-72 bg-[#1e293b] border-r border-slate-800 lg:translate-x-0 transition-all duration-300 ease-in-out">
<div class="flex items-center px-8 h-20 border-b border-slate-800">
<div class="flex items-center space-x-3">
<div class="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center shadow-lg shadow-blue-500/30">
<span class="text-white font-bold text-xl font-serif">L</span>
</div>
<span class="text-lg font-bold tracking-tight text-white uppercase italic">Intranet <span class="text-blue-600 not-italic">Ludikevent</span></span>
</div>
</div>
<nav class="flex flex-col p-6 space-y-8 h-[calc(100vh-80px)] overflow-y-auto custom-scrollbar">
<div>
<p class="px-4 mb-4 text-[10px] font-semibold text-slate-300 uppercase tracking-[0.2em]">Menu Principal</p>
<div class="space-y-1">
{% macro nav_link(path, label, icon_svg, current_route) %}
{% set isActive = app.current_route == current_route %}
<a data-turbo="false" href="{{ path }}" class="flex items-center space-x-3 px-4 py-3 rounded-xl transition-all duration-200 group {{ isActive ? 'bg-blue-600 text-white shadow-lg shadow-blue-500/30' : 'hover:bg-slate-800 text-slate-400' }}">
<svg class="w-5 h-5 {{ isActive ? 'text-white' : 'text-slate-400 group-hover:text-blue-500' }}" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">{{ icon_svg|raw }}</svg>
<span class="font-semibold text-sm">{{ label }}</span>
</a>
{% endmacro %}
{% import _self as menu %}
{{ menu.nav_link(path('app_crm'), 'Dashboard', '<path d="M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6"></path>', 'app_crm') }}
{{ menu.nav_link(path('app_crm_reservation'), 'Planing de réservation', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_reservation') }}
{{ menu.nav_link(path('app_crm_product'), 'Produits', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_product') }}
{{ menu.nav_link(path('app_crm_formules'), 'Formules', '<path d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"></path>', 'app_crm_formules') }}
{{ menu.nav_link(path('app_crm_facture'), 'Facture', '<path 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"></path>', 'app_crm_facture') }}
{{ menu.nav_link(path('app_crm_customer'), 'Clients', '<path d="M12 4.354a4 4 0 110 5.292M15 21H3v-1a6 6 0 0112 0v1zm0 0h6v-1a6 6 0 00-9-5.197M13 7a4 4 0 11-8 0 4 4 0 018 0z"></path>', 'app_clients') }}
{% set pendingCount = getPendingOrderSessionCount() %}
<a data-turbo="false" href="{{ path('app_crm_flow') }}" class="flex items-center justify-between px-4 py-3 rounded-xl transition-all duration-200 group {{ app.current_route == 'app_crm_flow' ? 'bg-blue-600 text-white shadow-lg shadow-blue-500/30' : 'hover:bg-slate-800 text-slate-400' }}">
<div class="flex items-center space-x-3">
<svg class="w-5 h-5 {{ app.current_route == 'app_crm_flow' ? 'text-white' : 'text-slate-400 group-hover:text-blue-500' }}" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 21a9.004 9.004 0 008.716-6.747M12 21a9.004 9.004 0 01-8.716-6.747M12 21c2.485 0 4.5-4.03 4.5-9S14.485 3 12 3m0 18c-2.485 0-4.5-4.03-4.5-9S9.515 3 12 3m0 0a8.997 8.997 0 017.843 4.582M12 3a8.997 8.997 0 00-7.843 4.582m15.686 0A11.953 11.953 0 0112 10.5c-2.998 0-5.74-1.1-7.843-2.918m15.686 0A8.959 8.959 0 0121 12c0 .778-.099 1.533-.284 2.253m0 0A17.919 17.919 0 0112 16.5c-3.162 0-6.133-.815-8.716-2.247m0 0A9.015 9.015 0 013 12c0-1.605.42-3.113 1.157-4.418" />
</svg>
<span class="font-semibold text-sm">Réservation sur internet</span>
</div>
{% if pendingCount > 0 %}
<span class="bg-red-500 text-white text-[10px] font-bold px-2 py-0.5 rounded-full">{{ pendingCount }}</span>
{% endif %}
</a>
</div>
</div>
<div>
<p class="px-4 mb-4 text-[10px] font-semibold text-slate-300 uppercase tracking-[0.2em]">Configuration</p>
<details class="group" {{ (app.current_route matches '/^app_crm_administrateur/' or app.current_route == 'app_crm_audit_logs') ? 'open' }}>
<summary class="list-none w-full flex items-center justify-between px-4 py-3 rounded-xl hover:bg-slate-800 text-slate-400 transition-all duration-200 cursor-pointer">
<div class="flex items-center space-x-3">
<svg class="w-5 h-5 text-slate-400 group-hover:text-blue-500" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37a1.724 1.724 0 002.572-1.065z"></path></svg>
<span class="font-semibold text-sm">Paramètres</span>
</div>
<svg class="w-4 h-4 transition-transform duration-300 arrow-icon" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path></svg>
</summary>
<div class="mt-2 space-y-1">
<a data-turbo="false" href="{{ path('app_crm_administrateur') }}" class="block px-12 py-2 text-sm text-slate-400 hover:text-blue-600 transition-colors">Gestion Admins</a>
<a data-turbo="false" href="{{ path('app_crm_audit_logs') }}" class="block px-12 py-2 text-sm text-slate-400 hover:text-blue-600 transition-colors">Audit Logs</a>
<a data-turbo="false" href="{{ path('app_crm_backup') }}" class="block px-12 py-2 text-sm text-slate-400 hover:text-blue-600 transition-colors">Sauvegarde</a>
</div>
</details>
</div>
</nav>
</aside>
{# MAIN CONTENT #}
<main class="flex-1 flex flex-col min-w-0 lg:ml-72 bg-[#0f172a] h-screen overflow-y-auto custom-scrollbar relative">
{# HEADER #}
<header class="h-20 flex-none flex items-center justify-between px-8 bg-[#1e293b] border-b border-slate-800 z-30 gap-6">
<div class="flex-1 max-w-2xl relative">
<form action="{{ path('app_crm_search') }}" method="GET" class="flex items-center">
<input required type="text" name="q" placeholder="Recherche rapide..."
class="w-full pl-4 py-3 bg-slate-900/50 border border-slate-700 rounded-2xl text-sm focus:ring-2 focus:ring-blue-500/20 focus:border-blue-500 transition-all outline-none text-slate-200">
<button type="submit" class="ml-2 px-5 py-2 bg-blue-600 text-white text-[10px] font-bold uppercase tracking-widest rounded-xl hover:bg-blue-700 transition-colors shadow-md">
Chercher
</button>
</form>
</div>
<div class="flex items-center gap-2">
<a data-turbo="false" href="{{ path('app_crm_profils') }}"
class="flex items-center space-x-3 px-4 py-2 bg-slate-900/50 rounded-2xl border border-slate-700 shrink-0 hover:bg-slate-800 transition-all group">
<div class="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center text-white font-bold text-xs shadow-lg shadow-blue-500/20 group-hover:scale-105 transition-transform">
{{ app.user.firstName|first|upper }}
</div>
<div class="text-left hidden sm:block">
<p class="text-xs font-bold text-white leading-none mb-0.5">{{ app.user.firstName }}</p>
<span class="text-[8px] text-blue-500 font-bold uppercase tracking-widest opacity-70 group-hover:opacity-100 transition-opacity">Mon Compte</span>
</div>
</a>
<a data-turbo="false" href="{{ path('app_logout') }}"
title="Déconnexion"
class="w-10 h-10 flex items-center justify-center bg-red-500/10 hover:bg-red-500 text-red-500 hover:text-white border border-red-500/20 rounded-xl transition-all duration-300">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M17 16l4-4m0 0l-4-4m4 4H7m6 4v1a3 3 0 01-3 3H6a3 3 0 01-3-3V7a3 3 0 013-3h4a3 3 0 013 3v1" />
</svg>
</a>
</div>
</header>
{# CONTENT #}
<div class="flex-1 overflow-y-auto custom-scrollbar flex flex-col p-6 md:p-10 page-transition w-full">
<div class="flex items-end justify-between mb-10 pb-8 border-b border-slate-800/50">
<div>
<p class="text-blue-600 font-bold text-[10px] uppercase tracking-[0.4em] mb-2">Ludikevent Intranet</p>
<h1 class="text-4xl font-extrabold text-white">
{% block title_header %}{{ block('title') }}{% endblock %}
</h1>
</div>
<div class="flex items-center space-x-3">
{% block actions %}{% endblock %}
</div>
</div>
{# MESSAGE D'ERREUR STRIPE #}
{% if syncStripe().state == false %}
<div class="mb-8 flex items-center p-6 backdrop-blur-xl bg-rose-500/5 border border-rose-500/20 rounded-[2rem] shadow-xl shadow-rose-500/5">
<div class="flex-shrink-0 w-12 h-12 rounded-2xl bg-rose-500/10 border border-rose-500/20 flex items-center justify-center text-rose-500 mr-5">
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
</svg>
</div>
<div>
<h4 class="text-[10px] font-black text-rose-500 uppercase tracking-[0.2em] mb-1">Erreur de synchronisation Stripe</h4>
<p class="text-sm text-slate-400 font-medium leading-relaxed italic">
"{{ syncStripe().message }}"
</p>
</div>
</div>
{% endif %}
{# FLASH MESSAGES #}
<div class="w-full space-y-4 mb-8">
{% for label, messages in app.flashes %}
{% for message in messages %}
{% set bgColor = label == 'success' ? 'bg-emerald-500/5' : (label == 'error' ? 'bg-rose-500/5' : 'bg-blue-500/5') %}
{% set borderColor = label == 'success' ? 'border-emerald-500/20' : (label == 'error' ? 'border-rose-500/20' : 'border-blue-500/20') %}
{% set textColor = label == 'success' ? 'text-emerald-500' : (label == 'error' ? 'text-rose-500' : 'text-blue-500') %}
<div class="flex items-center p-5 backdrop-blur-xl {{ bgColor }} border {{ borderColor }} rounded-[1.5rem] shadow-xl">
<div class="flex-shrink-0 w-10 h-10 rounded-xl {{ bgColor }} border {{ borderColor }} flex items-center justify-center {{ textColor }} mr-4">
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
{% if label == 'success' %}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
{% else %}
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
{% endif %}
</svg>
</div>
<div class="flex-1">
<p class="text-[10px] font-black {{ textColor }} uppercase tracking-[0.2em] mb-0.5">
{{ label|capitalize }}
</p>
<p class="text-sm text-slate-400 font-medium">{{ message }}</p>
</div>
<button type="button" onclick="this.parentElement.remove()" class="ml-auto text-slate-300 hover:text-slate-300">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path d="M6 18L18 6M6 6l12 12" /></svg>
</button>
</div>
{% endfor %}
{% endfor %}
</div>
<div class="w-full flex-1 flex flex-col">
{% block body %}{% endblock %}
{# FOOTER #}
<footer class="relative mt-auto p-6 md:p-10">
<div class="bg-slate-800/40 backdrop-blur-xl border border-slate-700/50 rounded-[2rem] p-6 shadow-xl">
<div class="flex flex-col md:flex-row items-center justify-between gap-4 text-center md:text-left">
<div class="space-y-1">
<p class="text-[10px] font-black text-slate-300 uppercase tracking-[0.2em]">Propulsé par</p>
<p class="text-sm font-bold text-white">
Développé par <span class="text-blue-600">SARL SITECONSEIL</span>
</p>
</div>
<div class="flex flex-col items-center md:items-end space-y-1">
<div class="flex items-center space-x-4">
<a href="https://www.siteconseil.fr" target="_blank" class="text-xs font-mediumtext-slate-300 hover:text-blue-600 transition-colors underline decoration-blue-500/30 underline-offset-4">www.siteconseil.fr</a>
<span class="w-1 h-1 bg-slate-700 rounded-full"></span>
<a href="mailto:s.com@siteconseil.fr" class="text-xs font-mediumtext-slate-300 hover:text-blue-600 transition-colors">s.com@siteconseil.fr</a>
</div>
<p class="text-[9px] font-black text-slate-300 uppercase tracking-widest">
Crm Engine <span class="text-slate-300">1.0.0</span>
</p>
</div>
</div>
</div>
</footer>
</div>
</div>
</main>
</div>
</body>
</html>