added client creation and fixed assignment
This commit is contained in:
206
website/js/pages/clients.js
Normal file
206
website/js/pages/clients.js
Normal file
@ -0,0 +1,206 @@
|
||||
import { apiGet, apiPost, apiDelete } from '../api.js';
|
||||
import { showToast } from '../app.js';
|
||||
|
||||
let clientsList = [];
|
||||
let coachesList = [];
|
||||
|
||||
export async function clientsPage() {
|
||||
const app = document.getElementById('app');
|
||||
|
||||
app.innerHTML = `
|
||||
<div class="page-header">
|
||||
<h1>Clients</h1>
|
||||
<div class="actions">
|
||||
<button class="btn btn-primary" id="showCreateFormBtn">+ Create Client</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="createClientWrapper" style="display:none"></div>
|
||||
<div id="clientsContent"><div class="spinner"></div></div>
|
||||
`;
|
||||
|
||||
document.getElementById('showCreateFormBtn').addEventListener('click', () => {
|
||||
const wrapper = document.getElementById('createClientWrapper');
|
||||
if (wrapper.style.display === 'none') {
|
||||
showCreateForm();
|
||||
} else {
|
||||
hideCreateForm();
|
||||
}
|
||||
});
|
||||
|
||||
await loadClients();
|
||||
}
|
||||
|
||||
async function loadClients() {
|
||||
try {
|
||||
const [clientsData, assignData] = await Promise.all([
|
||||
apiGet('clients.php'),
|
||||
apiGet('assignments.php'),
|
||||
]);
|
||||
clientsList = clientsData.clients || [];
|
||||
coachesList = assignData.coaches || [];
|
||||
renderClients();
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
document.getElementById('clientsContent').innerHTML =
|
||||
`<p class="error-text" style="padding:16px">${esc(e.message)}</p>`;
|
||||
}
|
||||
}
|
||||
|
||||
function renderClients() {
|
||||
const container = document.getElementById('clientsContent');
|
||||
|
||||
if (!clientsList.length) {
|
||||
container.innerHTML = `
|
||||
<div class="card">
|
||||
<div class="empty-state">
|
||||
<h3>No clients found</h3>
|
||||
<p>Create the first client above.</p>
|
||||
</div>
|
||||
</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="card">
|
||||
<div class="table-wrapper">
|
||||
<table class="data-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Client Code</th>
|
||||
<th>Current Coach</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
${clientsList.map(c => clientRowHTML(c)).join('')}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
container.querySelectorAll('.delete-client-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => deleteClient(btn.dataset.code));
|
||||
});
|
||||
}
|
||||
|
||||
function clientRowHTML(c) {
|
||||
const coach = c.coachUsername
|
||||
? `<strong>${esc(c.coachUsername)}</strong>`
|
||||
: `<span style="color:var(--text-secondary);font-style:italic">Unassigned</span>`;
|
||||
|
||||
return `
|
||||
<tr>
|
||||
<td><strong>${esc(c.clientCode)}</strong></td>
|
||||
<td>${coach}</td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-danger delete-client-btn" data-code="${esc(c.clientCode)}">Delete</button>
|
||||
</td>
|
||||
</tr>`;
|
||||
}
|
||||
|
||||
async function deleteClient(clientCode) {
|
||||
if (!confirm(`Delete client "${clientCode}"? This will also remove all their answers and results. This cannot be undone.`)) return;
|
||||
try {
|
||||
await apiDelete('clients.php', { clientCode });
|
||||
clientsList = clientsList.filter(c => c.clientCode !== clientCode);
|
||||
showToast(`Client "${clientCode}" deleted`, 'success');
|
||||
renderClients();
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
function showCreateForm() {
|
||||
if (!coachesList.length) {
|
||||
showToast('No coaches available. Create a coach first.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
const wrapper = document.getElementById('createClientWrapper');
|
||||
wrapper.style.display = '';
|
||||
wrapper.innerHTML = `
|
||||
<div class="inline-form-card" style="margin-bottom:20px">
|
||||
<h4>Create New Client</h4>
|
||||
<div class="form-row">
|
||||
<div class="form-group" style="flex:2">
|
||||
<label>Client Code</label>
|
||||
<input type="text" id="cc_code" autocomplete="off" placeholder="Enter unique client code">
|
||||
</div>
|
||||
<div class="form-group" style="flex:2">
|
||||
<label>Assign to Coach</label>
|
||||
<select id="cc_coach">
|
||||
<option value="">— select coach —</option>
|
||||
${coachesList.map(c =>
|
||||
`<option value="${c.coachID}">${esc(c.username)}${c.supervisorUsername ? ` (${esc(c.supervisorUsername)})` : ''}</option>`
|
||||
).join('')}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:flex;gap:8px">
|
||||
<button class="btn btn-primary btn-sm" id="cc_submit">Create Client</button>
|
||||
<button class="btn btn-sm" id="cc_cancel">Cancel</button>
|
||||
</div>
|
||||
<p id="cc_error" class="error-text" style="display:none;margin-top:8px"></p>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const input = document.getElementById('cc_code');
|
||||
input.focus();
|
||||
wrapper.querySelectorAll('input').forEach(inp => {
|
||||
inp.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') submitCreateClient();
|
||||
if (e.key === 'Escape') hideCreateForm();
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('cc_submit').addEventListener('click', submitCreateClient);
|
||||
document.getElementById('cc_cancel').addEventListener('click', hideCreateForm);
|
||||
}
|
||||
|
||||
function hideCreateForm() {
|
||||
const wrapper = document.getElementById('createClientWrapper');
|
||||
if (wrapper) { wrapper.style.display = 'none'; wrapper.innerHTML = ''; }
|
||||
}
|
||||
|
||||
async function submitCreateClient() {
|
||||
const errEl = document.getElementById('cc_error');
|
||||
errEl.style.display = 'none';
|
||||
|
||||
const clientCode = document.getElementById('cc_code').value.trim();
|
||||
const coachID = document.getElementById('cc_coach').value;
|
||||
|
||||
if (!clientCode) { showError(errEl, 'Client code is required'); return; }
|
||||
if (!coachID) { showError(errEl, 'Please select a coach'); return; }
|
||||
|
||||
const btn = document.getElementById('cc_submit');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Creating...';
|
||||
|
||||
try {
|
||||
const data = await apiPost('clients.php', { clientCode, coachID });
|
||||
const coach = coachesList.find(c => c.coachID === coachID);
|
||||
clientsList.push({
|
||||
clientCode: data.clientCode,
|
||||
coachID: coachID,
|
||||
coachUsername: coach?.username ?? null,
|
||||
});
|
||||
showToast(`Client "${data.clientCode}" created`, 'success');
|
||||
hideCreateForm();
|
||||
renderClients();
|
||||
} catch (e) {
|
||||
showError(errEl, e.message);
|
||||
btn.disabled = false;
|
||||
btn.textContent = 'Create Client';
|
||||
}
|
||||
}
|
||||
|
||||
function showError(el, msg) {
|
||||
el.textContent = msg;
|
||||
el.style.display = '';
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = s ?? '';
|
||||
return d.innerHTML;
|
||||
}
|
||||
Reference in New Issue
Block a user