diff --git a/app/xml_cdr/xml_cdr.php b/app/xml_cdr/xml_cdr.php index 6818658a8d..79694321ff 100644 --- a/app/xml_cdr/xml_cdr.php +++ b/app/xml_cdr/xml_cdr.php @@ -17,7 +17,7 @@ The Initial Developer of the Original Code is Mark J Crane - Portions created by the Initial Developer are Copyright (C) 2008-2025 + Portions created by the Initial Developer are Copyright (C) 2008-2026 the Initial Developer. All Rights Reserved. Contributor(s): @@ -141,8 +141,8 @@ if ($permission['xml_cdr_search_extension']) { $sql = "select extension_uuid, extension, number_alias from v_extensions "; $sql .= "where domain_uuid = :domain_uuid "; - if (!$permission['xml_cdr_domain'] && is_array($extension_uuids) && @sizeof($extension_uuids != 0)) { - $sql .= "and extension_uuid in ('".implode("','",$extension_uuids)."') "; //only show the user their extensions + if (!$permission['xml_cdr_domain'] && is_array($assigned_extension_uuids) && @sizeof($assigned_extension_uuids) != 0) { + $sql .= "and extension_uuid in ('".implode("','",$assigned_extension_uuids)."') "; //only show the user their extensions } $sql .= "order by extension asc, number_alias asc "; $parameters['domain_uuid'] = $_SESSION['domain_uuid']; @@ -393,11 +393,11 @@ echo " ".$text['label-extension']."\n"; echo " \n"; echo "
\n"; - echo " \n"; echo " "; if (is_array($extensions) && @sizeof($extensions) != 0) { foreach ($extensions as $row) { - $selected = ($row['extension_uuid'] == $extension_uuid) ? "selected" : null; + $selected = ($row['extension_uuid'] == $extension_uuid[0]) ? "selected" : null; echo " "; } } diff --git a/app/xml_cdr/xml_cdr_inc.php b/app/xml_cdr/xml_cdr_inc.php index 34f2f98e18..5804a2e855 100644 --- a/app/xml_cdr/xml_cdr_inc.php +++ b/app/xml_cdr/xml_cdr_inc.php @@ -108,7 +108,7 @@ $caller_id_name = $_REQUEST["caller_id_name"] ?? ''; $caller_id_number = $_REQUEST["caller_id_number"] ?? ''; $caller_destination = $_REQUEST["caller_destination"] ?? ''; - $extension_uuid = $_REQUEST["extension_uuid"] ?? ''; + $extension_uuids = $_REQUEST["extension_uuids"] ?? ''; $destination_number = $_REQUEST["destination_number"] ?? ''; $context = $_REQUEST["context"] ?? ''; $start_stamp_begin = $_REQUEST["start_stamp_begin"] ?? ''; @@ -197,7 +197,7 @@ if (!$permission['xml_cdr_domain'] && isset($_SESSION['user']['extension']) && is_array($_SESSION['user']['extension'])) { foreach ($_SESSION['user']['extension'] as $row) { if (is_uuid($row['extension_uuid'])) { - $extension_uuids[] = $row['extension_uuid']; + $assigned_extension_uuids[] = $row['extension_uuid']; } } } @@ -209,7 +209,11 @@ $param .= "&caller_id_name=".urlencode($caller_id_name ?? ''); $param .= "&caller_id_number=".urlencode($caller_id_number ?? ''); $param .= "&caller_destination=".urlencode($caller_destination ?? ''); - $param .= "&extension_uuid=".urlencode($extension_uuid ?? ''); + foreach ($extension_uuids as $key => $value) { + if (is_uuid($value)) { + $param .= "&extension_uuids[]=".urlencode($value); + } + } $param .= "&destination_number=".urlencode($destination_number ?? ''); $param .= "&context=".urlencode($context ?? ''); $param .= "&start_stamp_begin=".urlencode($start_stamp_begin ?? ''); @@ -379,8 +383,8 @@ $parameters['domain_uuid'] = $domain_uuid; } if (!$permission['xml_cdr_domain']) { //only show the user their calls - if (isset($extension_uuids) && is_array($extension_uuids) && @sizeof($extension_uuids)) { - $sql .= "and (c.extension_uuid = '".implode("' or c.extension_uuid = '", $extension_uuids)."') \n"; + if (isset($assigned_extension_uuids) && is_array($assigned_extension_uuids) && @sizeof($assigned_extension_uuids)) { + $sql .= "and (c.extension_uuid = '".implode("' or c.extension_uuid = '", $assigned_extension_uuids)."') \n"; } else { $sql .= "and false \n"; @@ -422,10 +426,8 @@ $parameters['caller_id_number'] = $mod_caller_id_number; } } - - if (!empty($extension_uuid) && is_uuid($extension_uuid)) { - $sql .= "and e.extension_uuid = :extension_uuid \n"; - $parameters['extension_uuid'] = $extension_uuid; + if (!empty($extension_uuids)) { + $sql .= "and e.extension_uuid in ('".implode("','",$extension_uuids)."') \n"; } if (!empty($caller_destination)) { $mod_caller_destination = str_replace("*", "%", $caller_destination); diff --git a/app/xml_cdr/xml_cdr_search.php b/app/xml_cdr/xml_cdr_search.php index e691be8efd..59e8644880 100644 --- a/app/xml_cdr/xml_cdr_search.php +++ b/app/xml_cdr/xml_cdr_search.php @@ -174,21 +174,36 @@ echo " \n"; echo " \n"; echo " \n"; - echo " "; - echo " ".$text['label-extension'].""; //source number - echo " "; - echo " \n"; + + echo "
".$text['label-no_results']."
\n"; + + echo "
\n"; if (is_array($extensions) && @sizeof($extensions) != 0) { foreach ($extensions as $row) { - $selected = (!empty($caller_extension_uuid) && $row['extension_uuid'] == $caller_extension_uuid) ? "selected" : null; - echo " "; + echo " \n"; } } unset($sql, $parameters, $extensions, $row, $selected); - echo " \n"; - echo " "; - echo " "; + echo "
\n"; + echo "
\n"; + echo " \n"; + echo " \n"; + echo " \n"; + echo " \n"; echo " "; echo " ".$text['label-destination'].""; echo " "; diff --git a/resources/app_languages.php b/resources/app_languages.php index a2a0595fc7..41a9d87bb0 100644 --- a/resources/app_languages.php +++ b/resources/app_languages.php @@ -7947,3 +7947,30 @@ $text['label-ar']['tr-tr'] = "Arapça"; $text['label-ar']['zh-cn'] = "阿拉伯语"; $text['label-ar']['ja-jp'] = "アラビア語"; $text['label-ar']['ko-kr'] = "아랍어"; + +$text['label-no_results']['en-us'] = "No matches found"; +$text['label-no_results']['en-gb'] = "No matches found"; +$text['label-no_results']['ar-eg'] = "لم يتم العثور على نتائج"; +$text['label-no_results']['de-at'] = "Keine Übereinstimmungen gefunden"; +$text['label-no_results']['de-ch'] = "Keine Übereinstimmungen gefunden"; +$text['label-no_results']['de-de'] = "Keine Übereinstimmungen gefunden"; +$text['label-no_results']['el-gr'] = "Δεν βρέθηκαν αποτελέσματα"; +$text['label-no_results']['es-cl'] = "No se encontraron coincidencias"; +$text['label-no_results']['es-mx'] = "No se encontraron coincidencias"; +$text['label-no_results']['fr-ca'] = "Aucune correspondance trouvée"; +$text['label-no_results']['fr-fr'] = "Aucune correspondance trouvée"; +$text['label-no_results']['he-il'] = "לא נמצאו תוצאות"; +$text['label-no_results']['it-it'] = "Nessun risultato trovato"; +$text['label-no_results']['ka-ge'] = "არ ნახება შედეგები"; +$text['label-no_results']['nl-nl'] = "Geen overeenkomsten gevonden"; +$text['label-no_results']['pl-pl'] = "Nie znaleziono dopasowań"; +$text['label-no_results']['pt-br'] = "Nenhuma correspondência encontrada"; +$text['label-no_results']['pt-pt'] = "Não foram encontrados resultados"; +$text['label-no_results']['ro-ro'] = "Nu s-au găsit potriviri"; +$text['label-no_results']['ru-ru'] = "Совпадений не найдено"; +$text['label-no_results']['sv-se'] = "Inga träffar hittades"; +$text['label-no_results']['uk-ua'] = "Збігів знайдено не було"; +$text['label-no_results']['tr-tr'] = "Eşleşme bulunamadı"; +$text['label-no_results']['zh-cn'] = "未找到匹配项"; +$text['label-no_results']['ja-jp'] = "一致するものが見つかりません"; +$text['label-no_results']['ko-kr'] = "일치하는 항목이 없습니다"; diff --git a/themes/default/css.php b/themes/default/css.php index 725c17df4c..b33f607d43 100644 --- a/themes/default/css.php +++ b/themes/default/css.php @@ -3988,6 +3988,158 @@ else { //default: white opacity: 1.0; } +/* MULTI SELECT DROPDOWN ********************************************************/ + + .multiselect_container { + position: relative; + width: 200px; + user-select: none; + } + + .selected_values { + display: flex; + align-items: center; + flex-wrap: wrap; + cursor: pointer; + font-family: ; + font-size: ; + color: ; + text-align: left; + min-height: ; + min-width: ; + padding: 4px 6px; + margin: 1px; + border-width: ; + border-style: ; + border-color: ; + outline-width: ; + + outline-style: ; + + + outline-color: ; + + background: ; + + -webkit-box-shadow: ; + -moz-box-shadow: ; + box-shadow: ; + + + -moz-border-radius: ; + -webkit-border-radius: ; + -khtml-border-radius: ; + border-radius: ; + + + outline-radius: + + vertical-align: middle; + } + + .selected_values:hover { + border-color: ; + } + + .tag { + background: #007bff25; + color: #007bff; + padding: 2px 8px; + margin: 2px; + border-radius: 15px; + font-size: 13px; + display: flex; + align-items: center; + } + + .tag span { + margin-left: 5px; + cursor: pointer; + font-weight: bold; + color: #007bff; + } + + .tag span:hover { + color: #d32f2f; + } + + .placeholder_text { + color: ; + } + + .dropdown_list { + display: none; + position: absolute; + top: 100%; + left: 0; + right: 0; + background: ; + border-width: ; + border-style: ; + border-color: ; + border-top: none; + border-radius: 0 0 5px 5px; + box-shadow: 0 4px 8px rgba(0,0,0,0.1); + z-index: 1000; + max-height: 300px; + overflow-y: auto; + box-sizing: border-box; + margin: 0 1px; + } + + .dropdown_list.open { + display: block; + } + + .search_box { + background-color: ; + color: ; + width: 100%; + padding: 10px; + border: none; + border-bottom: 1px solid #00000010; + box-sizing: border-box; + font-size: 14px; + outline: none; + } + + .option_item { + display: flex; + align-items: center; + padding: 10px; + cursor: pointer; + border-bottom: 1px solid #00000010; + margin: 0; + } + + .option_item:hover { + background-color: ; + } + + .option_item input[type="checkbox"] { + margin-right: 10px; + transform: scale(1.2); + cursor: pointer; + } + + .hidden { + display: none !important; + } + + .no_results { + display: none; + padding: 15px; + text-align: center; + color: ; + } + - + {*//link to custom css file *} {if !empty($settings.theme.custom_css)} @@ -734,6 +734,146 @@ } {/literal} + //multi select box with search + {literal} + const container = document.querySelector('.multiselect_container'); + const trigger_btn = container.querySelector('.selected_values'); + const dropdown_list = container.querySelector('.dropdown_list'); + const search_input = container.querySelector('.search_box'); + const options_list = container.querySelector('.options_list'); + const no_results = container.querySelector('#no_results'); + const placeholder = container.querySelector('.placeholder_text'); + let is_open = false; + + //toggle Dropdown Open/Close + trigger_btn.addEventListener('click', (event) => { + event.stopPropagation(); + is_open = !is_open; + if (is_open) { + dropdown_list.classList.add('open'); + search_input.focus(); + } else { + dropdown_list.classList.remove('open'); + } + }); + + //close dropdown if clicked outside + document.addEventListener('click', (event) => { + if (!container.contains(event.target)) { + is_open = false; + dropdown_list.classList.remove('open'); + } + }); + + //prevent dropdown from closing when clicking inside the dropdown + dropdown_list.addEventListener('click', (event) => { + event.stopPropagation(); + }); + + //handle Search Filtering + search_input.addEventListener('input', (event) => { + const search_term = event.target.value.toLowerCase(); + const option_items = document.querySelectorAll('.option_item'); + let visible_count = 0; + + option_items.forEach(item => { + const text = item.innerText.toLowerCase(); + + if (text.includes(search_term)) { + item.classList.remove('hidden'); + visible_count++; + } else { + item.classList.add('hidden'); + } + }); + + if (visible_count === 0) { + no_results.style.display = 'block'; + } else { + no_results.style.display = 'none'; + } + }); + + //handle Checkbox Selection + container.addEventListener('change', (event) => { + if (event.target.type === 'checkbox') { + update_selected_values(); + } + }); + + //also handle clicking the text part of the option + container.addEventListener('click', (event) => { + if (event.target.classList.contains('option_item')) { + const checkbox = event.target.querySelector('input[type="checkbox"]'); + if (checkbox) { + checkbox.checked = !checkbox.checked; + update_selected_values(); + } + } + }); + + //update display Logic (tags & hidden input) + function update_selected_values() { + const checked_boxes = document.querySelectorAll('.option_item input:checked'); + const selected_count = checked_boxes.length; + + //update visual tags + if (selected_count === 0) { + placeholder.style.display = 'block'; + trigger_btn.innerHTML = `{/literal}{$text.label_select}{literal}...`; + } else { + placeholder.style.display = 'none'; + let html = ''; + + checked_boxes.forEach(box => { + const label = box.parentElement.innerText; + const clean_label = box.parentElement.textContent.trim(); + + // Create a hidden input for each selected tag + create_hidden_input_for_tag(clean_label, box.value); + + html += ``; + html += ` ${clean_label}`; + html += ` ×`; + html += ``; + }); + + trigger_btn.innerHTML = html; + } + } + + //helper function to remove a tag when clicked (External to scope) + window.remove_option = function(value) { + const checkbox = document.querySelector(`input[value="${value}"]`); + if (checkbox) { + checkbox.checked = false; + + // Remove the hidden input corresponding to this tag + const hidden_input = document.querySelector(`input[name="extension_uuids[]"][value="${value}"]`); + if (hidden_input) { + hidden_input.remove(); + } + + update_selected_values(); + } + }; + + // Function to create a hidden input for each selected tag + function create_hidden_input_for_tag(label, value) { + const existing_hidden_input = document.querySelector(`input[name="extension_uuids[]"][value="${value}"]`); + if (!existing_hidden_input) { + const hidden_input = document.createElement('input'); + hidden_input.type = 'hidden'; + hidden_input.name = 'extension_uuids[]'; + hidden_input.value = value; + container.appendChild(hidden_input); + } + } + + //initialize state + update_selected_values(); + {/literal} + {literal} }); //document ready end {/literal}