export function hairSalonCalculator(form: FormTs) {
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Hair Salon Price Calculator',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Client Info Section
const clientSection = form.addSubform('client', { title: '๐ค Client Information' });
clientSection.addRow(row => {
row.addDropdown('clientType', {
label: 'Client Type',
options: [
{ id: 'women', name: 'Women' },
{ id: 'men', name: 'Men' },
{ id: 'child', name: 'Child (12 & under)' }
],
defaultValue: 'women',
isRequired: true
}, '1fr');
row.addDropdown('hairLength', {
label: 'Hair Length',
options: [
{ id: 'short', name: 'Short (above shoulders)' },
{ id: 'medium', name: 'Medium (shoulder to mid-back)' },
{ id: 'long', name: 'Long (mid-back & below)' },
{ id: 'extra-long', name: 'Extra Long (waist & below)' }
],
defaultValue: 'medium'
}, '1fr');
});
clientSection.addRow(row => {
row.addDropdown('hairType', {
label: 'Hair Type',
options: [
{ id: 'fine', name: 'Fine/Thin' },
{ id: 'normal', name: 'Normal/Medium' },
{ id: 'thick', name: 'Thick/Coarse' },
{ id: 'curly', name: 'Curly/Textured' }
],
defaultValue: 'normal'
}, '1fr');
});
// Service Type Section
const serviceSection = form.addSubform('service', { title: 'โ๏ธ Services' });
serviceSection.addRow(row => {
row.addCheckbox('haircut', {
label: 'Haircut',
defaultValue: true
}, '1fr');
row.addDropdown('cutType', {
label: 'Cut Type',
options: [
{ id: 'trim', name: 'Trim (maintenance)' },
{ id: 'style-cut', name: 'Style Cut' },
{ id: 'transformation', name: 'Major Transformation' },
{ id: 'bang-trim', name: 'Bang Trim Only' }
],
defaultValue: 'style-cut',
isVisible: () => serviceSection.checkbox('haircut')?.value() === true
}, '1fr');
});
// Color Section
const colorSection = form.addSubform('color', { title: '๐จ Color Services' });
colorSection.addRow(row => {
row.addCheckbox('colorService', {
label: 'Color Service',
defaultValue: false
}, '1fr');
});
colorSection.addRow(row => {
row.addDropdown('colorType', {
label: 'Color Type',
options: [
{ id: 'root-touch-up', name: 'Root Touch-Up' },
{ id: 'single-process', name: 'Single Process (All Over)' },
{ id: 'partial-highlights', name: 'Partial Highlights' },
{ id: 'full-highlights', name: 'Full Highlights' },
{ id: 'balayage', name: 'Balayage/Ombre' },
{ id: 'color-correction', name: 'Color Correction' },
{ id: 'vivid', name: 'Vivid/Fashion Colors' }
],
defaultValue: 'single-process',
isVisible: () => colorSection.checkbox('colorService')?.value() === true
}, '1fr');
});
colorSection.addRow(row => {
row.addCheckboxList('colorAddons', {
label: 'Color Add-ons',
options: [
{ id: 'toner', name: 'Toner/Gloss (+$35)' },
{ id: 'olaplex', name: 'Olaplex Treatment (+$45)' }
],
defaultValue: [],
orientation: 'vertical',
isVisible: () => colorSection.checkbox('colorService')?.value() === true
}, '1fr');
});
// Extensions Section
const extensionsSection = form.addSubform('extensions', { title: '๐ Extensions' });
extensionsSection.addRow(row => {
row.addCheckbox('extensions', {
label: 'Hair Extensions',
defaultValue: false
}, '1fr');
});
extensionsSection.addRow(row => {
row.addDropdown('extensionType', {
label: 'Extension Type',
options: [
{ id: 'clip-in', name: 'Clip-In Extensions' },
{ id: 'tape-in', name: 'Tape-In Extensions' },
{ id: 'sew-in', name: 'Sew-In/Weave' },
{ id: 'fusion', name: 'Fusion/Bonded' },
{ id: 'micro-link', name: 'Micro-Link/I-Tip' }
],
defaultValue: 'tape-in',
isVisible: () => extensionsSection.checkbox('extensions')?.value() === true
}, '1fr');
row.addDropdown('extensionRows', {
label: 'Amount',
options: [
{ id: 'partial', name: 'Partial (volume)' },
{ id: 'half', name: 'Half Head' },
{ id: 'full', name: 'Full Head' }
],
defaultValue: 'half',
isVisible: () => extensionsSection.checkbox('extensions')?.value() === true
}, '1fr');
});
// Styling Section
const stylingSection = form.addSubform('styling', { title: '๐ซ Styling Services' });
stylingSection.addRow(row => {
row.addCheckboxList('stylingOptions', {
label: 'Select Styling Services',
options: [
{ id: 'blowout', name: 'Blowout/Blow Dry' },
{ id: 'updo', name: 'Updo/Formal Style' },
{ id: 'flatIron', name: 'Flat Iron Styling (+$20)' },
{ id: 'curling', name: 'Curling/Waves (+$25)' }
],
defaultValue: [],
orientation: 'vertical'
}, '1fr');
});
stylingSection.addRow(row => {
row.addDropdown('updoType', {
label: 'Updo Style',
options: [
{ id: 'simple', name: 'Simple/Casual' },
{ id: 'elegant', name: 'Elegant/Formal' },
{ id: 'bridal', name: 'Bridal' },
{ id: 'braided', name: 'Braided Style' }
],
defaultValue: 'elegant',
isVisible: () => (stylingSection.checkboxList('stylingOptions')?.value() ?? []).includes('updo')
}, '1fr');
row.addCheckbox('bridalTrial', {
label: 'Include Trial Session',
defaultValue: true,
isVisible: () => stylingSection.dropdown('updoType')?.value() === 'bridal'
}, '1fr');
});
// Treatments Section
const treatmentSection = form.addSubform('treatments', { title: '๐งด Treatments' });
treatmentSection.addRow(row => {
row.addCheckboxList('treatmentOptions', {
label: 'Select Treatments',
options: [
{ id: 'deepCondition', name: 'Deep Conditioning Treatment' },
{ id: 'keratin', name: 'Keratin Treatment' },
{ id: 'scalp', name: 'Scalp Treatment' },
{ id: 'proteinTreatment', name: 'Protein Treatment' }
],
defaultValue: [],
orientation: 'vertical'
}, '1fr');
});
// Specialty Services Section
const specialtySection = form.addSubform('specialty', { title: 'โญ Specialty Services' });
specialtySection.addRow(row => {
row.addCheckbox('perm', {
label: 'Perm/Texture Service',
defaultValue: false
}, '1fr');
row.addDropdown('permType', {
label: 'Perm Type',
options: [
{ id: 'body-wave', name: 'Body Wave' },
{ id: 'spiral', name: 'Spiral Perm' },
{ id: 'relaxer', name: 'Relaxer' }
],
defaultValue: 'body-wave',
isVisible: () => specialtySection.checkbox('perm')?.value() === true
}, '1fr');
});
// Stylist Level Section
const stylistSection = form.addSubform('stylist', { title: '๐ฉโ๐จ Stylist Level' });
stylistSection.addRow(row => {
row.addRadioButton('stylistLevel', {
label: 'Select Stylist',
options: [
{ id: 'junior', name: 'Junior Stylist' },
{ id: 'senior', name: 'Senior Stylist' },
{ id: 'master', name: 'Master Stylist' },
{ id: 'director', name: 'Creative Director' }
],
defaultValue: 'senior',
orientation: 'horizontal'
});
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Pricing Section
const pricingSection = form.addSubform('pricing', { title: '๐ฐ Pricing', isCollapsible: false });
const calculatePrice = () => {
const clientType = clientSection.dropdown('clientType')?.value() || 'women';
const hairLength = clientSection.dropdown('hairLength')?.value() || 'medium';
const hairType = clientSection.dropdown('hairType')?.value() || 'normal';
const stylistLevel = stylistSection.radioButton('stylistLevel')?.value() || 'senior';
let cutPrice = 0;
let colorPrice = 0;
let stylingPrice = 0;
let treatmentPrice = 0;
let extensionPrice = 0;
let addonsPrice = 0;
// Length multiplier
const lengthMult: Record<string, number> = {
'short': 1.0,
'medium': 1.15,
'long': 1.35,
'extra-long': 1.55
};
const lengthFactor = lengthMult[hairLength] || 1.0;
// Hair type multiplier (thick/curly takes more time)
const typeMult: Record<string, number> = {
'fine': 0.95,
'normal': 1.0,
'thick': 1.15,
'curly': 1.2
};
const typeFactor = typeMult[hairType] || 1.0;
// Haircut pricing
if (serviceSection.checkbox('haircut')?.value()) {
const cutType = serviceSection.dropdown('cutType')?.value() || 'style-cut';
if (clientType === 'women') {
const womenCuts: Record<string, number> = {
'trim': 45,
'style-cut': 65,
'transformation': 85,
'bang-trim': 15
};
cutPrice = womenCuts[cutType] || 65;
} else if (clientType === 'men') {
const menCuts: Record<string, number> = {
'trim': 25,
'style-cut': 35,
'transformation': 45,
'bang-trim': 10
};
cutPrice = menCuts[cutType] || 35;
} else {
cutPrice = 25; // Child
}
if (cutType !== 'bang-trim') {
cutPrice *= lengthFactor;
}
}
// Color pricing
if (colorSection.checkbox('colorService')?.value()) {
const colorType = colorSection.dropdown('colorType')?.value() || 'single-process';
const colorPrices: Record<string, number> = {
'root-touch-up': 75,
'single-process': 95,
'partial-highlights': 125,
'full-highlights': 175,
'balayage': 200,
'color-correction': 300,
'vivid': 250
};
colorPrice = colorPrices[colorType] || 95;
colorPrice *= lengthFactor;
colorPrice *= typeFactor;
const colorAddons = colorSection.checkboxList('colorAddons')?.value() ?? [];
if (colorAddons.includes('toner')) addonsPrice += 35;
if (colorAddons.includes('olaplex')) addonsPrice += 45;
}
// Extensions pricing
if (extensionsSection.checkbox('extensions')?.value()) {
const extensionType = extensionsSection.dropdown('extensionType')?.value() || 'tape-in';
const extensionRows = extensionsSection.dropdown('extensionRows')?.value() || 'half';
const extensionBasePrices: Record<string, number> = {
'clip-in': 100,
'tape-in': 350,
'sew-in': 200,
'fusion': 500,
'micro-link': 450
};
const rowsMult: Record<string, number> = {
'partial': 0.5,
'half': 0.75,
'full': 1.0
};
extensionPrice = (extensionBasePrices[extensionType] || 350) * (rowsMult[extensionRows] || 0.75);
}
// Styling pricing
const stylingOptions = stylingSection.checkboxList('stylingOptions')?.value() ?? [];
if (stylingOptions.includes('blowout')) {
stylingPrice += 45 * lengthFactor;
}
if (stylingOptions.includes('updo')) {
const updoType = stylingSection.dropdown('updoType')?.value() || 'elegant';
const updoPrices: Record<string, number> = {
'simple': 55,
'elegant': 85,
'bridal': 150,
'braided': 95
};
stylingPrice += updoPrices[updoType] || 85;
if (updoType === 'bridal' && stylingSection.checkbox('bridalTrial')?.value()) {
stylingPrice += 75;
}
}
if (stylingOptions.includes('flatIron')) addonsPrice += 20;
if (stylingOptions.includes('curling')) addonsPrice += 25;
// Treatment pricing
const treatmentOptions = treatmentSection.checkboxList('treatmentOptions')?.value() ?? [];
if (treatmentOptions.includes('deepCondition')) {
treatmentPrice += 35 * lengthFactor;
}
if (treatmentOptions.includes('keratin')) {
treatmentPrice += 250 * lengthFactor;
}
if (treatmentOptions.includes('scalp')) {
treatmentPrice += 40;
}
if (treatmentOptions.includes('proteinTreatment')) {
treatmentPrice += 45 * lengthFactor;
}
// Specialty services
if (specialtySection.checkbox('perm')?.value()) {
const permType = specialtySection.dropdown('permType')?.value() || 'body-wave';
const permPrices: Record<string, number> = {
'body-wave': 150,
'spiral': 200,
'relaxer': 175
};
treatmentPrice += (permPrices[permType] || 150) * lengthFactor;
}
// Sum before stylist adjustment
let subtotal = cutPrice + colorPrice + stylingPrice + treatmentPrice + extensionPrice;
// Stylist level multiplier
const stylistMult: Record<string, number> = {
'junior': 0.8,
'senior': 1.0,
'master': 1.25,
'director': 1.5
};
subtotal *= stylistMult[stylistLevel] || 1.0;
const total = subtotal + addonsPrice;
const tipSuggestion = Math.round(total * 0.2);
return {
cutPrice: Math.round(cutPrice * (stylistMult[stylistLevel] || 1.0)),
colorPrice: Math.round(colorPrice * (stylistMult[stylistLevel] || 1.0)),
stylingPrice: Math.round(stylingPrice * (stylistMult[stylistLevel] || 1.0)),
treatmentPrice: Math.round(treatmentPrice * (stylistMult[stylistLevel] || 1.0)),
extensionPrice: Math.round(extensionPrice),
addonsPrice: Math.round(addonsPrice),
subtotal: Math.round(subtotal),
total: Math.round(total),
tipSuggestion
};
};
pricingSection.addRow(row => {
row.addPriceDisplay('cut', {
label: 'Haircut',
computedValue: () => calculatePrice().cutPrice,
variant: 'default',
isVisible: () => calculatePrice().cutPrice > 0
}, '1fr');
row.addPriceDisplay('color', {
label: 'Color Service',
computedValue: () => calculatePrice().colorPrice,
variant: 'default',
isVisible: () => calculatePrice().colorPrice > 0
}, '1fr');
});
pricingSection.addRow(row => {
row.addPriceDisplay('styling', {
label: 'Styling',
computedValue: () => calculatePrice().stylingPrice,
variant: 'default',
isVisible: () => calculatePrice().stylingPrice > 0
}, '1fr');
row.addPriceDisplay('treatment', {
label: 'Treatments',
computedValue: () => calculatePrice().treatmentPrice,
variant: 'default',
isVisible: () => calculatePrice().treatmentPrice > 0
}, '1fr');
});
pricingSection.addRow(row => {
row.addPriceDisplay('extensions', {
label: 'Extensions',
computedValue: () => calculatePrice().extensionPrice,
variant: 'default',
isVisible: () => calculatePrice().extensionPrice > 0
}, '1fr');
row.addPriceDisplay('addons', {
label: '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: 'Total',
computedValue: () => calculatePrice().total,
variant: 'large'
}, '1fr');
row.addPriceDisplay('tip', {
label: 'Suggested Tip (20%)',
computedValue: () => calculatePrice().tipSuggestion,
variant: 'default'
}, '1fr');
});
summarySection.addRow(row => {
row.addTextPanel('note', {
computedValue: () => 'Final prices may vary based on consultation. A $5 tip for the shampoo assistant is customary. Please arrive 10 minutes early.',
customStyles: { 'font-size': '0.85rem', 'color': '#64748b', 'text-align': 'center' }
});
});
form.configureSubmitButton({
label: 'Book Appointment'
});
}