export function swimmingLessonsCalculator(form: FormTs) {
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Swimming Lessons Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Lesson Type Section
const lessonSection = form.addSubform('lesson', { title: '๐ Lesson Type' });
lessonSection.addRow(row => {
row.addRadioButton('lessonType', {
label: 'Type of Lesson',
options: [
{ id: 'private', name: 'Private (1-on-1)' },
{ id: 'semi-private', name: 'Semi-Private (2 swimmers)' },
{ id: 'small-group', name: 'Small Group (3-4 swimmers)' },
{ id: 'group', name: 'Group Class (5-8 swimmers)' },
{ id: 'parent-child', name: 'Parent & Child' },
{ id: 'camp', name: 'Swim Camp/Intensive' }
],
defaultValue: 'private',
orientation: 'vertical',
isRequired: true
});
});
lessonSection.addRow(row => {
row.addDropdown('swimLevel', {
label: 'Swimming Level',
options: [
{ id: 'water-intro', name: 'Water Introduction (afraid/new)' },
{ id: 'beginner', name: 'Beginner (basics)' },
{ id: 'intermediate', name: 'Intermediate (can swim)' },
{ id: 'advanced', name: 'Advanced (strokes refinement)' },
{ id: 'competitive', name: 'Competitive Swimmer' },
{ id: 'adult-beginner', name: 'Adult Beginner' },
{ id: 'triathlon', name: 'Triathlon/Open Water' }
],
defaultValue: 'beginner',
isRequired: true
}, '1fr');
row.addDropdown('ageGroup', {
label: 'Age Group',
options: [
{ id: 'infant', name: 'Infant (6-12 months)' },
{ id: 'toddler', name: 'Toddler (1-3 years)' },
{ id: 'preschool', name: 'Preschool (3-5 years)' },
{ id: 'school-age', name: 'School Age (6-12 years)' },
{ id: 'teen', name: 'Teen (13-17 years)' },
{ id: 'adult', name: 'Adult (18+)' }
],
defaultValue: 'school-age'
}, '1fr');
});
// Session Details Section
const sessionSection = form.addSubform('session', { title: 'โฑ๏ธ Session Details' });
sessionSection.addRow(row => {
row.addDropdown('duration', {
label: 'Lesson Duration',
options: [
{ id: '20', name: '20 Minutes' },
{ id: '30', name: '30 Minutes' },
{ id: '45', name: '45 Minutes' },
{ id: '60', name: '1 Hour' }
],
defaultValue: '30',
isRequired: true
}, '1fr');
row.addDropdown('instructorLevel', {
label: 'Instructor Level',
options: [
{ id: 'assistant', name: 'Assistant Instructor' },
{ id: 'certified', name: 'Certified Instructor' },
{ id: 'senior', name: 'Senior Instructor' },
{ id: 'master', name: 'Master Instructor' },
{ id: 'competitive-coach', name: 'Competitive Coach' }
],
defaultValue: 'certified'
}, '1fr');
});
sessionSection.addRow(row => {
row.addDropdown('poolType', {
label: 'Pool Type',
options: [
{ id: 'indoor-public', name: 'Indoor Public Pool' },
{ id: 'indoor-private', name: 'Indoor Private/Club' },
{ id: 'outdoor', name: 'Outdoor Pool' },
{ id: 'backyard', name: 'Backyard Pool (mobile)' },
{ id: 'open-water', name: 'Open Water/Lake' }
],
defaultValue: 'indoor-public'
}, '1fr');
row.addInteger('swimmers', {
label: 'Number of Swimmers',
min: 2,
max: 8,
defaultValue: 2,
isVisible: () => ['semi-private', 'small-group', 'group'].includes(lessonSection.radioButton('lessonType')?.value() || '')
}, '1fr');
});
// Focus Areas Section
const focusSection = form.addSubform('focus', { title: '๐ฏ Focus Areas' });
focusSection.addRow(row => {
row.addDropdown('primaryGoal', {
label: 'Primary Goal',
options: [
{ id: 'water-safety', name: 'Water Safety & Comfort' },
{ id: 'learn-swim', name: 'Learn to Swim' },
{ id: 'stroke-development', name: 'Stroke Development' },
{ id: 'stroke-refinement', name: 'Stroke Refinement' },
{ id: 'endurance', name: 'Endurance Building' },
{ id: 'competitive', name: 'Competitive Training' },
{ id: 'triathlon', name: 'Triathlon/Open Water Prep' }
],
defaultValue: 'learn-swim'
}, '1fr');
});
// Camp Options (conditional)
const campSection = form.addSubform('camp', {
title: '๐๏ธ Camp Details',
isVisible: () => lessonSection.radioButton('lessonType')?.value() === 'camp'
});
campSection.addRow(row => {
row.addDropdown('campType', {
label: 'Camp Type',
options: [
{ id: 'daily', name: 'Daily Lessons (1 week)' },
{ id: 'intensive', name: 'Intensive (2 lessons/day)' },
{ id: 'half-day', name: 'Half Day Camp' },
{ id: 'full-day', name: 'Full Day Camp' }
],
defaultValue: 'daily'
}, '1fr');
row.addInteger('campDays', {
label: 'Number of Days',
min: 1,
max: 10,
defaultValue: 5
}, '1fr');
});
// Package Section
const packageSection = form.addSubform('package', { title: '๐ฆ Packages' });
packageSection.addRow(row => {
row.addDropdown('package', {
label: 'Lesson Package',
options: [
{ id: 'single', name: 'Single Lesson' },
{ id: '4-pack', name: '4 Lessons (5% off)' },
{ id: '8-pack', name: '8 Lessons (10% off)' },
{ id: '12-pack', name: '12 Lessons (15% off)' },
{ id: 'session', name: 'Full Session (8-10 weeks)' }
],
defaultValue: 'single'
}, '1fr');
row.addDropdown('frequency', {
label: 'Lessons per Week',
options: [
{ id: '1', name: '1x per week' },
{ id: '2', name: '2x per week' },
{ id: '3', name: '3x per week' }
],
defaultValue: '1'
}, '1fr');
});
// Add-ons Section
const addonsSection = form.addSubform('addons', { title: 'โจ Add-ons' });
addonsSection.addRow(row => {
row.addCheckbox('goggles', {
label: 'Goggles (+$10)',
defaultValue: false
}, '1fr');
row.addCheckbox('swimCap', {
label: 'Swim Cap (+$8)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('kickboard', {
label: 'Kickboard/Equipment (+$5)',
defaultValue: false
}, '1fr');
row.addCheckbox('videoAnalysis', {
label: 'Video Analysis (+$25)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('lifeguardCert', {
label: 'Lifeguard Training Add-on (+$150)',
defaultValue: false,
isVisible: () => ['advanced', 'competitive'].includes(lessonSection.dropdown('swimLevel')?.value() || '')
}, '1fr');
row.addCheckbox('divingBasics', {
label: 'Diving Basics (+$30)',
defaultValue: false
}, '1fr');
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Pricing Section
const pricingSection = form.addSubform('pricing', { title: '๐ฐ Pricing', isCollapsible: false });
const calculatePrice = () => {
const lessonType = lessonSection.radioButton('lessonType')?.value() || 'private';
const swimLevel = lessonSection.dropdown('swimLevel')?.value() || 'beginner';
const ageGroup = lessonSection.dropdown('ageGroup')?.value() || 'school-age';
const duration = parseInt(sessionSection.dropdown('duration')?.value() || '30');
const instructorLevel = sessionSection.dropdown('instructorLevel')?.value() || 'certified';
const poolType = sessionSection.dropdown('poolType')?.value() || 'indoor-public';
const packageType = packageSection.dropdown('package')?.value() || 'single';
const swimmers = sessionSection.integer('swimmers')?.value() || 2;
// Base rates for 30-minute private lesson
const baseRate = 35;
// Lesson type multiplier
const lessonMult: Record<string, number> = {
'private': 1.0,
'semi-private': 0.7,
'small-group': 0.55,
'group': 0.4,
'parent-child': 0.85,
'camp': 0
};
let lessonPrice = baseRate * (lessonMult[lessonType] || 1.0);
// Duration adjustment
const durationMult = duration / 30;
lessonPrice *= durationMult;
// Instructor level
const instructorMult: Record<string, number> = {
'assistant': 0.8,
'certified': 1.0,
'senior': 1.25,
'master': 1.5,
'competitive-coach': 1.6
};
lessonPrice *= instructorMult[instructorLevel] || 1.0;
// Pool type adjustment
const poolMult: Record<string, number> = {
'indoor-public': 1.0,
'indoor-private': 1.3,
'outdoor': 0.95,
'backyard': 1.5,
'open-water': 1.4
};
lessonPrice *= poolMult[poolType] || 1.0;
// Skill level adjustment
const skillMult: Record<string, number> = {
'water-intro': 1.1,
'beginner': 1.0,
'intermediate': 1.0,
'advanced': 1.1,
'competitive': 1.25,
'adult-beginner': 1.1,
'triathlon': 1.3
};
lessonPrice *= skillMult[swimLevel] || 1.0;
// Age adjustment (infants/toddlers often cost more)
if (['infant', 'toddler'].includes(ageGroup)) {
lessonPrice *= 1.15;
}
// Camp pricing
if (lessonType === 'camp') {
const campType = campSection.dropdown('campType')?.value() || 'daily';
const campDays = campSection.integer('campDays')?.value() || 5;
const campRates: Record<string, number> = {
'daily': 30,
'intensive': 50,
'half-day': 60,
'full-day': 100
};
lessonPrice = (campRates[campType] ?? 0) * campDays;
}
// Add-ons
let addons = 0;
if (addonsSection.checkbox('goggles')?.value()) addons += 10;
if (addonsSection.checkbox('swimCap')?.value()) addons += 8;
if (addonsSection.checkbox('kickboard')?.value()) addons += 5;
if (addonsSection.checkbox('videoAnalysis')?.value()) addons += 25;
if (addonsSection.checkbox('lifeguardCert')?.value()) addons += 150;
if (addonsSection.checkbox('divingBasics')?.value()) addons += 30;
lessonPrice += addons;
// Package discounts
const packageDiscounts: Record<string, number> = {
'single': 0,
'4-pack': 0.05,
'8-pack': 0.10,
'12-pack': 0.15,
'session': 0.20
};
const discount = packageDiscounts[packageType] || 0;
const packageQuantities: Record<string, number> = {
'single': 1,
'4-pack': 4,
'8-pack': 8,
'12-pack': 12,
'session': 10
};
const sessions = packageQuantities[packageType] || 1;
const discountedPrice = lessonPrice * (1 - discount);
const packageTotal = discountedPrice * sessions;
const monthlyEstimate = discountedPrice * 4;
return {
perLesson: Math.round(lessonPrice),
discountedLesson: Math.round(discountedPrice),
packageTotal: Math.round(packageTotal),
monthlyEstimate: Math.round(monthlyEstimate),
discount: Math.round(discount * 100),
sessions,
addons: Math.round(addons),
duration
};
};
pricingSection.addRow(row => {
row.addPriceDisplay('perLesson', {
label: () => `Per Lesson (${calculatePrice().duration} min)`,
computedValue: () => calculatePrice().perLesson,
variant: 'default'
}, '1fr');
row.addPriceDisplay('discounted', {
label: () => `After ${calculatePrice().discount}% Discount`,
computedValue: () => calculatePrice().discountedLesson,
variant: 'success',
isVisible: () => calculatePrice().discount > 0
}, '1fr');
});
pricingSection.addRow(row => {
row.addPriceDisplay('addons', {
label: 'Add-ons/Equipment',
computedValue: () => calculatePrice().addons,
variant: 'default',
isVisible: () => calculatePrice().addons > 0
});
});
// Summary Section
const summarySection = form.addSubform('summary', {
title: '๐งพ Summary',
isCollapsible: false,
sticky: 'bottom'
});
summarySection.addRow(row => {
row.addPriceDisplay('total', {
label: () => calculatePrice().sessions > 1 ? `Package Total (${calculatePrice().sessions} lessons)` : 'Lesson Total',
computedValue: () => calculatePrice().packageTotal,
variant: 'large'
}, '1fr');
row.addPriceDisplay('monthly', {
label: 'Monthly Estimate (4 lessons)',
computedValue: () => calculatePrice().monthlyEstimate,
variant: 'default',
isVisible: () => calculatePrice().sessions === 1
}, '1fr');
});
summarySection.addRow(row => {
row.addTextPanel('note', {
computedValue: () => 'Pool admission may be additional at some facilities. Swimwear not included.',
customStyles: { 'font-size': '0.85rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Book Swimming Lesson'
});
}