Refonte du créateur de badges et nombre de badges par bénévole
Créateur de badges refait d'après la maquette : champs sur le recto et le verso (BadgeField.face, verso placé en miroir dans le PDF pour le recto-verso), fond de zone, réduction automatique du texte, photo ovale, initiales sans photo, nom sur deux lignes (migration 0026) ; éditeur en trois colonnes (champs, aperçu recto/verso avec de vrais bénévoles, inspecteur), formats prédéfinis, annuler, flèches, zoom, repères de découpe, dépôt d'image sur l'aperçu, contrôle de tous les badges avant génération. Nombre de badges par bénévole (Volunteer.badge_count, 1 par défaut, migration 0027) : champ sur la fiche, autant d'exemplaires sur les planches, total affiché à la génération, colonne dans l'export Excel. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
1a23881e81
commit
ced0855d47
@@ -461,6 +461,64 @@ function checkDayBoundary() {
|
||||
console.log(`ok ${'dayBoundary'.padEnd(26)} ${cases.length} cas`);
|
||||
}
|
||||
|
||||
// Éditeur de badges (onglet « Personnaliser ») : un champ par face, rendu
|
||||
// avec les vraies données (nom du premier bénévole dans l'ordre
|
||||
// d'impression), contrôle avant génération présent ; le mock refuse un même
|
||||
// type de champ deux fois sur une face, mais l'accepte sur les deux.
|
||||
async function checkBadgeEditor() {
|
||||
if (!coordinator) throw new Error('badges : pas de session coordination pour ce test');
|
||||
const base = {
|
||||
x_mm: 4, y_mm: 4, width_mm: 40, height_mm: 12, font_size: 9, align: 'LEFT' as const,
|
||||
bold: true, color: '#14110E', bg_color: null, auto_fit: true, photo_shape: 'RECT' as const,
|
||||
};
|
||||
let duplicate = 'non';
|
||||
try {
|
||||
await api.badges.setFields(e, {
|
||||
fields: [
|
||||
{ ...base, field_type: 'FULL_NAME', face: 'FRONT' },
|
||||
{ ...base, field_type: 'FULL_NAME', face: 'FRONT' },
|
||||
],
|
||||
});
|
||||
} catch (err) {
|
||||
duplicate = err instanceof ApiError ? err.code : String(err);
|
||||
}
|
||||
if (duplicate !== 'badge.duplicate_field_type') throw new Error(`doublon accepté : ${duplicate}`);
|
||||
await api.badges.setFields(e, {
|
||||
fields: [
|
||||
{ ...base, field_type: 'FULL_NAME', face: 'FRONT' },
|
||||
{ ...base, field_type: 'POSITIONS', face: 'FRONT', y_mm: 18, height_mm: 30, bold: false },
|
||||
{ ...base, field_type: 'FULL_NAME', face: 'BACK', bg_color: '#FFD23F' },
|
||||
{ ...base, field_type: 'FREE_TEXT', face: 'BACK', y_mm: 30, text: 'STAFF' },
|
||||
],
|
||||
});
|
||||
const vols = [...(await api.volunteers.list(e))].sort(
|
||||
(a, b) =>
|
||||
a.last_name.localeCompare(b.last_name, 'fr') || a.first_name.localeCompare(b.first_name, 'fr'),
|
||||
);
|
||||
const client = new QueryClient({ defaultOptions: { queries: { retry: false } } });
|
||||
await prefetchCore(client);
|
||||
await Promise.all([
|
||||
client.prefetchQuery({ queryKey: ['badgeTemplate', e], queryFn: () => api.badges.getTemplate(e) }),
|
||||
client.prefetchQuery({ queryKey: ['dayParts', e], queryFn: () => api.dayParts.list(e) }),
|
||||
client.prefetchQuery({ queryKey: ['accounts', e], queryFn: () => api.accounts.list(e) }),
|
||||
]);
|
||||
const html = renderToString(
|
||||
createElement(
|
||||
QueryClientProvider,
|
||||
{ client },
|
||||
createElement(
|
||||
AuthContext.Provider,
|
||||
{ value: authValue(coordinator) },
|
||||
createElement(MemoryRouter, null, createElement(BadgesPage)),
|
||||
),
|
||||
),
|
||||
);
|
||||
const expected = [vols[0]?.last_name ?? '', 'STAFF', 'Contrôle des'];
|
||||
const missing = expected.filter((text) => !html.includes(text));
|
||||
if (missing.length) throw new Error(`badges : absent du rendu ${JSON.stringify(missing)}`);
|
||||
console.log(`ok ${'/badges'.padEnd(26)} ${html.length} octets`);
|
||||
}
|
||||
|
||||
// --------------------------------------------------------------------------- //
|
||||
try {
|
||||
checkDayBoundary();
|
||||
@@ -484,7 +542,7 @@ if (coordinator) {
|
||||
await renderProtected('/grille', coordinator, createElement(GrillePage));
|
||||
await renderProtected('/benevoles', coordinator, createElement(VolunteerViewPage));
|
||||
await renderProtected('/configuration', coordinator, createElement(ConfigPage));
|
||||
await renderProtected('/badges', coordinator, createElement(BadgesPage));
|
||||
await checkBadgeEditor().catch((err) => fail('checkBadgeEditor', err));
|
||||
await renderProtected('/inscriptions', coordinator, createElement(FormBuilderPage));
|
||||
await renderProtected('/statistiques', coordinator, createElement(StatsPage), async (client) => {
|
||||
await client.prefetchQuery({ queryKey: ['stats', e], queryFn: () => api.stats.get(e) });
|
||||
|
||||
Reference in New Issue
Block a user