templates/partenaires/index.html.twig line 1

  1. {% extends 'base.html.twig' %}
  2. {% block style %}Partenaires de santé | CAREN{% endblock %}
  3. {% block body %}
  4. <style>
  5.   /* ── Hero image ── */
  6.   .ps-hero-img {
  7.     width: 100%; height: 40vh; object-fit: cover; display: block;
  8.   }
  9.   .ps-hero-wrap {
  10.     position: relative; overflow: hidden;
  11.   }
  12.   .ps-hero-overlay {
  13.     position: absolute; inset: 0;
  14.     display: flex; flex-direction: column;
  15.     align-items: center; justify-content: center;
  16.     text-align: center; padding: 1rem;
  17.   }
  18.   .ps-hero-overlay h1 {
  19.     font-family: 'Poppins', sans-serif; font-weight: 700;
  20.     font-size: 2rem; color: #fff; margin-bottom: .4rem;
  21.   }
  22.   .ps-hero-overlay p {
  23.     color: rgba(255,255,255,.78); font-size: .9rem; margin-bottom: 1.25rem;
  24.   }
  25.   /* ── Bouton recherche hero ── */
  26.   .btn-search-toggle {
  27.     display: inline-flex; align-items: center; gap: .5rem;
  28.     padding: .55rem 1.6rem; border-radius: 50px;
  29.     background: #fff; color: #2a1266;
  30.     font-weight: 600; font-size: .85rem; border: none;
  31.     box-shadow: 0 4px 20px rgba(0,0,0,.18);
  32.     transition: transform .2s, box-shadow .2s;
  33.     cursor: pointer;
  34.   }
  35.   .btn-search-toggle:hover {
  36.     transform: translateY(-2px);
  37.     box-shadow: 0 8px 28px rgba(0,0,0,.22);
  38.   }
  39.   .btn-search-toggle .bi { font-size: 1rem; }
  40.   /* ── Search bar inline dans hero ── */
  41.   .hero-search-box {
  42.     display: none; width: 100%; max-width: 520px;
  43.     margin-top: .75rem; animation: fadeSlideDown .25s ease;
  44.   }
  45.   .hero-search-box.open { display: flex; }
  46.   @keyframes fadeSlideDown {
  47.     from { opacity: 0; transform: translateY(-8px); }
  48.     to   { opacity: 1; transform: translateY(0); }
  49.   }
  50.   .hero-search-box .form-control {
  51.     border-radius: 12px 0 0 12px; border: none;
  52.     padding: .6rem 1rem; font-size: .88rem;
  53.     box-shadow: 0 4px 20px rgba(0,0,0,.15);
  54.   }
  55.   .hero-search-box .form-control:focus { box-shadow: none; outline: none; }
  56.   .hero-search-box .btn-go {
  57.     border-radius: 0 12px 12px 0; background: #2a1266;
  58.     border: none; color: #fff; padding: .6rem 1.1rem;
  59.     display: flex; align-items: center; gap: .35rem;
  60.     font-size: .85rem; cursor: pointer;
  61.     box-shadow: 0 4px 20px rgba(0,0,0,.15);
  62.     transition: background .2s;
  63.   }
  64.   .hero-search-box .btn-go:hover { background: #1e0d4e; }
  65.   /* ── Tabs ── */
  66.   .ps-tab-bar {
  67.     background: #fff;
  68.     border-bottom: 2px solid #f0edf8;
  69.     padding: 0 1rem;
  70.     overflow-x: auto;
  71.     -webkit-overflow-scrolling: touch;
  72.     scrollbar-width: none;
  73.   }
  74.   .ps-tab-bar::-webkit-scrollbar { display: none; }
  75.   .ps-tab-bar .nav { flex-wrap: nowrap; min-width: max-content; }
  76.   .ps-tab-bar .nav-link {
  77.     border: none; border-radius: 0;
  78.     padding: .85rem clamp(.5rem, 2vw, 1.3rem);
  79.     font-family: 'Poppins', sans-serif;
  80.     font-size: clamp(.65rem, 1.5vw, .78rem);
  81.     font-weight: 600; letter-spacing: .04em; text-transform: uppercase;
  82.     color: #a098be; position: relative; white-space: nowrap;
  83.     display: flex; align-items: center; gap: .4rem;
  84.     transition: color .2s; background: none;
  85.   }
  86.   .ps-tab-bar .nav-link::after {
  87.     content: ''; position: absolute; bottom: -2px; left: 0; right: 0;
  88.     height: 2.5px; background: #2a1266; border-radius: 2px;
  89.     transform: scaleX(0); transition: transform .25s;
  90.   }
  91.   .ps-tab-bar .nav-link.active { color: #2a1266; }
  92.   .ps-tab-bar .nav-link.active::after { transform: scaleX(1); }
  93.   .ps-tab-bar .nav-link:hover { color: #2a1266; background: none; }
  94.   .ps-tab-bar .nav-link .bi { font-size: clamp(.85rem, 2vw, 1.05rem); flex-shrink: 0; }
  95.   .ps-tab-bar .nav-link .tab-label { overflow: hidden; text-overflow: ellipsis; max-width: 18ch; }
  96.   .tab-count {
  97.     background: #ede9f8; color: #6a5aaa;
  98.     font-size: .6rem; font-weight: 600;
  99.     padding: 1px 6px; border-radius: 20px; flex-shrink: 0;
  100.   }
  101.   .ps-tab-bar .nav-link.active .tab-count { background: #2a1266; color: #fff; }
  102.   /* Très petits écrans : icône seule, libellé masqué */
  103.   @media (max-width: 300px) {
  104.     .ps-tab-bar .nav-link .tab-label { display: none; }
  105.     .ps-tab-bar .nav-link { padding: .75rem .9rem; }
  106.     .tab-count { display: none; }
  107.   }
  108.   /* ── Content ── */
  109.   .ps-content { background: #f8f7fc; padding: 2rem 0 4rem; min-height: 55vh; }
  110.   /* ── Cards ── */
  111.   .ps-card {
  112.     border: 1.5px solid #ede9f8; border-radius: 14px;
  113.     background: #fff; height: 100%; overflow: hidden;
  114.     transition: transform .2s, box-shadow .2s, border-color .2s;
  115.   }
  116.   .ps-card:hover {
  117.     transform: translateY(-4px);
  118.     box-shadow: 0 14px 36px rgba(42,18,102,.11), 0 2px 6px rgba(42,18,102,.06);
  119.     border-color: #c8bfee;
  120.   }
  121.   .ps-card .card-top-bar {
  122.     height: 3px;
  123.     background: linear-gradient(90deg, #2a1266 0%, #7c5cbf 100%);
  124.   }
  125.   .ps-card .card-body { padding: 1.1rem 1.2rem .85rem; }
  126.   .ps-card .card-icon {
  127.     width: 36px; height: 36px; border-radius: 14px;
  128.     background: rgba(42,18,102,.06);
  129.     display: flex; align-items: center; justify-content: center; flex-shrink: 0;
  130.     padding: 6px;
  131.   }
  132.   .ps-card .card-icon img {
  133.     width: 100%; height: 100%; object-fit: contain;
  134.   }
  135.   .ps-card .card-name {
  136.     font-family: 'Poppins', sans-serif; font-weight: 700;
  137.     font-size: .9rem; color: #0d0820; line-height: 1.3;
  138.   }
  139.   .ps-card .card-addr {
  140.     font-size: .75rem; color: #9896b8; margin-top: 2px;
  141.     display: flex; align-items: center; gap: .25rem;
  142.   }
  143.   .ps-card .info-list {
  144.     margin-top: .6rem; padding-top: .6rem;
  145.     border-top: 1px solid #f0edf8;
  146.   }
  147.   .ps-card .info-row {
  148.     display: flex; align-items: center; gap: .45rem;
  149.     font-size: .79rem; color: #6a6890; padding: .12rem 0;
  150.   }
  151.   .ps-card .info-row .bi { font-size: .85rem; color: #2a1266; flex-shrink: 0; }
  152.   .ps-card .info-row a { color: #2a1266; text-decoration: none; font-weight: 500; }
  153.   .ps-card .info-row a:hover { text-decoration: underline; }
  154.   .ps-card .card-footer-btns {
  155.     padding: .7rem 1.2rem; border-top: 1px solid #f0edf8;
  156.     background: #faf9fe; display: flex; gap: .45rem;
  157.   }
  158.   .ps-card .card-footer-btns .btn-action {
  159.     flex: 1; font-size: .75rem; font-weight: 500;
  160.     border-radius: 8px; padding: .38rem .5rem;
  161.     display: flex; align-items: center; justify-content: center; gap: .3rem;
  162.     border: 1.5px solid #e0daf0; color: #2a1266; background: #fff;
  163.     text-decoration: none; transition: background .18s, border-color .18s;
  164.   }
  165.   .ps-card .card-footer-btns .btn-action:hover {
  166.     background: rgba(42,18,102,.07); border-color: #b9b0e0;
  167.   }
  168.   .ps-card .card-footer-btns .btn-action .bi { font-size: .88rem; }
  169.   /* ── Empty ── */
  170.   .ps-empty { text-align: center; padding: 3.5rem 1rem; color: #b0adcc; }
  171.   .ps-empty .bi { font-size: 3rem; display: block; margin-bottom: .75rem; color: #ddd9ef; }
  172.   /* ── Pagination ── */
  173.   .ps-pag-bar {
  174.     margin-top: 1.75rem; display: flex;
  175.     justify-content: space-between; align-items: center;
  176.     flex-wrap: wrap; gap: .5rem;
  177.   }
  178.   .ps-pag-bar .pg-info { font-size: .8rem; color: #9896b8; }
  179.   .ps-pag-bar .pagination { margin: 0; }
  180.   .ps-pag-bar .page-link {
  181.     color: #2a1266; border-color: #e0daf0;
  182.     font-size: .82rem; padding: .3rem .65rem;
  183.     border-radius: 8px !important; margin: 0 2px;
  184.   }
  185.   .ps-pag-bar .page-item.active .page-link {
  186.     background: #2a1266; border-color: #2a1266; color: #fff;
  187.   }
  188.   .ps-pag-bar .page-link:hover:not(.active) { background: rgba(42,18,102,.07); }
  189.   .ps-pag-bar .page-item.disabled .page-link { opacity: .4; }
  190. </style>
  191. {# ── Hero ── #}
  192. <div class="ps-hero-wrap wow fadeIn" data-wow-delay="0.1s">
  193.   <img class="ps-hero-img" src="{{ asset('assets/img/IMAGE4.jpg') }}" alt="Partenaires de santé" />
  194.   <div class="ps-hero-overlay">
  195.     <h1><i class="bi bi-heart-pulse-fill me-2"></i>Partenaires de santé</h1>
  196.     <p>Pharmacies, cliniques et établissements conventionnés avec CAREN</p>
  197.     {# Bouton qui révèle la recherche #}
  198.     <button class="btn-search-toggle" id="heroSearchToggle" onclick="toggleHeroSearch()">
  199.       <i class="bi bi-search"></i> Rechercher un partenaire
  200.     </button>
  201.     <div class="hero-search-box" id="heroSearchBox">
  202.       <input type="text" id="heroSearchInput" class="form-control"
  203.              placeholder="Nom ou adresse du partenaire…"
  204.              oninput="applyHeroSearch()" onkeydown="if(event.key==='Escape') closeHeroSearch()">
  205.       <button class="btn-go" onclick="applyHeroSearch()">
  206.         <i class="bi bi-search"></i> Rechercher
  207.       </button>
  208.     </div>
  209.   </div>
  210. </div>
  211. {# ── Tabs bar ── #}
  212. {% if partenaires_by_type is not empty %}
  213. <div class="ps-tab-bar shadow-sm">
  214.   <div class="container">
  215.     <ul class="nav" id="psTabs">
  216.       {% for typeLibelle, partenaires in partenaires_by_type %}
  217.       {% set tid = loop.index %}
  218.       <li class="nav-item">
  219.         <button class="nav-link {% if loop.first %}active{% endif %}"
  220.                 data-tid="{{ tid }}"
  221.                 data-bs-toggle="tab"
  222.                 data-bs-target="#panel{{ tid }}"
  223.                 type="button">
  224.           {% if typeLibelle|lower == 'pharmacie' %}
  225.             <i class="bi bi-capsule"></i>
  226.           {% elseif typeLibelle|lower in ['clinique','hôpital','hopital','polyclinique'] %}
  227.             <i class="bi bi-hospital"></i>
  228.           {% elseif typeLibelle|lower in ['laboratoire','labo'] %}
  229.             <i class="bi bi-eyedropper"></i>
  230.           {% elseif typeLibelle|lower in ['dentiste','cabinet dentaire'] %}
  231.             <i class="bi bi-emoji-smile"></i>
  232.           {% elseif typeLibelle|lower in ['opticien','optique'] %}
  233.             <i class="bi bi-eyeglasses"></i>
  234.           {% else %}
  235.             <i class="bi bi-building-plus"></i>
  236.           {% endif %}
  237.           <span class="tab-label">{{ typeLibelle }}</span>
  238.           <span class="tab-count">{{ partenaires|length }}</span>
  239.         </button>
  240.       </li>
  241.       {% endfor %}
  242.     </ul>
  243.   </div>
  244. </div>
  245. {% endif %}
  246. {# ── Content ── #}
  247. <div class="ps-content">
  248.   <div class="container">
  249.     {% if partenaires_by_type is empty %}
  250.     <div class="ps-empty">
  251.       <i class="bi bi-building-slash"></i>
  252.       <p class="mb-0">Aucun partenaire de santé enregistré pour le moment.</p>
  253.     </div>
  254.     {% else %}
  255.     <div class="tab-content" id="psTabContent">
  256.       {% for typeLibelle, partenaires in partenaires_by_type %}
  257.       {% set tid = loop.index %}
  258.       {% set tlower = typeLibelle|lower %}
  259.       {% if tlower == 'pharmacie' %}
  260.         {% set icon_file = 'pharmacie1.png' %}
  261.       {% elseif tlower in ['clinique','polyclinique'] %}
  262.         {% set icon_file = 'clinique1.png' %}
  263.       {% elseif tlower in ['hôpital','hopital','hopitaux'] %}
  264.         {% set icon_file = 'hopital1.png' %}
  265.       {% else %}
  266.         {% set icon_file = 'cabinet-medical1.png' %}
  267.       {% endif %}
  268.       <div class="tab-pane fade {% if loop.first %}show active{% endif %}"
  269.            id="panel{{ tid }}" role="tabpanel">
  270.         <div class="row g-3" id="grid{{ tid }}">
  271.           {% for p in partenaires %}
  272.           <div class="col-sm-6 col-lg-4 col-xl-3 ps-item-{{ tid }}"
  273.                data-nom="{{ p.nom|default('')|lower }}"
  274.                data-adr="{{ p.adresse|default('')|lower }}"
  275.                style="display:none;">
  276.             <div class="ps-card">
  277.               <div class="card-top-bar"></div>
  278.               <div class="card-body">
  279.                 <div class="d-flex align-items-start gap-3">
  280.                   <div class="card-icon">
  281.                     <img src="{{ asset('assets/media/partenaire_sante/' ~ icon_file) }}"
  282.                          alt="{{ typeLibelle }}">
  283.                   </div>
  284.                   <div class="flex-grow-1 min-w-0">
  285.                     <div class="card-name">{{ p.nom }}</div>
  286.                     <div class="card-addr">
  287.                       <i class="bi bi-geo-alt" style="font-size:.72rem;"></i>
  288.                       {{ p.adresse ?: '—' }}
  289.                     </div>
  290.                   </div>
  291.                 </div>
  292.                 {% if p.telephone or p.email or p.responsable %}
  293.                 <div class="info-list">
  294.                   {% if p.telephone %}
  295.                   <div class="info-row">
  296.                     <i class="bi bi-telephone-fill"></i>
  297.                     <a href="tel:{{ p.telephone }}">{{ p.telephone }}</a>
  298.                   </div>
  299.                   {% endif %}
  300.                   {% if p.email %}
  301.                   <div class="info-row">
  302.                     <i class="bi bi-envelope-fill"></i>
  303.                     <span>{{ p.email }}</span>
  304.                   </div>
  305.                   {% endif %}
  306.                   {% if p.responsable %}
  307.                   <div class="info-row">
  308.                     <i class="bi bi-person-fill"></i>
  309.                     <span>{{ p.responsable }}</span>
  310.                   </div>
  311.                   {% endif %}
  312.                 </div>
  313.                 {% endif %}
  314.               </div>
  315.               {% if p.telephone or (p.latitude and p.longitude) %}
  316.               <div class="card-footer-btns">
  317.                 {% if p.telephone %}
  318.                 <a href="tel:{{ p.telephone }}" class="btn-action">
  319.                   <i class="bi bi-telephone"></i> Appeler
  320.                 </a>
  321.                 {% endif %}
  322.                 {% if p.latitude and p.longitude %}
  323.                 <a href="https://maps.google.com/?q={{ p.latitude }},{{ p.longitude }}"
  324.                    target="_blank" rel="noopener" class="btn-action">
  325.                   <i class="bi bi-geo-alt-fill"></i> Carte
  326.                 </a>
  327.                 {% endif %}
  328.               </div>
  329.               {% endif %}
  330.             </div>
  331.           </div>
  332.           {% else %}
  333.           <div class="col-12">
  334.             <div class="ps-empty">
  335.               <i class="bi bi-building-slash"></i>
  336.               <p>Aucun partenaire enregistré pour ce type.</p>
  337.             </div>
  338.           </div>
  339.           {% endfor %}
  340.         </div>
  341.         <div class="ps-pag-bar" id="pgWrap{{ tid }}">
  342.           <span class="pg-info" id="pgInfo{{ tid }}"></span>
  343.           <ul class="pagination" id="pg{{ tid }}"></ul>
  344.         </div>
  345.       </div>
  346.       {% endfor %}
  347.     </div>
  348.     {% endif %}
  349.   </div>
  350. </div>
  351. {% endblock %}
  352. {% block script %}
  353. <script>
  354. (function () {
  355.   var PER = 12;
  356.   var tabIds = Array.from(document.querySelectorAll('[data-tid]')).map(function (el) {
  357.     return parseInt(el.dataset.tid);
  358.   });
  359.   var pages = {};
  360.   tabIds.forEach(function (id) { pages[id] = 1; });
  361.   /* ── Recherche hero ── */
  362.   window.toggleHeroSearch = function () {
  363.     var box = document.getElementById('heroSearchBox');
  364.     var toggle = document.getElementById('heroSearchToggle');
  365.     var isOpen = box.classList.contains('open');
  366.     if (isOpen) {
  367.       closeHeroSearch();
  368.     } else {
  369.       box.classList.add('open');
  370.       toggle.innerHTML = '<i class="bi bi-x-lg"></i> Fermer';
  371.       document.getElementById('heroSearchInput').focus();
  372.     }
  373.   };
  374.   window.closeHeroSearch = function () {
  375.     var box = document.getElementById('heroSearchBox');
  376.     var toggle = document.getElementById('heroSearchToggle');
  377.     box.classList.remove('open');
  378.     toggle.innerHTML = '<i class="bi bi-search"></i> Rechercher un partenaire';
  379.     document.getElementById('heroSearchInput').value = '';
  380.     applyHeroSearch();
  381.   };
  382.   window.applyHeroSearch = function () {
  383.     var q = (document.getElementById('heroSearchInput').value || '').toLowerCase().trim();
  384.     /* Applique sur l'onglet actif */
  385.     var activeTid = getActiveTid();
  386.     if (activeTid) {
  387.       applyFilter(activeTid, q);
  388.     }
  389.   };
  390.   function getActiveTid() {
  391.     var active = document.querySelector('[data-tid].active, [data-tid][aria-selected="true"]');
  392.     if (!active) active = document.querySelector('[data-tid]');
  393.     return active ? parseInt(active.dataset.tid) : null;
  394.   }
  395.   function applyFilter(tid, q) {
  396.     document.querySelectorAll('.ps-item-' + tid).forEach(function (col) {
  397.       var match = !q || col.dataset.nom.includes(q) || col.dataset.adr.includes(q);
  398.       col.style.display = match ? '' : 'none';
  399.     });
  400.     pages[tid] = 1;
  401.     paginate(tid);
  402.   }
  403.   /* ── Pagination ── */
  404.   function paginate(tid) {
  405.     var page = pages[tid];
  406.     var all  = Array.from(document.querySelectorAll('.ps-item-' + tid));
  407.     var vis  = all.filter(function (c) { return c.style.display !== 'none'; });
  408.     var total = vis.length;
  409.     var nPages = Math.ceil(total / PER) || 1;
  410.     var start  = (page - 1) * PER;
  411.     all.forEach(function (c) { c.style.display = 'none'; });
  412.     vis.forEach(function (c, i) { if (i >= start && i < start + PER) c.style.display = ''; });
  413.     var info = document.getElementById('pgInfo' + tid);
  414.     if (info) info.textContent = total ? total + ' résultat' + (total > 1 ? 's' : '') : 'Aucun résultat';
  415.     var pg = document.getElementById('pg' + tid);
  416.     if (!pg) return;
  417.     if (nPages <= 1) { pg.innerHTML = ''; return; }
  418.     var h = '<li class="page-item' + (page === 1 ? ' disabled' : '') + '">'
  419.           + '<button class="page-link" onclick="goPage(' + tid + ',' + (page - 1) + ')">'
  420.           + '<i class="bi bi-chevron-left" style="font-size:.75rem;"></i></button></li>';
  421.     for (var i = 1; i <= nPages; i++) {
  422.       if (i === 1 || i === nPages || Math.abs(i - page) <= 1) {
  423.         h += '<li class="page-item' + (i === page ? ' active' : '') + '">'
  424.            + '<button class="page-link" onclick="goPage(' + tid + ',' + i + ')">' + i + '</button></li>';
  425.       } else if (Math.abs(i - page) === 2) {
  426.         h += '<li class="page-item disabled"><span class="page-link">…</span></li>';
  427.       }
  428.     }
  429.     h += '<li class="page-item' + (page === nPages ? ' disabled' : '') + '">'
  430.        + '<button class="page-link" onclick="goPage(' + tid + ',' + (page + 1) + ')">'
  431.        + '<i class="bi bi-chevron-right" style="font-size:.75rem;"></i></button></li>';
  432.     pg.innerHTML = h;
  433.   }
  434.   window.goPage = function (tid, p) { pages[tid] = p; paginate(tid); };
  435.   /* ── Init ── */
  436.   tabIds.forEach(function (tid) { applyFilter(tid, ''); });
  437.   /* Ré-appliquer le filtre hero au changement d'onglet */
  438.   document.querySelectorAll('[data-tid]').forEach(function (btn) {
  439.     btn.addEventListener('shown.bs.tab', function () {
  440.       var tid = parseInt(this.dataset.tid);
  441.       var q = (document.getElementById('heroSearchInput').value || '').toLowerCase().trim();
  442.       applyFilter(tid, q);
  443.     });
  444.   });
  445. })();
  446. </script>
  447. {% endblock %}