## Backend Implementation
Enhanced TicketType model with helper methods and better validations So the full context is: ## Backend Implementation - Enhanced TicketType model with helper methods and better validations - New Promoter::TicketTypesController with full authorization - Sales status tracking (draft, available, upcoming, expired, sold_out) - New Promoter::TicketTypesController with full authorization - Safe calculation methods preventing nil value errors - Sales status tracking (draft, available, upcoming, expired, sold_out) ## Frontend Features - Modern responsive UI with Tailwind CSS styling - Interactive forms with Stimulus controller for dynamic calculations - Revenue calculators showing potential, current, and remaining revenue - Status indicators with appropriate colors and icons - Buyer analytics and purchase history display ## JavaScript Enhancements - New TicketTypeFormController for dynamic pricing calculations - Real-time total updates as users type price/quantity - Proper French currency formatting - Form validation for minimum quantities based on existing sales ## Bug Fixes Fixed nil value errors in price_euros method when price_cents is nil Added defensive programming for all calculation methods Graceful handling of incomplete ticket types during creation Proper default values for new ticket type instances ## Files Added/Modified - app/controllers/promoter/ticket_types_controller.rb (new) - app/javascript/controllers/ticket_type_form_controller.js (new) - app/views/promoter/ticket_types/*.html.erb (4 new view files) - app/models/ticket_type.rb (enhanced with helper methods) - config/routes.rb (added nested ticket_types routes) - db/migrate/*_add_requires_id_to_ticket_types.rb (new migration) ## Integration - Seamless integration with existing event management system - Updated promoter event show page with ticket management link - Proper scoping ensuring promoters only manage their own tickets - Compatible with existing ticket purchasing and checkout flow
This commit is contained in:
@@ -22,6 +22,9 @@ application.register("header", HeaderController);
|
||||
import EventFormController from "./event_form_controller"
|
||||
application.register("event-form", EventFormController);
|
||||
|
||||
import TicketTypeFormController from "./ticket_type_form_controller"
|
||||
application.register("ticket-type-form", TicketTypeFormController);
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
61
app/javascript/controllers/ticket_type_form_controller.js
Normal file
61
app/javascript/controllers/ticket_type_form_controller.js
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Controller } from "@hotwired/stimulus"
|
||||
|
||||
// Ticket Type Form Controller
|
||||
// Handles dynamic pricing calculations and form interactions
|
||||
export default class extends Controller {
|
||||
static targets = ["price", "quantity", "total"]
|
||||
|
||||
connect() {
|
||||
console.log("Ticket type form controller connected")
|
||||
this.updateTotal()
|
||||
}
|
||||
|
||||
// Update total revenue calculation when price or quantity changes
|
||||
updateTotal() {
|
||||
const price = parseFloat(this.priceTarget.value) || 0
|
||||
const quantity = parseInt(this.quantityTarget.value) || 0
|
||||
const total = price * quantity
|
||||
|
||||
// Format as currency
|
||||
const formatter = new Intl.NumberFormat('fr-FR', {
|
||||
style: 'currency',
|
||||
currency: 'EUR',
|
||||
minimumFractionDigits: 2
|
||||
})
|
||||
|
||||
if (this.hasQuantityTarget && this.hasTotalTarget) {
|
||||
// For new ticket types, calculate potential revenue
|
||||
this.totalTarget.textContent = formatter.format(total)
|
||||
} else if (this.hasTotalTarget) {
|
||||
// For edit forms, calculate remaining potential revenue
|
||||
const soldTickets = parseInt(this.element.dataset.soldTickets) || 0
|
||||
const remainingQuantity = Math.max(0, quantity - soldTickets)
|
||||
const remainingRevenue = price * remainingQuantity
|
||||
this.totalTarget.textContent = formatter.format(remainingRevenue)
|
||||
}
|
||||
}
|
||||
|
||||
// Validate minimum quantity (for edit forms with sold tickets)
|
||||
validateQuantity() {
|
||||
const soldTickets = parseInt(this.element.dataset.soldTickets) || 0
|
||||
const quantity = parseInt(this.quantityTarget.value) || 0
|
||||
|
||||
if (quantity < soldTickets) {
|
||||
this.quantityTarget.value = soldTickets
|
||||
this.quantityTarget.setCustomValidity(`La quantité ne peut pas être inférieure à ${soldTickets} (billets déjà vendus)`)
|
||||
} else {
|
||||
this.quantityTarget.setCustomValidity('')
|
||||
}
|
||||
|
||||
this.updateTotal()
|
||||
}
|
||||
|
||||
// Format price input to ensure proper decimal places
|
||||
formatPrice() {
|
||||
const price = parseFloat(this.priceTarget.value)
|
||||
if (!isNaN(price)) {
|
||||
this.priceTarget.value = price.toFixed(2)
|
||||
}
|
||||
this.updateTotal()
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user