export function pianoLessonsCalculator(form: FormTs) {
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Piano Lessons Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Lesson Format Section
const formatSection = form.addSubform('format', { title: '๐น Lesson Format' });
formatSection.addRow(row => {
row.addRadioButton('lessonType', {
label: 'Lesson Type',
options: [
{ id: 'private', name: 'Private Lessons (1-on-1)' },
{ id: 'semi-private', name: 'Semi-Private (2 students)' },
{ id: 'group', name: 'Group Class (3-6 students)' }
],
defaultValue: 'private',
orientation: 'horizontal'
});
});
formatSection.addRow(row => {
row.addDropdown('lessonDuration', {
label: 'Lesson Duration',
options: [
{ id: '30', name: '30 minutes' },
{ id: '45', name: '45 minutes' },
{ id: '60', name: '60 minutes' },
{ id: '90', name: '90 minutes' }
],
defaultValue: '45',
isRequired: true
}, '1fr');
row.addDropdown('frequency', {
label: 'Lessons per Week',
options: [
{ id: '1', name: 'Once a week' },
{ id: '2', name: 'Twice a week' },
{ id: 'biweekly', name: 'Every other week' }
],
defaultValue: '1',
isRequired: true
}, '1fr');
});
formatSection.addRow(row => {
row.addDropdown('location', {
label: 'Lesson Location',
options: [
{ id: 'studio', name: 'At Studio/School' },
{ id: 'home', name: 'In-Home (teacher travels)' },
{ id: 'online', name: 'Online/Virtual' }
],
defaultValue: 'studio'
}, '1fr');
});
// Student Info Section
const studentSection = form.addSubform('student', { title: '๐ค Student Information' });
studentSection.addRow(row => {
row.addDropdown('ageGroup', {
label: 'Student Age',
options: [
{ id: 'child', name: 'Child (5-12 years)' },
{ id: 'teen', name: 'Teen (13-17 years)' },
{ id: 'adult', name: 'Adult (18+ years)' },
{ id: 'senior', name: 'Senior (60+ years)' }
],
defaultValue: 'child',
isRequired: true
}, '1fr');
row.addDropdown('skillLevel', {
label: 'Skill Level',
options: [
{ id: 'beginner', name: 'Beginner (0-1 years)' },
{ id: 'elementary', name: 'Elementary (1-3 years)' },
{ id: 'intermediate', name: 'Intermediate (3-5 years)' },
{ id: 'advanced', name: 'Advanced (5-8 years)' },
{ id: 'pre-professional', name: 'Pre-Professional (8+ years)' }
],
defaultValue: 'beginner',
isRequired: true
}, '1fr');
});
studentSection.addRow(row => {
row.addDropdown('goals', {
label: 'Learning Goals',
options: [
{ id: 'recreational', name: 'Recreational/Hobby' },
{ id: 'classical', name: 'Classical Training' },
{ id: 'popular', name: 'Pop/Contemporary Music' },
{ id: 'jazz', name: 'Jazz/Improvisation' },
{ id: 'exam', name: 'Exam Preparation (ABRSM, RCM, etc.)' },
{ id: 'competition', name: 'Competition Preparation' }
],
defaultValue: 'recreational'
}, '1fr');
});
// Instructor Section
const instructorSection = form.addSubform('instructor', { title: '๐ฉโ๐ซ Instructor Level' });
instructorSection.addRow(row => {
row.addRadioButton('teacherLevel', {
label: 'Instructor Experience',
options: [
{ id: 'student', name: 'Music Student Teacher' },
{ id: 'junior', name: 'Junior Teacher (1-5 yrs exp)' },
{ id: 'experienced', name: 'Experienced Teacher (5-15 yrs)' },
{ id: 'master', name: 'Master Teacher (15+ yrs)' },
{ id: 'concert', name: 'Concert Pianist/Professor' }
],
defaultValue: 'experienced',
orientation: 'vertical'
});
});
// Package Section
const packageSection = form.addSubform('package', { title: '๐ฆ Payment Options' });
packageSection.addRow(row => {
row.addDropdown('paymentPlan', {
label: 'Payment Plan',
options: [
{ id: 'per-lesson', name: 'Pay Per Lesson' },
{ id: 'monthly', name: 'Monthly Package (4 lessons, 5% off)' },
{ id: 'quarterly', name: 'Quarterly Package (12 lessons, 10% off)' },
{ id: 'semester', name: 'Semester Package (18 lessons, 15% off)' },
{ id: 'annual', name: 'Annual Package (48 lessons, 20% off)' }
],
defaultValue: 'monthly',
isRequired: true
}, '1fr');
});
// Add-ons Section
const addonsSection = form.addSubform('addons', { title: 'โจ Additional Services' });
addonsSection.addRow(row => {
row.addCheckbox('materials', {
label: 'Lesson Materials & Sheet Music',
defaultValue: false
}, '1fr');
row.addCheckbox('theory', {
label: 'Music Theory Supplement',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('practiceTools', {
label: 'Practice App/Tools Subscription',
defaultValue: false
}, '1fr');
row.addCheckbox('recording', {
label: 'Lesson Recording',
defaultValue: false
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('examPrep', {
label: 'Exam/Audition Preparation',
defaultValue: false,
isVisible: () => studentSection.dropdown('goals')?.value() === 'exam' || studentSection.dropdown('goals')?.value() === 'competition'
}, '1fr');
row.addCheckbox('accompaniment', {
label: 'Accompaniment Services',
defaultValue: false,
isVisible: () => studentSection.dropdown('skillLevel')?.value() === 'advanced' || studentSection.dropdown('skillLevel')?.value() === 'pre-professional'
}, '1fr');
});
addonsSection.addRow(row => {
row.addCheckbox('recital', {
label: 'Recital Participation Fee',
defaultValue: false
}, '1fr');
row.addCheckbox('familyDiscount', {
label: 'Family/Sibling Discount (10% off)',
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 = formatSection.radioButton('lessonType')?.value() || 'private';
const duration = parseInt(formatSection.dropdown('lessonDuration')?.value() || '45');
const frequency = formatSection.dropdown('frequency')?.value() || '1';
const location = formatSection.dropdown('location')?.value() || 'studio';
const skillLevel = studentSection.dropdown('skillLevel')?.value() || 'beginner';
const goals = studentSection.dropdown('goals')?.value() || 'recreational';
const teacherLevel = instructorSection.radioButton('teacherLevel')?.value() || 'experienced';
const paymentPlan = packageSection.dropdown('paymentPlan')?.value() || 'monthly';
// Base rate per 30 minutes for private lesson at studio
const baseRates: Record<string, number> = {
'student': 20,
'junior': 30,
'experienced': 45,
'master': 65,
'concert': 100
};
let baseRate = baseRates[teacherLevel] || 45;
// Duration multiplier (not linear - longer lessons better value)
const durationMult: Record<number, number> = {
30: 1.0,
45: 1.4,
60: 1.75,
90: 2.5
};
let lessonPrice = baseRate * (durationMult[duration] || 1.0);
// Lesson type adjustment
const typeMult: Record<string, number> = {
'private': 1.0,
'semi-private': 0.75,
'group': 0.5
};
lessonPrice *= typeMult[lessonType] || 1.0;
// Location adjustment
const locationMult: Record<string, number> = {
'studio': 1.0,
'home': 1.25,
'online': 0.9
};
lessonPrice *= locationMult[location] || 1.0;
// Skill level adjustment (advanced needs more prep)
const levelMult: Record<string, number> = {
'beginner': 1.0,
'elementary': 1.0,
'intermediate': 1.1,
'advanced': 1.2,
'pre-professional': 1.35
};
lessonPrice *= levelMult[skillLevel] || 1.0;
// Goals adjustment
const goalsMult: Record<string, number> = {
'recreational': 1.0,
'classical': 1.05,
'popular': 1.0,
'jazz': 1.1,
'exam': 1.15,
'competition': 1.25
};
lessonPrice *= goalsMult[goals] || 1.0;
// Calculate lessons per month
let lessonsPerMonth = 4;
if (frequency === '2') lessonsPerMonth = 8;
if (frequency === 'biweekly') lessonsPerMonth = 2;
// Package discount
const packageDiscount: Record<string, number> = {
'per-lesson': 0,
'monthly': 0.05,
'quarterly': 0.10,
'semester': 0.15,
'annual': 0.20
};
const discount = packageDiscount[paymentPlan] || 0;
// Package sizes
const packageLessons: Record<string, number> = {
'per-lesson': 1,
'monthly': lessonsPerMonth,
'quarterly': lessonsPerMonth * 3,
'semester': lessonsPerMonth * 4.5,
'annual': lessonsPerMonth * 12
};
const numLessons = packageLessons[paymentPlan] || 1;
// Monthly cost before add-ons
let monthlyBase = lessonPrice * lessonsPerMonth * (1 - discount);
// Add-ons (monthly costs)
let addonsPrice = 0;
if (addonsSection.checkbox('materials')?.value()) addonsPrice += 25;
if (addonsSection.checkbox('theory')?.value()) addonsPrice += 15;
if (addonsSection.checkbox('practiceTools')?.value()) addonsPrice += 10;
if (addonsSection.checkbox('recording')?.value()) addonsPrice += 5;
if (addonsSection.checkbox('examPrep')?.value()) addonsPrice += 30;
if (addonsSection.checkbox('accompaniment')?.value()) addonsPrice += 50;
if (addonsSection.checkbox('recital')?.value()) addonsPrice += 10; // Averaged monthly
// Family discount
if (addonsSection.checkbox('familyDiscount')?.value()) {
monthlyBase *= 0.9;
}
const monthlyTotal = monthlyBase + addonsPrice;
const packageTotal = (lessonPrice * numLessons * (1 - discount)) + (addonsPrice * (numLessons / lessonsPerMonth));
const effectivePerLesson = packageTotal / numLessons;
return {
lessonPrice: Math.round(lessonPrice),
lessonsPerMonth,
discount: Math.round(discount * 100),
monthlyBase: Math.round(monthlyBase),
addonsPrice: Math.round(addonsPrice),
monthlyTotal: Math.round(monthlyTotal),
packageTotal: Math.round(packageTotal),
numLessons: Math.round(numLessons),
effectivePerLesson: Math.round(effectivePerLesson),
duration
};
};
pricingSection.addRow(row => {
row.addPriceDisplay('perLesson', {
label: 'Per Lesson Rate',
computedValue: () => calculatePrice().lessonPrice,
variant: 'default'
}, '1fr');
row.addTextPanel('duration', {
computedValue: () => `(${calculatePrice().duration} min)`,
customStyles: { 'font-size': '0.9rem', 'color': '#64748b', 'align-self': 'center' }
}, '1fr');
});
pricingSection.addRow(row => {
row.addTextPanel('discount', {
computedValue: () => {
const price = calculatePrice();
if (price.discount > 0) {
return `Package Discount: ${price.discount}% off`;
}
return 'No package discount (pay per lesson)';
},
customStyles: { 'font-size': '0.95rem', 'color': '#16a34a', 'font-weight': '500' },
isVisible: () => calculatePrice().discount > 0
});
});
pricingSection.addRow(row => {
row.addPriceDisplay('monthly', {
label: 'Monthly Lessons',
computedValue: () => calculatePrice().monthlyBase,
variant: 'default'
}, '1fr');
row.addPriceDisplay('addons', {
label: 'Monthly Add-ons',
computedValue: () => calculatePrice().addonsPrice,
variant: 'default',
isVisible: () => calculatePrice().addonsPrice > 0
}, '1fr');
});
// Summary Section
const summarySection = form.addSubform('summary', {
title: '๐งพ Summary',
isCollapsible: false,
sticky: 'bottom'
});
summarySection.addRow(row => {
row.addPriceDisplay('total', {
label: 'Monthly Total',
computedValue: () => calculatePrice().monthlyTotal,
variant: 'large'
}, '1fr');
row.addTextPanel('perLessonFinal', {
computedValue: () => {
const price = calculatePrice();
return `$${price.effectivePerLesson}/lesson`;
},
customStyles: { 'font-size': '1rem', 'color': '#64748b', 'text-align': 'center', 'align-self': 'center' }
}, '1fr');
});
summarySection.addRow(row => {
row.addTextPanel('packageInfo', {
computedValue: () => {
const price = calculatePrice();
if (price.numLessons > price.lessonsPerMonth) {
return `Package: ${price.numLessons} lessons for $${price.packageTotal}`;
}
return '';
},
customStyles: { 'font-size': '0.9rem', 'color': '#475569', 'text-align': 'center' },
isVisible: () => calculatePrice().numLessons > calculatePrice().lessonsPerMonth
});
});
summarySection.addRow(row => {
row.addTextPanel('note', {
computedValue: () => 'A trial lesson may be available at a reduced rate. Cancellation policies and makeup lesson rules vary by instructor.',
customStyles: { 'font-size': '0.85rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Book Trial Lesson'
});
}