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.                     {% set contacts = contacts_by_type[business_type.id] ?? [] %}
  96.                     {% for business_contact in contacts %}
  97.                         {% set fullAddress = '' %}
  98.                         {% if business_contact.addressStreet %}
  99.                             {% set fullAddress = fullAddress ~ business_contact.addressStreet %}
  100.                         {% endif %}
  101.                         {% if business_contact.addressCity %}
  102.                             {% set fullAddress = fullAddress ~ (fullAddress ? ', ' : '') ~ business_contact.addressCity %}
  103.                         {% endif %}
  104.                         {% if business_contact.addressPostCode %}
  105.                             {% set fullAddress = fullAddress ~ (fullAddress ? ', ' : '') ~ business_contact.addressPostCode %}
  106.                         {% endif %}
  107.                         <tr class="contact-row" data-type="{{ business_type.businessType }}">
  108.                             {% include 'business_contacts/parts/1 photo.html.twig' %}
  109.                             {% include 'business_contacts/parts/2 company_and_user_body.html.twig' %}
  110.                             {% include 'business_contacts/parts/3 website_email_tels_body.html.twig' %}
  111.                             {% include 'business_contacts/parts/4 map marker_body.html.twig' %}
  112.                             {% include 'business_contacts/parts/6 notes_buttons_files_body.html.twig' %}
  113.                             {# Waze column construction #}
  114.                             {% set hasCoords  = business_contact.locationLatitude is not null and business_contact.locationLongitude is not null %}
  115.                             {% set hasAddress = fullAddress|trim != '' %}
  116.                             {% if hasCoords or hasAddress %}
  117.                                 {% set lat = hasCoords ? (business_contact.locationLatitude|number_format(6, '.', '')) : null %}
  118.                                 {% set lng = hasCoords ? (business_contact.locationLongitude|number_format(6, '.', '')) : null %}
  119.                                 {% set wazeUrl = hasCoords
  120.                                     ? ('https://waze.com/ul?ll=' ~ lat ~ ',' ~ lng ~ '&navigate=yes')
  121.                                     : ('https://waze.com/ul?q=' ~ (fullAddress|url_encode) ~ '&navigate=yes') %}
  122.                                 <td style="vertical-align: top">
  123.                                     <a href="{{ wazeUrl }}" target="_blank" rel="noopener">
  124.                                         <i class="fab fa-waze"></i>
  125.                                     </a>
  126.                                 </td>
  127.                             {% else %}
  128.                                 {# No coords and no address → no Waze link #}
  129.                                 <td></td>
  130.                             {% endif %}
  131.                             {% include 'business_contacts/parts/7 access_count_body.html.twig' %}
  132.                         </tr>
  133.                     {% endfor %}
  134.                 {% endif %}
  135.             {% endfor %}
  136.             </tbody>
  137.         </table>
  138.     </div>
  139. {% endblock %}
  140. {% block datatable %}
  141.     <style>
  142.         /* Keep columns aligned and tidy on desktop */
  143.         .fixed-cols {
  144.             table-layout: fixed;
  145.             width: 100%;
  146.         }
  147.         .fixed-cols th,
  148.         .fixed-cols td {
  149.             white-space: nowrap;
  150.             overflow: hidden;
  151.             text-overflow: ellipsis;
  152.             vertical-align: middle;
  153.         }
  154.         .group-header td {
  155.             background: #f7f7f7;
  156.             font-weight: bold;
  157.             border-top: 2px solid #ddd;
  158.             border-bottom: 2px solid #eee;
  159.         }
  160.         .fixed-cols img {
  161.             max-width: 100%;
  162.             height: auto;
  163.         }
  164.         /* Desktop widths – reduced slightly */
  165.         .company-col,
  166.         .person-col {
  167.             max-width: 180px;
  168.             white-space: nowrap;
  169.             overflow: hidden;
  170.             text-overflow: ellipsis;
  171.         }
  172.         /***************************
  173.          🔹 Mobile optimisation
  174.         ****************************/
  175.         @media (max-width: 768px) {
  176.             .custom-table { font-size: 0.80rem; }
  177.             .custom-table th,
  178.             .custom-table td {
  179.                 padding: 0.28rem 0.30rem;
  180.             }
  181.             /* Let cells wrap so table shrinks naturally */
  182.             .custom-table.fixed-cols td,
  183.             .custom-table.fixed-cols th {
  184.                 white-space: normal !important;
  185.                 overflow: visible !important;
  186.                 text-overflow: clip !important;
  187.             }
  188.             /* Hide desktop heavy columns */
  189.             .custom-table th.desktop,
  190.             .custom-table td.desktop {
  191.                 display: none !important;
  192.             }
  193.             /* Hide Address column entirely on mobile */
  194.             .custom-table th[title="Address"],
  195.             .custom-table td.address-col {
  196.                 display: none !important;
  197.             }
  198.             /* Company Column — slimmed down */
  199.             .custom-table th.company-header {
  200.                 max-width: 70px !important;
  201.                 white-space: nowrap;
  202.                 overflow: hidden;
  203.                 text-overflow: ellipsis;
  204.             }
  205.             /* Improve Waze tap target */
  206.             .custom-table .fa-waze {
  207.                 font-size: 1.1rem;
  208.             }
  209.         }
  210.     </style>
  211.     <script>
  212.         $(document).ready(function () {
  213.             const table = $('.table').DataTable({
  214.                 autoWidth: false,
  215.                 paging: false,
  216.                 searching: false,
  217.                 bInfo: false,
  218.                 ordering: false
  219.             });
  220.             /* Responsive width enforcement */
  221.             function applyMobileColumnWidths() {
  222.                 const isMobile = window.innerWidth <= 768;
  223.                 if (isMobile) {
  224.                     $('.company-col').css({
  225.                         'max-width': '65px',
  226.                         'white-space': 'nowrap',
  227.                         'overflow': 'hidden',
  228.                         'text-overflow': 'ellipsis'
  229.                     });
  230.                     $('.person-col').css({
  231.                         'max-width': '65px',
  232.                         'white-space': 'nowrap',
  233.                         'overflow': 'hidden',
  234.                         'text-overflow': 'ellipsis'
  235.                     });
  236.                 } else {
  237.                     $('.company-col, .person-col').css({
  238.                         'max-width': '180px',
  239.                         'white-space': 'nowrap',
  240.                         'overflow': 'hidden',
  241.                         'text-overflow': 'ellipsis'
  242.                     });
  243.                 }
  244.             }
  245.             applyMobileColumnWidths();
  246.             table.on('draw', applyMobileColumnWidths);
  247.             $(window).on('resize orientationchange', applyMobileColumnWidths);
  248.         });
  249.     </script>
  250. {% endblock datatable %}
  251. {% block additionaljs %}
  252.     <script>
  253.         (function () {
  254.             function applyFilter(type) {
  255.                 const rows = document.querySelectorAll('tr.contact-row');
  256.                 const headers = document.querySelectorAll('tr.group-header');
  257.                 const wanted = (type || '').toString().trim();
  258.                 rows.forEach(tr => {
  259.                     const t = tr.getAttribute('data-type') || '';
  260.                     tr.style.display = (!wanted || t === wanted) ? '' : 'none';
  261.                 });
  262.                 // Show header if it has at least one visible contact beneath it
  263.                 headers.forEach(h => {
  264.                     const t = h.getAttribute('data-type') || '';
  265.                     if (!wanted) {
  266.                         h.style.display = ''; // All
  267.                     } else {
  268.                         // Find the next sibling contact rows until next header
  269.                         let hasVisible = false;
  270.                         let el = h.nextElementSibling;
  271.                         while (el && !el.classList.contains('group-header')) {
  272.                             if (el.classList.contains('contact-row') && el.style.display !== 'none') {
  273.                                 hasVisible = true;
  274.                                 break;
  275.                             }
  276.                             el = el.nextElementSibling;
  277.                         }
  278.                         h.style.display = hasVisible ? '' : 'none';
  279.                     }
  280.                 });
  281.                 // Toggle active classes on buttons and dropdown items
  282.                 document.querySelectorAll('.js-type').forEach(a => {
  283.                     const f = (a.getAttribute('data-filter') || '').trim();
  284.                     if (f === wanted) a.classList.add('active'); else a.classList.remove('active');
  285.                 });
  286.                 // Update dropdown button label on mobile
  287.                 const ddBtn = document.getElementById('businessTypeDropdown');
  288.                 if (ddBtn) {
  289.                     ddBtn.textContent = wanted ? wanted : `All (${rows.length})`;
  290.                 }
  291.                 // Push state to the URL (no reload)
  292.                 const url = new URL(window.location.href);
  293.                 if (wanted) url.searchParams.set('business_type', wanted);
  294.                 else url.searchParams.delete('business_type');
  295.                 window.history.replaceState({}, '', url);
  296.             }
  297.             // Hook up clicks
  298.             document.addEventListener('click', function (e) {
  299.                 const a = e.target.closest('a.js-type');
  300.                 if (!a) return;
  301.                 e.preventDefault();
  302.                 applyFilter(a.getAttribute('data-filter') || '');
  303.             });
  304.             // Apply initial filter from query string on load (so reload/links keep state)
  305.             document.addEventListener('DOMContentLoaded', function () {
  306.                 const url = new URL(window.location.href);
  307.                 applyFilter(url.searchParams.get('business_type') || '');
  308.             });
  309.         })();
  310.     </script>
  311.     <script>
  312.         var businessContactId = '';
  313.         function getLocation(id) {
  314.             businessContactId = id;
  315.             if (navigator.geolocation) {
  316.                 navigator.geolocation.getCurrentPosition(showLocation);
  317.             } else {
  318.                 $('#location').html('Geolocation is not supported by this browser.');
  319.             }
  320.         }
  321.         function showLocation(position) {
  322.             var latitude = position.coords.latitude;
  323.             var longitude = position.coords.longitude;
  324.             $.ajax({
  325.                 type: 'POST',
  326.                 url: 'update/location',
  327.                 data: 'latitude=' + latitude + '&longitude=' + longitude + '&id=' + businessContactId,
  328.                 success: function () {
  329.                     location.reload();
  330.                 }
  331.             });
  332.         }
  333.         $('.action').click(function (e) {
  334.             e.preventDefault();
  335.             let elem = $(this);
  336.             let id = $(this).attr('data-id');
  337.             let action = $(this).attr('data-action');
  338.             let url = '/referrals/new_from_businesscontacts/' + id + '/' + action;
  339.             $.ajax({
  340.                 type: 'GET',
  341.                 url: url,
  342.                 success: function () {
  343.                     let goTo = elem.attr('href');
  344.                     window.open(goTo, '_blank');
  345.                 }
  346.             });
  347.         });
  348.     </script>
  349. {% endblock %}