feat(crm): Améliore l'interface et la recherche de produits/options

Ce commit modernise l'interface utilisateur pour la recherche et la sélection de produits et d'options. Il améliore l'apparence
visuelle, l'ergonomie et la réactivité, en utilisant des composants plus modernes et des animations plus fluides. Les
fonctionnalités de recherche ont été optimisées pour une meilleure expérience utilisateur. Ajout de nouvelles classes
'SearchProductDevis' et 'SearchOptionsDevis' pour la gestion des options dans Devis.
```
This commit is contained in:
Serreau Jovann
2026-01-29 18:12:06 +01:00
parent a55dc4b49c
commit 35e24491f4
7 changed files with 389 additions and 423 deletions

View File

@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="fr" class="dark">
<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">
@@ -10,30 +10,29 @@
{% endif %}
</head>
<body class="bg-slate-50 dark:bg-[#0f172a] text-slate-900 dark:text-slate-200 antialiased overflow-hidden font-sans">
<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-white dark:bg-[#1e293b] border-r border-slate-200 dark:border-slate-800 lg:translate-x-0 transition-all duration-300 ease-in-out">
<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-100 dark:border-slate-800">
<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-slate-800 dark:text-white uppercase italic">Intranet <span class="text-blue-600 not-italic">Ludikevent</span></span>
<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-400 dark:text-slate-500 uppercase tracking-[0.2em]">Menu Principal</p>
<p class="px-4 mb-4 text-[10px] font-semibold text-slate-500 uppercase tracking-[0.2em]">Menu Principal</p>
<div class="space-y-1">
{% macro nav_link(path, label, icon_svg, current_route) %}
{% set isActive = app.request.get('_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-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-400' }}">
<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>
@@ -44,17 +43,15 @@
{{ 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_contrats'), 'Contrat de location', '<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_contrats') }}#}
{{ menu.nav_link(path('app_crm_facture'), 'Facture', '<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_facture') }}
{# {{ menu.nav_link(path('app_crm_devis'), 'Devis', '<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_devis') }}#}
{{ menu.nav_link(path('app_crm_customer'), 'Clients', '<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_clients') }}
{{ 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') }}
</div>
</div>
<div>
<p class="px-4 mb-4 text-[10px] font-semibold text-slate-400 dark:text-slate-500 uppercase tracking-[0.2em]">Configuration</p>
<p class="px-4 mb-4 text-[10px] font-semibold text-slate-500 uppercase tracking-[0.2em]">Configuration</p>
<details class="group" {{ (app.request.get('_route') matches '/^app_crm_administrateur/' or app.request.get('_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-100 dark:hover:bg-slate-800 text-slate-600 dark:text-slate-400 transition-all duration-200 cursor-pointer">
<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>
@@ -62,9 +59,9 @@
<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 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 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 hover:text-blue-600 transition-colors">Sauvegarde</a>
<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>
@@ -72,45 +69,36 @@
</aside>
{# MAIN CONTENT #}
<main class="flex-1 flex flex-col min-w-0 lg:ml-72 bg-slate-50 dark:bg-[#0f172a] h-screen overflow-y-auto custom-scrollbar relative">
<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 CORRIGÉ #}
<header class="h-20 flex items-center justify-between px-8 bg-white dark:bg-[#1e293b] border-b border-slate-200 dark:border-slate-800 sticky top-0 z-30 gap-6">
<header class="h-20 flex items-center justify-between px-8 bg-[#1e293b] border-b border-slate-800 sticky top-0 z-30 gap-6">
{# Barre de recherche parfaitement alignée #}
<div class="flex-1 max-w-2xl relative">
<form action="{{ path('app_crm_search') }}" method="GET" class="flex items-center">
{# Input avec padding droit suffisant pour le bouton #}
<input required type="text" name="q" placeholder="Recherche rapide..."
class="w-full pl-2 py-3 bg-slate-50 dark:bg-slate-900/50 border border-slate-200 dark: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-700 dark:text-slate-200">
{# Bouton ancré à droite SANS chevauchement #}
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>
{# Profil utilisateur #}
<div class="flex items-center gap-2">
{# Zone Profil cliquable #}
<a data-turbo="false" href="{{ path('app_crm_profils') }}"
class="flex items-center space-x-3 px-4 py-2 bg-slate-50 dark:bg-slate-900/50 rounded-2xl border border-slate-200 dark:border-slate-700 shrink-0 hover:bg-slate-100 dark:hover:bg-slate-800 transition-all group">
<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-slate-800 dark:text-white leading-none mb-0.5">{{ app.user.firstName }}</p>
<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>
{# Bouton Déconnexion séparé #}
<a data-turbo="false" href="{{ path('app_logout') }}"
<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">
@@ -122,10 +110,10 @@
{# CONTENT #}
<div class="p-6 md:p-10 page-transition w-full">
<div class="flex items-end justify-between mb-10 pb-8 border-b border-slate-200 dark:border-slate-800/50">
<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-slate-900 dark:text-white">
<h1 class="text-4xl font-extrabold text-white">
{% block title_header %}{{ block('title') }}{% endblock %}
</h1>
</div>
@@ -136,7 +124,7 @@
{# 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 animate-in fade-in slide-in-from-top-4 duration-500">
<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" />
@@ -150,34 +138,33 @@
</div>
</div>
{% endif %}
{# SECTION DES MESSAGES FLASH #}
{# 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') %}
{% set icon = 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" />'
: '<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" />'
%}
<div class="flex items-center p-5 backdrop-blur-xl {{ bgColor }} border {{ borderColor }} rounded-[1.5rem] shadow-xl animate-in slide-in-from-right-4 duration-500" role="alert">
<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">
{{ icon|raw }}
{% 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 == 'success' ? 'Succès' : (label == 'error' ? 'Erreur' : 'Information') }}
</p>
<p class="text-sm text-slate-400 font-medium">
{{ message }}
{{ 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-500 hover:text-slate-300 transition-colors">
<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" d="M6 18L18 6M6 6l12 12" /></svg>
<button type="button" onclick="this.parentElement.remove()" class="ml-auto text-slate-500 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 %}
@@ -186,37 +173,35 @@
<div class="w-full">
{% block body %}{% endblock %}
{# FOOTER GLASSMORPHISM #}
<footer class="relative mt-auto p-6 md:p-10">
<div class="bg-white/40 dark:bg-slate-800/40 backdrop-blur-xl border border-white/20 dark: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">
{# Copyright & Branding #}
{# 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-400 dark:text-slate-500 uppercase tracking-[0.2em]">Propulsé par</p>
<p class="text-sm font-bold text-slate-800 dark:text-white">
<p class="text-[10px] font-black text-slate-500 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>
{# Versioning & Links #}
<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-medium text-slate-500 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-300 dark:bg-slate-700 rounded-full"></span>
<span class="w-1 h-1 bg-slate-700 rounded-full"></span>
<a href="mailto:s.com@siteconseil.fr" class="text-xs font-medium text-slate-500 hover:text-blue-600 transition-colors">s.com@siteconseil.fr</a>
</div>
<p class="text-[9px] font-black text-slate-400/60 uppercase tracking-widest">
Crm Engine <span class="text-slate-500 dark:text-slate-300">1.0.0</span>
<p class="text-[9px] font-black text-slate-500 uppercase tracking-widest">
Crm Engine <span class="text-slate-300">1.0.0</span>
</p>
</div>
</div>
</div>
</footer>
</div>
</div>
</main> {# Fin du main existant #}
</main>
</div>
</body>
</html>