export function horseRidingLessonsCalculator(form: FormTs) {
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Horse Riding 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: 'beginner', name: 'Beginner/Introductory' },
{ id: 'private', name: 'Private Lesson' },
{ id: 'semi-private', name: 'Semi-Private (2-3 riders)' },
{ id: 'group', name: 'Group Lesson (4-6 riders)' },
{ id: 'camp', name: 'Riding Camp' },
{ id: 'trail', name: 'Trail Ride' }
],
defaultValue: 'private',
orientation: 'vertical',
isRequired: true
});
});
lessonSection.addRow(row => {
row.addDropdown('discipline', {
label: 'Riding Discipline',
options: [
{ id: 'english', name: 'English (General)' },
{ id: 'western', name: 'Western' },
{ id: 'dressage', name: 'Dressage' },
{ id: 'jumping', name: 'Show Jumping' },
{ id: 'eventing', name: 'Eventing' },
{ id: 'hunter', name: 'Hunter/Equitation' },
{ id: 'reining', name: 'Reining' },
{ id: 'trail-riding', name: 'Trail Riding' }
],
defaultValue: 'english'
}, '1fr');
row.addDropdown('riderLevel', {
label: 'Rider Experience',
options: [
{ id: 'never', name: 'Never Ridden' },
{ id: 'beginner', name: 'Beginner (< 1 year)' },
{ id: 'intermediate', name: 'Intermediate (1-3 years)' },
{ id: 'advanced', name: 'Advanced (3+ years)' },
{ id: 'competitive', name: 'Competitive Rider' }
],
defaultValue: 'beginner'
}, '1fr');
});
// Rider Details Section
const riderSection = form.addSubform('rider', { title: '๐ค Rider Details' });
riderSection.addRow(row => {
row.addDropdown('ageGroup', {
label: 'Age Group',
options: [
{ id: 'child', name: 'Child (6-12)' },
{ id: 'teen', name: 'Teen (13-17)' },
{ id: 'adult', name: 'Adult (18+)' },
{ id: 'senior', name: 'Senior (55+)' }
],
defaultValue: 'child'
}, '1fr');
row.addInteger('riders', {
label: 'Number of Riders',
min: 1,
max: 10,
defaultValue: 1,
isVisible: () => ['semi-private', 'group'].includes(lessonSection.radioButton('lessonType')?.value() || '')
}, '1fr');
});
// Session Details Section
const sessionSection = form.addSubform('session', { title: 'โฑ๏ธ Session Details' });
sessionSection.addRow(row => {
row.addDropdown('duration', {
label: 'Lesson Duration',
options: [
{ id: '30', name: '30 Minutes' },
{ id: '45', name: '45 Minutes' },
{ id: '60', name: '1 Hour' },
{ id: '90', name: '1.5 Hours' },
{ id: '120', name: '2 Hours (Trail Ride)' }
],
defaultValue: '60',
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/Head Trainer' }
],
defaultValue: 'certified'
}, '1fr');
});
sessionSection.addRow(row => {
row.addDropdown('horseProvided', {
label: 'Horse',
options: [
{ id: 'school', name: 'School Horse (included)' },
{ id: 'own', name: 'Own Horse' },
{ id: 'lease', name: 'Leased Horse' }
],
defaultValue: 'school'
}, '1fr');
row.addDropdown('frequency', {
label: 'Lesson Frequency',
options: [
{ id: 'once', name: 'One-time/Trial' },
{ id: 'weekly', name: 'Weekly' },
{ id: 'twice-week', name: 'Twice a Week' },
{ id: 'monthly', name: 'Monthly Package' }
],
defaultValue: 'weekly'
}, '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: 'half-day', name: 'Half Day Camp' },
{ id: 'full-day', name: 'Full Day Camp' },
{ id: 'week', name: 'Week-long Camp' },
{ id: 'overnight', name: 'Overnight Camp' }
],
defaultValue: 'full-day'
}, '1fr');
row.addInteger('campDays', {
label: 'Number of Days',
min: 1,
max: 14,
defaultValue: 5
}, '1fr');
});
// Package Section
const packageSection = form.addSubform('package', { title: '๐ฆ Packages & Discounts' });
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: 'monthly', name: 'Monthly Unlimited' }
],
defaultValue: 'single'
}, '1fr');
});
// Add-ons Section
const addonsSection = form.addSubform('addons', { title: 'โจ Add-ons & Extras' });
addonsSection.addRow(row => {
row.addCheckbox('helmet', {
label: 'Helmet Rental (+$5)',
defaultValue: false,
tooltip: 'If you don\'t have your own'
}, '1fr');
row.addCheckbox('boots', {
label: 'Boot Rental (+$10)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('grooming', {
label: 'Learn Grooming & Tacking (+$15)',
defaultValue: false,
tooltip: 'Pre-lesson horse care instruction'
}, '1fr');
row.addCheckbox('videoReview', {
label: 'Video Review Session (+$25)',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('showPrep', {
label: 'Show Preparation (+$40)',
defaultValue: false,
isVisible: () => lessonSection.dropdown('riderLevel')?.value() === 'competitive'
}, '1fr');
row.addCheckbox('horseCare', {
label: 'Horse Care Basics (+$20)',
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 discipline = lessonSection.dropdown('discipline')?.value() || 'english';
const riderLevel = lessonSection.dropdown('riderLevel')?.value() || 'beginner';
const duration = parseInt(sessionSection.dropdown('duration')?.value() || '60');
const instructorLevel = sessionSection.dropdown('instructorLevel')?.value() || 'certified';
const horseProvided = sessionSection.dropdown('horseProvided')?.value() || 'school';
const packageType = packageSection.dropdown('package')?.value() || 'single';
const riders = riderSection.integer('riders')?.value() || 1;
// Base rates per hour by lesson type
const lessonRates: Record<string, number> = {
'beginner': 65,
'private': 85,
'semi-private': 65,
'group': 50,
'camp': 0,
'trail': 55
};
let baseRate = lessonRates[lessonType] || 85;
// Discipline premium
const disciplineMult: Record<string, number> = {
'english': 1.0,
'western': 1.0,
'dressage': 1.3,
'jumping': 1.25,
'eventing': 1.35,
'hunter': 1.2,
'reining': 1.15,
'trail-riding': 0.9
};
baseRate *= disciplineMult[discipline] || 1.0;
// Instructor level
const instructorMult: Record<string, number> = {
'assistant': 0.85,
'certified': 1.0,
'senior': 1.25,
'master': 1.5
};
baseRate *= instructorMult[instructorLevel] || 1.0;
// Duration adjustment
let lessonPrice = baseRate * (duration / 60);
// Camp pricing (overrides)
if (lessonType === 'camp') {
const campType = campSection.dropdown('campType')?.value() || 'full-day';
const campDays = campSection.integer('campDays')?.value() || 5;
const campRates: Record<string, number> = {
'half-day': 75,
'full-day': 125,
'week': 500,
'overnight': 200
};
if (campType === 'week') {
lessonPrice = campRates[campType] ?? 0;
} else {
lessonPrice = (campRates[campType] ?? 0) * campDays;
}
}
// Own horse discount
if (horseProvided === 'own') {
lessonPrice *= 0.75;
}
// Semi-private/group per person
if (['semi-private', 'group'].includes(lessonType)) {
// Price shown is per person
}
// Add-ons
let addons = 0;
if (addonsSection.checkbox('helmet')?.value()) addons += 5;
if (addonsSection.checkbox('boots')?.value()) addons += 10;
if (addonsSection.checkbox('grooming')?.value()) addons += 15;
if (addonsSection.checkbox('videoReview')?.value()) addons += 25;
if (addonsSection.checkbox('showPrep')?.value()) addons += 40;
if (addonsSection.checkbox('horseCare')?.value()) addons += 20;
lessonPrice += addons;
// Package discounts
const packageDiscounts: Record<string, number> = {
'single': 0,
'4-pack': 0.05,
'8-pack': 0.10,
'12-pack': 0.15,
'monthly': 0.20
};
const discount = packageDiscounts[packageType] || 0;
const packageQuantities: Record<string, number> = {
'single': 1,
'4-pack': 4,
'8-pack': 8,
'12-pack': 12,
'monthly': 4
};
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 Included',
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: () => 'Prices may vary by location. Helmet required for all riders.',
customStyles: { 'font-size': '0.85rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Book Lesson'
});
}