templates/partials/_header.html.twig line 1

Open in your IDE?
  1. {% macro icon_for_type(type) %}
      {% set t = (type|default('')|upper)|replace({'-':'_',' ':'_'}) %}
      {% if t starts with 'GARAGE' %}
        fa-solid fa-car
      {% elseif t starts with 'DISTR' %}
        fa-solid fa-store
      {% elseif t starts with 'GROUPE_DISTRIBUTEUR' or t starts with 'GROUPE' or t starts with 'GROUP' %}
        fa-solid fa-object-group
      {% elseif t starts with 'DPAN' %}
        fa-solid fa-building-columns
      {% else %}
        fa-regular fa-circle-question
      {% endif %}
    {% endmacro %}
    {% from _self import icon_for_type %}
    
    <header class="header z-index-50">
      <nav class="navbar py-3 px-0 shadow-sm text-white position-relative">
        <div class="container-fluid w-100">
          <div class="navbar-holder d-flex align-items-center justify-content-between w-100">
            
            <!-- === LOGO === -->
            <div class="navbar-header">
              <a class="menu-btn active" id="toggle-btn" href="#"><span></span><span></span><span></span></a>
              <a href="{{ path('home.index') }}" class="brand-text d-none d-lg-inline-block px-3">
                <img src="/images/identite/LOGO_LOGISTIC_FULL_SERVICE_DPAN001.png" class="img-fluid" width="150">
              </a>
            </div>
    
            <!-- === MENU PRINCIPAL === -->
            <ul class="nav-menu list-unstyled d-flex flex-md-row align-items-md-center gap-3 mb-0">
    
              {% if app.user %}
                {% set active = app.session.get('active_entity') %}
                {% set userType = (app.user.userType|default('')|upper)|replace({'-':'_',' ':'_'}) %}
                {% set fallbackType = null %}
                {% set fallbackLabel = 'AUCUNE ENTITÉ' %}
    
                {# Détermine le libellé et le type pour les non-mixed #}
                {% if userType == 'MIXED' and not active %}
                  {% set fallbackLabel = 'CHOISIR UNE ENTITÉ' %}
                {% elseif userType starts with 'GARAGE' and app.user.customerUser %}
                  {% set fallbackType = 'GARAGE' %}
                  {% set fallbackLabel = app.user.customerUser.raisonSociale|upper %}
                {% elseif userType starts with 'DISTR' and app.user.distributeur %}
                  {% set fallbackType = 'DISTRIBUTEUR' %}
                  {% set fallbackLabel = app.user.distributeur.nomMagasin|upper %}
                {% elseif userType starts with 'GROUPE' and app.user.groupDistributeurId %}
                  {% set fallbackType = 'GROUPE_DISTRIBUTEUR' %}
                  {% set fallbackLabel = app.user.groupDistributeurId.nomGroupeDistributeur|upper %}
                {% elseif userType starts with 'DPAN' or is_granted('ROLE_DPAN') %}
                  {% set fallbackType = 'DPAN' %}
                  {% set fallbackLabel = 'DPAN' %}
                {% endif %}
                  {% if userType starts with 'DPAN' or is_granted('ROLE_DPAN') %}      
                    <!-- === BARRE DE RECHERCHE GLOBALE === -->
                    <li class="nav-item position-relative flex-grow-1 mx-2">
                      <div class="global-search w-100">
                        <div class="search-chip d-flex align-items-center gap-2 w-100">
                          <i class="fa-solid fa-magnifying-glass text-white-50"></i>
                          <input 
                            id="globalSearchInput"
                            type="text"
                            class="search-input flex-grow-1"
                            placeholder="Rechercher une demande, un garage, un distributeur, une plaque…"
                            autocomplete="off"
                          >
                        </div>
                      </div>
    
                      <!-- === Conteneur dropdown dynamique === -->
                      <ul id="globalSearchResults" class="dropdown-menu shadow fade search-results"></ul>
                    </li>
                  {% endif %}
    
                {# === BADGE ENTITÉ / DROPDOWN === #}
                {% if active %}
                  {% set typeKey = active.type|lower|replace({' ':'-','_':'-'}) %}
                  <li class="nav-item dropdown">
                    <a class="nav-link dropdown-toggle d-flex align-items-center gap-2 entity-chip entity-chip--type-{{ typeKey }}"
                       id="entityMenu" href="#" role="button" data-bs-toggle="dropdown" aria-expanded="false">
                      <i class="{{ icon_for_type(active.type) }} fa-fw chip-icon"></i>
                      <span class="entity-label">{{ active.label|upper }}</span>
                    </a>
                    <ul class="dropdown-menu dropdown-menu-end shadow fade" aria-labelledby="entityMenu">
                      <li>
                        <a class="dropdown-item d-flex align-items-center gap-2"
                           href="#" data-bs-toggle="modal" data-bs-target="#entityModalQuick">
                          <i class="fa-solid fa-shuffle text-primary"></i>
                          <span>Changer d’entité active</span>
                        </a>
                      </li>
                      <li><hr class="dropdown-divider my-1"></li>
                      <li>
                        <a class="dropdown-item d-flex align-items-center gap-2"
                           href="{{ path('profile.edit', { open: 'working-entity' }) }}">
                          <i class="fa-solid fa-briefcase text-secondary"></i>
                          <span>Modifier l’entité de travail</span>
                        </a>
                      </li>
                    </ul>
                  </li>
                {% else %}
                  <li class="nav-item">
                    <span class="nav-link entity-chip d-inline-flex align-items-center gap-2 pe-none
                                 {% if fallbackType %}entity-chip--type-{{ fallbackType|lower|replace({' ':'-','_':'-'}) }}{% endif %}">
                      <i class="{{ fallbackType ? icon_for_type(fallbackType) : 'fa-regular fa-circle-question' }} fa-fw chip-icon"></i>
                      <span class="entity-label">{{ fallbackLabel|upper }}</span>
                    </span>
                  </li>
                {% endif %}
    
                {# === MENU UTILISATEUR === #}
                <li class="nav-item dropdown">
                  <a class="nav-link text-white dropdown-toggle d-flex align-items-center gap-2"
                     id="user" href="#" data-bs-toggle="dropdown" aria-expanded="false">
                    <i class="fa-regular fa-user"></i>
                    <span class="user-label">{{ app.user.fullName }}</span>
                  </a>
                  <ul class="dropdown-menu dropdown-menu-end shadow fade" aria-labelledby="user">
                    {% if app.token is defined and app.token.originalToken is defined %}
                      <li>
                        <a class="dropdown-item d-flex align-items-center gap-2 text-danger"
                           href="{{ path('admin') ~ '?_switch_user=_exit' }}">
                          <i class="fa-solid fa-person-walking-dashed-line-arrow-right"></i>
                          <span>Quitter l’impersonation</span>
                        </a>
                      </li>
                      <li><hr class="dropdown-divider my-1"></li>
                    {% endif %}
                    <li>
                      <a class="dropdown-item d-flex align-items-center gap-2"
                         href="{{ path('profile.edit') }}">
                        <i class="fa-solid fa-user-gear text-primary"></i>
                        <span>Modifier les informations</span>
                      </a>
                    </li>
                    <li><hr class="dropdown-divider my-1"></li>
                    <li>
                      <a class="dropdown-item d-flex align-items-center gap-2"
                         href="{{ path('security.logout', { id: app.user.id }) }}">
                        <i class="fa-solid fa-right-from-bracket text-muted"></i>
                        <span>Déconnexion</span>
                      </a>
                    </li>
                  </ul>
                </li>
    
              {% else %}
                <li class="nav-item">
                  <a class="nav-link text-white d-flex align-items-center gap-2" href="{{ path('security.login') }}">
                    <i class="fa-solid fa-right-to-bracket"></i>
                    <span>Connexion</span>
                  </a>
                </li>
              {% endif %}
            </ul>
          </div>
        </div>
      </nav>
    </header>
    
    <script>
    document.addEventListener('DOMContentLoaded', () => {
      const input = document.getElementById('globalSearchInput');
      const resultsBox = document.getElementById('globalSearchResults');
      let debounceTimer = null;
    
      // === Gestion de l'historique local ===
      const HISTORY_KEY = 'fullservice_search_history';
      let history = JSON.parse(localStorage.getItem(HISTORY_KEY) || '[]');
    
      const saveToHistory = (term) => {
        if (!term) return;
        history = [term, ...history.filter(t => t !== term)].slice(0, 5);
        localStorage.setItem(HISTORY_KEY, JSON.stringify(history));
      };
    
      // Affiche l'historique au focus (si champ vide)
      input.addEventListener('focus', () => {
        if (history.length && input.value.trim() === '') {
          renderResults({ history });
        }
      });
    
      input.addEventListener('input', function() {
        const query = this.value.trim();
        clearTimeout(debounceTimer);
    
        if (query.length < 2) {
          resultsBox.classList.remove('show');
          return;
        }
    
        debounceTimer = setTimeout(() => {
          fetch(`/search/live?q=${encodeURIComponent(query)}`, {
            headers: { 'X-Requested-With': 'XMLHttpRequest' }
          })
            .then(res => res.json())
            .then(data => {
              saveToHistory(query);
              renderResults(data, query);
            })
            .catch(err => console.error('Erreur recherche :', err));
        }, 250);
      });
    
      // === Rendu du dropdown ===
      function renderResults(data, query = '') {
        resultsBox.innerHTML = '';
        const regex = query ? new RegExp('(' + query + ')', 'ig') : null;
    
        // --- Historique ---
        if (data.history) {
          resultsBox.innerHTML = '<div class="section-title">Dernières recherches</div>';
          data.history.forEach(term => {
            const li = document.createElement('li');
            li.innerHTML = `
              <button type="button" class="dropdown-item text-muted text-start search-history-item">
                <i class="fa-regular fa-clock me-1"></i> ${term}
              </button>`;
            li.querySelector('.search-history-item').addEventListener('click', () => {
              input.value = term;
              resultsBox.classList.remove('show');
              input.dispatchEvent(new Event('input'));
            });
            resultsBox.appendChild(li);
          });
          resultsBox.classList.add('show');
          return;
        }
    
        // --- Regroupement par section ---
        const sections = ['demandes', 'garages', 'distributeurs'];
        let totalResults = 0;
    
        sections.forEach(section => {
          const results = data[section] || [];
          if (!results.length) return;
    
          totalResults += results.length;
          const title = {
            demandes: 'Demandes',
            garages: 'Garages',
            distributeurs: 'Distributeurs'
          }[section];
    
          // Titre de section
          resultsBox.innerHTML += `<div class="section-title">${title}</div>`;
    
          // Résultats
          results.forEach(item => {
            const li = document.createElement('li');
            const text = regex ? item.title.replace(regex, '<span class="highlight">$1</span>') : item.title;
    
            // Choix de l’icône selon le type
            const icon = {
              demandes: 'fa-solid fa-file-lines text-primary',
              garages: 'fa-solid fa-car text-info',
              distributeurs: 'fa-solid fa-store text-warning'
            }[section];
    
            // Badge d’état (si fourni)
            const stateBadge = item.state
              ? `<span class="badge bg-${item.state.color} ms-auto">${item.state.label}</span>`
              : '';
    
            li.innerHTML = `
              <a href="${item.url}" class="dropdown-item d-flex justify-content-between align-items-center gap-2">
                <div class="d-flex align-items-center gap-2">
                  <i class="${icon} icon-type"></i>
                  <span class="result-text">${text}</span>
                </div>
                ${stateBadge}
              </a>`;
            resultsBox.appendChild(li);
          });
        });
    
        // Aucun résultat ?
        if (totalResults === 0) {
          resultsBox.innerHTML = `
            <div class="section-title">Aucun résultat</div>
            <li class="dropdown-item text-muted small">Aucun élément ne correspond à votre recherche.</li>`;
        }
    
        resultsBox.classList.add('show');
      }
    
      // Fermer au clic extérieur
      document.addEventListener('click', e => {
        if (!resultsBox.contains(e.target) && e.target !== input) {
          resultsBox.classList.remove('show');
        }
      });
    });
    </script>
    
    <style>
    /* === DROPDOWNS === */
    .dropdown-menu {
      font-size: 0.9rem;
      border-radius: .5rem;
      min-width: 14rem;
      margin-top: 0;
      top: 100%;
      transition: opacity .15s ease-in-out, transform .15s ease-in-out;
    }
    .dropdown-menu.show {
      opacity: 1;
      transform: translateY(2px); /* léger mouvement fluide */
    }
    .dropdown-item i {
      width: 1.2rem;
      text-align: center;
    }
    .dropdown-item:hover {
      background-color: #f1f3f5;
    }
    
    /* === BADGE ENTITÉ === */
    .entity-chip {
      color: #e9ecef;
      background: rgba(255,255,255,.06);
      border: 1px solid rgba(255,255,255,.22);
      border-radius: .5rem;
      height: 36px;
      padding: 0 .9rem;
      font-size: .9rem;
      font-weight: 500;
      letter-spacing: .3px;
      text-transform: uppercase;
      transition: all .2s ease;
    }
    .entity-chip:hover {
      background: rgba(255,255,255,.12);
      border-color: rgba(255,255,255,.35);
      text-decoration: none;
    }
    .entity-chip .chip-icon {
      opacity: .9;
      transform: translateY(1px);
      font-size: 1rem;
    }
    
    /* === ALIGNEMENT / HARMONISATION DU TEXTE === */
    .entity-label, .user-label {
      font-size: .9rem;
      font-weight: 500;
      color: #f8f9fa;
      text-transform: none;
      line-height: 1;
      display: inline-block;
    }
    
    /* === COULEURS CONTEXTUELLES === */
    .entity-chip--type-garage .chip-icon { color: #4dabf7; }
    .entity-chip--type-distributor .chip-icon { color: #ffd43b; }
    .entity-chip--type-group .chip-icon { color: #63e6be; }
    .entity-chip--type-dpan .chip-icon { color: #4CAF50; }
    
    /* === RÉPONSE RESPONSIVE === */
    @media (max-width: 991.98px) {
      .entity-label, .user-label { display: none; }
      .entity-chip { padding: 0 .5rem; height: 34px; }
    }
    
    /* === BARRE DE RECHERCHE === */
    .global-search {
      position: relative;
      min-width: 560px; /* élargie */
    }
    
    .search-chip {
      background: rgba(255,255,255,.06);
      border: 1px solid rgba(255,255,255,.22);
      border-radius: .5rem;
      padding: 0 .9rem;
      height: 38px;
      transition: all .2s ease;
    }
    
    .search-chip:hover,
    .search-chip:focus-within {
      background: rgba(255,255,255,.12);
      border-color: rgba(255,255,255,.35);
    }
    
    .search-input {
      border: none;
      background: transparent;
      color: #f8f9fa;
      font-size: .9rem;
      width: 100%;
      outline: none;
    }
    
    .search-input::placeholder {
      color: rgba(255,255,255,.4);
    }
    
    /* === MENU DES RÉSULTATS === */
    .search-results {
      position: absolute;
      top: 42px;
      left: 0;
      width: 100%;
      background: #fff;
      border-radius: .5rem;
      overflow: hidden;
      z-index: 2000;
      max-height: 350px;
      overflow-y: auto;
      display: none;
      font-size: .9rem;
      box-shadow: 0 4px 12px rgba(0,0,0,0.1);
    }
    
    .search-results.show { 
      display: block; 
    }
    
    .search-results .section-title {
      font-size: .75rem;
      text-transform: uppercase;
      color: #868e96;
      padding: .5rem .75rem .25rem;
      background: #f8f9fa;
      border-bottom: 1px solid #e9ecef;
      font-weight: 600;
      letter-spacing: .3px;
    }
    
    /* === ÉLÉMENTS DES RÉSULTATS === */
    .search-results .dropdown-item {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: .6rem;
      padding: .4rem .75rem;
      white-space: normal;
      transition: background-color .15s ease;
    }
    
    .search-results .dropdown-item:hover {
      background-color: #f8f9fa;
    }
    
    .search-results .icon-type {
      flex-shrink: 0;
      width: 1.2rem;
      text-align: center;
      opacity: .7;
    }
    
    .search-results .result-text {
      flex: 1 1 auto;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
    }
    
    .search-results .highlight {
      background-color: #fff3bf;
      padding: 0 2px;
    }
    
    /* === BADGE D'ÉTAT === */
    .search-results .badge {
      flex-shrink: 0;
      margin-left: auto;
      font-size: .7rem;
      font-weight: 500;
      border-radius: .4rem;
      padding: .3em .6em;
      line-height: 1.1;
      text-transform: none;
    }
    
    /* Réactive les couleurs Bootstrap */
    .search-results .badge.bg-success  { background-color: #198754 !important; color: #fff !important; }
    .search-results .badge.bg-danger   { background-color: #dc3545 !important; color: #fff !important; }
    .search-results .badge.bg-secondary{ background-color: #6c757d !important; color: #fff !important; }
    .search-results .badge.bg-dark     { background-color: #212529 !important; color: #fff !important; }
    .search-results .badge.bg-teal     { background-color: #20c997 !important; color: #fff !important; }
    
    /* === HISTORIQUE DE RECHERCHE === */
    .search-results .search-history-item {
      display: flex;
      align-items: center;
      gap: .5rem;
      justify-content: flex-start;
      font-size: .9rem;
      padding: .4rem .75rem;
      color: #6c757d;
      background: none;
      border: none;
      width: 100%;
      text-align: left;
    }
    
    .search-results .search-history-item i {
      opacity: .6;
      width: 1rem;
      text-align: center;
    }
    
    .search-results .search-history-item:hover {
      background-color: #f8f9fa;
      color: #212529;
    }
    
    </style>