templates/business_contacts/index.html.twig line 1

  1. {# templates/business_contacts/index.html.twig #}
  2. {% extends 'base.html.twig' %}
  3. {% block title %}Business Contacts{% endblock %}
  4. {% block body %}
  5.     <div class="row">
  6.         {% include 'business_contacts/parts/business_type_selection_buttons.html.twig' %}
  7.     </div>
  8.     <br>
  9.     {% include 'business_contacts/parts/import_and_export.html.twig' %}
  10.     <div class="table-responsive">
  11.         <table class="table custom-table fixed-cols">
  12.             <thead>
  13.             <tr>
  14.                 <th class="desktop">Photo</th>
  15.                 <th style="width: 150px; text-align: left">Company</th>
  16.                 {% if list_or_map == 'map' %}
  17.                     <th></th>
  18.                 {% endif %}
  19.                 <th style="width: 70px; text-align: left">
  20.                     <i class="fa fa-user"></i>
  21.                 </th>
  22.                 <th title="Website" style="width: 10px; text-align: center">
  23.                     <i class="fa fa-link"></i>
  24.                 </th>
  25.                 <th title="Email" style="width: 10px; text-align: center">
  26.                     <i class="fa fa-envelope"></i>
  27.                 </th>
  28.                 <th title="Mobile" style="width: 10px; text-align: center; border-left: 1px dotted;">
  29.                     <i class="fab fa-whatsapp"></i>
  30.                 </th>
  31.                 {# Mobile analyser header columns (keep as-is) #}
  32.                 {% for type in ['mobile'] %}
  33.                     {% include 'business_contacts/parts/5 telephone_numbers_analyser_header.html.twig' %}
  34.                 {% endfor %}
  35.                 <th title="Phone" style="width: 10px; text-align: center">
  36.                     <i class="fa fa-phone"></i>
  37.                 </th>
  38.                 {# Landline analyser header columns (keep as-is) #}
  39.                 {% for type in ['landline'] %}
  40.                     {% include 'business_contacts/parts/5 telephone_numbers_analyser_header.html.twig' %}
  41.                 {% endfor %}
  42.                 <th title="Address" style="border-left: 1px dotted; width: 400px; text-align: left">
  43.                     <i class="fa fa-building"></i>
  44.                 </th>
  45.                 <th class="desktop" title="Notes" style="width: 100px; text-align: left">
  46.                     <i class="fa fa-pen"></i>
  47.                 </th>
  48.                 <th class="desktop" style="width: 10px; text-align: center" title="Business card">
  49.                     <i class="fa fa-address-card"></i>
  50.                 </th>
  51.                 <th class="desktop" title="Files" style="width: 10px; text-align: center">
  52.                     <i class="fa fa-file-pdf"></i>
  53.                 </th>
  54.                 <th>
  55.                     <i style="color: darkblue" class="fab fa-waze"></i>
  56.                 </th>
  57.                 {% include 'business_contacts/parts/7 access_count_headers.html.twig' %}
  58.             </tr>
  59.             </thead>
  60.             <tbody>
  61.             {# One pass over business types; render a group header row, then that group's contacts #}
  62.             {% for business_type in business_types %}
  63.                 {% set business_contacts_count = CountBusinessContactsService.count(business_type) %}
  64.                 {% set business_contacts_count_with_map_locations = CountBusinessContactsService.countWithMapLocations(business_type) %}
  65.                 {% if business_type.mapicon is not null %}
  66.                     {% set file = asset('administration/ATSSharedFiles/BusinessContactsMapIcons/' ~ business_type.mapIcon.iconFile) %}
  67.                 {% endif %}
  68.                 {% if business_contacts_count > 0 %}
  69.                     {# Group header row for this business type #}
  70.                     <tr class="group-header" data-type="{{ business_type.businessType }}">
  71.                         <td colspan="100">
  72.                             <h2 style="color: red; margin: 0;">
  73.                                 {% if business_contacts_count_with_map_locations > 0 %}
  74.                                     <a class="btn btn-danger btn-sm"
  75.                                        href="{{ path('business_contacts_map', {subset: business_type.businessType}) }}">
  76.                                         <i class="fa fa-map-marker"></i>
  77.                                     </a>
  78.                                 {% endif %}
  79.                                 {% if business_type.mapIcon is not empty %}
  80.                                     <img height="40" width="40" src="{{ file }}" alt="">
  81.                                 {% endif %}
  82.                                 {% if is_granted('ROLE_ADMIN') %}
  83.                                     <a target="_blank" href="{{ path('business_types_edit', {id: business_type.id}) }}">
  84.                                         {{ business_type.businessType }}
  85.                                     </a>
  86.                                 {% else %}
  87.                                     {{ business_type.businessType }}
  88.                                 {% endif %}
  89.                                 {% if app.user %}
  90.                                     <a href="{{ path('business_contacts_new', {business_type: business_type.id}) }}">+</a>
  91.                                 {% endif %}
  92.                             </h2>
  93.                         </td>
  94.                     </tr>
  95.                     {# Rows for contacts in this group — use the grouped array, not business_contacts #}
  96.                     {% set contacts = contacts_by_type[business_type.id] ?? [] %}
  97.                     {% for business_contact in contacts %}
  98.                         <tr class="contact-row" data-type="{{ business_type.businessType }}">
  99.                             {% include 'business_contacts/parts/1 photo.html.twig' %}
  100.                             {% include 'business_contacts/parts/2 company_and_user_body.html.twig' %}
  101.                             {% include 'business_contacts/parts/4 website_email_tels_body.html.twig' %}
  102.                             {% include 'business_contacts/parts/3 map marker_body.html.twig' %}
  103.                             {% include 'business_contacts/parts/6 notes_buttons_files_body.html.twig' %}
  104.                             {# Waze column construction #}
  105.                             {% set hasCoords = business_contact.locationLatitude is not null and business_contact.locationLongitude is not null %}
  106.                             {% set lat = hasCoords ? (business_contact.locationLatitude|number_format(6, '.', '')) : null %}
  107.                             {% set lng = hasCoords ? (business_contact.locationLongitude|number_format(6, '.', '')) : null %}
  108.                             {% set fullAddress = '35 Lillian Road, Barnes, SW13 9LH, UK' %}
  109.                             {% set wazeUrl = hasCoords
  110.                                 ? ('https://waze.com/ul?ll=' ~ lat ~ ',' ~ lng ~ '&navigate=yes')
  111.                                 : ('https://waze.com/ul?q=' ~ (fullAddress|url_encode) ~ '&navigate=yes') %}
  112.                             <td>
  113.                                 <a href="{{ wazeUrl }}" target="_blank" rel="noopener">
  114.                                     <i class="fab fa-waze"></i>
  115.                                 </a>
  116.                             </td>
  117.                             {% include 'business_contacts/parts/7 access_count_body.html.twig' %}
  118.                         </tr>
  119.                     {% endfor %}
  120.                 {% endif %}
  121.             {% endfor %}
  122.             </tbody>
  123.         </table>
  124.     </div>
  125. {% endblock %}
  126. {% block datatable %}
  127.     <style>
  128.         /* Keep columns aligned and content tidy */
  129.         .fixed-cols { table-layout: fixed; width: 100%; }
  130.         .fixed-cols th, .fixed-cols td {
  131.             white-space: nowrap;
  132.             overflow: hidden;
  133.             text-overflow: ellipsis;
  134.             vertical-align: middle;
  135.         }
  136.         .group-header td {
  137.             background: #f7f7f7;
  138.             font-weight: bold;
  139.             border-top: 2px solid #ddd;
  140.             border-bottom: 2px solid #eee;
  141.         }
  142.         .fixed-cols img { max-width: 100%; height: auto; }
  143.     </style>
  144.     <script>
  145.         $(document).ready(function () {
  146.             $('.table').DataTable({
  147.                 autoWidth: false,
  148.                 paging: false,
  149.                 searching: false,
  150.                 bInfo: false,
  151.                 ordering: false  // important: keep group headers in place
  152.             });
  153.         });
  154.     </script>
  155. {% endblock datatable %}
  156. {% block additionaljs %}
  157.     <script>
  158.         (function () {
  159.             function applyFilter(type) {
  160.                 const rows = document.querySelectorAll('tr.contact-row');
  161.                 const headers = document.querySelectorAll('tr.group-header');
  162.                 const wanted = (type || '').toString().trim();
  163.                 rows.forEach(tr => {
  164.                     const t = tr.getAttribute('data-type') || '';
  165.                     tr.style.display = (!wanted || t === wanted) ? '' : 'none';
  166.                 });
  167.                 // Show header if it has at least one visible contact beneath it
  168.                 headers.forEach(h => {
  169.                     const t = h.getAttribute('data-type') || '';
  170.                     if (!wanted) {
  171.                         h.style.display = ''; // All
  172.                     } else {
  173.                         // Find the next sibling contact rows until next header
  174.                         let hasVisible = false;
  175.                         let el = h.nextElementSibling;
  176.                         while (el && !el.classList.contains('group-header')) {
  177.                             if (el.classList.contains('contact-row') && el.style.display !== 'none') {
  178.                                 hasVisible = true; break;
  179.                             }
  180.                             el = el.nextElementSibling;
  181.                         }
  182.                         h.style.display = hasVisible ? '' : 'none';
  183.                     }
  184.                 });
  185.                 // Toggle active classes on buttons and dropdown items
  186.                 document.querySelectorAll('.js-type').forEach(a => {
  187.                     const f = (a.getAttribute('data-filter') || '').trim();
  188.                     if (f === wanted) a.classList.add('active'); else a.classList.remove('active');
  189.                 });
  190.                 // Update dropdown button label on mobile
  191.                 const ddBtn = document.getElementById('businessTypeDropdown');
  192.                 if (ddBtn) {
  193.                     ddBtn.textContent = wanted ? wanted : `All (${rows.length})`;
  194.                 }
  195.                 // Push state to the URL (no reload)
  196.                 const url = new URL(window.location.href);
  197.                 if (wanted) url.searchParams.set('business_type', wanted);
  198.                 else url.searchParams.delete('business_type');
  199.                 window.history.replaceState({}, '', url);
  200.             }
  201.             // Hook up clicks
  202.             document.addEventListener('click', function (e) {
  203.                 const a = e.target.closest('a.js-type');
  204.                 if (!a) return;
  205.                 e.preventDefault();
  206.                 applyFilter(a.getAttribute('data-filter') || '');
  207.             });
  208.             // Apply initial filter from query string on load (so reload/links keep state)
  209.             document.addEventListener('DOMContentLoaded', function () {
  210.                 const url = new URL(window.location.href);
  211.                 applyFilter(url.searchParams.get('business_type') || '');
  212.             });
  213.         })();
  214.     </script>
  215.     <script>
  216.         var businessContactId = '';
  217.         function getLocation(id) {
  218.             businessContactId = id;
  219.             if (navigator.geolocation) {
  220.                 navigator.geolocation.getCurrentPosition(showLocation);
  221.             } else {
  222.                 $('#location').html('Geolocation is not supported by this browser.');
  223.             }
  224.         }
  225.         function showLocation(position) {
  226.             var latitude = position.coords.latitude;
  227.             var longitude = position.coords.longitude;
  228.             $.ajax({
  229.                 type: 'POST',
  230.                 url: 'update/location',
  231.                 data: 'latitude=' + latitude + '&longitude=' + longitude + '&id=' + businessContactId,
  232.                 success: function () {
  233.                     location.reload();
  234.                 }
  235.             });
  236.         }
  237.         $('.action').click(function (e) {
  238.             e.preventDefault();
  239.             let elem = $(this);
  240.             let id = $(this).attr('data-id');
  241.             let action = $(this).attr('data-action');
  242.             let url = '/referrals/new_from_businesscontacts/' + id + '/' + action;
  243.             $.ajax({
  244.                 type: 'GET',
  245.                 url: url,
  246.                 success: function () {
  247.                     let goTo = elem.attr('href');
  248.                     window.open(goTo, '_blank');
  249.                 }
  250.             });
  251.         });
  252.     </script>
  253. {% endblock %}