Badges : format dérivé de l'image importée, postes avec horaire et référent
- Le format du badge n'est plus fixé à CR80 : il est déduit des pixels de l'image du recto à l'import (résolution supposée 300 dpi, app/badges/pdf.py:image_size_mm), stocké sur BadgeTemplate (migration 0012). L'éditeur (BadgesPage.tsx) et la génération PDF s'alignent sur ce format réel ; CR80 reste le repère par défaut tant qu'aucune image n'est importée. Le verso est ajusté sans déformation (preserveAspectRatio). - Le champ « Postes affectés » liste désormais une ligne par créneau (triées chronologiquement) : nom du poste + horaire, et le référent du poste s'il y en a un et que ce n'est pas le bénévole badgé lui-même. Texte multi-lignes pris en charge par la génération PDF (découpe sur `\n`, hauteur de ligne calculée en mm) et par l'aperçu de l'éditeur. - Corrige au passage un bug d'unités dans le positionnement vertical du texte (police en points confondue avec des millimètres). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
b7fc80a7dc
commit
579ea5d9db
@@ -86,6 +86,21 @@ const notFound = (): never => {
|
||||
throw new ApiError(404, 'error.not_found');
|
||||
};
|
||||
|
||||
// Format CR80 (85,6 × 54 mm) par défaut tant qu'aucune image de recto n'est
|
||||
// importée — sinon le format du badge est dérivé de ses pixels (voir
|
||||
// `imageSizeMm`), comme côté back-end (`app/badges/pdf.py:image_size_mm`).
|
||||
const DEFAULT_BADGE_WIDTH_MM = 85.6;
|
||||
const DEFAULT_BADGE_HEIGHT_MM = 54;
|
||||
const BADGE_ASSUMED_DPI = 300;
|
||||
|
||||
const imageSizeMm = async (file: File): Promise<{ width_mm: number; height_mm: number }> => {
|
||||
const bitmap = await createImageBitmap(file);
|
||||
const width_mm = (bitmap.width / BADGE_ASSUMED_DPI) * 25.4;
|
||||
const height_mm = (bitmap.height / BADGE_ASSUMED_DPI) * 25.4;
|
||||
bitmap.close();
|
||||
return { width_mm, height_mm };
|
||||
};
|
||||
|
||||
const fileToDataUrl = (file: File): Promise<string> =>
|
||||
new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
@@ -1333,15 +1348,22 @@ export function createMockApi(): PlanningApi {
|
||||
edition_id: state.edition.id,
|
||||
has_front_image: state.badgeTemplate.frontImage !== null,
|
||||
has_back_image: state.badgeTemplate.backImage !== null,
|
||||
badge_width_mm: state.badgeTemplate.frontImageWidthMm ?? DEFAULT_BADGE_WIDTH_MM,
|
||||
badge_height_mm: state.badgeTemplate.frontImageHeightMm ?? DEFAULT_BADGE_HEIGHT_MM,
|
||||
fields: state.badgeTemplate.fields,
|
||||
}),
|
||||
uploadFrontImage: async (_edition, file: File) => {
|
||||
const { width_mm, height_mm } = await imageSizeMm(file);
|
||||
state.badgeTemplate.frontImage = await fileToDataUrl(file);
|
||||
state.badgeTemplate.frontImageType = file.type;
|
||||
state.badgeTemplate.frontImageWidthMm = width_mm;
|
||||
state.badgeTemplate.frontImageHeightMm = height_mm;
|
||||
return delay<BadgeTemplateRead>({
|
||||
edition_id: state.edition.id,
|
||||
has_front_image: true,
|
||||
has_back_image: state.badgeTemplate.backImage !== null,
|
||||
badge_width_mm: width_mm,
|
||||
badge_height_mm: height_mm,
|
||||
fields: state.badgeTemplate.fields,
|
||||
});
|
||||
},
|
||||
@@ -1352,6 +1374,8 @@ export function createMockApi(): PlanningApi {
|
||||
edition_id: state.edition.id,
|
||||
has_front_image: state.badgeTemplate.frontImage !== null,
|
||||
has_back_image: true,
|
||||
badge_width_mm: state.badgeTemplate.frontImageWidthMm ?? DEFAULT_BADGE_WIDTH_MM,
|
||||
badge_height_mm: state.badgeTemplate.frontImageHeightMm ?? DEFAULT_BADGE_HEIGHT_MM,
|
||||
fields: state.badgeTemplate.fields,
|
||||
});
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user