templates/base.html.twig line 1

Open in your IDE?
  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4.     <base href=""/>
  5.     <title>Paynetics - Admin</title>
  6.     <meta charset="utf-8"/>
  7.     
  8.     <!-- Theme initialization (must be early to prevent flash) -->
  9.     <script>
  10.         (function() {
  11.             // Cookie helper functions
  12.             function getCookie(name) {
  13.                 var match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
  14.                 return match ? match[2] : null;
  15.             }
  16.             
  17.             function setCookie(name, value, days) {
  18.                 var expires = '';
  19.                 if (days) {
  20.                     var date = new Date();
  21.                     date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
  22.                     expires = '; expires=' + date.toUTCString();
  23.                 }
  24.                 document.cookie = name + '=' + value + expires + '; path=/; SameSite=Lax';
  25.             }
  26.             
  27.             // Get theme from cookie, default to 'light'
  28.             var savedTheme = getCookie('theme') || 'light';
  29.             
  30.             // Apply theme immediately to prevent flash
  31.             document.documentElement.setAttribute('data-bs-theme', savedTheme);
  32.             
  33.             // Expose functions globally for theme toggle
  34.             window.getThemeCookie = getCookie;
  35.             window.setThemeCookie = setCookie;
  36.             window.currentTheme = savedTheme;
  37.         })();
  38.     </script>
  39.     <meta name="viewport" content="width=device-width, initial-scale=1"/>
  40.     <meta property="og:locale" content="en_US"/>
  41.     <meta property="og:type" content="article"/>
  42.     <link rel="shortcut icon" href="/assets/media/logos/favicon.ico"/>
  43.     <!-- Google Fonts - IBM Plex Sans -->
  44.     <link rel="preconnect" href="https://fonts.googleapis.com">
  45.     <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  46.     <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap" rel="stylesheet">
  47.     <!-- Velok Theme CSS -->
  48.     <link href="{{ asset('assets/velok/css/vendor.min.css') }}" rel="stylesheet" type="text/css"/>
  49.     <link href="{{ asset('assets/velok/css/app.min.css') }}" rel="stylesheet" type="text/css"/>
  50.     <!-- Dark/Light mode toggle styles -->
  51.     <style>
  52.         /* Theme toggle button icons */
  53.         /* In light mode: show moon icon (to switch to dark) */
  54.         html[data-bs-theme="light"] #light-dark-mode .light-mode,
  55.         html:not([data-bs-theme]) #light-dark-mode .light-mode {
  56.             display: inline-block;
  57.         }
  58.         html[data-bs-theme="light"] #light-dark-mode .dark-mode,
  59.         html:not([data-bs-theme]) #light-dark-mode .dark-mode {
  60.             display: none;
  61.         }
  62.         /* In dark mode: show sun icon (to switch to light) */
  63.         html[data-bs-theme="dark"] #light-dark-mode .light-mode {
  64.             display: none;
  65.         }
  66.         html[data-bs-theme="dark"] #light-dark-mode .dark-mode {
  67.             display: inline-block;
  68.         }
  69.         /* Logo visibility based on theme */
  70.         html[data-bs-theme="light"] .logo-light,
  71.         html:not([data-bs-theme]) .logo-light {
  72.             display: none !important;
  73.         }
  74.         html[data-bs-theme="light"] .logo-dark,
  75.         html:not([data-bs-theme]) .logo-dark {
  76.             display: block !important;
  77.         }
  78.         html[data-bs-theme="dark"] .logo-dark {
  79.             display: none !important;
  80.         }
  81.         html[data-bs-theme="dark"] .logo-light {
  82.             display: block !important;
  83.         }
  84.         /* Pagination styling */
  85.         .pagination .page-link {
  86.             display: flex;
  87.             align-items: center;
  88.             justify-content: center;
  89.             min-width: 32px;
  90.             width: auto;
  91.             height: 32px;
  92.             padding: 0.25rem 0.5rem;
  93.             white-space: nowrap;
  94.         }
  95.         .pagination-sm .page-link {
  96.             min-width: 28px;
  97.             width: auto;
  98.             height: 28px;
  99.             font-size: 0.8125rem;
  100.         }
  101.         .pagination-lg .page-link {
  102.             min-width: calc(1.5rem + 1.5em + 2px);
  103.             width: auto;
  104.         }
  105.         .pagination .page-link svg {
  106.             width: 14px;
  107.             height: 14px;
  108.         }
  109.         .pagination .page-item.active .page-link {
  110.             background-color: var(--bs-primary);
  111.             border-color: var(--bs-primary);
  112.         }
  113.     </style>
  114.     <!-- Vendor CSS (plugins) -->
  115.     <link href="{{ asset('assets/velok/vendor/flatpickr/flatpickr.min.css') }}" rel="stylesheet" type="text/css"/>
  116.     <link href="{{ asset('assets/velok/vendor/flatpickr/plugins/monthSelect/style.css') }}" rel="stylesheet" type="text/css"/>
  117.     <link href="{{ asset('assets/velok/vendor/choices.js/choices.min.css') }}" rel="stylesheet" type="text/css"/>
  118.     <link href="{{ asset('assets/velok/vendor/gridjs/theme/mermaid.min.css') }}" rel="stylesheet" type="text/css"/>
  119.     <link href="{{ asset('assets/velok/vendor/sweetalert2/sweetalert2.min.css') }}" rel="stylesheet" type="text/css"/>
  120.     <link href="{{ asset('assets/velok/vendor/simplebar/simplebar.min.css') }}" rel="stylesheet" type="text/css"/>
  121.     <link href="{{ asset('assets/velok/vendor/daterangepicker/daterangepicker.css') }}" rel="stylesheet" type="text/css"/>
  122.     
  123.     <!-- Select2 CSS -->
  124.     <link href="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/css/select2.min.css" rel="stylesheet" />
  125.     <link href="https://cdn.jsdelivr.net/npm/select2-bootstrap-5-theme@1.3.0/dist/select2-bootstrap-5-theme.min.css" rel="stylesheet" />
  126.     <link href="{{ asset('assets/css/select2-dark-theme.min.css') }}" rel="stylesheet" type="text/css"/>
  127.     <link href="{{ asset('assets/css/vanilla-jsoneditor-dark-theme.min.css') }}" rel="stylesheet" type="text/css"/>
  128.     <style>
  129.         /* Select2 compact styling */
  130.         .select2-container--bootstrap-5 .select2-selection--single {
  131.             min-height: 31px;
  132.             padding: 0.25rem 0.5rem;
  133.             font-size: 0.8125rem;
  134.         }
  135.         .select2-container--bootstrap-5 .select2-selection--single .select2-selection__rendered {
  136.             padding: 0;
  137.             line-height: 1.5;
  138.         }
  139.         .select2-container--bootstrap-5 .select2-selection--single .select2-selection__arrow {
  140.             height: 29px;
  141.         }
  142.         .select2-container--bootstrap-5 .select2-dropdown .select2-results__option {
  143.             padding: 0.375rem 0.75rem !important;
  144.             font-size: 0.8125rem !important;
  145.         }
  146.         /* Fix sidebar submenu visibility - ensure .show overrides sidebar-hover hiding */
  147.         .main-nav .navbar-nav .collapse.show,
  148.         .main-nav .navbar-nav .collapsing {
  149.             display: block !important;
  150.         }
  151.         html.sidebar-hover .main-nav:hover .menu-item .collapse.show,
  152.         html.sidebar-hover .main-nav:hover .menu-item .collapsing {
  153.             display: block !important;
  154.         }
  155.         /* Ensure submenus are visible and properly styled */
  156.         .main-nav .sub-menu-nav {
  157.             display: flex;
  158.             flex-direction: column;
  159.             padding-left: 25px;
  160.             list-style: none;
  161.             margin: 0;
  162.         }
  163.         /* Fix dropdown menus in nav-pills (application tabs) */
  164.         .nav-pills .nav-item.dropdown {
  165.             position: relative;
  166.         }
  167.         .nav-pills .dropdown-toggle {
  168.             cursor: pointer;
  169.         }
  170.         .nav-pills .dropdown-toggle.btn-link {
  171.             padding: 0.25rem 0.75rem;
  172.             border: none;
  173.             background: transparent;
  174.         }
  175.         .nav-pills .dropdown-toggle.btn-link:hover,
  176.         .nav-pills .dropdown-toggle.btn-link:focus {
  177.             background-color: rgba(var(--bs-primary-rgb), 0.1);
  178.             border-radius: 0.375rem;
  179.         }
  180.         .nav-pills .dropdown-menu {
  181.             position: absolute;
  182.             z-index: 1055;
  183.             min-width: 12rem;
  184.             padding: 0.5rem 0;
  185.             margin-top: 0.125rem;
  186.             background-color: var(--bs-body-bg, #fff);
  187.             border: 1px solid var(--bs-border-color, rgba(0,0,0,.15));
  188.             border-radius: 0.375rem;
  189.             box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
  190.         }
  191.         .nav-pills .dropdown-item {
  192.             display: flex;
  193.             align-items: center;
  194.             padding: 0.5rem 1rem;
  195.             font-size: 0.8125rem;
  196.             color: var(--bs-body-color, #212529);
  197.             transition: background-color 0.15s ease-in-out;
  198.         }
  199.         .nav-pills .dropdown-item:hover,
  200.         .nav-pills .dropdown-item:focus {
  201.             background-color: var(--bs-tertiary-bg, #f8f9fa);
  202.             color: var(--bs-body-color, #1e2125);
  203.         }
  204.         .nav-pills .dropdown-divider {
  205.             margin: 0.5rem 0;
  206.         }
  207.         
  208.         /* Nav tabs dropdown fix */
  209.         .nav-tabs .nav-item.dropdown {
  210.             position: relative;
  211.         }
  212.         .nav-tabs button.nav-link.dropdown-toggle {
  213.             background: none;
  214.             border: none;
  215.             border-bottom: 2px solid transparent;
  216.             cursor: pointer;
  217.         }
  218.         .nav-tabs button.nav-link.dropdown-toggle:hover {
  219.             border-bottom-color: var(--bs-primary);
  220.         }
  221.         .nav-tabs button.nav-link.dropdown-toggle.active {
  222.             border-bottom-color: var(--bs-primary);
  223.         }
  224.         .nav-tabs .dropdown-toggle::after {
  225.             margin-left: 0.25em;
  226.         }
  227.         .nav-tabs .dropdown-menu {
  228.             position: absolute;
  229.             z-index: 1055;
  230.             min-width: 12rem;
  231.             padding: 0.5rem 0;
  232.             margin-top: 0;
  233.             background-color: var(--bs-body-bg, #fff);
  234.             border: 1px solid var(--bs-border-color, rgba(0,0,0,.15));
  235.             border-radius: 0.375rem;
  236.             box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
  237.         }
  238.         .nav-tabs .dropdown-item {
  239.             padding: 0.5rem 1rem;
  240.             font-size: 0.875rem;
  241.         }
  242.         .nav-tabs .dropdown-item:hover,
  243.         .nav-tabs .dropdown-item:focus {
  244.             background-color: var(--bs-tertiary-bg, #f8f9fa);
  245.         }
  246.         .nav-tabs .dropdown-item.active {
  247.             background-color: var(--bs-primary);
  248.             color: #fff;
  249.         }
  250.     </style>
  251.     {% block css %}{% endblock %}
  252.     <!-- Lucide Icons (loaded in head to prevent icon flash) -->
  253.     <script src="{{ asset('assets/velok/vendor/lucide/umd/lucide.min.js') }}"></script>
  254. </head>
  255. <body>
  256.     <div class="wrapper">
  257.         <!-- Sidebar Navigation -->
  258.         {% if is_granted('ROLE_READ_ONLY') %}
  259.             {{ include('main-nav-read-only.html.twig') }}
  260.         {% else %}
  261.             {{ include('main-nav.html.twig') }}
  262.         {% endif %}
  263.         <!-- Top Header -->
  264.         <header class="topbar d-flex">
  265.             <div class="container-fluid">
  266.                 <div class="navbar-header">
  267.                     <div class="d-flex align-items-center gap-2 ms-auto">
  268.                         <!-- Theme Color (Light/Dark) -->
  269.                         <div class="topbar-item">
  270.                             <button type="button" class="topbar-button fs-24" id="light-dark-mode" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Toggle Dark Mode">
  271.                                 <i data-lucide="moon" class="light-mode"></i>
  272.                                 <i data-lucide="sun" class="dark-mode"></i>
  273.                             </button>
  274.                         </div>
  275.                         <!-- User Info & Logout -->
  276.                         <div class="d-flex align-items-center gap-2">
  277.                             <span class="d-flex align-items-center gap-2">
  278.                                 <span class="bg-primary text-white rounded-circle d-flex align-items-center justify-content-center" style="width: 28px; height: 28px; font-size: 12px; font-weight: 600;">
  279.                                     {{ app.user.username|first|upper }}
  280.                                 </span>
  281.                                 <span class="d-none d-md-block text-body fw-medium" style="font-size: 13px;">{{ app.user.username }}</span>
  282.                             </span>
  283.                             <a href="{{ path('app_logout') }}" class="btn btn-sm btn-outline-danger d-flex align-items-center gap-1" data-bs-toggle="tooltip" data-bs-placement="bottom" title="Logout">
  284.                                 <i data-lucide="log-out" style="width: 14px; height: 14px;"></i>
  285.                                 <span class="d-none d-lg-inline">Logout</span>
  286.                             </a>
  287.                         </div>
  288.                     </div>
  289.                 </div>
  290.             </div>
  291.         </header>
  292.         <!-- Page Container -->
  293.         <div class="page-container">
  294.             <!-- Page Content -->
  295.             <div class="page-content">
  296.                 {% block toolbar %}
  297.                     {% if toolbarPage|default() %}
  298.                         <div class="page-title-box mb-4">
  299.                             <div class="d-flex align-items-center justify-content-between">
  300.                                 <h4 class="page-title mb-0">{{ toolbarPage }}</h4>
  301.                             </div>
  302.                         </div>
  303.                     {% endif %}
  304.                 {% endblock %}
  305.                 {% for label, messages in app.flashes %}
  306.                     {% for message in messages %}
  307.                         <div class="alert alert-{{ label == 'error' ? 'danger' : (label == 'notice' ? 'info' : label) }} alert-dismissible fade show" role="alert">
  308.                             {{ message }}
  309.                             <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
  310.                         </div>
  311.                     {% endfor %}
  312.                 {% endfor %}
  313.                 {% block content %}{% endblock %}
  314.             </div>
  315.             <!-- Footer -->
  316.             <footer class="footer">
  317.                 <div class="container-fluid">
  318.                     <div class="row">
  319.                         <div class="col-12 text-center">
  320.                             {{ "now"|date("Y") }} &copy; Paynetics Admin. All rights reserved.
  321.                         </div>
  322.                     </div>
  323.                 </div>
  324.             </footer>
  325.         </div>
  326.     </div>
  327.     <!-- jQuery -->
  328.     <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
  329.     <!-- FOS JS Routing -->
  330.     <script src="{{ asset('bundles/fosjsrouting/js/router.min.js') }}"></script>
  331.     <script src="{{ path('fos_js_routing_js', { callback: 'fos.Router.setData' }) }}"></script>
  332.     <!-- Vendor JS (includes Bootstrap 5 bundle with Popper) -->
  333.     <script src="{{ asset('assets/velok/js/vendor.min.js') }}"></script>
  334.     <!-- Simplebar -->
  335.     <script src="{{ asset('assets/velok/vendor/simplebar/simplebar.min.js') }}"></script>
  336.     <!-- Initialize Lucide icons immediately (script loaded in head) -->
  337.     <script>if(typeof lucide!=='undefined'){lucide.createIcons();}</script>
  338.     <!-- Choices.js -->
  339.     <script src="{{ asset('assets/velok/vendor/choices.js/choices.min.js') }}"></script>
  340.     <!-- Select2 -->
  341.     <script src="https://cdn.jsdelivr.net/npm/select2@4.1.0-rc.0/dist/js/select2.min.js"></script>
  342.     <!-- Flatpickr -->
  343.     <script src="{{ asset('assets/velok/vendor/flatpickr/flatpickr.min.js') }}"></script>
  344.     <script src="{{ asset('assets/velok/vendor/flatpickr/plugins/monthSelect/index.js') }}"></script>
  345.     <!-- Daterangepicker (requires moment.js) -->
  346.     <script src="{{ asset('assets/velok/vendor/daterangepicker/moment.min.js') }}"></script>
  347.     <script src="{{ asset('assets/velok/vendor/daterangepicker/daterangepicker.js') }}"></script>
  348.     <!-- GridJS -->
  349.     <script src="{{ asset('assets/velok/vendor/gridjs/gridjs.umd.js') }}"></script>
  350.     <!-- SweetAlert2 -->
  351.     <script src="{{ asset('assets/velok/vendor/sweetalert2/sweetalert2.min.js') }}"></script>
  352.     <!-- ApexCharts -->
  353.     <script src="{{ asset('assets/velok/vendor/apexcharts/apexcharts.min.js') }}"></script>
  354.     <!-- App JS -->
  355.     <script src="{{ asset('assets/velok/js/config.js') }}"></script>
  356.     <script src="{{ asset('assets/velok/js/layout.js') }}"></script>
  357.     <script src="{{ asset('assets/velok/js/app.js') }}"></script>
  358.     <!-- Initialize theme config immediately to prevent flash -->
  359.     <script>
  360.         if (typeof loadConfig === 'function') {
  361.             loadConfig();
  362.         }
  363.     </script>
  364.     <script>
  365.         document.addEventListener('DOMContentLoaded', function() {
  366.             // Theme toggle handler
  367.             var themeToggle = document.getElementById('light-dark-mode');
  368.             if (themeToggle) {
  369.                 themeToggle.addEventListener('click', function() {
  370.                     var currentTheme = document.documentElement.getAttribute('data-bs-theme') || 'light';
  371.                     var newTheme = currentTheme === 'dark' ? 'light' : 'dark';
  372.                     
  373.                     // Apply new theme
  374.                     document.documentElement.setAttribute('data-bs-theme', newTheme);
  375.                     
  376.                     // Save to cookie (365 days)
  377.                     if (typeof window.setThemeCookie === 'function') {
  378.                         window.setThemeCookie('theme', newTheme, 365);
  379.                     }
  380.                     
  381.                     window.currentTheme = newTheme;
  382.                     
  383.                     // Re-initialize Lucide icons after theme change
  384.                     if (typeof lucide !== 'undefined') {
  385.                         lucide.createIcons();
  386.                     }
  387.                 });
  388.             }
  389.             
  390.             // Initialize theme layout (includes dark mode toggle)
  391.             if (typeof loadLayout === 'function') {
  392.                 loadLayout();
  393.             }
  394.             // Initialize app components
  395.             if (typeof loadApps === 'function') {
  396.                 loadApps();
  397.             }
  398.             // Initialize Lucide icons
  399.             if (typeof lucide !== 'undefined') {
  400.                 lucide.createIcons();
  401.             }
  402.             // Initialize Bootstrap components
  403.             var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
  404.             tooltipTriggerList.map(function (tooltipTriggerEl) {
  405.                 return new bootstrap.Tooltip(tooltipTriggerEl);
  406.             });
  407.             
  408.             // Initialize nav dropdowns (tabs and pills)
  409.             var navDropdowns = document.querySelectorAll('.nav-tabs .dropdown-toggle, .nav-pills .dropdown-toggle');
  410.             navDropdowns.forEach(function(dropdownToggle) {
  411.                 new bootstrap.Dropdown(dropdownToggle);
  412.             });
  413.             // Fix dropdowns inside .table-responsive containers being clipped by overflow
  414.             document.querySelectorAll('.table-responsive').forEach(function(container) {
  415.                 container.addEventListener('show.bs.dropdown', function() {
  416.                     container.style.overflow = 'visible';
  417.                 });
  418.                 container.addEventListener('hide.bs.dropdown', function() {
  419.                     container.style.overflow = '';
  420.                 });
  421.             });
  422.             // Initialize Select2 AJAX dropdowns using jQuery ready
  423.             jQuery(function($) {
  424.                 if ($.fn.select2) {
  425.                     // Custom AJAX transport to set proper headers for JSON response
  426.                     var jsonAjaxTransport = function(params, success, failure) {
  427.                         var $request = $.ajax({
  428.                             url: params.url,
  429.                             data: params.data,
  430.                             dataType: 'json',
  431.                             beforeSend: function(xhr) {
  432.                                 xhr.setRequestHeader('Content-Type', 'application/json');
  433.                                 xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
  434.                             }
  435.                         });
  436.                         $request.then(success);
  437.                         $request.fail(failure);
  438.                         return $request;
  439.                     };
  440.                     // Instance dropdowns
  441.                     if ($('.select2-ajax-instances').length) {
  442.                         $('.select2-ajax-instances').select2({
  443.                             theme: 'bootstrap-5',
  444.                             allowClear: true,
  445.                             width: '100%',
  446.                             placeholder: 'Type to search...',
  447.                             minimumInputLength: 1,
  448.                             ajax: {
  449.                                 url: function(params) {
  450.                                     return '/instances/' + (params.page || 1);
  451.                                 },
  452.                                 data: function(params) {
  453.                                     return { name: params.term };
  454.                                 },
  455.                                 transport: jsonAjaxTransport,
  456.                                 delay: 300,
  457.                                 cache: true,
  458.                                 processResults: function(data) {
  459.                                     var items = data && data.items ? data.items : [];
  460.                                     return {
  461.                                         results: items.map(function(item) {
  462.                                             return { id: item.token, text: item.name };
  463.                                         }),
  464.                                         pagination: { more: items.length >= 20 }
  465.                                     };
  466.                                 },
  467.                                 error: function(xhr, status, error) {
  468.                                     console.error('Instance AJAX error:', status, error);
  469.                                 }
  470.                             }
  471.                         });
  472.                     }
  473.                     // Merchant dropdowns
  474.                     if ($('.select2-ajax-merchants').length) {
  475.                         $('.select2-ajax-merchants').select2({
  476.                             theme: 'bootstrap-5',
  477.                             allowClear: true,
  478.                             width: '100%',
  479.                             placeholder: 'Type to search...',
  480.                             minimumInputLength: 1,
  481.                             ajax: {
  482.                                 url: function(params) {
  483.                                     return '/merchants/' + (params.page || 1);
  484.                                 },
  485.                                 data: function(params) {
  486.                                     return { name: params.term };
  487.                                 },
  488.                                 transport: jsonAjaxTransport,
  489.                                 delay: 300,
  490.                                 cache: true,
  491.                                 processResults: function(data) {
  492.                                     var items = data && data.items ? data.items : [];
  493.                                     return {
  494.                                         results: items.map(function(item) {
  495.                                             return { id: item.token, text: item.name };
  496.                                         }),
  497.                                         pagination: { more: items.length >= 20 }
  498.                                     };
  499.                                 },
  500.                                 error: function(xhr, status, error) {
  501.                                     console.error('Merchant AJAX error:', status, error);
  502.                                 }
  503.                             }
  504.                         });
  505.                     }
  506.                     // Program dropdowns
  507.                     if ($('.select2-ajax-programs').length) {
  508.                         $('.select2-ajax-programs').select2({
  509.                             theme: 'bootstrap-5',
  510.                             allowClear: true,
  511.                             width: '100%',
  512.                             placeholder: 'Type to search...',
  513.                             minimumInputLength: 1,
  514.                             ajax: {
  515.                                 url: function(params) {
  516.                                     return '/programs/' + (params.page || 1);
  517.                                 },
  518.                                 data: function(params) {
  519.                                     return { name: params.term };
  520.                                 },
  521.                                 transport: jsonAjaxTransport,
  522.                                 delay: 300,
  523.                                 cache: true,
  524.                                 processResults: function(data) {
  525.                                     var items = data && data.items ? data.items : [];
  526.                                     return {
  527.                                         results: items.map(function(item) {
  528.                                             return { id: item.token, text: item.name };
  529.                                         }),
  530.                                         pagination: { more: items.length >= 20 }
  531.                                     };
  532.                                 },
  533.                                 error: function(xhr, status, error) {
  534.                                     console.error('Program AJAX error:', status, error);
  535.                                 }
  536.                             }
  537.                         });
  538.                     }
  539.                     // Static select2 dropdowns (non-AJAX)
  540.                     if ($('.select2-static').length) {
  541.                         $('.select2-static').each(function() {
  542.                             var $select = $(this);
  543.                             var placeholder = $select.attr('data-placeholder') || $select.attr('placeholder') || $select.find('option[value=""]').text() || 'Select...';
  544.                             $select.select2({
  545.                                 theme: 'bootstrap-5',
  546.                                 allowClear: true,
  547.                                 width: '100%',
  548.                                 placeholder: placeholder
  549.                             });
  550.                         });
  551.                     }
  552.                     console.log('Select2 initialized for', 
  553.                         $('.select2-ajax-instances').length, 'instances,',
  554.                         $('.select2-ajax-merchants').length, 'merchants,',
  555.                         $('.select2-ajax-programs').length, 'programs,',
  556.                         $('.select2-static').length, 'static dropdowns'
  557.                     );
  558.                 } else {
  559.                     console.warn('Select2 not loaded');
  560.                 }
  561.             });
  562.         });
  563.     </script>
  564.     {% block js %}{% endblock %}
  565.     <!-- n8n Chat Widget -->
  566.     <div id="n8n-chat-container"></div>
  567.     <link href="https://cdn.jsdelivr.net/npm/@n8n/chat/dist/style.css" rel="stylesheet" />
  568.     <script type="module">
  569.         import { createChat } from 'https://cdn.jsdelivr.net/npm/@n8n/chat/dist/chat.bundle.es.js';
  570.         createChat({
  571.             target: '#n8n-chat-container',
  572.             webhookUrl: 'https://dnstart.app.n8n.cloud/webhook/04149076-d358-4fab-8961-7c38e09477d7/chat',
  573.             mode: 'window',
  574.             showWelcomeScreen: true,
  575.             defaultLanguage: 'en',
  576.             initialMessages: [
  577.                 'Hello! ðŸ‘‹',
  578.                 'How can I help you today?'
  579.             ],
  580.             i18n: {
  581.                 en: {
  582.                     title: 'Admin Assistant',
  583.                     subtitle: 'Powered by n8n',
  584.                     footer: '',
  585.                     getStarted: 'Start Chat',
  586.                     inputPlaceholder: 'Type your question...',
  587.                 }
  588.             }
  589.         });
  590.     </script>
  591.     <style>
  592.         /* n8n Chat Widget Positioning */
  593.         #n8n-chat-container {
  594.             position: fixed;
  595.             bottom: 0;
  596.             right: 0;
  597.             z-index: 9999;
  598.         }
  599.     </style>
  600. </body>
  601. </html>