Skip to content

Commit

Permalink
Filters UI_columns_filters #953
Browse files Browse the repository at this point in the history
jokob-sk committed Jan 25, 2025
1 parent 890e533 commit 4443c69
Showing 23 changed files with 448 additions and 12 deletions.
14 changes: 14 additions & 0 deletions front/css/app.css
Original file line number Diff line number Diff line change
@@ -1245,6 +1245,20 @@ input[readonly] {
/* Devices page */
/* ----------------------------------------------------------------- */


#columnFilters {
display: flex;
flex-wrap: wrap;
gap: 10px; /* Add spacing between items */
}

.filter-group {
flex: 1 100px;
box-sizing: border-box; /* Ensure padding and borders are included in the width */
padding: 1em;
padding-top: 0;
}

.modal-header .close
{
display: flex;
207 changes: 200 additions & 7 deletions front/devices.php
Original file line number Diff line number Diff line change
@@ -32,17 +32,16 @@
<section class="content">

<!-- Tile toggle cards ------------------------------------------------------- -->
<div class="row" id="TileCards">
<div class="row " id="TileCards">
<!-- Placeholder ------------------------------------------------------- -->

</div>

<!-- Device presence / Activity Chart ------------------------------------------------------- -->

<div class="row" id="DevicePresence">
<div class="col-md-12">
<div class="box" id="clients">
<div class="box-header with-border">
<div class="box-header ">
<h3 class="box-title"><?= lang('Device_Shortcut_OnlineChart');?> </h3>
</div>
<div class="box-body">
@@ -59,6 +58,15 @@
</div>
</div>

<!-- Device Filters ------------------------------------------------------- -->
<div class="box box-aqua hidden" id="columnFiltersWrap">
<div class="box-header ">
<h3 class="box-title"><?= lang('Devices_Filters');?> </h3>
</div>
<!-- Placeholder ------------------------------------------------------- -->
<div id="columnFilters" ></div>
</div>

<!-- datatable ------------------------------------------------------------- -->
<div class="row">
<div class="col-xs-12">
@@ -133,6 +141,8 @@ function main () {

showSpinner();

initFilters();

// render tiles
getDevicesTotals();

@@ -319,6 +329,184 @@ function renderInfoboxes(customData) {
}
}

// -----------------------------------------------------------------------------
//Render filters if specified
let columnFilters = [];

function initFilters() {
// Attempt to fetch data
$.ajax({
url: '/php/server/query_json.php',
type: "GET",
dataType: "json",
data: {
file: 'table_devices_filters.json', // Pass the file parameter
nocache: Date.now() // Prevent caching with a timestamp
},
success: function(response) {
if (response && response.data) {

let resultJSON = response.data;

// Save the result to cache
setCache("devicesFilters", JSON.stringify(resultJSON));

// Get the displayed filters from settings
const displayedFilters = createArray(getSetting("UI_columns_filters"));

// Clear any existing filters in the DOM
$('#columnFilters').empty();

console.log(displayedFilters);

// Ensure displayedFilters is an array and not empty
if (Array.isArray(displayedFilters) && displayedFilters.length > 0) {
$('#columnFiltersWrap').removeClass("hidden");

displayedFilters.forEach(columnHeaderStringKey => {
// Get the column name using the mapping function
const columnName = getColumnNameFromLangString(columnHeaderStringKey);

// Ensure columnName is valid before proceeding
if (columnName) {
// Add the filter to the columnFilters array as [columnName, columnHeaderStringKey]
columnFilters.push([columnName, columnHeaderStringKey]);
} else {
console.warn(`Invalid column header string key: ${columnHeaderStringKey}`);
}
});

// Filter resultJSON to include only entries with columnName in columnFilters
resultJSON = resultJSON.filter(entry =>
columnFilters.some(filter => filter[0] === entry.columnName)
);

// Expand resultJSON to include the columnHeaderStringKey
resultJSON.forEach(entry => {
// Find the matching columnHeaderStringKey from columnFilters
const matchingFilter = columnFilters.find(filter => filter[0] === entry.columnName);

// Add the columnHeaderStringKey to the entry
if (matchingFilter) {
entry['columnHeaderStringKey'] = matchingFilter[1];
}
});

console.log(resultJSON);

// Transforming the data
const transformed = {
filters: []
};

// Group data by columnName
resultJSON.forEach(entry => {
const existingFilter = transformed.filters.find(filter => filter.column === entry.columnName);

if (existingFilter) {
// Add the unique columnValue to options if not already present
if (!existingFilter.options.includes(entry.columnValue)) {
existingFilter.options.push(entry.columnValue);
}
} else {
// Create a new filter entry
transformed.filters.push({
column: entry.columnName,
headerKey: entry.columnHeaderStringKey,
options: [entry.columnValue]
});
}
});

// Sort options alphabetically for better readability
transformed.filters.forEach(filter => {
filter.options.sort();
});

// Output the result
transformedJson = transformed

// Process the fetched data
renderFilters(transformedJson);
} else {
console.log("No filters to display.");
}
} else {
console.error("Invalid response format from API");
}
},
error: function(xhr, status, error) {
console.error("Failed to fetch devices data 'table_devices_filters.json':", error);
}
});
}


// -------------------------------------------
// Server side component
function renderFilters(customData) {

console.log(JSON.stringify(customData));

// Load filter data from the JSON file
$.ajax({
url: 'php/components/devices_filters.php', // PHP script URL
data: { filterObject: JSON.stringify(customData) }, // Send customData as JSON
type: 'POST',
dataType: 'html',
success: function(response) {
console.log(response);

$('#columnFilters').html(response); // Replace container content with fetched HTML
$('#columnFilters').removeClass('hidden'); // Show the filters container

// Trigger the draw after select change
$('.filter-dropdown').on('change', function() {
// Collect filters
const columnFilters = collectFilters();

// Update DataTable with the new filters or search value (if applicable)
$('#tableDevices').DataTable().draw();

// Optionally, apply column filters (if using filters for individual columns)
const table = $('#tableDevices').DataTable();
table.columnFilters = columnFilters; // Apply your column filters logic
table.draw();
});

},
error: function(xhr, status, error) {
console.error('Error fetching filters:', error);
}
});
}

// -------------------------------------------
// Function to collect filters
function collectFilters() {
const columnFilters = [];

// Loop through each filter group
document.querySelectorAll('.filter-group').forEach(filterGroup => {
const dropdown = filterGroup.querySelector('.filter-dropdown');

if (dropdown) {
const filterColumn = dropdown.getAttribute('data-column');
const filterValue = dropdown.value;

if (filterValue && filterColumn) {
columnFilters.push({
filterColumn: filterColumn,
filterValue: filterValue
});
}
}
});

return columnFilters;
}


// -----------------------------------------------------------------------------
// Map column index to column name for GraphQL query
function mapColumnIndexToFieldName(index, tableColumnVisible) {
@@ -411,8 +599,6 @@ function initializeDatatable (status) {
}

// todo: dynamically filter based on status


var table = $('#tableDevices').DataTable({
"serverSide": true,
"processing": true,
@@ -469,7 +655,13 @@ function initializeDatatable (status) {
`;

console.log(d);


// Handle empty filters
let columnFilters = collectFilters();
if (columnFilters.length === 0) {
columnFilters = [];
}


// Prepare query variables for pagination, sorting, and search
let query = {
@@ -484,7 +676,8 @@ function initializeDatatable (status) {
"order": d.order[0].dir.toUpperCase() // Sort direction (ASC/DESC)
}] : [], // Default to an empty array if no sorting is defined
"search": d.search.value, // Search query
"status": deviceStatus
"status": deviceStatus,
"filters" : columnFilters
}

}
44 changes: 44 additions & 0 deletions front/js/ui_components.js
Original file line number Diff line number Diff line change
@@ -508,8 +508,52 @@ function showIconSelection() {

}

// "Device_TableHead_Owner",
// "Device_TableHead_Type",
// "Device_TableHead_Group",
// "Device_TableHead_Status",
// "Device_TableHead_Location",
// "Device_TableHead_Vendor",
// "Device_TableHead_SyncHubNodeName",
// "Device_TableHead_NetworkSite",
// "Device_TableHead_SSID",
// "Device_TableHead_SourcePlugin"

// -----------------------------------------------------------------------------
// Get teh correct db column code name based on table header title string
function getColumnNameFromLangString(headStringKey) {
columnNameMap = {
"Device_TableHead_Name": "devName",
"Device_TableHead_Owner": "devOwner",
"Device_TableHead_Type": "devType",
"Device_TableHead_Icon": "devIcon",
"Device_TableHead_Favorite": "devFavorite",
"Device_TableHead_Group": "devGroup",
"Device_TableHead_FirstSession": "devFirstConnection",
"Device_TableHead_LastSession": "devLastConnection",
"Device_TableHead_LastIP": "devLastIP",
"Device_TableHead_MAC": "devMac",
"Device_TableHead_Status": "devStatus",
"Device_TableHead_MAC_full": "devMac",
"Device_TableHead_LastIPOrder": "devIpLong",
"Device_TableHead_Rowid": "rowid",
"Device_TableHead_Parent_MAC": "devParentMAC",
"Device_TableHead_Connected_Devices": "devParentChildrenCount",
"Device_TableHead_Location": "devLocation",
"Device_TableHead_Vendor": "devVendor",
"Device_TableHead_Port": "devParentPort",
"Device_TableHead_GUID": "devGUID",
"Device_TableHead_SyncHubNodeName": "devSyncHubNode",
"Device_TableHead_NetworkSite": "devSite",
"Device_TableHead_SSID": "devSSID",
"Device_TableHead_SourcePlugin": "devSourcePlugin",
"Device_TableHead_PresentLastScan": "devPresentLastScan",
"Device_TableHead_AlertDown": "devAlertDown",
"Device_TableHead_CustomProps": "devCustomProps"
};

return columnNameMap[headStringKey] || "";
}


// -----------------------------------------------------------------------------
52 changes: 52 additions & 0 deletions front/php/components/devices_filters.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

//------------------------------------------------------------------------------
// Check if authenticated
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/security.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/server/db.php';
require_once $_SERVER['DOCUMENT_ROOT'] . '/php/templates/language/lang.php';

// Function to render a filter dropdown
function renderFilterDropdown($headerKey, $columnName, $values) {
// Generate dropdown options
$optionsHtml = '<option value="" selected>All</option>'; // Default "All" option
foreach ($values as $value) {
$escapedValue = htmlspecialchars($value);
$optionsHtml .= '<option value="' . $escapedValue . '">' . $escapedValue . '</option>';
}

// Generate the dropdown HTML
return '
<div class="filter-group">
<label for="filter_' . htmlspecialchars($columnName) . '">' . lang($headerKey) . '</label>
<select id="filter_' . htmlspecialchars($columnName) . '" class="filter-dropdown" data-column="' . htmlspecialchars($columnName) . '">
' . $optionsHtml . '
</select>
</div>';
}

// Get filterObject from POST data
$filterObject = isset($_POST['filterObject']) ? json_decode($_POST['filterObject'], true) : [];

// Validate filterObject structure
if (!isset($filterObject['filters']) || !is_array($filterObject['filters'])) {
echo '<p class="error">Invalid filter data provided.</p>';
exit();
}

// Generate HTML for each filter in the filterObject
$html = '';
foreach ($filterObject['filters'] as $filter) {
if (isset($filter['column'], $filter['headerKey'], $filter['options'])) {
$html .= renderFilterDropdown($filter['headerKey'], $filter['column'], $filter['options']);
} else {
// Skip invalid entries
continue;
}
}

// Output the generated HTML
echo $html;
exit();

?>
1 change: 1 addition & 0 deletions front/php/templates/language/ar_ar.json
Original file line number Diff line number Diff line change
@@ -242,6 +242,7 @@
"Device_Tablelenght": "",
"Device_Tablelenght_all": "",
"Device_Title": "",
"Devices_Filters": "",
"Donations_Others": "",
"Donations_Platforms": "",
"Donations_Text": "",
Loading

0 comments on commit 4443c69

Please sign in to comment.