mirror of
https://github.com/LD-Reborn/Berufsschule_HAM.git
synced 2025-12-20 06:51:55 +00:00
Revert "Add server-side filtering with AJAX pagination for users"
This reverts commit cd0173a38c.
This commit is contained in:
@@ -75,14 +75,7 @@ public class HomeController : Controller
|
|||||||
|
|
||||||
[Authorize(Roles = "CanManageUsers")]
|
[Authorize(Roles = "CanManageUsers")]
|
||||||
[HttpGet("Users")]
|
[HttpGet("Users")]
|
||||||
public async Task<ActionResult> UsersAsync(
|
public async Task<ActionResult> UsersAsync([FromQuery] int page = 1, [FromQuery] int pageSize = 50)
|
||||||
[FromQuery] int page = 1,
|
|
||||||
[FromQuery] int pageSize = 50,
|
|
||||||
[FromQuery] string? username = null,
|
|
||||||
[FromQuery] string? title = null,
|
|
||||||
[FromQuery] string? name = null,
|
|
||||||
[FromQuery] string? surname = null,
|
|
||||||
[FromQuery] string? workplace = null)
|
|
||||||
{
|
{
|
||||||
page = Math.Max(1, page);
|
page = Math.Max(1, page);
|
||||||
pageSize = Math.Clamp(pageSize, 10, 100);
|
pageSize = Math.Clamp(pageSize, 10, 100);
|
||||||
@@ -90,29 +83,6 @@ public class HomeController : Controller
|
|||||||
// Fetch all users with jpegPhoto (but not userPassword)
|
// Fetch all users with jpegPhoto (but not userPassword)
|
||||||
IEnumerable<UserModel> allUsers = await _ldap.ListUsersAsync([.. _ldap.UsersAttributes.Where(attr => attr != "userPassword")]);
|
IEnumerable<UserModel> allUsers = await _ldap.ListUsersAsync([.. _ldap.UsersAttributes.Where(attr => attr != "userPassword")]);
|
||||||
List<UserModel> usersList = allUsers.ToList();
|
List<UserModel> usersList = allUsers.ToList();
|
||||||
|
|
||||||
// Apply filters
|
|
||||||
if (!string.IsNullOrWhiteSpace(username))
|
|
||||||
{
|
|
||||||
usersList = usersList.Where(u => u.Uid?.Contains(username, StringComparison.OrdinalIgnoreCase) == true).ToList();
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrWhiteSpace(title))
|
|
||||||
{
|
|
||||||
usersList = usersList.Where(u => u.Title?.Contains(title, StringComparison.OrdinalIgnoreCase) == true).ToList();
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrWhiteSpace(name))
|
|
||||||
{
|
|
||||||
usersList = usersList.Where(u => u.Cn?.Contains(name, StringComparison.OrdinalIgnoreCase) == true).ToList();
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrWhiteSpace(surname))
|
|
||||||
{
|
|
||||||
usersList = usersList.Where(u => u.Sn?.Contains(surname, StringComparison.OrdinalIgnoreCase) == true).ToList();
|
|
||||||
}
|
|
||||||
if (!string.IsNullOrWhiteSpace(workplace))
|
|
||||||
{
|
|
||||||
usersList = usersList.Where(u => u.Description?.Workplace?.Contains(workplace, StringComparison.OrdinalIgnoreCase) == true).ToList();
|
|
||||||
}
|
|
||||||
|
|
||||||
int totalUsers = usersList.Count;
|
int totalUsers = usersList.Count;
|
||||||
|
|
||||||
List<UserModel> paginatedUsers = usersList
|
List<UserModel> paginatedUsers = usersList
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createModal">
|
<button class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#createModal">
|
||||||
@T["Create user"]
|
@T["Create user"]
|
||||||
</button>
|
</button>
|
||||||
<div class="text-muted" id="userCountInfo">
|
<div class="text-muted">
|
||||||
<strong>@Model.TotalUsers</strong> @T["users"] @T["total"]
|
<strong>@Model.TotalUsers</strong> @T["users"] @T["total"]
|
||||||
@if (Model.TotalPages > 1)
|
@if (Model.TotalPages > 1)
|
||||||
{
|
{
|
||||||
@@ -28,40 +28,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@* Filter Section *@
|
|
||||||
<div class="card mb-4">
|
<div class="table-responsive">
|
||||||
<div class="card-body">
|
|
||||||
<h5 class="card-title">@T["Filter Users"]</h5>
|
|
||||||
<div class="row g-3">
|
|
||||||
<div class="col-md-2">
|
|
||||||
<input type="text" id="filterUsername" class="form-control form-control-sm" placeholder="@T["Username"]" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<input type="text" id="filterTitle" class="form-control form-control-sm" placeholder="@T["Title"]" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<input type="text" id="filterName" class="form-control form-control-sm" placeholder="@T["Name"]" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<input type="text" id="filterSurname" class="form-control form-control-sm" placeholder="@T["Surname"]" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<input type="text" id="filterWorkplace" class="form-control form-control-sm" placeholder="@T["Workplace"]" />
|
|
||||||
</div>
|
|
||||||
<div class="col-md-2">
|
|
||||||
<button id="clearFilters" class="btn btn-sm btn-secondary w-100">@T["Clear Filters"]</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="loadingSpinner" class="text-center my-4" style="display: none;">
|
|
||||||
<div class="spinner-border text-primary" role="status">
|
|
||||||
<span class="visually-hidden">Loading...</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="table-responsive" id="userTableContainer">
|
|
||||||
<table class="table table-striped align-middle">
|
<table class="table table-striped align-middle">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -74,7 +42,7 @@
|
|||||||
<th>@T["Action"]</th>
|
<th>@T["Action"]</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody id="userTableBody">
|
<tbody>
|
||||||
@{
|
@{
|
||||||
foreach (UserTableViewModel userTableViewModel in Model.UserTableViewModels)
|
foreach (UserTableViewModel userTableViewModel in Model.UserTableViewModels)
|
||||||
{
|
{
|
||||||
@@ -129,224 +97,64 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
@* Pagination Controls *@
|
@* Pagination Controls *@
|
||||||
<nav aria-label="User pagination" class="mt-4" id="paginationContainer">
|
@if (Model.TotalPages > 1)
|
||||||
<div class="d-flex justify-content-between align-items-center">
|
{
|
||||||
<div class="text-muted" id="paginationInfo">
|
<nav aria-label="User pagination" class="mt-4">
|
||||||
@T["Showing"] @((Model.CurrentPage - 1) * Model.PageSize + 1) - @Math.Min(Model.CurrentPage * Model.PageSize, Model.TotalUsers) @T["of"] @Model.TotalUsers @T["users"]
|
<div class="d-flex justify-content-between align-items-center">
|
||||||
|
<div class="text-muted">
|
||||||
|
@T["Showing"] @((Model.CurrentPage - 1) * Model.PageSize + 1) - @Math.Min(Model.CurrentPage * Model.PageSize, Model.TotalUsers) @T["of"] @Model.TotalUsers @T["users"]
|
||||||
|
</div>
|
||||||
|
<ul class="pagination mb-0">
|
||||||
|
@* Previous Button *@
|
||||||
|
<li class="page-item @(Model.CurrentPage == 1 ? "disabled" : "")">
|
||||||
|
<a class="page-link" href="?page=@(Model.CurrentPage - 1)&pageSize=@Model.PageSize" aria-label="Previous">
|
||||||
|
<span aria-hidden="true">«</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
@* Page Numbers *@
|
||||||
|
@{
|
||||||
|
int startPage = Math.Max(1, Model.CurrentPage - 2);
|
||||||
|
int endPage = Math.Min(Model.TotalPages, Model.CurrentPage + 2);
|
||||||
|
|
||||||
|
if (startPage > 1)
|
||||||
|
{
|
||||||
|
<li class="page-item"><a class="page-link" href="?page=1&pageSize=@Model.PageSize">1</a></li>
|
||||||
|
if (startPage > 2)
|
||||||
|
{
|
||||||
|
<li class="page-item disabled"><span class="page-link">...</span></li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = startPage; i <= endPage; i++)
|
||||||
|
{
|
||||||
|
<li class="page-item @(i == Model.CurrentPage ? "active" : "")">
|
||||||
|
<a class="page-link" href="?page=@i&pageSize=@Model.PageSize">@i</a>
|
||||||
|
</li>
|
||||||
|
}
|
||||||
|
|
||||||
|
if (endPage < Model.TotalPages)
|
||||||
|
{
|
||||||
|
if (endPage < Model.TotalPages - 1)
|
||||||
|
{
|
||||||
|
<li class="page-item disabled"><span class="page-link">...</span></li>
|
||||||
|
}
|
||||||
|
<li class="page-item"><a class="page-link" href="?page=@Model.TotalPages&pageSize=@Model.PageSize">@Model.TotalPages</a></li>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@* Next Button *@
|
||||||
|
<li class="page-item @(Model.CurrentPage >= Model.TotalPages ? "disabled" : "")">
|
||||||
|
<a class="page-link" href="?page=@(Model.CurrentPage + 1)&pageSize=@Model.PageSize" aria-label="Next">
|
||||||
|
<span aria-hidden="true">»</span>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<ul class="pagination mb-0" id="paginationButtons">
|
</nav>
|
||||||
@* Pagination buttons will be generated dynamically via JavaScript *@
|
}
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</nav>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
|
||||||
// Global state for pagination and filtering
|
|
||||||
let currentPage = @Model.CurrentPage;
|
|
||||||
let pageSize = @Model.PageSize;
|
|
||||||
let totalUsers = @Model.TotalUsers;
|
|
||||||
let totalPages = @Model.TotalPages;
|
|
||||||
let currentFilters = {
|
|
||||||
username: '',
|
|
||||||
title: '',
|
|
||||||
name: '',
|
|
||||||
surname: '',
|
|
||||||
workplace: ''
|
|
||||||
};
|
|
||||||
|
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
|
||||||
// Initialize pagination
|
|
||||||
renderPaginationButtons();
|
|
||||||
|
|
||||||
// Setup filter inputs with debounce
|
|
||||||
const filterInputs = {
|
|
||||||
username: document.getElementById('filterUsername'),
|
|
||||||
title: document.getElementById('filterTitle'),
|
|
||||||
name: document.getElementById('filterName'),
|
|
||||||
surname: document.getElementById('filterSurname'),
|
|
||||||
workplace: document.getElementById('filterWorkplace')
|
|
||||||
};
|
|
||||||
|
|
||||||
let debounceTimer;
|
|
||||||
Object.keys(filterInputs).forEach(key => {
|
|
||||||
filterInputs[key].addEventListener('input', (e) => {
|
|
||||||
clearTimeout(debounceTimer);
|
|
||||||
debounceTimer = setTimeout(() => {
|
|
||||||
currentFilters[key] = e.target.value;
|
|
||||||
currentPage = 1; // Reset to first page on filter change
|
|
||||||
loadUsers();
|
|
||||||
}, 500); // Wait 500ms after user stops typing
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Clear filters button
|
|
||||||
document.getElementById('clearFilters').addEventListener('click', () => {
|
|
||||||
Object.keys(filterInputs).forEach(key => {
|
|
||||||
filterInputs[key].value = '';
|
|
||||||
currentFilters[key] = '';
|
|
||||||
});
|
|
||||||
currentPage = 1;
|
|
||||||
loadUsers();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
async function loadUsers() {
|
|
||||||
const spinner = document.getElementById('loadingSpinner');
|
|
||||||
const tableContainer = document.getElementById('userTableContainer');
|
|
||||||
|
|
||||||
// Show spinner, hide table
|
|
||||||
spinner.style.display = 'block';
|
|
||||||
tableContainer.style.opacity = '0.5';
|
|
||||||
|
|
||||||
// Build query string
|
|
||||||
const params = new URLSearchParams({
|
|
||||||
page: currentPage,
|
|
||||||
pageSize: pageSize
|
|
||||||
});
|
|
||||||
|
|
||||||
Object.keys(currentFilters).forEach(key => {
|
|
||||||
if (currentFilters[key]) {
|
|
||||||
params.append(key, currentFilters[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch(`/Home/Users?${params.toString()}`, {
|
|
||||||
headers: {
|
|
||||||
'Accept': 'text/html'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const html = await response.text();
|
|
||||||
|
|
||||||
// Parse the response HTML to extract table rows and pagination info
|
|
||||||
const parser = new DOMParser();
|
|
||||||
const doc = parser.parseFromString(html, 'text/html');
|
|
||||||
|
|
||||||
// Extract table body
|
|
||||||
const newTableBody = doc.querySelector('#userTableBody');
|
|
||||||
if (newTableBody) {
|
|
||||||
document.getElementById('userTableBody').innerHTML = newTableBody.innerHTML;
|
|
||||||
|
|
||||||
// Re-register row click handlers
|
|
||||||
document.querySelectorAll('#userTableBody tr').forEach(row => {
|
|
||||||
registerRowDetailviewClick(row);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Extract pagination info from the response
|
|
||||||
const userCountInfo = doc.querySelector('#userCountInfo');
|
|
||||||
if (userCountInfo) {
|
|
||||||
const totalUsersMatch = userCountInfo.textContent.match(/(\d+)\s+users/i);
|
|
||||||
if (totalUsersMatch) {
|
|
||||||
totalUsers = parseInt(totalUsersMatch[1]);
|
|
||||||
totalPages = Math.ceil(totalUsers / pageSize);
|
|
||||||
|
|
||||||
// Update count info
|
|
||||||
document.getElementById('userCountInfo').innerHTML = userCountInfo.innerHTML;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update pagination info text
|
|
||||||
const start = (currentPage - 1) * pageSize + 1;
|
|
||||||
const end = Math.min(currentPage * pageSize, totalUsers);
|
|
||||||
document.getElementById('paginationInfo').textContent =
|
|
||||||
`@T["Showing"] ${start} - ${end} @T["of"] ${totalUsers} @T["users"]`;
|
|
||||||
|
|
||||||
// Render pagination buttons
|
|
||||||
renderPaginationButtons();
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error loading users:', error);
|
|
||||||
showToast('@T["Error loading users"]', 'danger');
|
|
||||||
} finally {
|
|
||||||
// Hide spinner, restore table
|
|
||||||
spinner.style.display = 'none';
|
|
||||||
tableContainer.style.opacity = '1';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderPaginationButtons() {
|
|
||||||
const container = document.getElementById('paginationButtons');
|
|
||||||
if (totalPages <= 1) {
|
|
||||||
document.getElementById('paginationContainer').style.display = 'none';
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
document.getElementById('paginationContainer').style.display = 'block';
|
|
||||||
container.innerHTML = '';
|
|
||||||
|
|
||||||
// Previous button
|
|
||||||
const prevLi = document.createElement('li');
|
|
||||||
prevLi.className = `page-item ${currentPage === 1 ? 'disabled' : ''}`;
|
|
||||||
prevLi.innerHTML = `<a class="page-link" href="#" aria-label="Previous"><span aria-hidden="true">«</span></a>`;
|
|
||||||
if (currentPage > 1) {
|
|
||||||
prevLi.querySelector('a').addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
currentPage--;
|
|
||||||
loadUsers();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
container.appendChild(prevLi);
|
|
||||||
|
|
||||||
// Page numbers
|
|
||||||
const startPage = Math.max(1, currentPage - 2);
|
|
||||||
const endPage = Math.min(totalPages, currentPage + 2);
|
|
||||||
|
|
||||||
if (startPage > 1) {
|
|
||||||
appendPageButton(container, 1);
|
|
||||||
if (startPage > 2) {
|
|
||||||
const ellipsis = document.createElement('li');
|
|
||||||
ellipsis.className = 'page-item disabled';
|
|
||||||
ellipsis.innerHTML = '<span class="page-link">...</span>';
|
|
||||||
container.appendChild(ellipsis);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = startPage; i <= endPage; i++) {
|
|
||||||
appendPageButton(container, i);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (endPage < totalPages) {
|
|
||||||
if (endPage < totalPages - 1) {
|
|
||||||
const ellipsis = document.createElement('li');
|
|
||||||
ellipsis.className = 'page-item disabled';
|
|
||||||
ellipsis.innerHTML = '<span class="page-link">...</span>';
|
|
||||||
container.appendChild(ellipsis);
|
|
||||||
}
|
|
||||||
appendPageButton(container, totalPages);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Next button
|
|
||||||
const nextLi = document.createElement('li');
|
|
||||||
nextLi.className = `page-item ${currentPage >= totalPages ? 'disabled' : ''}`;
|
|
||||||
nextLi.innerHTML = `<a class="page-link" href="#" aria-label="Next"><span aria-hidden="true">»</span></a>`;
|
|
||||||
if (currentPage < totalPages) {
|
|
||||||
nextLi.querySelector('a').addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
currentPage++;
|
|
||||||
loadUsers();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
container.appendChild(nextLi);
|
|
||||||
}
|
|
||||||
|
|
||||||
function appendPageButton(container, pageNum) {
|
|
||||||
const li = document.createElement('li');
|
|
||||||
li.className = `page-item ${pageNum === currentPage ? 'active' : ''}`;
|
|
||||||
li.innerHTML = `<a class="page-link" href="#">${pageNum}</a>`;
|
|
||||||
if (pageNum !== currentPage) {
|
|
||||||
li.querySelector('a').addEventListener('click', (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
currentPage = pageNum;
|
|
||||||
loadUsers();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
container.appendChild(li);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<!-- User Delete Confirmation Modal -->
|
<!-- User Delete Confirmation Modal -->
|
||||||
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
<div class="modal fade" id="deleteModal" tabindex="-1" aria-labelledby="deleteModalLabel" aria-hidden="true">
|
||||||
<div class="modal-dialog modal-dialog-centered">
|
<div class="modal-dialog modal-dialog-centered">
|
||||||
|
|||||||
Reference in New Issue
Block a user