UX enhancements on data views
This commit is contained in:
@ -1,8 +1,14 @@
|
||||
import { apiGet, apiPost, apiDelete } from '../api.js';
|
||||
import { showToast } from '../app.js';
|
||||
import { isDevTestClientCode, testDataRowClassForClient } from '../test-data.js';
|
||||
|
||||
const PAGE_SIZE = 50;
|
||||
|
||||
let clientsList = [];
|
||||
let coachesList = [];
|
||||
let filterSearch = '';
|
||||
let filterTestData = '';
|
||||
let page = 0;
|
||||
|
||||
export async function clientsPage() {
|
||||
const app = document.getElementById('app');
|
||||
@ -62,6 +68,15 @@ function renderClients() {
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="card">
|
||||
<div class="filter-bar">
|
||||
<input type="search" id="clientListSearch" class="filter-search" placeholder="Search client code…" value="${esc(filterSearch)}">
|
||||
<select id="clientTestFilter">
|
||||
<option value="">All clients</option>
|
||||
<option value="test" ${filterTestData === 'test' ? 'selected' : ''}>Test data only</option>
|
||||
<option value="hide-test" ${filterTestData === 'hide-test' ? 'selected' : ''}>Hide test data</option>
|
||||
</select>
|
||||
<span class="data-toolbar-hint" id="clientListCount"></span>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
@ -71,16 +86,76 @@ function renderClients() {
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${clientsList.map(c => clientRowHTML(c)).join('')}
|
||||
</tbody>
|
||||
<tbody id="clientsTableBody"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="pagination-bar" id="clientsPagination"></div>
|
||||
</div>`;
|
||||
|
||||
container.querySelectorAll('.delete-client-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => deleteClient(btn.dataset.code));
|
||||
document.getElementById('clientListSearch').addEventListener('input', (e) => {
|
||||
filterSearch = e.target.value.trim();
|
||||
page = 0;
|
||||
renderClientTableBody();
|
||||
});
|
||||
document.getElementById('clientTestFilter').addEventListener('change', (e) => {
|
||||
filterTestData = e.target.value;
|
||||
page = 0;
|
||||
renderClientTableBody();
|
||||
});
|
||||
|
||||
renderClientTableBody();
|
||||
}
|
||||
|
||||
function getFilteredClientsList() {
|
||||
let list = clientsList;
|
||||
if (filterSearch) {
|
||||
const q = filterSearch.toLowerCase();
|
||||
list = list.filter(c => (c.clientCode || '').toLowerCase().includes(q));
|
||||
}
|
||||
if (filterTestData === 'test') {
|
||||
list = list.filter(c => isDevTestClientCode(c.clientCode));
|
||||
} else if (filterTestData === 'hide-test') {
|
||||
list = list.filter(c => !isDevTestClientCode(c.clientCode));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
function renderClientTableBody() {
|
||||
const body = document.getElementById('clientsTableBody');
|
||||
const countEl = document.getElementById('clientListCount');
|
||||
const filtered = getFilteredClientsList();
|
||||
const totalPages = Math.max(1, Math.ceil(filtered.length / PAGE_SIZE));
|
||||
if (page >= totalPages) page = totalPages - 1;
|
||||
const slice = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
|
||||
|
||||
if (countEl) {
|
||||
countEl.textContent = `${filtered.length} client${filtered.length === 1 ? '' : 's'}`;
|
||||
}
|
||||
|
||||
if (!slice.length) {
|
||||
body.innerHTML = `<tr><td colspan="3" style="text-align:center;color:var(--text-secondary);padding:24px">No clients match</td></tr>`;
|
||||
} else {
|
||||
body.innerHTML = slice.map(c => clientRowHTML(c)).join('');
|
||||
body.querySelectorAll('.delete-client-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => deleteClient(btn.dataset.code));
|
||||
});
|
||||
}
|
||||
|
||||
const pag = document.getElementById('clientsPagination');
|
||||
if (!pag) return;
|
||||
if (filtered.length <= PAGE_SIZE) {
|
||||
pag.innerHTML = '';
|
||||
return;
|
||||
}
|
||||
const from = page * PAGE_SIZE + 1;
|
||||
const to = Math.min((page + 1) * PAGE_SIZE, filtered.length);
|
||||
pag.innerHTML = `
|
||||
<button type="button" class="btn btn-sm" id="clientsPagePrev" ${page <= 0 ? 'disabled' : ''}>Prev</button>
|
||||
<span>Rows ${from}–${to} of ${filtered.length}</span>
|
||||
<button type="button" class="btn btn-sm" id="clientsPageNext" ${page >= totalPages - 1 ? 'disabled' : ''}>Next</button>
|
||||
`;
|
||||
document.getElementById('clientsPagePrev')?.addEventListener('click', () => { page--; renderClientTableBody(); });
|
||||
document.getElementById('clientsPageNext')?.addEventListener('click', () => { page++; renderClientTableBody(); });
|
||||
}
|
||||
|
||||
function clientRowHTML(c) {
|
||||
@ -89,7 +164,7 @@ function clientRowHTML(c) {
|
||||
: `<span style="color:var(--text-secondary);font-style:italic">Unassigned</span>`;
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<tr class="${testDataRowClassForClient(c.clientCode).trim()}">
|
||||
<td><strong>${esc(c.clientCode)}</strong></td>
|
||||
<td>${coach}</td>
|
||||
<td>
|
||||
@ -104,7 +179,8 @@ async function deleteClient(clientCode) {
|
||||
await apiDelete('clients.php', { clientCode });
|
||||
clientsList = clientsList.filter(c => c.clientCode !== clientCode);
|
||||
showToast(`Client "${clientCode}" deleted`, 'success');
|
||||
renderClients();
|
||||
if (!clientsList.length) renderClients();
|
||||
else renderClientTableBody();
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user