export function djBandBookingCalculator(form: FormTs) {
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'DJ/Band Booking Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Entertainment Type Section
const typeSection = form.addSubform('type', { title: '🎵 Entertainment Type' });
typeSection.addRow(row => {
row.addRadioButton('entertainmentType', {
label: 'Select Entertainment',
options: [
{ id: 'dj', name: 'DJ' },
{ id: 'solo', name: 'Solo Musician' },
{ id: 'duo', name: 'Duo/Acoustic' },
{ id: 'band-small', name: 'Small Band (3-4 piece)' },
{ id: 'band-large', name: 'Large Band (5+ piece)' }
],
defaultValue: 'dj',
isRequired: true
});
});
typeSection.addRow(row => {
row.addDropdown('experienceLevel', {
label: 'Experience Level',
options: [
{ id: 'beginner', name: 'Beginner (1-2 years)' },
{ id: 'intermediate', name: 'Intermediate (3-5 years)' },
{ id: 'experienced', name: 'Experienced (5-10 years)' },
{ id: 'premium', name: 'Premium/Celebrity DJ' }
],
defaultValue: 'experienced',
isVisible: () => typeSection.radioButton('entertainmentType')?.value() === 'dj'
}, '1fr');
row.addDropdown('bandReputation', {
label: 'Band Reputation',
options: [
{ id: 'local', name: 'Local Band' },
{ id: 'regional', name: 'Regional Band' },
{ id: 'touring', name: 'Touring Band' },
{ id: 'premium', name: 'Premium/Award-Winning' }
],
defaultValue: 'regional',
isVisible: () => typeSection.radioButton('entertainmentType')?.value() !== 'dj'
}, '1fr');
});
// Event Details Section
const eventSection = form.addSubform('event', { title: '📅 Event Details' });
eventSection.addRow(row => {
row.addDropdown('eventType', {
label: 'Event Type',
options: [
{ id: 'wedding', name: 'Wedding' },
{ id: 'corporate', name: 'Corporate Event' },
{ id: 'birthday', name: 'Birthday Party' },
{ id: 'club', name: 'Club/Nightclub' },
{ id: 'festival', name: 'Festival/Concert' },
{ id: 'private', name: 'Private Party' },
{ id: 'school', name: 'School Dance/Prom' },
{ id: 'other', name: 'Other Event' }
],
defaultValue: 'wedding',
isRequired: true
}, '1fr');
row.addInteger('guestCount', {
label: 'Expected Guests',
min: 10,
max: 5000,
defaultValue: 150,
tooltip: 'Affects sound equipment needs'
}, '1fr');
});
eventSection.addRow(row => {
row.addDropdown('performanceHours', {
label: 'Performance Duration',
options: [
{ id: '2', name: '2 Hours' },
{ id: '3', name: '3 Hours' },
{ id: '4', name: '4 Hours' },
{ id: '5', name: '5 Hours' },
{ id: '6', name: '6 Hours' },
{ id: '8', name: '8 Hours (Full Day)' }
],
defaultValue: '4',
isRequired: true
}, '1fr');
row.addDropdown('dayOfWeek', {
label: 'Day of Event',
options: [
{ id: 'weekday', name: 'Monday-Thursday' },
{ id: 'friday', name: 'Friday' },
{ id: 'saturday', name: 'Saturday' },
{ id: 'sunday', name: 'Sunday' },
{ id: 'holiday', name: 'Holiday' }
],
defaultValue: 'saturday',
tooltip: 'Weekend and holiday rates are higher'
}, '1fr');
});
eventSection.addRow(row => {
row.addDropdown('venue', {
label: 'Venue Type',
options: [
{ id: 'indoor', name: 'Indoor Venue' },
{ id: 'outdoor', name: 'Outdoor Venue' },
{ id: 'tent', name: 'Tent/Marquee' },
{ id: 'multiple', name: 'Multiple Areas' }
],
defaultValue: 'indoor'
}, '1fr');
row.addInteger('travelDistance', {
label: 'Travel Distance (miles)',
min: 0,
max: 500,
defaultValue: 25,
tooltip: 'Distance from performer to venue'
}, '1fr');
});
// Equipment Section
const equipmentSection = form.addSubform('equipment', { title: '🔊 Equipment & Setup' });
equipmentSection.addRow(row => {
row.addDropdown('soundSystem', {
label: 'Sound System',
options: [
{ id: 'basic', name: 'Basic (up to 100 guests)' },
{ id: 'standard', name: 'Standard (100-200 guests)' },
{ id: 'premium', name: 'Premium (200-500 guests)' },
{ id: 'concert', name: 'Concert Grade (500+)' }
],
defaultValue: 'standard',
isRequired: true
}, '1fr');
row.addDropdown('lighting', {
label: 'Lighting Package',
options: [
{ id: 'none', name: 'No Lighting' },
{ id: 'basic', name: 'Basic Dance Floor Lights' },
{ id: 'standard', name: 'Standard Package' },
{ id: 'premium', name: 'Premium (Moving Heads, Lasers)' },
{ id: 'concert', name: 'Full Concert Lighting' }
],
defaultValue: 'standard'
}, '1fr');
});
equipmentSection.addRow(row => {
row.addCheckbox('wirelessMics', {
label: 'Wireless Microphones',
defaultValue: true,
tooltip: 'For speeches and announcements'
}, '1fr');
row.addCheckbox('subwoofers', {
label: 'Additional Subwoofers',
defaultValue: false,
tooltip: 'Extra bass for larger venues'
}, '1fr');
});
equipmentSection.addRow(row => {
row.addCheckbox('fogMachine', {
label: 'Fog/Haze Machine',
defaultValue: false
}, '1fr');
row.addCheckbox('uplighting', {
label: 'Venue Uplighting',
defaultValue: false,
tooltip: 'Ambient colored lighting around venue'
}, '1fr');
});
// Additional Services Section
const servicesSection = form.addSubform('services', { title: '✨ Additional Services' });
servicesSection.addRow(row => {
row.addCheckbox('mcServices', {
label: 'MC/Emcee Services',
defaultValue: true,
tooltip: 'Professional announcements and hosting'
}, '1fr');
row.addCheckbox('ceremonyMusic', {
label: 'Ceremony Music',
defaultValue: false,
tooltip: 'Separate setup for ceremony (weddings)',
isVisible: () => eventSection.dropdown('eventType')?.value() === 'wedding'
}, '1fr');
});
servicesSection.addRow(row => {
row.addCheckbox('cocktailHour', {
label: 'Cocktail Hour Music',
defaultValue: false,
tooltip: 'Background music during cocktails'
}, '1fr');
row.addCheckbox('photoBooth', {
label: 'Photo Booth',
defaultValue: false,
tooltip: 'Photo booth with props and prints'
}, '1fr');
});
servicesSection.addRow(row => {
row.addCheckbox('customPlaylist', {
label: 'Custom Playlist Creation',
defaultValue: false,
tooltip: 'Detailed song selection consultation'
}, '1fr');
row.addCheckbox('backupEquipment', {
label: 'Backup Equipment',
defaultValue: false,
tooltip: 'On-site backup gear for emergencies'
}, '1fr');
});
// Special Requests Section
const specialSection = form.addSubform('special', { title: '🌟 Special Requests' });
specialSection.addRow(row => {
row.addCheckbox('specialSongs', {
label: 'Special Song Learning',
defaultValue: false,
tooltip: 'Band learns specific songs for your event',
isVisible: () => typeSection.radioButton('entertainmentType')?.value() !== 'dj'
}, '1fr');
row.addCheckbox('earlySetup', {
label: 'Early Setup (2+ hours before)',
defaultValue: false
}, '1fr');
});
specialSection.addRow(row => {
row.addInteger('overtimeHours', {
label: 'Estimated Overtime (hours)',
min: 0,
max: 4,
defaultValue: 0,
tooltip: 'Additional hours beyond booked time'
});
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Results Section
const resultsSection = form.addSubform('results', { title: '📊 Price Breakdown', isCollapsible: false });
const calculatePricing = () => {
const entertainmentType = typeSection.radioButton('entertainmentType')?.value() || 'dj';
const experienceLevel = typeSection.dropdown('experienceLevel')?.value() || 'experienced';
const bandReputation = typeSection.dropdown('bandReputation')?.value() || 'regional';
const eventType = eventSection.dropdown('eventType')?.value() || 'wedding';
const hours = parseInt(eventSection.dropdown('performanceHours')?.value() || '4');
const dayOfWeek = eventSection.dropdown('dayOfWeek')?.value() || 'saturday';
const travelDistance = eventSection.integer('travelDistance')?.value() || 25;
// Base hourly rates by entertainment type
const baseRates = {
'dj': { 'beginner': 75, 'intermediate': 125, 'experienced': 200, 'premium': 400 },
'solo': { 'local': 100, 'regional': 175, 'touring': 300, 'premium': 500 },
'duo': { 'local': 200, 'regional': 350, 'touring': 500, 'premium': 800 },
'band-small': { 'local': 400, 'regional': 700, 'touring': 1200, 'premium': 2000 },
'band-large': { 'local': 700, 'regional': 1200, 'touring': 2000, 'premium': 4000 }
};
const level = entertainmentType === 'dj' ? experienceLevel : bandReputation;
const hourlyRate = baseRates[entertainmentType][level] || 200;
// Calculate base performance fee
let performanceFee = hourlyRate * hours;
// Event type premium
const eventPremiums = {
'wedding': 1.3,
'corporate': 1.2,
'festival': 1.4,
'club': 1.0,
'birthday': 1.0,
'private': 1.1,
'school': 0.9,
'other': 1.0
};
performanceFee *= eventPremiums[eventType];
// Day of week pricing
const dayMultipliers = {
'weekday': 0.85,
'friday': 1.0,
'saturday': 1.15,
'sunday': 1.0,
'holiday': 1.3
};
performanceFee *= dayMultipliers[dayOfWeek];
// Sound system
const soundSystem = equipmentSection.dropdown('soundSystem')?.value() || 'standard';
const soundCosts = { 'basic': 0, 'standard': 150, 'premium': 400, 'concert': 1000 };
const soundCost = soundCosts[soundSystem];
// Lighting
const lighting = equipmentSection.dropdown('lighting')?.value() || 'standard';
const lightingCosts = { 'none': 0, 'basic': 100, 'standard': 250, 'premium': 500, 'concert': 1200 };
const lightingCost = lightingCosts[lighting];
// Equipment addons
let equipmentAddons = 0;
if (equipmentSection.checkbox('wirelessMics')?.value()) equipmentAddons += 50;
if (equipmentSection.checkbox('subwoofers')?.value()) equipmentAddons += 150;
if (equipmentSection.checkbox('fogMachine')?.value()) equipmentAddons += 75;
if (equipmentSection.checkbox('uplighting')?.value()) equipmentAddons += 200;
// Services
let servicesCost = 0;
if (servicesSection.checkbox('mcServices')?.value()) servicesCost += 150;
if (servicesSection.checkbox('ceremonyMusic')?.value()) servicesCost += 300;
if (servicesSection.checkbox('cocktailHour')?.value()) servicesCost += 200;
if (servicesSection.checkbox('photoBooth')?.value()) servicesCost += 400;
if (servicesSection.checkbox('customPlaylist')?.value()) servicesCost += 100;
if (servicesSection.checkbox('backupEquipment')?.value()) servicesCost += 150;
// Special requests
let specialCost = 0;
if (specialSection.checkbox('specialSongs')?.value()) specialCost += 200;
if (specialSection.checkbox('earlySetup')?.value()) specialCost += 100;
// Overtime
const overtimeHours = specialSection.integer('overtimeHours')?.value() || 0;
const overtimeCost = overtimeHours * hourlyRate * 1.5;
// Travel fee
const travelFee = travelDistance > 30 ? (travelDistance - 30) * 1.5 : 0;
const totalEquipment = soundCost + lightingCost + equipmentAddons;
const totalServices = servicesCost + specialCost;
const grandTotal = Math.round(performanceFee + totalEquipment + totalServices + overtimeCost + travelFee);
return {
performanceFee: Math.round(performanceFee),
equipmentCost: Math.round(totalEquipment),
servicesCost: Math.round(totalServices),
overtimeCost: Math.round(overtimeCost),
travelFee: Math.round(travelFee),
grandTotal,
hourlyRate: Math.round(hourlyRate),
depositAmount: Math.round(grandTotal * 0.25)
};
};
resultsSection.addRow(row => {
row.addPriceDisplay('performanceFee', {
label: 'Performance Fee',
computedValue: () => calculatePricing().performanceFee,
variant: 'default'
}, '1fr');
row.addPriceDisplay('equipmentCost', {
label: 'Equipment & Lighting',
computedValue: () => calculatePricing().equipmentCost,
variant: 'default'
}, '1fr');
});
resultsSection.addRow(row => {
row.addPriceDisplay('servicesCost', {
label: 'Additional Services',
computedValue: () => calculatePricing().servicesCost,
variant: 'default',
isVisible: () => calculatePricing().servicesCost > 0
}, '1fr');
row.addPriceDisplay('travelFee', {
label: 'Travel Fee',
computedValue: () => calculatePricing().travelFee,
variant: 'default',
isVisible: () => calculatePricing().travelFee > 0
}, '1fr');
});
resultsSection.addRow(row => {
row.addPriceDisplay('overtimeCost', {
label: 'Estimated Overtime',
computedValue: () => calculatePricing().overtimeCost,
variant: 'default',
isVisible: () => calculatePricing().overtimeCost > 0
});
});
// Summary Section
const summarySection = form.addSubform('summary', {
title: '🎶 Booking Summary',
isCollapsible: false,
sticky: 'bottom'
});
summarySection.addRow(row => {
row.addPriceDisplay('grandTotal', {
label: 'Total Quote',
computedValue: () => calculatePricing().grandTotal,
variant: 'large'
}, '1fr');
row.addPriceDisplay('deposit', {
label: 'Deposit (25%)',
computedValue: () => calculatePricing().depositAmount,
variant: 'success'
}, '1fr');
});
summarySection.addRow(row => {
row.addTextPanel('note', {
computedValue: () => 'Prices are estimates. Final quote depends on availability and specific requirements.',
customStyles: { 'font-size': '0.8rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Check Availability'
});
}