mirror of
https://github.com/getnamingo/registry.git
synced 2025-05-15 09:07:00 +02:00
Further work on issue 192
This commit is contained in:
parent
561f8f514e
commit
79eca91eb1
5 changed files with 337 additions and 69 deletions
|
@ -186,4 +186,184 @@ class DapiController extends Controller
|
|||
$response->getBody()->write(json_encode($payload, JSON_UNESCAPED_UNICODE));
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function listApplications(Request $request, Response $response): Response
|
||||
{
|
||||
$params = $request->getQueryParams();
|
||||
$db = $this->container->get('db');
|
||||
|
||||
// Map fields to fully qualified columns
|
||||
$allowedFieldsMap = [
|
||||
'name' => 'd.name',
|
||||
'crdate' => 'd.crdate',
|
||||
'exdate' => 'd.exdate',
|
||||
'registrant_identifier' => 'c.identifier'
|
||||
];
|
||||
|
||||
// --- SORTING ---
|
||||
$sortField = 'd.crdate'; // default
|
||||
$sortDir = 'desc';
|
||||
if (!empty($params['order'])) {
|
||||
$orderParts = explode(',', $params['order']);
|
||||
if (count($orderParts) === 2) {
|
||||
$fieldCandidate = preg_replace('/[^a-zA-Z0-9_]/', '', $orderParts[0]);
|
||||
if (array_key_exists($fieldCandidate, $allowedFieldsMap)) {
|
||||
$sortField = $allowedFieldsMap[$fieldCandidate];
|
||||
}
|
||||
$sortDir = strtolower($orderParts[1]) === 'asc' ? 'asc' : 'desc';
|
||||
}
|
||||
}
|
||||
|
||||
// --- PAGINATION ---
|
||||
$page = 1;
|
||||
$size = 10;
|
||||
if (!empty($params['page'])) {
|
||||
$pageParts = explode(',', $params['page']);
|
||||
if (count($pageParts) === 2) {
|
||||
$pageNum = (int)$pageParts[0];
|
||||
$pageSize = (int)$pageParts[1];
|
||||
if ($pageNum > 0) {
|
||||
$page = $pageNum;
|
||||
}
|
||||
if ($pageSize > 0) {
|
||||
$size = $pageSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
$offset = ($page - 1) * $size;
|
||||
|
||||
// --- FILTERING ---
|
||||
$whereClauses = [];
|
||||
$bindParams = [];
|
||||
foreach ($params as $key => $value) {
|
||||
if (preg_match('/^filter\d+$/', $key)) {
|
||||
$fParts = explode(',', $value);
|
||||
if (count($fParts) === 3) {
|
||||
list($fField, $fOp, $fVal) = $fParts;
|
||||
$fField = preg_replace('/[^a-zA-Z0-9_]/', '', $fField);
|
||||
|
||||
// Ensure the field is allowed and fully qualify it
|
||||
if (!array_key_exists($fField, $allowedFieldsMap)) {
|
||||
// Skip unknown fields
|
||||
continue;
|
||||
}
|
||||
$column = $allowedFieldsMap[$fField];
|
||||
|
||||
switch ($fOp) {
|
||||
case 'eq':
|
||||
$whereClauses[] = "$column = :f_{$key}";
|
||||
$bindParams["f_{$key}"] = $fVal;
|
||||
break;
|
||||
case 'cs':
|
||||
// If searching in 'name' and user might enter Cyrillic
|
||||
if ($fField === 'name') {
|
||||
// Convert to punycode
|
||||
$punyVal = idn_to_ascii($fVal, IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
|
||||
if ($punyVal !== false && $punyVal !== $fVal) {
|
||||
// Search for both punycode and original term
|
||||
// (d.name LIKE '%cyrillic%' OR d.name LIKE '%punycode%')
|
||||
$whereClauses[] = "($column LIKE :f_{$key}_original OR $column LIKE :f_{$key}_puny)";
|
||||
$bindParams["f_{$key}_original"] = "%$fVal%";
|
||||
$bindParams["f_{$key}_puny"] = "%$punyVal%";
|
||||
} else {
|
||||
// Just search normally
|
||||
$whereClauses[] = "$column LIKE :f_{$key}";
|
||||
$bindParams["f_{$key}"] = "%$fVal%";
|
||||
}
|
||||
} else {
|
||||
// Non-name field, just search as usual
|
||||
$whereClauses[] = "$column LIKE :f_{$key}";
|
||||
$bindParams["f_{$key}"] = "%$fVal%";
|
||||
}
|
||||
break;
|
||||
case 'sw':
|
||||
$whereClauses[] = "$column LIKE :f_{$key}";
|
||||
$bindParams["f_{$key}"] = "$fVal%";
|
||||
break;
|
||||
case 'ew':
|
||||
$whereClauses[] = "$column LIKE :f_{$key}";
|
||||
$bindParams["f_{$key}"] = "%$fVal";
|
||||
break;
|
||||
// Add other cases if needed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Base SQL
|
||||
$sqlBase = "
|
||||
FROM application d
|
||||
LEFT JOIN contact c ON d.registrant = c.id
|
||||
LEFT JOIN application_status ds ON d.id = ds.domain_id
|
||||
";
|
||||
|
||||
$sqlWhere = '';
|
||||
if (!empty($whereClauses)) {
|
||||
$sqlWhere = "WHERE " . implode(" OR ", $whereClauses);
|
||||
}
|
||||
|
||||
// Count total results
|
||||
$totalSql = "SELECT COUNT(DISTINCT d.id) AS total $sqlBase $sqlWhere";
|
||||
$totalCount = $db->selectValue($totalSql, $bindParams);
|
||||
|
||||
// Data query
|
||||
$selectFields = "
|
||||
d.id,
|
||||
d.name,
|
||||
d.crdate,
|
||||
d.exdate,
|
||||
d.phase_type,
|
||||
c.identifier AS registrant_identifier,
|
||||
GROUP_CONCAT(ds.status) AS application_status
|
||||
";
|
||||
|
||||
$dataSql = "
|
||||
SELECT $selectFields
|
||||
$sqlBase
|
||||
$sqlWhere
|
||||
GROUP BY d.id
|
||||
ORDER BY $sortField $sortDir
|
||||
LIMIT $offset, $size
|
||||
";
|
||||
|
||||
$records = $db->select($dataSql, $bindParams);
|
||||
|
||||
// Ensure records is always an array
|
||||
if (!$records) {
|
||||
$records = [];
|
||||
}
|
||||
|
||||
// Format API results
|
||||
foreach ($records as &$row) {
|
||||
// Check if name is punycode by checking if it starts with 'xn--'
|
||||
if (stripos($row['name'], 'xn--') === 0) {
|
||||
// Convert punycode to Unicode and store it in 'name'
|
||||
$unicode_name = idn_to_utf8($row['name'], IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
|
||||
$row['name_o'] = $row['name']; // Keep the original punycode in 'name_o'
|
||||
$row['name'] = $unicode_name; // Store the Unicode version in 'name'
|
||||
} else {
|
||||
// For regular names, both 'name' and 'name_o' are the same
|
||||
$row['name_o'] = $row['name'];
|
||||
}
|
||||
|
||||
// Format application_status as array of {status: '...'} objects
|
||||
if (!empty($row['application_status'])) {
|
||||
$statuses = explode(',', $row['application_status']);
|
||||
$row['application_status'] = array_map(function($status) {
|
||||
return ['status' => $status];
|
||||
}, $statuses);
|
||||
} else {
|
||||
$row['application_status'] = [];
|
||||
}
|
||||
}
|
||||
|
||||
$payload = [
|
||||
'records' => $records,
|
||||
'results' => $totalCount
|
||||
];
|
||||
|
||||
$response = $response->withHeader('Content-Type', 'application/json; charset=UTF-8');
|
||||
$response->getBody()->write(json_encode($payload, JSON_UNESCAPED_UNICODE));
|
||||
return $response;
|
||||
}
|
||||
}
|
|
@ -9,8 +9,9 @@
|
|||
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
function domainLinkFormatter(cell){
|
||||
var value = cell.getValue();
|
||||
return `<a href="/application/view/${value}" style="font-weight:bold;">${value}</a>`;
|
||||
var displayName = cell.getValue();
|
||||
var punycodeName = cell.getRow().getData().name_o;
|
||||
return `<a href="/domain/view/${punycodeName}" style="font-weight:bold;">${displayName}</a>`;
|
||||
}
|
||||
|
||||
function actionsFormatter(cell, formatterParams, onRendered) {
|
||||
|
@ -21,10 +22,10 @@
|
|||
var isInvalid = rowData.application_status.some(statusObj => statusObj.status && statusObj.status.includes('invalid'));
|
||||
|
||||
if (!isRejected && !isInvalid) {
|
||||
actionButtons += `<a class="btn btn-outline-green btn-icon" href="application/approve/${cell.getRow().getData().name}" title="Approve Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l5 5l10 -10" /></svg></a> `;
|
||||
actionButtons += `<a class="btn btn-outline-warning btn-icon" href="application/reject/${cell.getRow().getData().name}" title="Reject Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M5.7 5.7l12.6 12.6" /></svg></a> `;
|
||||
actionButtons += `<a class="btn btn-outline-info btn-icon" href="application/update/${cell.getRow().getData().name}" title="Update Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a> `;
|
||||
actionButtons += `<a class="btn btn-outline-danger btn-icon delete-btn" id="delete-btn" href="javascript:void(0);" data-delete-url="application/delete/${cell.getRow().getData().name}" title="Delete Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 7h16"></path><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path><path d="M10 12l4 4m0 -4l-4 4"></path></svg></a>`;
|
||||
actionButtons += `<a class="btn btn-outline-green btn-icon" href="application/approve/${cell.getRow().getData().name_o}" title="Approve Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l5 5l10 -10" /></svg></a> `;
|
||||
actionButtons += `<a class="btn btn-outline-warning btn-icon" href="application/reject/${cell.getRow().getData().name_o}" title="Reject Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M5.7 5.7l12.6 12.6" /></svg></a> `;
|
||||
actionButtons += `<a class="btn btn-outline-info btn-icon" href="application/update/${cell.getRow().getData().name_o}" title="Update Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M7 7h-1a2 2 0 0 0 -2 2v9a2 2 0 0 0 2 2h9a2 2 0 0 0 2 -2v-1"></path><path d="M20.385 6.585a2.1 2.1 0 0 0 -2.97 -2.97l-8.415 8.385v3h3l8.385 -8.415z"></path><path d="M16 5l3 3"></path></svg></a> `;
|
||||
actionButtons += `<a class="btn btn-outline-danger btn-icon delete-btn" id="delete-btn" href="javascript:void(0);" data-delete-url="application/delete/${cell.getRow().getData().name_o}" title="Delete Application"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M4 7h16"></path><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12"></path><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3"></path><path d="M10 12l4 4m0 -4l-4 4"></path></svg></a>`;
|
||||
} else {
|
||||
actionButtons += `<strong class="text-success"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M9 12l2 2l4 -4" /></svg> Completed</strong>`;
|
||||
}
|
||||
|
@ -71,21 +72,30 @@
|
|||
pagination: true,
|
||||
paginationMode: "remote",
|
||||
paginationSize: 10,
|
||||
ajaxURL: "/api/records/application",
|
||||
ajaxParams: {
|
||||
join: "contact",
|
||||
join: "application_status"
|
||||
},
|
||||
sortMode: "remote",
|
||||
ajaxURL: "/dapi/applications",
|
||||
ajaxURLGenerator: function(url, config, params) {
|
||||
var queryParts = ["join=contact", "join=application_status"];
|
||||
var queryParts = [];
|
||||
|
||||
// Handle search term
|
||||
if (searchTerm) {
|
||||
queryParts.push("filter1=name,cs," + encodeURIComponent(searchTerm));
|
||||
queryParts.push("filter2=crdate,cs," + encodeURIComponent(searchTerm));
|
||||
queryParts.push("filter3=phase_type,cs," + encodeURIComponent(searchTerm));
|
||||
queryParts.push("filter4=registrant_identifier,cs," + encodeURIComponent(searchTerm));
|
||||
queryParts.push("filter5=name_o,cs," + encodeURIComponent(searchTerm));
|
||||
}
|
||||
|
||||
queryParts.push("order=id");
|
||||
// Handle sorting from Tabulator
|
||||
if (params.sort && params.sort.length > 0) {
|
||||
var sorter = params.sort[0]; // single-column sorting
|
||||
var sortField = encodeURIComponent(sorter.field);
|
||||
var sortDir = (sorter.dir === "asc" ? "asc" : "desc");
|
||||
queryParts.push("order=" + sortField + "," + sortDir);
|
||||
} else {
|
||||
// fallback default order if no sorters
|
||||
queryParts.push("order=crdate,desc");
|
||||
}
|
||||
|
||||
// Include pagination parameters
|
||||
if (params.page) {
|
||||
|
@ -106,9 +116,6 @@
|
|||
return { last_page: 1, data: [] };
|
||||
}
|
||||
},
|
||||
dataReceiveParams: {
|
||||
"last_page": "results", // Mapping 'results' to 'last_page'
|
||||
},
|
||||
layout:"fitDataFill",
|
||||
responsiveLayout: "collapse",
|
||||
responsiveLayoutCollapseStartOpen:false,
|
||||
|
@ -116,11 +123,11 @@
|
|||
placeholder: "{{ __('No Data') }}",
|
||||
columns:[
|
||||
{formatter:"responsiveCollapse", width:30, minWidth:30, hozAlign:"center", resizable:false, headerSort:false, responsive:0},
|
||||
{title:"{{ __('Name') }}", field:"name", width:250, resizable:false, headerSort:false, formatter: domainLinkFormatter, responsive:0},
|
||||
{title:"{{ __('Applicant') }}", width:150, field:"registrant.identifier", resizable:false, headerSort:false, responsive:2},
|
||||
{title:"{{ __('Creation Date') }}", width:250, minWidth:150, field:"crdate", resizable:false, headerSort:false, responsive:2},
|
||||
{title:"{{ __('Phase') }}", width:150, minWidth:100, field:"phase_type", formatter: phaseFormatter, resizable:false, headerSort:false, responsive:2},
|
||||
{title:"{{ __('Status') }}", width:200, field:"application_status", formatter: statusFormatter, resizable:false, headerSort:false, download:false, responsive:2},
|
||||
{title:"{{ __('Name') }}", field:"name", width:250, resizable:false, headerSort:true, formatter: domainLinkFormatter, responsive:0},
|
||||
{title:"{{ __('Applicant') }}", width:150, field:"registrant_identifier", resizable:false, headerSort:true, responsive:2},
|
||||
{title:"{{ __('Creation Date') }}", width:250, minWidth:150, field:"crdate", resizable:false, headerSort:true, responsive:2},
|
||||
{title:"{{ __('Phase') }}", width:150, minWidth:100, field:"phase_type", formatter: phaseFormatter, resizable:false, headerSort:true, responsive:2},
|
||||
{title:"{{ __('Status') }}", width:200, field:"application_status", formatter: statusFormatter, resizable:false, headerSort:true, download:false, responsive:2},
|
||||
{title: "{{ __('Actions') }}", formatter: actionsFormatter, resizable:false, headerSort:false, download:false, hozAlign: "center", responsive:0, cellClick: function(e, cell){
|
||||
if (e.target.closest('.delete-btn')) {
|
||||
e.preventDefault(); // Prevent the default link behavior
|
||||
|
@ -139,8 +146,13 @@
|
|||
]
|
||||
});
|
||||
var searchInput = document.getElementById("search-input");
|
||||
let searchTimeout;
|
||||
|
||||
searchInput.addEventListener("input", function () {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
updateSearchTerm(searchInput.value);
|
||||
}, 300); // 300ms delay
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
<script src="/assets/js/jspdf.plugin.autotable.min.js" defer></script>
|
||||
<script>
|
||||
var table;
|
||||
|
||||
document.addEventListener("DOMContentLoaded", function(){
|
||||
function registrarLinkFormatter(cell){
|
||||
var value = cell.getValue();
|
||||
|
@ -27,13 +28,59 @@
|
|||
|
||||
currency = "{{ currency }} ";
|
||||
|
||||
var searchTerm = ""; // global variable to hold the search term
|
||||
|
||||
function updateSearchTerm(term) {
|
||||
searchTerm = term;
|
||||
table.replaceData();
|
||||
}
|
||||
|
||||
table = new Tabulator("#registrarTable", {
|
||||
ajaxURL:"/api/records/registrar", // Set the URL for your JSON data
|
||||
ajaxConfig:"GET",
|
||||
pagination:"local",
|
||||
pagination: true,
|
||||
paginationMode: "remote",
|
||||
paginationSize: 10,
|
||||
sortMode: "remote",
|
||||
ajaxURL:"/api/records/registrar",
|
||||
ajaxURLGenerator: function(url, config, params) {
|
||||
var queryParts = [];
|
||||
|
||||
// Handle search term
|
||||
if (searchTerm) {
|
||||
queryParts.push("filter1=name,cs," + encodeURIComponent(searchTerm));
|
||||
queryParts.push("filter2=iana_id,cs," + encodeURIComponent(searchTerm));
|
||||
queryParts.push("filter3=email,cs," + encodeURIComponent(searchTerm));
|
||||
queryParts.push("filter4=accountBalance,cs," + encodeURIComponent(searchTerm));
|
||||
}
|
||||
|
||||
// Handle sorting from Tabulator
|
||||
if (params.sort && params.sort.length > 0) {
|
||||
var sorter = params.sort[0]; // single-column sorting
|
||||
var sortField = encodeURIComponent(sorter.field);
|
||||
var sortDir = (sorter.dir === "asc" ? "asc" : "desc");
|
||||
queryParts.push("order=" + sortField + "," + sortDir);
|
||||
} else {
|
||||
// fallback default order if no sorters
|
||||
queryParts.push("order=name,asc");
|
||||
}
|
||||
|
||||
// Include pagination parameters
|
||||
if (params.page) {
|
||||
queryParts.push("page=" + params.page + "," + params.size);
|
||||
}
|
||||
|
||||
return url + "?" + queryParts.join("&");
|
||||
},
|
||||
ajaxResponse: function(url, params, response) {
|
||||
return response.records;
|
||||
if (response && Array.isArray(response.records) && typeof response.results === 'number') {
|
||||
var lastPage = Math.ceil(response.results / this.options.paginationSize);
|
||||
return {
|
||||
last_page: lastPage, // Calculated total number of pages
|
||||
data: response.records, // Data for the current page
|
||||
};
|
||||
} else {
|
||||
console.error('Unexpected response format', response);
|
||||
return { last_page: 1, data: [] };
|
||||
}
|
||||
},
|
||||
layout:"fitDataFill",
|
||||
responsiveLayout: "collapse",
|
||||
|
@ -45,7 +92,7 @@
|
|||
{title:"{{ __('Name') }}", field:"name", width:200, resizable:false, headerSort:true, formatter: registrarLinkFormatter, responsive:0},
|
||||
{title:"IANA ID", field:"iana_id", width:300, resizable:false, headerSort:true, responsive:2},
|
||||
{title:"{{ __('Email') }}", field:"email", width:300, resizable:false, headerSort:true, responsive:2},
|
||||
{title:"{{ __('Balance') }}", field:"accountBalance", width:300, resizable:false, headerSort:false, download:false, responsive:2, formatter:"money", formatterParams:{
|
||||
{title:"{{ __('Balance') }}", field:"accountBalance", width:300, resizable:false, headerSort:true, responsive:2, formatter:"money", formatterParams:{
|
||||
decimal:".",
|
||||
thousand:" ",
|
||||
symbol:currency,
|
||||
|
@ -55,21 +102,13 @@
|
|||
]
|
||||
});
|
||||
var searchInput = document.getElementById("search-input");
|
||||
searchInput.addEventListener("input", function () {
|
||||
var term = searchInput.value.toLowerCase();
|
||||
let searchTimeout;
|
||||
|
||||
if (term) { // Only apply filter when there's a term to search for
|
||||
table.setFilter(function (data) {
|
||||
return (
|
||||
String(data.name).toLowerCase().includes(term) ||
|
||||
String(data.iana_id).toLowerCase().includes(term) ||
|
||||
String(data.email).toLowerCase().includes(term) ||
|
||||
String(data.accountBalance).toString().toLowerCase().includes(term)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
table.clearFilter(); // Clear the filter when the search box is emptied
|
||||
}
|
||||
searchInput.addEventListener("input", function () {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
updateSearchTerm(searchInput.value);
|
||||
}, 300); // 300ms delay
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -53,13 +53,58 @@
|
|||
}
|
||||
}
|
||||
|
||||
var searchTerm = ""; // global variable to hold the search term
|
||||
|
||||
function updateSearchTerm(term) {
|
||||
searchTerm = term;
|
||||
table.replaceData();
|
||||
}
|
||||
|
||||
table = new Tabulator("#userTable", {
|
||||
ajaxURL:"/api/records/users", // Set the URL for your JSON data
|
||||
ajaxConfig:"GET",
|
||||
pagination:"local",
|
||||
pagination: true,
|
||||
paginationMode: "remote",
|
||||
paginationSize: 10,
|
||||
sortMode: "remote",
|
||||
ajaxURL: "/api/records/users",
|
||||
ajaxURLGenerator: function(url, config, params) {
|
||||
var queryParts = [];
|
||||
|
||||
// Handle search term
|
||||
if (searchTerm) {
|
||||
queryParts.push("filter1=username,cs," + encodeURIComponent(searchTerm));
|
||||
queryParts.push("filter2=email,cs," + encodeURIComponent(searchTerm));
|
||||
queryParts.push("filter3=roles_mask,cs," + encodeURIComponent(searchTerm));
|
||||
}
|
||||
|
||||
// Handle sorting from Tabulator
|
||||
if (params.sort && params.sort.length > 0) {
|
||||
var sorter = params.sort[0]; // single-column sorting
|
||||
var sortField = encodeURIComponent(sorter.field);
|
||||
var sortDir = (sorter.dir === "asc" ? "asc" : "desc");
|
||||
queryParts.push("order=" + sortField + "," + sortDir);
|
||||
} else {
|
||||
// fallback default order if no sorters
|
||||
queryParts.push("order=id,desc");
|
||||
}
|
||||
|
||||
// Include pagination parameters
|
||||
if (params.page) {
|
||||
queryParts.push("page=" + params.page + "," + params.size);
|
||||
}
|
||||
|
||||
return url + "?" + queryParts.join("&");
|
||||
},
|
||||
ajaxResponse: function(url, params, response) {
|
||||
return response.records;
|
||||
if (response && Array.isArray(response.records) && typeof response.results === 'number') {
|
||||
var lastPage = Math.ceil(response.results / this.options.paginationSize);
|
||||
return {
|
||||
last_page: lastPage, // Calculated total number of pages
|
||||
data: response.records, // Data for the current page
|
||||
};
|
||||
} else {
|
||||
console.error('Unexpected response format', response);
|
||||
return { last_page: 1, data: [] };
|
||||
}
|
||||
},
|
||||
layout:"fitDataFill",
|
||||
responsiveLayout: "collapse",
|
||||
|
@ -76,22 +121,13 @@
|
|||
]
|
||||
});
|
||||
var searchInput = document.getElementById("search-input");
|
||||
searchInput.addEventListener("input", function () {
|
||||
var term = searchInput.value.toLowerCase();
|
||||
let searchTimeout;
|
||||
|
||||
if (term) { // Only apply filter when there's a term to search for
|
||||
table.setFilter(function (data) {
|
||||
return (
|
||||
String(data.username).toLowerCase().includes(term) ||
|
||||
String(data.email).toLowerCase().includes(term) ||
|
||||
String(data.roles_mask).toString().toLowerCase().includes(term) ||
|
||||
String(data.verified).toLowerCase().includes(term) ||
|
||||
String(data.status).toLowerCase().includes(term)
|
||||
);
|
||||
});
|
||||
} else {
|
||||
table.clearFilter(); // Clear the filter when the search box is emptied
|
||||
}
|
||||
searchInput.addEventListener("input", function () {
|
||||
clearTimeout(searchTimeout);
|
||||
searchTimeout = setTimeout(() => {
|
||||
updateSearchTerm(searchInput.value);
|
||||
}, 300); // 300ms delay
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -150,6 +150,7 @@ $app->group('', function ($route) {
|
|||
$route->post('/change-password', PasswordController::class . ':changePassword')->setName('change.password');
|
||||
|
||||
$route->get('/dapi/domains', [DapiController::class, 'listDomains']);
|
||||
$route->get('/dapi/applications', [DapiController::class, 'listApplications']);
|
||||
})->add(new AuthMiddleware($container));
|
||||
|
||||
$app->any('/api[/{params:.*}]', function (
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue