2026-03-20 17:02:56 +01:00
|
|
|
{% extends 'base.html.twig' %}
|
|
|
|
|
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
{% block title %}{{ event.title }} - E-Ticket{% endblock %}
|
2026-03-20 17:02:56 +01:00
|
|
|
|
|
|
|
|
{% block body %}
|
2026-03-20 19:11:02 +01:00
|
|
|
<div class="w-full md:w-[80%] mx-auto py-12 px-4">
|
2026-03-20 17:02:56 +01:00
|
|
|
<a href="{{ path('app_account', {tab: 'events'}) }}" class="inline-flex items-center gap-2 text-sm font-black uppercase tracking-widest text-gray-500 hover:text-gray-900 transition-colors mb-8">
|
|
|
|
|
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M15 19l-7-7 7-7"/></svg>
|
|
|
|
|
Retour aux evenements
|
|
|
|
|
</a>
|
|
|
|
|
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
<h1 class="text-3xl font-black uppercase tracking-tighter italic heading-page">{{ event.title }}</h1>
|
|
|
|
|
<p class="font-bold text-gray-600 italic mb-4">Gestion de l'evenement.</p>
|
2026-03-20 17:02:56 +01:00
|
|
|
|
2026-03-20 17:42:08 +01:00
|
|
|
{% for message in app.flashes('success') %}
|
|
|
|
|
<div class="flash-success"><p class="font-black text-sm">{{ message }}</p></div>
|
|
|
|
|
{% endfor %}
|
2026-03-20 17:02:56 +01:00
|
|
|
{% for message in app.flashes('error') %}
|
|
|
|
|
<div class="flash-error"><p class="font-black text-sm">{{ message }}</p></div>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
<div class="flex flex-wrap gap-4 mb-6">
|
2026-03-20 17:42:08 +01:00
|
|
|
{% if event.online %}
|
|
|
|
|
<form method="post" action="{{ path('app_account_toggle_event_online', {id: event.id}) }}">
|
|
|
|
|
<button type="submit" class="px-4 py-2 border-2 border-red-800 bg-red-600 text-white font-black uppercase text-xs tracking-widest cursor-pointer hover:bg-red-800 transition-all">
|
|
|
|
|
Passer hors ligne
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
2026-03-20 17:47:31 +01:00
|
|
|
{% elseif app.user.stripeChargesEnabled and app.user.stripePayoutsEnabled %}
|
2026-03-20 17:42:08 +01:00
|
|
|
<form method="post" action="{{ path('app_account_toggle_event_online', {id: event.id}) }}">
|
|
|
|
|
<button type="submit" class="px-4 py-2 border-2 border-gray-900 bg-green-500 text-white font-black uppercase text-xs tracking-widest cursor-pointer hover:bg-green-700 transition-all">
|
|
|
|
|
Mettre en ligne
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
2026-03-20 17:47:31 +01:00
|
|
|
{% else %}
|
|
|
|
|
<span class="px-4 py-2 border-2 border-gray-300 bg-gray-100 text-gray-400 font-black uppercase text-xs tracking-widest cursor-not-allowed">
|
|
|
|
|
Stripe requis pour mettre en ligne
|
|
|
|
|
</span>
|
2026-03-20 17:42:08 +01:00
|
|
|
{% endif %}
|
2026-03-20 17:02:56 +01:00
|
|
|
|
2026-03-20 17:42:08 +01:00
|
|
|
{% if event.secret %}
|
|
|
|
|
<form method="post" action="{{ path('app_account_toggle_event_secret', {id: event.id}) }}">
|
|
|
|
|
<button type="submit" class="px-4 py-2 border-2 border-gray-900 bg-[#fabf04] font-black uppercase text-xs tracking-widest cursor-pointer hover:bg-yellow-500 transition-all">
|
|
|
|
|
Rendre public
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
{% else %}
|
|
|
|
|
<form method="post" action="{{ path('app_account_toggle_event_secret', {id: event.id}) }}">
|
|
|
|
|
<button type="submit" class="px-4 py-2 border-2 border-gray-900 bg-gray-200 font-black uppercase text-xs tracking-widest cursor-pointer hover:bg-gray-300 transition-all">
|
|
|
|
|
Rendre secret
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
{% endif %}
|
2026-03-20 17:02:56 +01:00
|
|
|
|
2026-03-20 17:42:08 +01:00
|
|
|
<div class="flex items-center gap-2 ml-auto">
|
|
|
|
|
{% if event.online %}
|
|
|
|
|
<span class="badge-green text-xs font-black uppercase">En ligne</span>
|
|
|
|
|
{% else %}
|
|
|
|
|
<span class="badge-red text-xs font-black uppercase">Hors ligne</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if event.secret %}
|
|
|
|
|
<span class="badge-yellow text-xs font-black uppercase">Secret</span>
|
|
|
|
|
{% endif %}
|
2026-03-20 17:02:56 +01:00
|
|
|
</div>
|
2026-03-20 17:42:08 +01:00
|
|
|
</div>
|
2026-03-20 17:02:56 +01:00
|
|
|
|
2026-03-20 17:54:02 +01:00
|
|
|
{% if event.online %}
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
<div class="card-brutal-green mb-6 flex flex-wrap items-center justify-between gap-4">
|
2026-03-20 17:54:02 +01:00
|
|
|
<div>
|
|
|
|
|
<p class="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-1">URL publique</p>
|
|
|
|
|
<p class="text-sm font-mono font-bold break-all" id="event-url">{{ absolute_url(path('app_event_detail', {orgaSlug: event.account.slug, id: event.id, eventSlug: event.slug})) }}</p>
|
|
|
|
|
</div>
|
|
|
|
|
<button type="button" id="copy-url-btn" class="px-4 py-2 border-2 border-gray-900 bg-white font-black uppercase text-xs tracking-widest cursor-pointer hover:bg-gray-100 transition-all">
|
|
|
|
|
Copier le lien
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
{% set current_tab = app.request.query.get('tab', 'info') %}
|
|
|
|
|
<div class="flex flex-wrap overflow-x-auto mb-8">
|
|
|
|
|
<a href="{{ path('app_account_edit_event', {id: event.id, tab: 'info'}) }}" class="flex-1 min-w-[100px] text-center py-3 border-3 border-gray-900 border-r-0 {{ current_tab == 'info' ? 'bg-yellow-400' : 'bg-white' }} font-black uppercase text-xs tracking-widest transition-all">Informations</a>
|
Add Billet entity, BilletDesign, ticket designer, CRUD billets, commissions
- Create Billet entity: name, position, priceHT, quantity (nullable=unlimited),
isGeneratedBillet, hasDefinedExit, notBuyable, type (billet/reservation_brocante/vote),
stripeProductId, description, picture (VichUploader), category (ManyToOne CASCADE)
- Create BilletDesign entity (OneToOne Event): accentColor, invitationTitle, invitationColor
- Billet CRUD: add/edit/delete with access control, Stripe product sync on connected account
- Billet reorder: drag & drop with position field, refactored sortable.js for both categories and billets
- Ticket designer tab (custom offer only): accent color, invitation title/color, live iframe preview
- A4 ticket preview: 4 zones (HG infos+billet, HD affiche, BG association, BD sortie+invitation), fake QR code SVG
- Commission calculator JS: live breakdown of E-Ticket fee, Stripe fee (1.5%+0.25EUR), net amount
- Sales recap on categories tab: qty sold, total HT, total commissions, total net
- DisableProfilerSubscriber: disable web profiler toolbar on preview iframe
- CSP: allow self in frame-src and frame-ancestors for preview iframe
- Flysystem: dedicated billets.storage for billet images
- Upload accept restricted to png/jpeg/webp/gif (no HEIC)
- Makefile: add force_sql_dev command
- CLAUDE.md: add rule to never modify existing migrations
- Consolidate all migrations into single Version20260321111125
- Tests: BilletTest (20), BilletDesignTest (6), DisableProfilerSubscriberTest (5),
billet-designer.test.js (7), commission-calculator.test.js (7),
AccountControllerTest billet CRUD tests (11)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:19:46 +01:00
|
|
|
<a href="{{ path('app_account_edit_event', {id: event.id, tab: 'categories'}) }}" class="flex-1 min-w-[100px] text-center py-3 border-3 border-gray-900 border-r-0 {{ current_tab == 'categories' ? 'bg-yellow-400' : 'bg-white' }} font-black uppercase text-xs tracking-widest transition-all">Categories</a>
|
|
|
|
|
{% if is_granted('ROLE_ROOT') or app.user.offer == 'custom' %}
|
|
|
|
|
<a href="{{ path('app_account_edit_event', {id: event.id, tab: 'billets'}) }}" class="flex-1 min-w-[100px] text-center py-3 border-3 border-gray-900 border-r-0 {{ current_tab == 'billets' ? 'bg-yellow-400' : 'bg-white' }} font-black uppercase text-xs tracking-widest transition-all">Billets</a>
|
|
|
|
|
{% endif %}
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
<a href="{{ path('app_account_edit_event', {id: event.id, tab: 'stats'}) }}" class="flex-1 min-w-[100px] text-center py-3 border-3 border-gray-900 border-r-0 {{ current_tab == 'stats' ? 'bg-yellow-400' : 'bg-white' }} font-black uppercase text-xs tracking-widest transition-all">Statistiques</a>
|
|
|
|
|
<a href="{{ path('app_account_edit_event', {id: event.id, tab: 'settings'}) }}" class="flex-1 min-w-[100px] text-center py-3 border-3 border-gray-900 {{ current_tab == 'settings' ? 'bg-yellow-400' : 'bg-white' }} font-black uppercase text-xs tracking-widest transition-all">Parametres</a>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% if current_tab == 'info' %}
|
2026-03-20 17:42:08 +01:00
|
|
|
<div class="flex flex-col lg:flex-row gap-8">
|
|
|
|
|
<div class="flex-1 min-w-0">
|
|
|
|
|
<form method="post" action="{{ path('app_account_edit_event', {id: event.id}) }}" enctype="multipart/form-data" class="form-col">
|
|
|
|
|
<div>
|
|
|
|
|
<label for="event_title" class="text-xs font-black uppercase tracking-widest form-label">Titre de l'evenement</label>
|
|
|
|
|
<input type="text" id="event_title" name="title" required class="form-input focus:border-indigo-600" value="{{ event.title }}">
|
|
|
|
|
</div>
|
2026-03-20 17:02:56 +01:00
|
|
|
|
2026-03-20 17:42:08 +01:00
|
|
|
<div>
|
|
|
|
|
<label for="event_description" class="text-xs font-black uppercase tracking-widest form-label">Description</label>
|
|
|
|
|
<e-ticket-editor>
|
|
|
|
|
<textarea id="event_description" name="description" rows="5" placeholder="Decrivez votre evenement...">{{ event.description }}</textarea>
|
|
|
|
|
</e-ticket-editor>
|
|
|
|
|
</div>
|
2026-03-20 17:02:56 +01:00
|
|
|
|
2026-03-20 17:42:08 +01:00
|
|
|
<div class="form-row">
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="event_start_at" class="text-xs font-black uppercase tracking-widest form-label">Date et heure de debut</label>
|
|
|
|
|
<input type="datetime-local" id="event_start_at" name="start_at" required class="form-input focus:border-indigo-600" value="{{ event.startAt|date('Y-m-d\\TH:i') }}">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="form-group">
|
|
|
|
|
<label for="event_end_at" class="text-xs font-black uppercase tracking-widest form-label">Date et heure de fin</label>
|
|
|
|
|
<input type="datetime-local" id="event_end_at" name="end_at" required class="form-input focus:border-indigo-600" value="{{ event.endAt|date('Y-m-d\\TH:i') }}">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
2026-03-20 17:02:56 +01:00
|
|
|
|
2026-03-20 17:42:08 +01:00
|
|
|
<div>
|
|
|
|
|
<label for="event_address" class="text-xs font-black uppercase tracking-widest form-label">Adresse</label>
|
|
|
|
|
<input type="text" id="event_address" name="address" required class="form-input focus:border-indigo-600" value="{{ event.address }}">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="form-row">
|
|
|
|
|
<div class="flex-1 min-w-[120px] max-w-[200px]">
|
|
|
|
|
<label for="event_zipcode" class="text-xs font-black uppercase tracking-widest form-label">Code postal</label>
|
|
|
|
|
<input type="text" id="event_zipcode" name="zipcode" required maxlength="10" class="form-input focus:border-indigo-600" value="{{ event.zipcode }}">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex-[2] min-w-[200px]">
|
|
|
|
|
<label for="event_city" class="text-xs font-black uppercase tracking-widest form-label">Ville</label>
|
|
|
|
|
<input type="text" id="event_city" name="city" required class="form-input focus:border-indigo-600" value="{{ event.city }}">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label for="event_main_picture" class="text-xs font-black uppercase tracking-widest form-label">Changer l'affiche</label>
|
Add Billet entity, BilletDesign, ticket designer, CRUD billets, commissions
- Create Billet entity: name, position, priceHT, quantity (nullable=unlimited),
isGeneratedBillet, hasDefinedExit, notBuyable, type (billet/reservation_brocante/vote),
stripeProductId, description, picture (VichUploader), category (ManyToOne CASCADE)
- Create BilletDesign entity (OneToOne Event): accentColor, invitationTitle, invitationColor
- Billet CRUD: add/edit/delete with access control, Stripe product sync on connected account
- Billet reorder: drag & drop with position field, refactored sortable.js for both categories and billets
- Ticket designer tab (custom offer only): accent color, invitation title/color, live iframe preview
- A4 ticket preview: 4 zones (HG infos+billet, HD affiche, BG association, BD sortie+invitation), fake QR code SVG
- Commission calculator JS: live breakdown of E-Ticket fee, Stripe fee (1.5%+0.25EUR), net amount
- Sales recap on categories tab: qty sold, total HT, total commissions, total net
- DisableProfilerSubscriber: disable web profiler toolbar on preview iframe
- CSP: allow self in frame-src and frame-ancestors for preview iframe
- Flysystem: dedicated billets.storage for billet images
- Upload accept restricted to png/jpeg/webp/gif (no HEIC)
- Makefile: add force_sql_dev command
- CLAUDE.md: add rule to never modify existing migrations
- Consolidate all migrations into single Version20260321111125
- Tests: BilletTest (20), BilletDesignTest (6), DisableProfilerSubscriberTest (5),
billet-designer.test.js (7), commission-calculator.test.js (7),
AccountControllerTest billet CRUD tests (11)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:19:46 +01:00
|
|
|
<input type="file" id="event_main_picture" name="event_main_picture" accept="image/png, image/jpeg, image/webp, image/gif" class="form-file">
|
2026-03-20 17:42:08 +01:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<button type="submit" class="btn-brutal font-black uppercase text-sm tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
|
|
|
|
|
Enregistrer les modifications
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
2026-03-20 17:02:56 +01:00
|
|
|
</div>
|
|
|
|
|
|
2026-03-20 19:11:02 +01:00
|
|
|
<div class="w-full lg:w-[350px] flex-shrink-0">
|
2026-03-20 17:42:08 +01:00
|
|
|
<div class="card-brutal overflow-hidden sticky top-24">
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
<h2 class="text-[10px] font-black uppercase tracking-widest text-white">Affiche</h2>
|
|
|
|
|
</div>
|
|
|
|
|
{% if event.eventMainPictureName %}
|
2026-03-20 17:47:56 +01:00
|
|
|
<img src="{{ ('/uploads/events/' ~ event.eventMainPictureName) | imagine_filter('medium') }}" alt="{{ event.title }}" class="w-full max-h-[350px] object-contain">
|
2026-03-20 17:42:08 +01:00
|
|
|
{% else %}
|
|
|
|
|
<div class="p-12 text-center bg-gray-50">
|
|
|
|
|
<div class="text-4xl mb-4 opacity-30">📷</div>
|
|
|
|
|
<p class="text-gray-400 font-bold text-sm">Aucune affiche</p>
|
|
|
|
|
<p class="text-gray-300 text-xs font-bold mt-1">Ajoutez une image via le formulaire</p>
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
2026-03-20 17:02:56 +01:00
|
|
|
</div>
|
2026-03-20 17:42:08 +01:00
|
|
|
</div>
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
|
|
|
|
|
{% elseif current_tab == 'categories' %}
|
|
|
|
|
<div class="card-brutal overflow-hidden mb-6">
|
|
|
|
|
<div class="section-header flex justify-between items-center">
|
|
|
|
|
<h2 class="text-[10px] font-black uppercase tracking-widest text-white">Categories</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="p-6">
|
|
|
|
|
<form method="post" action="{{ path('app_account_event_add_category', {id: event.id}) }}" class="flex flex-wrap gap-3 items-end mb-6">
|
|
|
|
|
<div class="flex-1 min-w-[200px]">
|
|
|
|
|
<label for="cat_name" class="text-xs font-black uppercase tracking-widest form-label">Nouvelle categorie</label>
|
|
|
|
|
<input type="text" id="cat_name" name="name" required class="form-input focus:border-indigo-600" placeholder="Ex: PMR, VIP, Tribune...">
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label for="cat_start" class="text-xs font-black uppercase tracking-widest form-label">Debut vente</label>
|
|
|
|
|
<input type="datetime-local" id="cat_start" name="start_at" class="form-input focus:border-indigo-600" value="{{ "now"|date('Y-m-d\\TH:i') }}">
|
|
|
|
|
</div>
|
|
|
|
|
<div>
|
|
|
|
|
<label for="cat_end" class="text-xs font-black uppercase tracking-widest form-label">Fin vente</label>
|
2026-03-20 23:13:53 +01:00
|
|
|
<input type="datetime-local" id="cat_end" name="end_at" class="form-input focus:border-indigo-600" value="{{ event.endAt|date('Y-m-d\\TH:i') }}">
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
</div>
|
|
|
|
|
<button type="submit" class="btn-brutal font-black uppercase text-xs tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
|
|
|
|
|
+ Ajouter
|
|
|
|
|
</button>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
{% if categories|length > 0 %}
|
2026-03-20 23:16:52 +01:00
|
|
|
<div id="categories-list" data-reorder-url="{{ path('app_account_event_reorder_categories', {id: event.id}) }}">
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
{% for category in categories %}
|
Add Billet entity, BilletDesign, ticket designer, CRUD billets, commissions
- Create Billet entity: name, position, priceHT, quantity (nullable=unlimited),
isGeneratedBillet, hasDefinedExit, notBuyable, type (billet/reservation_brocante/vote),
stripeProductId, description, picture (VichUploader), category (ManyToOne CASCADE)
- Create BilletDesign entity (OneToOne Event): accentColor, invitationTitle, invitationColor
- Billet CRUD: add/edit/delete with access control, Stripe product sync on connected account
- Billet reorder: drag & drop with position field, refactored sortable.js for both categories and billets
- Ticket designer tab (custom offer only): accent color, invitation title/color, live iframe preview
- A4 ticket preview: 4 zones (HG infos+billet, HD affiche, BG association, BD sortie+invitation), fake QR code SVG
- Commission calculator JS: live breakdown of E-Ticket fee, Stripe fee (1.5%+0.25EUR), net amount
- Sales recap on categories tab: qty sold, total HT, total commissions, total net
- DisableProfilerSubscriber: disable web profiler toolbar on preview iframe
- CSP: allow self in frame-src and frame-ancestors for preview iframe
- Flysystem: dedicated billets.storage for billet images
- Upload accept restricted to png/jpeg/webp/gif (no HEIC)
- Makefile: add force_sql_dev command
- CLAUDE.md: add rule to never modify existing migrations
- Consolidate all migrations into single Version20260321111125
- Tests: BilletTest (20), BilletDesignTest (6), DisableProfilerSubscriberTest (5),
billet-designer.test.js (7), commission-calculator.test.js (7),
AccountControllerTest billet CRUD tests (11)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:19:46 +01:00
|
|
|
<div class="border-2 border-gray-900 mb-2 bg-white" data-id="{{ category.id }}">
|
|
|
|
|
<div class="flex flex-wrap items-center gap-4 p-4 cursor-move">
|
|
|
|
|
<span class="text-gray-400 cursor-grab">☰</span>
|
|
|
|
|
<span class="font-black text-sm uppercase flex-1">{{ category.name }}</span>
|
|
|
|
|
<span class="text-xs font-bold text-gray-400">{{ category.startAt|date('d/m/Y H:i') }} — {{ category.endAt|date('d/m/Y H:i') }}</span>
|
|
|
|
|
{% if category.hidden %}
|
|
|
|
|
<span class="badge-yellow text-xs font-black uppercase">Masquee</span>
|
|
|
|
|
{% elseif category.active %}
|
|
|
|
|
<span class="badge-green text-xs font-black uppercase">Active</span>
|
|
|
|
|
{% else %}
|
|
|
|
|
<span class="badge-red text-xs font-black uppercase">Inactive</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
<a href="{{ path('app_account_event_edit_category', {id: event.id, categoryId: category.id}) }}" class="px-2 py-1 border-2 border-gray-900 bg-white text-xs font-black uppercase hover:bg-gray-100 transition-all">✎</a>
|
|
|
|
|
<form method="post" action="{{ path('app_account_event_delete_category', {id: event.id, categoryId: category.id}) }}" data-confirm="Supprimer cette categorie ?" class="inline">
|
|
|
|
|
<button type="submit" class="px-2 py-1 border-2 border-red-800 bg-red-600 text-white text-xs font-black uppercase cursor-pointer hover:bg-red-800 transition-all">✕</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% set category_billets = billets[category.id] ?? [] %}
|
|
|
|
|
{% if category_billets|length > 0 %}
|
|
|
|
|
<div class="border-t-2 border-gray-900 bg-gray-50 px-4 py-3 billets-list" data-reorder-url="{{ path('app_account_event_reorder_billets', {id: event.id}) }}">
|
|
|
|
|
{% for billet in category_billets %}
|
|
|
|
|
{% set sold = sold_counts[billet.id] ?? 0 %}
|
|
|
|
|
<div class="flex flex-wrap items-center gap-3 py-2 {{ not loop.last ? 'border-b border-gray-200' : '' }} cursor-move" data-billet-id="{{ billet.id }}">
|
|
|
|
|
<span class="text-gray-400 cursor-grab">☰</span>
|
|
|
|
|
{% if billet.pictureName %}
|
|
|
|
|
<img src="{{ ('/uploads/billets/' ~ billet.pictureName) | imagine_filter('thumbnail') }}" alt="{{ billet.name }}" class="w-10 h-10 object-cover border border-gray-300">
|
|
|
|
|
{% endif %}
|
|
|
|
|
<span class="font-bold text-sm flex-1">{{ billet.name }}</span>
|
|
|
|
|
<span class="font-black text-sm text-indigo-600">{{ billet.priceHTDecimal|number_format(2, ',', ' ') }} € HT</span>
|
|
|
|
|
<span class="text-[10px] font-bold text-gray-400">{{ billet.unlimited ? 'Illimite' : billet.quantity ~ ' places' }}</span>
|
|
|
|
|
<span class="text-[10px] font-bold text-gray-600">{{ sold }} vendu{{ sold > 1 ? 's' : '' }}</span>
|
|
|
|
|
{% if billet.generatedBillet %}
|
|
|
|
|
<span class="badge-green text-[10px] font-black uppercase">Billet</span>
|
|
|
|
|
{% else %}
|
|
|
|
|
<span class="badge-red text-[10px] font-black uppercase">Sans billet</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if billet.definedExit %}
|
|
|
|
|
<span class="badge-yellow text-[10px] font-black uppercase">Sortie def.</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if billet.notBuyable %}
|
|
|
|
|
<span class="badge-red text-[10px] font-black uppercase">Non achetable</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
{% if billet.type != 'billet' %}
|
|
|
|
|
<span class="badge-yellow text-[10px] font-black uppercase">{{ billet.type == 'reservation_brocante' ? 'Brocante' : 'Vote' }}</span>
|
|
|
|
|
{% endif %}
|
|
|
|
|
<a href="{{ path('app_account_event_edit_billet', {id: event.id, billetId: billet.id}) }}" class="px-2 py-1 border-2 border-gray-900 bg-white text-xs font-black uppercase hover:bg-gray-100 transition-all">✎</a>
|
|
|
|
|
<form method="post" action="{{ path('app_account_event_delete_billet', {id: event.id, billetId: billet.id}) }}" data-confirm="Supprimer ce billet ?" class="inline">
|
|
|
|
|
<button type="submit" class="px-2 py-1 border-2 border-red-800 bg-red-600 text-white text-xs font-black uppercase cursor-pointer hover:bg-red-800 transition-all">✕</button>
|
|
|
|
|
</form>
|
|
|
|
|
</div>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</div>
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
{% endif %}
|
Add Billet entity, BilletDesign, ticket designer, CRUD billets, commissions
- Create Billet entity: name, position, priceHT, quantity (nullable=unlimited),
isGeneratedBillet, hasDefinedExit, notBuyable, type (billet/reservation_brocante/vote),
stripeProductId, description, picture (VichUploader), category (ManyToOne CASCADE)
- Create BilletDesign entity (OneToOne Event): accentColor, invitationTitle, invitationColor
- Billet CRUD: add/edit/delete with access control, Stripe product sync on connected account
- Billet reorder: drag & drop with position field, refactored sortable.js for both categories and billets
- Ticket designer tab (custom offer only): accent color, invitation title/color, live iframe preview
- A4 ticket preview: 4 zones (HG infos+billet, HD affiche, BG association, BD sortie+invitation), fake QR code SVG
- Commission calculator JS: live breakdown of E-Ticket fee, Stripe fee (1.5%+0.25EUR), net amount
- Sales recap on categories tab: qty sold, total HT, total commissions, total net
- DisableProfilerSubscriber: disable web profiler toolbar on preview iframe
- CSP: allow self in frame-src and frame-ancestors for preview iframe
- Flysystem: dedicated billets.storage for billet images
- Upload accept restricted to png/jpeg/webp/gif (no HEIC)
- Makefile: add force_sql_dev command
- CLAUDE.md: add rule to never modify existing migrations
- Consolidate all migrations into single Version20260321111125
- Tests: BilletTest (20), BilletDesignTest (6), DisableProfilerSubscriberTest (5),
billet-designer.test.js (7), commission-calculator.test.js (7),
AccountControllerTest billet CRUD tests (11)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:19:46 +01:00
|
|
|
|
|
|
|
|
<div class="border-t-2 border-gray-900 px-4 py-3 bg-gray-50">
|
|
|
|
|
<a href="{{ path('app_account_event_add_billet', {id: event.id, categoryId: category.id}) }}" class="inline-flex items-center gap-2 px-3 py-1 border-2 border-gray-900 bg-white text-xs font-black uppercase hover:bg-indigo-600 hover:text-white transition-all">
|
|
|
|
|
+ Ajouter un billet
|
|
|
|
|
</a>
|
|
|
|
|
</div>
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
</div>
|
|
|
|
|
{% endfor %}
|
|
|
|
|
</div>
|
Add Billet entity, BilletDesign, ticket designer, CRUD billets, commissions
- Create Billet entity: name, position, priceHT, quantity (nullable=unlimited),
isGeneratedBillet, hasDefinedExit, notBuyable, type (billet/reservation_brocante/vote),
stripeProductId, description, picture (VichUploader), category (ManyToOne CASCADE)
- Create BilletDesign entity (OneToOne Event): accentColor, invitationTitle, invitationColor
- Billet CRUD: add/edit/delete with access control, Stripe product sync on connected account
- Billet reorder: drag & drop with position field, refactored sortable.js for both categories and billets
- Ticket designer tab (custom offer only): accent color, invitation title/color, live iframe preview
- A4 ticket preview: 4 zones (HG infos+billet, HD affiche, BG association, BD sortie+invitation), fake QR code SVG
- Commission calculator JS: live breakdown of E-Ticket fee, Stripe fee (1.5%+0.25EUR), net amount
- Sales recap on categories tab: qty sold, total HT, total commissions, total net
- DisableProfilerSubscriber: disable web profiler toolbar on preview iframe
- CSP: allow self in frame-src and frame-ancestors for preview iframe
- Flysystem: dedicated billets.storage for billet images
- Upload accept restricted to png/jpeg/webp/gif (no HEIC)
- Makefile: add force_sql_dev command
- CLAUDE.md: add rule to never modify existing migrations
- Consolidate all migrations into single Version20260321111125
- Tests: BilletTest (20), BilletDesignTest (6), DisableProfilerSubscriberTest (5),
billet-designer.test.js (7), commission-calculator.test.js (7),
AccountControllerTest billet CRUD tests (11)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:19:46 +01:00
|
|
|
|
|
|
|
|
{% set total_sold = 0 %}
|
|
|
|
|
{% set total_ht = 0 %}
|
|
|
|
|
{% set total_commission = 0 %}
|
|
|
|
|
{% set total_net = 0 %}
|
|
|
|
|
{% for cat_billets in billets %}
|
|
|
|
|
{% for billet in cat_billets %}
|
|
|
|
|
{% set sold = sold_counts[billet.id] ?? 0 %}
|
|
|
|
|
{% set line_ht = billet.priceHTDecimal * sold %}
|
|
|
|
|
{% set eticket_fee = line_ht * (commission_rate / 100) %}
|
|
|
|
|
{% set stripe_fee = sold > 0 ? (line_ht * 0.015) + (0.25 * sold) : 0 %}
|
|
|
|
|
{% set line_commission = eticket_fee + stripe_fee %}
|
|
|
|
|
{% set total_sold = total_sold + sold %}
|
|
|
|
|
{% set total_ht = total_ht + line_ht %}
|
|
|
|
|
{% set total_commission = total_commission + line_commission %}
|
|
|
|
|
{% set total_net = total_net + (line_ht - line_commission) %}
|
|
|
|
|
{% endfor %}
|
|
|
|
|
{% endfor %}
|
|
|
|
|
|
|
|
|
|
<div class="card-brutal overflow-hidden mt-4">
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
<h2 class="text-[10px] font-black uppercase tracking-widest text-white">Recapitulatif ventes</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="p-6">
|
|
|
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
|
|
|
<div class="border-2 border-gray-900 p-4 text-center">
|
|
|
|
|
<div class="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-1">Qt vendue</div>
|
|
|
|
|
<div class="text-2xl font-black">{{ total_sold }}</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="border-2 border-gray-900 p-4 text-center">
|
|
|
|
|
<div class="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-1">Total HT</div>
|
|
|
|
|
<div class="text-2xl font-black text-indigo-600">{{ total_ht|number_format(2, ',', ' ') }} €</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="border-2 border-gray-900 p-4 text-center">
|
|
|
|
|
<div class="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-1">Total commissions</div>
|
|
|
|
|
<div class="text-2xl font-black text-red-600">{{ total_commission|number_format(2, ',', ' ') }} €</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="border-2 border-gray-900 p-4 text-center bg-green-50">
|
|
|
|
|
<div class="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-1">Total percu</div>
|
|
|
|
|
<div class="text-2xl font-black text-green-600">{{ total_net|number_format(2, ',', ' ') }} €</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
{% else %}
|
|
|
|
|
<p class="text-gray-400 font-bold text-sm text-center py-8">Aucune categorie. Ajoutez-en une pour commencer a vendre des billets.</p>
|
|
|
|
|
{% endif %}
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
Add Billet entity, BilletDesign, ticket designer, CRUD billets, commissions
- Create Billet entity: name, position, priceHT, quantity (nullable=unlimited),
isGeneratedBillet, hasDefinedExit, notBuyable, type (billet/reservation_brocante/vote),
stripeProductId, description, picture (VichUploader), category (ManyToOne CASCADE)
- Create BilletDesign entity (OneToOne Event): accentColor, invitationTitle, invitationColor
- Billet CRUD: add/edit/delete with access control, Stripe product sync on connected account
- Billet reorder: drag & drop with position field, refactored sortable.js for both categories and billets
- Ticket designer tab (custom offer only): accent color, invitation title/color, live iframe preview
- A4 ticket preview: 4 zones (HG infos+billet, HD affiche, BG association, BD sortie+invitation), fake QR code SVG
- Commission calculator JS: live breakdown of E-Ticket fee, Stripe fee (1.5%+0.25EUR), net amount
- Sales recap on categories tab: qty sold, total HT, total commissions, total net
- DisableProfilerSubscriber: disable web profiler toolbar on preview iframe
- CSP: allow self in frame-src and frame-ancestors for preview iframe
- Flysystem: dedicated billets.storage for billet images
- Upload accept restricted to png/jpeg/webp/gif (no HEIC)
- Makefile: add force_sql_dev command
- CLAUDE.md: add rule to never modify existing migrations
- Consolidate all migrations into single Version20260321111125
- Tests: BilletTest (20), BilletDesignTest (6), DisableProfilerSubscriberTest (5),
billet-designer.test.js (7), commission-calculator.test.js (7),
AccountControllerTest billet CRUD tests (11)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-21 12:19:46 +01:00
|
|
|
{% elseif current_tab == 'billets' and (is_granted('ROLE_ROOT') or app.user.offer == 'custom') %}
|
|
|
|
|
|
|
|
|
|
{% set bd = billet_design %}
|
|
|
|
|
<div class="flex flex-col lg:flex-row gap-6" id="billet-designer" data-preview-url="{{ path('app_account_event_billet_preview', {id: event.id}) }}" data-save-url="{{ path('app_account_event_save_billet_design', {id: event.id}) }}">
|
|
|
|
|
<div class="w-full lg:w-[350px] flex-shrink-0">
|
|
|
|
|
<div class="card-brutal overflow-hidden">
|
|
|
|
|
<div class="section-header">
|
|
|
|
|
<h2 class="text-[10px] font-black uppercase tracking-widest text-white">Personnalisation</h2>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="p-6 space-y-4">
|
|
|
|
|
<div>
|
|
|
|
|
<label for="design_accent_color" class="text-xs font-black uppercase tracking-widest form-label">Couleur d'accent</label>
|
|
|
|
|
<input type="color" id="design_accent_color" name="accent_color" value="{{ bd ? bd.accentColor : '#4f46e5' }}" class="w-full h-10 border-2 border-gray-900 cursor-pointer">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<hr class="border-gray-300">
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label for="design_invitation_title" class="text-xs font-black uppercase tracking-widest form-label">Titre invitation</label>
|
|
|
|
|
<input type="text" id="design_invitation_title" name="invitation_title" value="{{ bd ? bd.invitationTitle : 'Invitation' }}" class="form-input focus:border-indigo-600">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div>
|
|
|
|
|
<label for="design_invitation_color" class="text-xs font-black uppercase tracking-widest form-label">Couleur fond invitation</label>
|
|
|
|
|
<input type="color" id="design_invitation_color" name="invitation_color" value="{{ bd ? bd.invitationColor : '#d4a017' }}" class="w-full h-10 border-2 border-gray-900 cursor-pointer">
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<hr class="border-gray-300">
|
|
|
|
|
|
|
|
|
|
<button type="button" id="billet-save-design" class="w-full btn-brutal font-black uppercase text-xs tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
|
|
|
|
|
Sauvegarder le design
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="flex-1 min-w-0">
|
|
|
|
|
<div class="card-brutal overflow-hidden">
|
|
|
|
|
<div class="section-header flex justify-between items-center">
|
|
|
|
|
<h2 class="text-[10px] font-black uppercase tracking-widest text-white">Apercu du billet — A4</h2>
|
|
|
|
|
<button type="button" id="billet-reload-preview" class="px-3 py-1 bg-white text-gray-900 text-[10px] font-black uppercase tracking-widest border-2 border-white hover:bg-gray-100 transition-all cursor-pointer">
|
|
|
|
|
↻ Recharger
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="bg-gray-100 p-4 flex justify-center">
|
|
|
|
|
<iframe id="billet-preview-frame" src="{{ path('app_account_event_billet_preview', {id: event.id}) }}" class="bg-white shadow-lg" style="width: 595px; height: 842px; border: 1px solid #ccc; transform-origin: top center;"></iframe>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
Add Category entity, edit event tabs (info/categories/stats/settings), CRUD categories
- Create Category entity: name, position (sortable), event, startAt, endAt, isActive()
- Default endAt: event.startAt - 1 day
- Add 4 tabs on edit event page: Informations, Categories/Billets, Statistiques, Parametres
- Add routes: add category, delete category, reorder categories (JSON API)
- Categories sorted by position, drag handle for future Sortable.js
- Active/Inactive badge based on date range
- Add migration for category table
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 22:51:25 +01:00
|
|
|
{% elseif current_tab == 'stats' %}
|
|
|
|
|
<div class="card-brutal">
|
|
|
|
|
<div class="p-12 text-center">
|
|
|
|
|
<p class="text-gray-400 font-bold text-sm">Les statistiques seront disponibles prochainement.</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{% elseif current_tab == 'settings' %}
|
|
|
|
|
<div class="card-brutal">
|
|
|
|
|
<div class="p-12 text-center">
|
|
|
|
|
<p class="text-gray-400 font-bold text-sm">Les parametres avances seront disponibles prochainement.</p>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% endif %}
|
2026-03-20 17:02:56 +01:00
|
|
|
</div>
|
|
|
|
|
{% endblock %}
|