Add attendee type management and staff codes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-25 07:17:47 +00:00
parent dec6446d7d
commit 64ab1d0412
16 changed files with 844 additions and 78 deletions
+27 -15
View File
@@ -9,6 +9,8 @@
<p>{{ 'attendee_badges'|t }}</p>
<div class="header-actions">
<a href="{{ url_for('event_scan', event_id=event.id) }}" class="btn btn-secondary">📷 {{ 'scan_qr_codes'|t }}</a>
<a href="{{ url_for('download_rectangular_badges', event_id=event.id) }}" class="btn btn-secondary">🏷️ Rectangular Labels (80x50mm)</a>
<a href="{{ url_for('download_attendees_excel', event_id=event.id) }}" class="btn btn-secondary">📥 Download Excel</a>
<button onclick="window.print()" class="btn btn-primary">{{ 'print_badges'|t }}</button>
</div>
</div>
@@ -18,7 +20,7 @@
<div class="badge-card" data-first="{{ attendee.first_name }}" data-last="{{ attendee.last_name }}">
<div class="badge-header">
<h3 class="badge-name">
<span class="first-name">{{ attendee.first_name }}</span><br>
<span class="first-name">{{ attendee.first_name }}</span>
<span class="last-name">{{ attendee.last_name }}</span>
</h3>
</div>
@@ -38,27 +40,32 @@
{% if attendee.checked_in %}✓ {{ 'checked_in'|t }}{% else %}{{ 'pending'|t }}{% endif %}
</span>
</div>
<div class="badge-event-bar">
{{ event.name }} — {{ event.start_time|localized_date if event.start_time else 'TBD' }}
</div>
</div>
{% endfor %}
</div>
<style>
.badge-header h3 { margin: 0; font-size: 1rem; line-height: 1.2; }
.badge-header h3 { margin: 0; font-size: 30pt; line-height: 1.1; }
.badge-header h3 .first-name,
.badge-header h3 .last-name { display: block; line-height: 1.1; }
.badge-header h3 .last-name { font-size: 0.8rem; }
.badge-header h3 .last-name { display: inline; line-height: 1.1; font-size: 30pt; }
.badge-header h3 .last-name:before { content: ' '; }
@media print {
.navbar, .footer, .badges-header .header-actions button, .flash-messages { display: none !important; }
.badges-page { padding: 0; }
.badges-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 20px; }
.badge-card { border: 2px solid #000; padding: 15px; page-break-inside: avoid; }
.badge-header h3 { margin: 0; font-size: 1rem; line-height: 1.2; }
.badge-header h3 { margin: 0; font-size: 30pt; line-height: 1.1; }
.badge-header h3 .first-name,
.badge-header h3 .last-name { display: block !important; line-height: 1.1; }
.badge-header h3 .last-name { font-size: 0.8rem !important; }
.badge-header h3 .last-name { display: inline !important; line-height: 1.1; font-size: 30pt; }
.badge-header h3 .last-name:before { content: ' '; }
.badge-qr img { width: 80px !important; height: 80px !important; }
.badge-body p { margin: 5px 0; }
.badge-footer { display: flex; justify-content: space-between; margin-top: 10px; padding-top: 10px; border-top: 1px solid #ccc; }
.badge-event-bar { background: #000; color: #fff; padding: 8px 15px; margin: 10px -15px -15px; font-size: 12pt; text-align: center; }
.badge-event-bar { background: #000; color: #fff; padding: 8px 15px; margin: 10px -15px -15px; font-size: 12pt; text-align: center; }
}
</style>
<script>
@@ -66,9 +73,10 @@
// Create offscreen canvas for text measurement
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
ctx.font = '30pt sans-serif';
// Width limit for last name before it needs font reduction
const LAST_NAME_WIDTH_LIMIT = 120;
// Container width limit (badge width)
const BADGE_WIDTH_LIMIT = 280;
function measureText(text, fontSize, fontFamily) {
ctx.font = fontSize + 'px ' + (fontFamily || 'sans-serif');
@@ -78,16 +86,20 @@
function formatBadgeNames() {
const cards = document.querySelectorAll('.badge-card');
cards.forEach(card => {
const firstName = card.dataset.first || '';
const lastName = card.dataset.last || '';
const firstSpan = card.querySelector('.first-name');
const lastSpan = card.querySelector('.last-name');
// Measure last name at reduced size (0.8rem = 12.8px)
const reducedFontSize = 12.8;
const reducedLastWidth = measureText(lastName, reducedFontSize);
// Measure first + last name at 20pt
const combinedWidth = measureText(firstName + ' ' + lastName, 30);
// If still too wide, reduce further
if (reducedLastWidth > LAST_NAME_WIDTH_LIMIT) {
lastSpan.style.fontSize = '0.7rem';
// If combined width exceeds limit, insert br before last name
if (combinedWidth > BADGE_WIDTH_LIMIT) {
if (!lastSpan.previousElementSibling || lastSpan.previousElementSibling.tagName !== 'BR') {
const br = document.createElement('br');
lastSpan.parentNode.insertBefore(br, lastSpan);
}
}
});
}