This commit is contained in:
@ -204,12 +204,8 @@ function renderClientTableBody() {
|
||||
body.querySelectorAll('.restore-client-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => setClientArchived(btn.dataset.code, false));
|
||||
});
|
||||
body.querySelectorAll('.save-client-note-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const tr = btn.closest('tr');
|
||||
const ta = tr?.querySelector('.client-note-input');
|
||||
saveClientNote(btn.dataset.client, ta);
|
||||
});
|
||||
body.querySelectorAll('.edit-client-note-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => openClientNoteEditor(btn.dataset.client));
|
||||
});
|
||||
bindCoachBandPickerEvents(body);
|
||||
}
|
||||
@ -747,20 +743,20 @@ function clientRowHTML(c) {
|
||||
archived,
|
||||
showDelete: true,
|
||||
});
|
||||
const note = String(c.note || '');
|
||||
const note = String(c.note || '').trim();
|
||||
const noteCell = `
|
||||
<td class="followup-note-cell client-note-cell">
|
||||
<textarea class="followup-note-input client-note-input" rows="2"
|
||||
data-client="${esc(c.clientCode)}" maxlength="200"
|
||||
placeholder="Client note…">${esc(note)}</textarea>
|
||||
<div class="followup-note-actions">
|
||||
<button type="button" class="btn btn-sm btn-primary save-client-note-btn"
|
||||
data-client="${esc(c.clientCode)}">Save note</button>
|
||||
<td class="client-note-cell">
|
||||
<div class="client-note-row">
|
||||
${note
|
||||
? `<span class="client-note-text">${esc(note)}</span>`
|
||||
: `<span class="client-note-empty">—</span>`}
|
||||
<button type="button" class="btn btn-sm btn-link edit-client-note-btn"
|
||||
data-client="${esc(c.clientCode)}" title="Edit note">Edit</button>
|
||||
</div>
|
||||
</td>`;
|
||||
|
||||
return `
|
||||
<tr class="${archived ? 'client-row-archived' : ''}" data-client="${esc(c.clientCode)}">
|
||||
<tr class="${archived ? 'client-row-archived' : ''}">
|
||||
<td>
|
||||
${expandControl}<strong>${esc(c.clientCode)}</strong>${clientArchivedLabel(c)}
|
||||
</td>
|
||||
@ -820,23 +816,80 @@ function toggleVersion(clientCode, questionnaireID, submissionID) {
|
||||
renderClientTableBody();
|
||||
}
|
||||
|
||||
async function saveClientNote(clientCode, ta) {
|
||||
if (!ta || !clientCode) return;
|
||||
const note = ta.value.trim();
|
||||
if (note.length > 200) {
|
||||
showToast('Note must be at most 200 characters', 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await apiPut('analytics.php', { clientCode, note });
|
||||
const saved = res?.note ?? note;
|
||||
ta.value = saved;
|
||||
const row = clientsList.find(c => c.clientCode === clientCode);
|
||||
if (row) row.note = saved;
|
||||
showToast('Note saved', 'success');
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
}
|
||||
async function openClientNoteEditor(clientCode) {
|
||||
const row = clientsList.find(c => c.clientCode === clientCode);
|
||||
if (!row) return;
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'modal-overlay client-note-modal';
|
||||
overlay.setAttribute('role', 'dialog');
|
||||
overlay.setAttribute('aria-modal', 'true');
|
||||
overlay.innerHTML = `
|
||||
<div class="modal-dialog client-note-modal-dialog">
|
||||
<h2>Edit note</h2>
|
||||
<p class="modal-subtitle"><strong>${esc(clientCode)}</strong></p>
|
||||
<label class="client-note-modal-label" for="clientNoteModalInput">Note (max 200 characters)</label>
|
||||
<textarea id="clientNoteModalInput" class="client-note-modal-input" rows="4"
|
||||
maxlength="200" placeholder="Client note…">${esc(row.note || '')}</textarea>
|
||||
<p class="client-note-modal-count" id="clientNoteModalCount"></p>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn" data-note-cancel>Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-note-save>Save</button>
|
||||
</div>
|
||||
</div>`;
|
||||
document.body.appendChild(overlay);
|
||||
document.body.classList.add('modal-open');
|
||||
|
||||
const ta = overlay.querySelector('#clientNoteModalInput');
|
||||
const countEl = overlay.querySelector('#clientNoteModalCount');
|
||||
const saveBtn = overlay.querySelector('[data-note-save]');
|
||||
|
||||
const updateCount = () => {
|
||||
const len = ta.value.length;
|
||||
countEl.textContent = `${len} / 200`;
|
||||
};
|
||||
updateCount();
|
||||
ta.addEventListener('input', updateCount);
|
||||
ta.focus();
|
||||
ta.setSelectionRange(ta.value.length, ta.value.length);
|
||||
|
||||
const close = () => {
|
||||
overlay.remove();
|
||||
document.body.classList.remove('modal-open');
|
||||
document.removeEventListener('keydown', onKey);
|
||||
};
|
||||
const onKey = (e) => {
|
||||
if (e.key === 'Escape') close();
|
||||
};
|
||||
document.addEventListener('keydown', onKey);
|
||||
|
||||
overlay.querySelector('[data-note-cancel]').addEventListener('click', close);
|
||||
overlay.addEventListener('click', (e) => {
|
||||
if (e.target === overlay) close();
|
||||
});
|
||||
overlay.querySelector('.modal-dialog').addEventListener('click', (e) => e.stopPropagation());
|
||||
|
||||
saveBtn.addEventListener('click', async () => {
|
||||
const note = ta.value.trim();
|
||||
if (note.length > 200) {
|
||||
showToast('Note must be at most 200 characters', 'error');
|
||||
return;
|
||||
}
|
||||
saveBtn.disabled = true;
|
||||
saveBtn.textContent = 'Saving…';
|
||||
try {
|
||||
const res = await apiPut('analytics.php', { clientCode, note });
|
||||
const saved = res?.note ?? note;
|
||||
row.note = saved;
|
||||
showToast('Note saved', 'success');
|
||||
close();
|
||||
renderClientTableBody();
|
||||
} catch (e) {
|
||||
showToast(e.message, 'error');
|
||||
saveBtn.disabled = false;
|
||||
saveBtn.textContent = 'Save';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function setClientArchived(clientCode, archived) {
|
||||
|
||||
Reference in New Issue
Block a user