Écrans bénévole et référent redessinés, messages aux bénévoles
É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:
co-authored by
Claude Opus 5.5
parent
4a3f93e137
commit
16d8fb44a7
@@ -195,6 +195,11 @@ function demoEmail(firstName: string, lastName: string): string {
|
||||
return `${n(firstName)}.${n(lastName)}@mail.fr`;
|
||||
}
|
||||
|
||||
const DEMO_POSITION_MESSAGES: Record<string, string> = {
|
||||
'Parking — Nord': 'Gilet jaune fourni à la base bénévoles. Lampe frontale conseillée le soir.',
|
||||
'Bar — Grande scène': 'Caisse à récupérer auprès du référent en début de créneau. Pas de vente aux mineurs.',
|
||||
};
|
||||
|
||||
export function buildMockState(): MockState {
|
||||
const rnd = mulberry32(20260715);
|
||||
const edition_id = MOCK_EDITION_ID;
|
||||
@@ -206,6 +211,9 @@ export function buildMockState(): MockState {
|
||||
location: p.location,
|
||||
category: p.category,
|
||||
lead_account_id: null,
|
||||
// Quelques consignes de démo (voir `PositionRead.message`).
|
||||
message: DEMO_POSITION_MESSAGES[p.name] ?? null,
|
||||
message_updated_at: DEMO_POSITION_MESSAGES[p.name] ? NOW : null,
|
||||
created_at: NOW,
|
||||
updated_at: NOW,
|
||||
}));
|
||||
@@ -466,6 +474,9 @@ export function buildMockState(): MockState {
|
||||
start_date: DAY_DATES[0],
|
||||
end_date: DAY_DATES[DAY_DATES.length - 1],
|
||||
retention_days: 365,
|
||||
volunteer_message:
|
||||
"Badges et t-shirts à récupérer à l'accueil bénévoles dès 17:00. Repas chaud servi au catering de 17:30 à 18:30.",
|
||||
volunteer_message_updated_at: NOW,
|
||||
created_at: NOW,
|
||||
updated_at: NOW,
|
||||
};
|
||||
|
||||
@@ -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 } : {}),
|
||||
|
||||
@@ -39,16 +39,25 @@ export interface EditionRead {
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
retention_days: number | null;
|
||||
/** Message de l'organisation, le même pour tous les bénévoles (écran
|
||||
* bénévole), et la date de sa dernière modification. */
|
||||
volunteer_message: string | null;
|
||||
volunteer_message_updated_at: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
/** Longueur maximale d'un message (organisation, poste) — comme l'API. */
|
||||
export const MESSAGE_MAX_LENGTH = 2000;
|
||||
|
||||
export interface EditionUpdate {
|
||||
name?: string | null;
|
||||
year?: number;
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
retention_days?: number | null;
|
||||
/** Vide = plus de message. */
|
||||
volunteer_message?: string | null;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- //
|
||||
@@ -185,6 +194,9 @@ export interface VolunteerPlanningEntry {
|
||||
* rattaché à une fiche bénévole (pas de téléphone à donner alors). */
|
||||
referent_name: string | null;
|
||||
referent_phone: string | null;
|
||||
/** Message du poste (voir `PositionRead.message`). */
|
||||
position_message: string | null;
|
||||
position_message_updated_at: string | null;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- //
|
||||
@@ -290,6 +302,10 @@ export interface PositionRead {
|
||||
location: string | null;
|
||||
category: PositionCategory;
|
||||
lead_account_id: string | null;
|
||||
/** Message du poste (consignes…), saisi par son référent, montré aux
|
||||
* bénévoles affectés au poste. */
|
||||
message: string | null;
|
||||
message_updated_at: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
@@ -306,6 +322,8 @@ export interface PositionUpdate {
|
||||
location?: string | null;
|
||||
category?: PositionCategory;
|
||||
lead_account_id?: string | null;
|
||||
/** Vide = plus de message. */
|
||||
message?: string | null;
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- //
|
||||
|
||||
Reference in New Issue
Block a user