updated terminology from "Coach" to "Counselor" across multiple files for consistency and clarity
Some checks failed
PHPUnit / test (push) Has been cancelled
Some checks failed
PHPUnit / test (push) Has been cancelled
This commit is contained in:
@ -291,7 +291,7 @@ function qdb_reject_coach_web_login(string $role): void {
|
||||
if ($role === 'coach' && qdb_is_web_client_request()) {
|
||||
json_error(
|
||||
'FORBIDDEN',
|
||||
'Coach accounts can only sign in through the mobile app.',
|
||||
'Counselor accounts can only sign in through the mobile app.',
|
||||
403
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"version": 2,
|
||||
"description": "Coach-visible app UI strings only (excludes dev settings \/ database tools).",
|
||||
"description": "Berater-visible app UI strings only (excludes dev settings / database tools).",
|
||||
"keys": [
|
||||
"all_clients",
|
||||
"answer",
|
||||
@ -128,7 +128,7 @@
|
||||
"august": "August",
|
||||
"auth_footer_accounts": "Konten werden von Ihrer Organisation vergeben.",
|
||||
"auth_subtitle_change_password": "Bitte legen Sie jetzt ein eigenes Passwort fest.",
|
||||
"auth_subtitle_login": "Melden Sie sich mit Ihrem Coach-Konto an.",
|
||||
"auth_subtitle_login": "Melden Sie sich mit Ihrem Berater-Konto an.",
|
||||
"auth_title": "BW Schützt - NAT-AS",
|
||||
"cancel": "Abbrechen",
|
||||
"category_green": "Grün",
|
||||
@ -139,7 +139,7 @@
|
||||
"client": "Klient",
|
||||
"client_code": "Klientencode",
|
||||
"client_code_exists": "Dieser Client-Code existiert bereits.",
|
||||
"coach_code": "Code Coach",
|
||||
"coach_code": "Berater-Code",
|
||||
"confirm_password_hint": "Passwort bestätigen",
|
||||
"date_after": "Das Datum muss nach",
|
||||
"date_before": "Das Datum muss vor",
|
||||
@ -148,7 +148,7 @@
|
||||
"done": "Erledigt",
|
||||
"download": "Herunterladen",
|
||||
"download_failed_use_offline": "Download fehlgeschlagen – arbeite offline mit vorhandener Datenbank",
|
||||
"enter_coach_code": "Bitte geben Sie Ihren Coach Code ein, um fortzufahren!",
|
||||
"enter_coach_code": "Bitte geben Sie Ihren Berater-Code ein, um fortzufahren!",
|
||||
"error": "Fehler",
|
||||
"error_invalid_data": "Ungültige Daten",
|
||||
"error_not_found": "Nicht gefunden",
|
||||
@ -157,7 +157,7 @@
|
||||
"extreme_glass": "Extrem",
|
||||
"february": "Februar",
|
||||
"fill_all_fields": "Bitte alle Felder ausfüllen!",
|
||||
"fill_both_fields": "Bitte Klienten-Code und Coach-Code prüfen.",
|
||||
"fill_both_fields": "Bitte Klienten-Code und Berater-Code prüfen.",
|
||||
"hours_short": "h",
|
||||
"january": "Januar",
|
||||
"july": "Juli",
|
||||
@ -210,7 +210,7 @@
|
||||
"review_scores": "Punkte prüfen",
|
||||
"review_scores_agree": "Berechnete Kategorie bestätigen",
|
||||
"review_scores_calculated_category": "Berechnete Kategorie",
|
||||
"review_scores_coach_category": "Coach-Kategorie",
|
||||
"review_scores_coach_category": "Berater-Kategorie",
|
||||
"review_scores_empty": "Noch keine vollständigen Profilwerte zum Prüfen.",
|
||||
"review_scores_pending_upload": "Ausstehend — wird mit dem Upload gesendet",
|
||||
"review_scores_save_failed": "Kategorie konnte nicht gespeichert werden.",
|
||||
@ -233,10 +233,10 @@
|
||||
"upload_item_scoring_review": "Punkteprüfung: {profile}",
|
||||
"upload_nothing_pending": "Es wurde nichts hochgeladen. Es liegen keine abgeschlossenen Fragebögen zum Upload vor. Bitte zuerst einen Fragebogen abschließen.",
|
||||
"upload_prepare_failed": "Es wurde nichts hochgeladen. Die Daten konnten nicht für den Upload vorbereitet werden. Bitte erneut synchronisieren oder den Support kontaktieren.",
|
||||
"upload_scoring_review_summary": "Coach: {band} · Summe {total}",
|
||||
"upload_scoring_review_summary": "Berater: {band} · Summe {total}",
|
||||
"upload_summary": "{qn} Fragebögen · {scores} Punkteprüfungen · {clients} Klienten",
|
||||
"upload_success_message": "Hochladen: {count} erledigt.",
|
||||
"upload_success_questionnaires": "{count} Fragebogen\/Fragebögen",
|
||||
"upload_success_questionnaires": "{count} Fragebogen/Fragebögen",
|
||||
"upload_success_scoring_reviews": "{count} Punkteprüfung(en)",
|
||||
"username_hint": "Benutzername",
|
||||
"year": "Jahr"
|
||||
|
||||
@ -70,7 +70,7 @@ case 'POST':
|
||||
}
|
||||
if (!$chk->fetch()) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('NOT_FOUND', 'Coach not found or not authorized', 404);
|
||||
json_error('NOT_FOUND', 'Counselor not found or not authorized', 404);
|
||||
}
|
||||
|
||||
[$rbacClause, $rbacParams] = rbac_client_filter($tokenRec);
|
||||
|
||||
@ -83,7 +83,7 @@ case 'POST':
|
||||
}
|
||||
if (!$chkCoach->fetch()) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('NOT_FOUND', 'Coach not found or not authorized', 404);
|
||||
json_error('NOT_FOUND', 'Counselor not found or not authorized', 404);
|
||||
}
|
||||
|
||||
$chk = $pdo->prepare("SELECT clientCode FROM client WHERE clientCode = :cc");
|
||||
|
||||
@ -20,7 +20,7 @@ if (!$rec) {
|
||||
|
||||
$role = (string)($rec['role'] ?? '');
|
||||
if ($role === 'coach') {
|
||||
json_error('FORBIDDEN', 'Coach accounts can only sign in through the mobile app.', 403);
|
||||
json_error('FORBIDDEN', 'Counselor accounts can only sign in through the mobile app.', 403);
|
||||
}
|
||||
|
||||
$username = '';
|
||||
|
||||
@ -207,7 +207,7 @@ case 'PATCH':
|
||||
$chk->execute([':cid' => $target['entityID'], ':svid' => $callerEntityID]);
|
||||
if (!$chk->fetch()) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('FORBIDDEN', 'Coach is not under your supervision', 403);
|
||||
json_error('FORBIDDEN', 'Counselor is not under your supervision', 403);
|
||||
}
|
||||
} else {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
@ -274,7 +274,7 @@ case 'PATCH':
|
||||
$chk->execute([':cid' => $target['entityID'], ':svid' => $callerEntityID]);
|
||||
if (!$chk->fetch()) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('FORBIDDEN', 'Coach is not under your supervision', 403);
|
||||
json_error('FORBIDDEN', 'Counselor is not under your supervision', 403);
|
||||
}
|
||||
} else {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
@ -396,7 +396,7 @@ case 'DELETE':
|
||||
$chk->execute([':cid' => $target['entityID'], ':svid' => $callerEntityID]);
|
||||
if (!$chk->fetch()) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
json_error('FORBIDDEN', 'Coach is not under your supervision', 403);
|
||||
json_error('FORBIDDEN', 'Counselor is not under your supervision', 403);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -346,7 +346,7 @@ function qdb_coach_recent_submissions(
|
||||
$chk->execute([':id' => $coachID]);
|
||||
$coach = $chk->fetch(PDO::FETCH_ASSOC);
|
||||
if (!$coach) {
|
||||
json_error('NOT_FOUND', 'Coach not found', 404);
|
||||
json_error('NOT_FOUND', 'Counselor not found', 404);
|
||||
}
|
||||
if ($role === 'supervisor' && ($coach['supervisorID'] ?? '') !== $entityID) {
|
||||
json_error('FORBIDDEN', 'Not authorized for this coach', 403);
|
||||
|
||||
@ -93,13 +93,13 @@ function qdb_import_dev_fixture(PDO $pdo, array $fixture): array
|
||||
$username = qdb_dev_norm_username($row, $prefix);
|
||||
$svUser = trim($row['supervisor'] ?? $row['supervisorUsername'] ?? '');
|
||||
if ($svUser === '' || !qdb_dev_has_prefix($svUser, $prefix)) {
|
||||
$errors[] = "Coach $username: missing or invalid supervisor";
|
||||
$errors[] = "Counselor $username: missing or invalid supervisor";
|
||||
continue;
|
||||
}
|
||||
$supervisorID = $supervisorIdByUsername[$svUser]
|
||||
?? qdb_dev_lookup_entity_id($pdo, $svUser);
|
||||
if ($supervisorID === '') {
|
||||
$errors[] = "Coach $username: supervisor $svUser not found";
|
||||
$errors[] = "Counselor $username: supervisor $svUser not found";
|
||||
continue;
|
||||
}
|
||||
if (qdb_dev_user_exists($pdo, $username)) {
|
||||
|
||||
@ -35,6 +35,13 @@ export function canEdit() {
|
||||
return canAccessWebsite();
|
||||
}
|
||||
|
||||
/** User-facing label for the coach role (internal key remains `coach`). */
|
||||
export function formatRoleLabel(role) {
|
||||
if (role === 'coach') return 'Counselor';
|
||||
if (!role) return '';
|
||||
return role.charAt(0).toUpperCase() + role.slice(1);
|
||||
}
|
||||
|
||||
function clearSession() {
|
||||
invalidateSessionCheck();
|
||||
localStorage.removeItem('qdb_token');
|
||||
@ -109,7 +116,7 @@ function updateNav() {
|
||||
if (isLoggedIn()) {
|
||||
nav.style.display = '';
|
||||
const role = getRole();
|
||||
const roleLabel = role === 'coach' ? 'counselor' : role;
|
||||
const roleLabel = formatRoleLabel(role);
|
||||
userInfo.textContent = `${getUser()} (${roleLabel})`;
|
||||
|
||||
// Show/hide role-gated nav items
|
||||
|
||||
@ -108,7 +108,7 @@ function renderAssignments() {
|
||||
<input type="checkbox" id="selectAllCb" title="Toggle all visible rows" aria-label="Select all visible">
|
||||
</th>
|
||||
<th>Client Code</th>
|
||||
<th>Current Counselor</th>
|
||||
<th>Current counselor</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="clientTableBody"></tbody>
|
||||
|
||||
@ -105,7 +105,7 @@ function renderClients() {
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Client Code</th>
|
||||
<th>Current Counselor</th>
|
||||
<th>Current counselor</th>
|
||||
${showProfileColumn ? '<th>Profile scores</th>' : ''}
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
@ -305,7 +305,7 @@ function scoringLegendHTML() {
|
||||
<dl class="client-scoring-legend-dl">
|
||||
<div>
|
||||
<dt>Calculated</dt>
|
||||
<dd>Automatic category from answers and profile rules (shown before counselor review).</dd>
|
||||
<dd>Automatic category from answers and profile rules (shown before Counselor review).</dd>
|
||||
</div>
|
||||
<div>
|
||||
<dt>Counselor</dt>
|
||||
@ -371,7 +371,7 @@ function coachBandPickerHTML(p, clientCode) {
|
||||
data-weighted="${esc(String(p.weightedTotal ?? ''))}"
|
||||
data-coach="${esc(current)}">
|
||||
<button type="button" class="coach-band-trigger" aria-haspopup="listbox" aria-expanded="false"
|
||||
title="Change counselor category">
|
||||
title="Change Counselor category">
|
||||
<span class="coach-band-trigger-badge">${triggerBadge}</span>
|
||||
<span class="coach-band-trigger-caret" aria-hidden="true">▾</span>
|
||||
</button>
|
||||
@ -423,7 +423,7 @@ function renderClientScoringProfiles(profiles, clientCode = '') {
|
||||
<div class="client-scoring-profiles-detail">
|
||||
<strong>Scoring profiles</strong>
|
||||
<p class="data-toolbar-hint" style="margin:4px 0 10px">
|
||||
Weighted totals and calculated vs counselor categories for this client.
|
||||
Weighted totals and calculated vs Counselor categories for this client.
|
||||
</p>
|
||||
<div class="client-profile-scores">${profiles.map(p => {
|
||||
const calc = p.calculatedBand || p.band;
|
||||
@ -595,7 +595,7 @@ function openCoachBandConfirmModal({ clientCode, profileName, calculatedBand, co
|
||||
overlay.setAttribute('aria-modal', 'true');
|
||||
overlay.innerHTML = `
|
||||
<div class="modal-dialog">
|
||||
<h2>Set counselor category</h2>
|
||||
<h2>Set Counselor category</h2>
|
||||
<p class="modal-subtitle">
|
||||
<strong>${esc(profileName)}</strong> · ${esc(clientCode)}
|
||||
</p>
|
||||
@ -610,7 +610,7 @@ function openCoachBandConfirmModal({ clientCode, profileName, calculatedBand, co
|
||||
${bandBadgeHTML(coachBand, { label: bandLabel(coachBand) })}
|
||||
</div>
|
||||
</div>
|
||||
<p class="modal-hint">This overrides the counselor category for this client and profile.</p>
|
||||
<p class="modal-hint">This overrides the Counselor category for this client and profile.</p>
|
||||
<div class="modal-actions">
|
||||
<button type="button" class="btn" data-coach-cancel>Cancel</button>
|
||||
<button type="button" class="btn btn-primary" data-coach-confirm>Save category</button>
|
||||
@ -859,7 +859,7 @@ function showCreateForm() {
|
||||
<input type="text" id="cc_code" autocomplete="off" placeholder="Enter unique client code">
|
||||
</div>
|
||||
<div class="form-group" style="flex:2">
|
||||
<label>Assign to Counselor</label>
|
||||
<label>Assign to counselor</label>
|
||||
<select id="cc_coach">
|
||||
<option value="">— select counselor —</option>
|
||||
${coachesList.map(c =>
|
||||
|
||||
@ -325,7 +325,7 @@ function renderCoachTableBody() {
|
||||
const q = filterSearch.trim();
|
||||
countEl.textContent = q
|
||||
? `${filtered.length} of ${coachesList.length} counselors`
|
||||
: `${coachesList.length} counselor${coachesList.length === 1 ? '' : 's'}`;
|
||||
: `${coachesList.length} counselors${coachesList.length === 1 ? '' : ''}`;
|
||||
}
|
||||
|
||||
body.querySelectorAll('.coach-expand-btn').forEach(btn => {
|
||||
|
||||
@ -84,7 +84,7 @@ export function devPage() {
|
||||
<div class="card" style="max-width:720px;margin-top:16px">
|
||||
<h2 style="margin:0 0 8px;font-size:1.1rem">Reset database</h2>
|
||||
<p class="field-hint callout-danger">
|
||||
<strong>Destructive.</strong> Deletes every client, completion, counselor, and supervisor
|
||||
<strong>Destructive.</strong> Deletes all clients, completions, counselors, and supervisors
|
||||
(including non-dev accounts). Admin logins and questionnaire definitions are kept.
|
||||
</p>
|
||||
<button type="button" class="btn btn-danger" id="devWipeBtn">Remove all data (keep admins)</button>
|
||||
|
||||
@ -28,7 +28,7 @@ const LAYOUT_TYPES = [
|
||||
{ value: 'date_spinner', label: 'Date Spinner' },
|
||||
{ value: 'string_spinner', label: 'String Spinner' },
|
||||
{ value: 'free_text', label: 'Free Text' },
|
||||
{ value: 'client_coach_code_question', label: 'Client / Counselor Code' },
|
||||
{ value: 'client_coach_code_question', label: 'Client / Counselor code' },
|
||||
{ value: 'last_page', label: 'Last Page' },
|
||||
];
|
||||
|
||||
@ -659,7 +659,7 @@ function configFormHTML(layout, config, prefix, opts = {}) {
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Hint 2 key</label>
|
||||
<input type="text" id="${prefix}_hint2" value="${esc(config.hint2 || '')}" placeholder="counselor_code">
|
||||
<input type="text" id="${prefix}_hint2" value="${esc(config.hint2 || '')}" placeholder="berater_code">
|
||||
</div>
|
||||
</div>`;
|
||||
break;
|
||||
@ -824,7 +824,7 @@ function renderEditor() {
|
||||
<div id="retiredQuestionsSection" style="display:none;margin-top:20px">
|
||||
<details class="retired-questions-panel">
|
||||
<summary>Retired questions (<span id="retiredCount">0</span>)</summary>
|
||||
<p class="field-hint">Retired questions stay in upload history; counselors on older forms can still submit.</p>
|
||||
<p class="field-hint">Retired questions stay in upload history; Counselors on older forms can still submit.</p>
|
||||
<ul class="question-list retired-question-list" id="retiredQuestionList"></ul>
|
||||
</details>
|
||||
</div>
|
||||
|
||||
@ -102,7 +102,7 @@ function renderInsights() {
|
||||
<p class="insights-chart-hint">
|
||||
${sp.clientCount ?? 0} client(s) with complete profile
|
||||
${sp.averageTotal != null ? ` · avg ${sp.averageTotal}` : ''}
|
||||
${sp.pendingReview ? ` · ${sp.pendingReview} pending counselor review` : ''}
|
||||
${sp.pendingReview ? ` · ${sp.pendingReview} pending Counselor review` : ''}
|
||||
</p>
|
||||
<p class="insights-chart-hint" style="margin:0 0 6px"><strong>Calculated bands</strong></p>
|
||||
${computedChart}
|
||||
@ -141,7 +141,7 @@ function renderInsights() {
|
||||
${scoringProfiles.length ? `
|
||||
<div class="card" style="margin-top:20px">
|
||||
<h3 style="margin:0 0 8px">Scoring profiles</h3>
|
||||
<p class="insights-chart-hint" style="margin:0 0 12px">Calculated vs counselor band distribution among clients with a complete weighted score</p>
|
||||
<p class="insights-chart-hint" style="margin:0 0 12px">Calculated vs Counselor band distribution among clients with a complete weighted score</p>
|
||||
<div class="insights-charts-grid">${scoringCharts}</div>
|
||||
</div>` : ''}
|
||||
<div class="card" style="margin-top:20px">
|
||||
|
||||
@ -575,7 +575,7 @@ function openScoringProfileModal(profile, questionnaires, allProfiles) {
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="spDesc">Description</label>
|
||||
<textarea id="spDesc" rows="2" placeholder="Optional note for counselors and admins">${esc(profile?.description || '')}</textarea>
|
||||
<textarea id="spDesc" rows="2" placeholder="Optional note for Counselors and admins">${esc(profile?.description || '')}</textarea>
|
||||
</div>
|
||||
<div class="form-group scoring-profile-active-row">
|
||||
<label class="checkbox-inline">
|
||||
|
||||
@ -94,7 +94,7 @@ function renderResults() {
|
||||
to
|
||||
<input type="date" id="filterDateTo" value="${esc(filterDateTo)}">
|
||||
</label>
|
||||
<input type="search" id="filterSearch" class="filter-search" placeholder="Search client, counselor, status…" value="${esc(filterSearch)}">
|
||||
<input type="search" id="filterSearch" class="filter-search" placeholder="Search client, counselors, status…" value="${esc(filterSearch)}">
|
||||
<input type="search" id="filterQuestion" placeholder="Go to question key…" value="${esc(filterQuestion)}" style="min-width:160px" list="questionKeyList">
|
||||
<datalist id="questionKeyList"></datalist>
|
||||
<button type="button" class="btn btn-sm" id="gotoQuestionBtn">Go to column</button>
|
||||
@ -423,7 +423,7 @@ function exportCSV() {
|
||||
});
|
||||
});
|
||||
|
||||
const headers = ['clientCode', 'coach', 'supervisor', 'status', 'sumPoints', 'completedAt',
|
||||
const headers = ['clientCode', 'Counselor', 'supervisor', 'status', 'sumPoints', 'completedAt',
|
||||
...resultColumns.map(col => col.label)];
|
||||
|
||||
const rows = clients.map(c => {
|
||||
|
||||
@ -17,11 +17,11 @@ let filterSearchByRole = { admin: '', supervisor: '', coach: '' };
|
||||
|
||||
function roleGroupsForCaller() {
|
||||
return callerRole === 'supervisor'
|
||||
? [{ label: 'Counselors', key: 'coach' }]
|
||||
? [{ label: 'Counselor', key: 'coach' }]
|
||||
: [
|
||||
{ label: 'Admins', key: 'admin' },
|
||||
{ label: 'Supervisors', key: 'supervisor' },
|
||||
{ label: 'Counselors', key: 'coach' },
|
||||
{ label: 'Counselor', key: 'coach' },
|
||||
];
|
||||
}
|
||||
|
||||
@ -541,7 +541,7 @@ function downloadUsersCSV() {
|
||||
const headers = ['username', 'role', 'location', 'supervisor', 'mustChangePassword', 'createdAt', 'userID'];
|
||||
const rows = usersList.map(u => [
|
||||
u.username,
|
||||
u.role === 'coach' ? 'counselor' : u.role,
|
||||
u.role === 'coach' ? 'Counselor' : u.role,
|
||||
u.role === 'coach' ? '' : (u.location || ''),
|
||||
u.role === 'coach' ? (u.supervisorUsername || u.supervisorID || '') : '',
|
||||
u.mustChangePassword == 1 ? 'yes' : 'no',
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { apiPatch, apiPost, invalidateSessionCheck } from './api.js';
|
||||
import { getRole, getUser, showToast } from './app.js';
|
||||
import { getRole, getUser, showToast, formatRoleLabel } from './app.js';
|
||||
|
||||
/** @typedef {'admin_reset' | 'self'} PasswordModalMode */
|
||||
|
||||
@ -13,9 +13,7 @@ function esc(s) {
|
||||
}
|
||||
|
||||
function roleLabel(role) {
|
||||
if (!role) return '';
|
||||
if (role === 'coach') return 'Counselor';
|
||||
return role.charAt(0).toUpperCase() + role.slice(1);
|
||||
return formatRoleLabel(role);
|
||||
}
|
||||
|
||||
function signInHintForRole(role) {
|
||||
|
||||
@ -399,7 +399,7 @@ function renderQuestionBody(q, sectionIdx) {
|
||||
|
||||
case 'client_coach_code_question':
|
||||
inner += `
|
||||
<p class="qp-hint qp-review-note">In the app, client and counselor codes are loaded automatically — counselors review them here, not type them in.</p>
|
||||
<p class="qp-hint qp-review-note">In the app, client and Counselor codes are loaded automatically — Counselor review them here, not type them in.</p>
|
||||
<div class="qp-form-stack qp-review-codes">
|
||||
<label>Client code
|
||||
<input type="text" class="qp-readonly-field" data-field="client"
|
||||
@ -434,7 +434,7 @@ function layoutLabel(type) {
|
||||
date_spinner: 'Date',
|
||||
string_spinner: 'String spinner',
|
||||
free_text: 'Free text',
|
||||
client_coach_code_question: 'Client / counselor code',
|
||||
client_coach_code_question: 'Client / Counselor code',
|
||||
last_page: 'Last page',
|
||||
};
|
||||
return labels[type] || type || 'Unknown';
|
||||
|
||||
Reference in New Issue
Block a user