diff --git a/handlers/clients.php b/handlers/clients.php index 77d776d..92fb4c3 100644 --- a/handlers/clients.php +++ b/handlers/clients.php @@ -64,43 +64,6 @@ case 'GET': case 'POST': $body = read_json_body(); - if (($body['action'] ?? '') === 'reset') { - $clientCode = trim((string)($body['clientCode'] ?? '')); - if ($clientCode === '') { - json_error('MISSING_FIELDS', 'clientCode is required', 400); - } - - try { - [$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail(); - - [$clause, $params] = rbac_client_filter($tokenRec, 'cl', 'all'); - $chk = $pdo->prepare( - "SELECT clientCode FROM client cl WHERE cl.clientCode = :cc AND ($clause)" - ); - $chk->execute(array_merge([':cc' => $clientCode], $params)); - if (!$chk->fetch()) { - qdb_discard($tmpDb, $lockFp); - json_error('NOT_FOUND', 'Client not found or not authorized', 404); - } - - require_once __DIR__ . '/../lib/submissions.php'; - - $pdo->beginTransaction(); - $cleared = qdb_delete_client_response_data($pdo, [$clientCode]); - $pdo->commit(); - - qdb_save($tmpDb, $lockFp); - json_success([ - 'clientCode' => $clientCode, - 'reset' => true, - 'cleared' => $cleared, - ]); - } catch (Throwable $e) { - qdb_handler_fail($e, 'clients', $pdo ?? null, $tmpDb ?? null, $lockFp ?? null); - } - break; - } - $coachID = trim($body['coachID'] ?? ''); $bulk = !empty($body['bulk']); diff --git a/tests/Integration/ClientsLifecycleTest.php b/tests/Integration/ClientsLifecycleTest.php index c95d363..5870053 100644 --- a/tests/Integration/ClientsLifecycleTest.php +++ b/tests/Integration/ClientsLifecycleTest.php @@ -91,43 +91,6 @@ final class ClientsLifecycleTest extends QdbTestCase $this->assertContains($code, array_column($activeAgain['data']['clients'], 'clientCode')); } - public function testAdminResetsClient(): void - { - $this->submitFixtureQuestionnaire(); - $token = $this->api()->loginWebAndGetToken( - DatabaseSeeder::ADMIN_USERNAME, - DatabaseSeeder::PASSWORD - )['token']; - $code = $this->fixture()->clientCode; - - $reset = $this->api()->withToken($token, 'POST', 'clients', [ - 'action' => 'reset', - 'clientCode' => $code, - ]); - $this->assertApiOk($reset); - $this->assertTrue($reset['data']['reset'] ?? false); - - $detail = $this->api()->withToken($token, 'GET', 'clients', null, [ - 'clientCode' => $code, - 'detail' => '1', - ]); - $this->assertApiOk($detail); - $this->assertSame($code, $detail['data']['client']['clientCode'] ?? ''); - $this->assertSame([], $detail['data']['questionnaires'] ?? null); - - $list = $this->api()->withToken($token, 'GET', 'clients'); - $this->assertApiOk($list); - $row = null; - foreach ($list['data']['clients'] as $client) { - if (($client['clientCode'] ?? '') === $code) { - $row = $client; - break; - } - } - $this->assertNotNull($row); - $this->assertSame(0, (int)($row['hasResponseData'] ?? 1)); - } - public function testAdminDeletesClient(): void { $token = $this->api()->loginWebAndGetToken( diff --git a/website/js/client-archive.js b/website/js/client-archive.js index 6a0fa25..35bbd66 100644 --- a/website/js/client-archive.js +++ b/website/js/client-archive.js @@ -34,19 +34,15 @@ export async function confirmAndPatchClientArchive(clientCode, archived) { * @param {string} opts.clientCode Escaped client code for data-code attributes * @param {boolean} opts.archived * @param {boolean} [opts.showDelete] - * @param {boolean} [opts.showReset] * @param {string} [opts.rawCode] Unescaped code for button labels (optional) */ -export function clientTableActionsHTML({ clientCode, archived, showDelete = false, showReset = false }) { +export function clientTableActionsHTML({ clientCode, archived, showDelete = false }) { const archiveBtn = archived ? `` : ``; - const resetBtn = showReset - ? `` - : ''; const deleteBtn = showDelete ? `` : ''; - return `
${archiveBtn}${resetBtn}${deleteBtn}
`; + return `
${archiveBtn}${deleteBtn}
`; } diff --git a/website/js/pages/clients.js b/website/js/pages/clients.js index 98f9701..e9638b0 100644 --- a/website/js/pages/clients.js +++ b/website/js/pages/clients.js @@ -197,9 +197,6 @@ function renderClientTableBody() { body.querySelectorAll('.delete-client-btn').forEach(btn => { btn.addEventListener('click', () => deleteClient(btn.dataset.code)); }); - body.querySelectorAll('.reset-client-btn').forEach(btn => { - btn.addEventListener('click', () => resetClient(btn.dataset.code)); - }); body.querySelectorAll('.archive-client-btn').forEach(btn => { btn.addEventListener('click', () => setClientArchived(btn.dataset.code, true)); }); @@ -741,7 +738,6 @@ function clientRowHTML(c) { clientCode: esc(c.clientCode), archived, showDelete: true, - showReset: clientHasResponseData(c), }); return ` @@ -828,44 +824,6 @@ async function setClientArchived(clientCode, archived) { else renderClientTableBody(); } -async function resetClient(clientCode) { - if (!(await confirmAction({ - title: 'Reset client', - message: `Reset client "${clientCode}"? This clears all questionnaire answers, upload history, and scoring results. The client code and counselor assignment are kept.`, - confirmLabel: 'Reset', - variant: 'danger', - }))) return; - try { - await apiPost('clients.php', { action: 'reset', clientCode }); - const row = clientsList.find(c => c.clientCode === clientCode); - if (row) { - row.hasResponseData = 0; - row.scoringProfiles = (row.scoringProfiles || []).map(p => ({ - ...p, - band: null, - calculatedBand: null, - coachBand: null, - effectiveBand: null, - pendingReview: false, - coachOverride: false, - coachReviewedAt: '', - coachReviewedByUsername: '', - weightedTotal: null, - })); - } - delete detailByClient[clientCode]; - if (expandedClientCode === clientCode) { - expandedClientCode = null; - expandedQnKey = null; - expandedVersionKey = null; - } - showToast(`Client "${clientCode}" reset`, 'success'); - renderClientTableBody(); - } catch (e) { - showToast(e.message, 'error'); - } -} - async function deleteClient(clientCode) { if (!(await confirmAction({ title: 'Delete client',