Écrans bénévole et référent redessinés, messages aux bénévoles
ci-cd / build-backend (push) Successful in 29s
ci-cd / build-frontend (push) Successful in 21s
ci-cd / deploy (push) Successful in 12s

Étape 55 : écrans /mobile-benevole et /mobile-referent d'après la maquette
(design/Planning bénévole mobile) — créneau en cours ou prochain mis en
avant, planning par journée de festival avec frise horaire, compteurs et
pointage du référent par créneau.

Étape 56 : message de l'organisation (Edition.volunteer_message, onglet
« Message aux bénévoles ») et message de chaque poste (Position.message,
saisi par le référent), migration 0029, affichés sur l'écran bénévole.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
AdamThomas
2026-10-27 23:17:14 +01:00
co-authored by Claude Opus 5.5
parent 4a3f93e137
commit 16d8fb44a7
21 changed files with 1305 additions and 274 deletions
+27
View File
@@ -44,6 +44,7 @@ import type {
VolunteerRead,
EditionStats,
} from '../schema';
import { MESSAGE_MAX_LENGTH } from '../schema';
import { getAuthToken } from '../session';
import { ApiError, type PlanningApi } from '../types';
import { buildMockState, type MockPendingSubmission, type MockState } from './dataset';
@@ -836,6 +837,14 @@ function runSolver(): SolverRunResult {
};
}
/** Message (organisation, poste) comme l'API : bords retirés, vide = plus
* de message, 2000 caractères au plus. */
function checkMessage(value: string | null | undefined): string | null {
const text = (value ?? '').trim() || null;
if (text && text.length > MESSAGE_MAX_LENGTH) throw new ApiError(422, 'error.generic');
return text;
}
export function createMockApi(): PlanningApi {
return {
editions: {
@@ -847,6 +856,13 @@ export function createMockApi(): PlanningApi {
if (body.start_date) next.start_date = body.start_date;
if (body.end_date) next.end_date = body.end_date;
if ('retention_days' in body) next.retention_days = body.retention_days ?? null;
if ('volunteer_message' in body) {
const text = checkMessage(body.volunteer_message);
if (text !== next.volunteer_message) {
next.volunteer_message = text;
next.volunteer_message_updated_at = text ? touch() : null;
}
}
if (next.end_date < next.start_date) {
throw new ApiError(422, 'edition.dates_inconsistent');
}
@@ -1124,6 +1140,8 @@ export function createMockApi(): PlanningApi {
position_location: position?.location ?? null,
referent_name: referent ? `${referent.first_name} ${referent.last_name}` : null,
referent_phone: referent?.phone ?? null,
position_message: position?.message ?? null,
position_message_updated_at: position?.message_updated_at ?? null,
};
})
.sort((a, b) => a.starts_at.localeCompare(b.starts_at));
@@ -1384,6 +1402,8 @@ export function createMockApi(): PlanningApi {
location: body.location ?? null,
category: body.category,
lead_account_id: body.lead_account_id ?? null,
message: null,
message_updated_at: null,
created_at: now,
updated_at: now,
};
@@ -1395,6 +1415,13 @@ export function createMockApi(): PlanningApi {
update: (_edition, id, body) => {
const p = state.positions.find((x) => x.id === id) ?? notFound();
if ('lead_account_id' in body) checkLeadAccount(body.lead_account_id);
if ('message' in body) {
const text = checkMessage(body.message);
if (text !== p.message) {
p.message = text;
p.message_updated_at = text ? touch() : null;
}
}
Object.assign(p, {
...('name' in body ? { name: body.name } : {}),
...('location' in body ? { location: body.location ?? null } : {}),