This commit is contained in:
@ -40,11 +40,12 @@ case 'POST':
|
||||
if ($name === '') {
|
||||
json_error('BAD_REQUEST', 'name is required', 400);
|
||||
}
|
||||
$sessionNumber = max(1, (int)($body['sessionNumber'] ?? 1));
|
||||
$orderIndex = max(0, (int)($body['orderIndex'] ?? 0));
|
||||
$members = qdb_parse_program_session_questionnaires($body['questionnaires'] ?? []);
|
||||
try {
|
||||
[$pdo, $tmpDb, $lockFp] = qdb_open_write_or_fail();
|
||||
$order = qdb_program_session_resolve_order($pdo, $body);
|
||||
$sessionNumber = $order['sessionNumber'];
|
||||
$orderIndex = $order['orderIndex'];
|
||||
$err = qdb_validate_program_session_questionnaires($pdo, $members);
|
||||
if ($err !== null) {
|
||||
qdb_discard($tmpDb, $lockFp);
|
||||
@ -103,6 +104,7 @@ case 'PUT':
|
||||
json_error('INVALID_FIELD', $err, 400);
|
||||
}
|
||||
}
|
||||
$order = qdb_program_session_resolve_order($pdo, $body, $existing);
|
||||
$pdo->prepare(
|
||||
'UPDATE program_session SET
|
||||
name = :name,
|
||||
@ -115,12 +117,8 @@ case 'PUT':
|
||||
)->execute([
|
||||
':name' => $name,
|
||||
':desc' => trim($body['description'] ?? $existing['description']),
|
||||
':num' => isset($body['sessionNumber'])
|
||||
? max(1, (int)$body['sessionNumber'])
|
||||
: $existing['sessionNumber'],
|
||||
':ord' => isset($body['orderIndex'])
|
||||
? max(0, (int)$body['orderIndex'])
|
||||
: $existing['orderIndex'],
|
||||
':num' => $order['sessionNumber'],
|
||||
':ord' => $order['orderIndex'],
|
||||
':act' => isset($body['isActive'])
|
||||
? (!empty($body['isActive']) ? 1 : 0)
|
||||
: $existing['isActive'],
|
||||
|
||||
@ -109,6 +109,48 @@ function qdb_get_program_session(PDO $pdo, string $sessionID): ?array {
|
||||
return $row;
|
||||
}
|
||||
|
||||
/**
|
||||
* sessionNumber (1-based visit index) and orderIndex (0-based sort key) stay in sync:
|
||||
* orderIndex = sessionNumber - 1.
|
||||
*
|
||||
* @param array<string, mixed> $body
|
||||
* @param array<string, mixed>|null $existing
|
||||
* @return array{sessionNumber: int, orderIndex: int}
|
||||
*/
|
||||
function qdb_program_session_resolve_order(PDO $pdo, array $body, ?array $existing = null): array
|
||||
{
|
||||
if (isset($body['sessionNumber'])) {
|
||||
$sessionNumber = max(1, (int)$body['sessionNumber']);
|
||||
return [
|
||||
'sessionNumber' => $sessionNumber,
|
||||
'orderIndex' => $sessionNumber - 1,
|
||||
];
|
||||
}
|
||||
if (isset($body['orderIndex'])) {
|
||||
$orderIndex = max(0, (int)$body['orderIndex']);
|
||||
return [
|
||||
'sessionNumber' => $orderIndex + 1,
|
||||
'orderIndex' => $orderIndex,
|
||||
];
|
||||
}
|
||||
if ($existing !== null) {
|
||||
$sessionNumber = max(1, (int)$existing['sessionNumber']);
|
||||
return [
|
||||
'sessionNumber' => $sessionNumber,
|
||||
'orderIndex' => $sessionNumber - 1,
|
||||
];
|
||||
}
|
||||
|
||||
$maxNum = (int)$pdo->query('SELECT COALESCE(MAX(sessionNumber), 0) FROM program_session')->fetchColumn();
|
||||
$maxOrd = (int)$pdo->query('SELECT COALESCE(MAX(orderIndex), -1) FROM program_session')->fetchColumn();
|
||||
$sessionNumber = max($maxNum, $maxOrd + 1, 0) + 1;
|
||||
|
||||
return [
|
||||
'sessionNumber' => $sessionNumber,
|
||||
'orderIndex' => $sessionNumber - 1,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{questionnaireID: string, orderIndex: int}>
|
||||
*/
|
||||
|
||||
@ -34,6 +34,36 @@ final class ProgramSessionsTest extends TestCase
|
||||
qdb_save($tmpDb, $lockFp);
|
||||
}
|
||||
|
||||
public function testResolveOrderFromSessionNumber(): void
|
||||
{
|
||||
[$pdo] = qdb_open(true);
|
||||
require_once dirname(__DIR__, 2) . '/lib/program_sessions.php';
|
||||
|
||||
$order = qdb_program_session_resolve_order($pdo, ['sessionNumber' => 3]);
|
||||
$this->assertSame(3, $order['sessionNumber']);
|
||||
$this->assertSame(2, $order['orderIndex']);
|
||||
}
|
||||
|
||||
public function testResolveOrderFromOrderIndexLegacy(): void
|
||||
{
|
||||
[$pdo] = qdb_open(true);
|
||||
require_once dirname(__DIR__, 2) . '/lib/program_sessions.php';
|
||||
|
||||
$order = qdb_program_session_resolve_order($pdo, ['orderIndex' => 4]);
|
||||
$this->assertSame(5, $order['sessionNumber']);
|
||||
$this->assertSame(4, $order['orderIndex']);
|
||||
}
|
||||
|
||||
public function testResolveOrderDefaultsToNextSession(): void
|
||||
{
|
||||
[$pdo] = qdb_open(true);
|
||||
require_once dirname(__DIR__, 2) . '/lib/program_sessions.php';
|
||||
|
||||
$order = qdb_program_session_resolve_order($pdo, []);
|
||||
$this->assertSame(2, $order['sessionNumber']);
|
||||
$this->assertSame(1, $order['orderIndex']);
|
||||
}
|
||||
|
||||
public function testAdvanceCycleWhenSessionComplete(): void
|
||||
{
|
||||
[$pdo, $tmpDb, $lockFp] = qdb_open(true);
|
||||
|
||||
@ -36,8 +36,7 @@ export async function sessionsPage() {
|
||||
function renderSessions(sessions, questionnaires, settings) {
|
||||
const el = document.getElementById('sessionsContent');
|
||||
const sorted = [...sessions].sort((a, b) =>
|
||||
(a.orderIndex ?? 0) - (b.orderIndex ?? 0)
|
||||
|| (a.sessionNumber ?? 0) - (b.sessionNumber ?? 0)
|
||||
effectiveSessionNumber(a) - effectiveSessionNumber(b)
|
||||
|| String(a.name).localeCompare(String(b.name)),
|
||||
);
|
||||
const autoRepeat = !!settings.autoRepeatSessions;
|
||||
@ -145,7 +144,7 @@ function sessionCardHTML(session, questionnaires) {
|
||||
<strong>${esc(session.name)}</strong>
|
||||
${activeBadge}
|
||||
</div>
|
||||
<p class="scoring-profile-bands">Session ${session.sessionNumber} · Order ${session.orderIndex}</p>
|
||||
<p class="scoring-profile-bands">Session ${effectiveSessionNumber(session)}</p>
|
||||
${session.description
|
||||
? `<p class="scoring-profile-desc">${esc(session.description)}</p>`
|
||||
: ''}
|
||||
@ -174,9 +173,7 @@ function openSessionModal(session, questionnaires, allSessions) {
|
||||
}))
|
||||
: [];
|
||||
const selectedIds = new Set(members.map(m => m.questionnaireID));
|
||||
const defaultOrder = allSessions?.length
|
||||
? Math.max(...allSessions.map(s => s.orderIndex ?? 0)) + 1
|
||||
: 0;
|
||||
const defaultSessionNumber = (allSessions?.length ?? 0) + 1;
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'modal-overlay';
|
||||
@ -191,20 +188,14 @@ function openSessionModal(session, questionnaires, allSessions) {
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sessNumber">Session number</label>
|
||||
<input type="number" id="sessNumber" min="1" value="${session?.sessionNumber ?? 1}">
|
||||
<input type="number" id="sessNumber" min="1" value="${session ? effectiveSessionNumber(session) : defaultSessionNumber}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-row" style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
|
||||
<div class="form-group">
|
||||
<label for="sessOrder">Display order</label>
|
||||
<input type="number" id="sessOrder" min="0" value="${session?.orderIndex ?? defaultOrder}">
|
||||
</div>
|
||||
<div class="form-group scoring-profile-active-row" style="align-self:end;padding-bottom:8px">
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" id="sessActive" ${session?.isActive !== 0 ? 'checked' : ''}>
|
||||
Active (visible in app)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group scoring-profile-active-row">
|
||||
<label class="checkbox-inline">
|
||||
<input type="checkbox" id="sessActive" ${session?.isActive !== 0 ? 'checked' : ''}>
|
||||
Active (visible in app)
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="sessDesc">Description</label>
|
||||
@ -314,7 +305,6 @@ function openSessionModal(session, questionnaires, allSessions) {
|
||||
const name = overlay.querySelector('#sessName')?.value.trim();
|
||||
const description = overlay.querySelector('#sessDesc')?.value.trim() || '';
|
||||
const sessionNumber = parseInt(overlay.querySelector('#sessNumber')?.value || '1', 10);
|
||||
const orderIndex = parseInt(overlay.querySelector('#sessOrder')?.value || '0', 10);
|
||||
const isActive = overlay.querySelector('#sessActive')?.checked;
|
||||
if (!name) {
|
||||
showToast('Session name is required', 'error');
|
||||
@ -324,7 +314,6 @@ function openSessionModal(session, questionnaires, allSessions) {
|
||||
name,
|
||||
description,
|
||||
sessionNumber: Number.isFinite(sessionNumber) ? Math.max(1, sessionNumber) : 1,
|
||||
orderIndex: Number.isFinite(orderIndex) ? Math.max(0, orderIndex) : 0,
|
||||
isActive,
|
||||
questionnaires: members.map((m, idx) => ({
|
||||
questionnaireID: m.questionnaireID,
|
||||
@ -347,6 +336,11 @@ function openSessionModal(session, questionnaires, allSessions) {
|
||||
});
|
||||
}
|
||||
|
||||
function effectiveSessionNumber(session) {
|
||||
if (!session) return 1;
|
||||
return Math.max(session.sessionNumber ?? 1, (session.orderIndex ?? 0) + 1);
|
||||
}
|
||||
|
||||
function esc(s) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = s ?? '';
|
||||
|
||||
Reference in New Issue
Block a user