export function photographySessionCalculator(form: FormTs) {
// Base session rates
const sessionRates: Record<string, number> = {
'mini': 150, // 30 min, 10 images
'standard': 300, // 1 hour, 25 images
'extended': 500, // 2 hours, 50 images
'half-day': 800, // 4 hours, 100 images
'full-day': 1500, // 8 hours, 200 images
'wedding-basic': 2500, // 6 hours, 1 photographer
'wedding-premium': 4500 // 10 hours, 2 photographers
};
// Session type categories
const sessionTypeMultipliers: Record<string, number> = {
'portrait': 1.0,
'family': 1.1,
'headshot': 1.0,
'maternity': 1.1,
'newborn': 1.3,
'event': 1.2,
'corporate': 1.4,
'product': 1.3,
'real-estate': 1.0
};
form.addRow(row => {
row.addTextPanel('header', {
computedValue: () => 'Photography Session Quote',
customStyles: { 'font-size': '1.5rem', 'font-weight': '600', 'color': '#1e293b' }
});
});
form.addSpacer({ height: 20 });
// Session Type Section
const sessionSection = form.addSubform('sessionType', { title: '๐ธ Session Type' });
sessionSection.addRow(row => {
row.addDropdown('category', {
label: 'Photography Type',
options: [
{ id: 'portrait', name: 'Individual Portrait' },
{ id: 'family', name: 'Family Portrait' },
{ id: 'headshot', name: 'Professional Headshot' },
{ id: 'maternity', name: 'Maternity Session' },
{ id: 'newborn', name: 'Newborn Session' },
{ id: 'event', name: 'Event Coverage' },
{ id: 'corporate', name: 'Corporate/Business' },
{ id: 'product', name: 'Product Photography' },
{ id: 'real-estate', name: 'Real Estate Photography' }
],
defaultValue: 'portrait',
isRequired: true
}, '1fr');
});
sessionSection.addRow(row => {
row.addRadioButton('sessionLength', {
label: 'Session Package',
options: [
{ id: 'mini', name: 'Mini Session - 30 min, 10 edited images ($150)' },
{ id: 'standard', name: 'Standard Session - 1 hour, 25 images ($300)' },
{ id: 'extended', name: 'Extended Session - 2 hours, 50 images ($500)' },
{ id: 'half-day', name: 'Half-Day - 4 hours, 100 images ($800)' },
{ id: 'full-day', name: 'Full-Day - 8 hours, 200 images ($1,500)' }
],
defaultValue: 'standard',
orientation: 'vertical',
isRequired: true
});
});
sessionSection.addRow(row => {
row.addInteger('additionalHours', {
label: 'Additional Hours (+$150/hr)',
min: 0,
max: 8,
defaultValue: 0
}, '1fr');
row.addInteger('additionalImages', {
label: 'Additional Edited Images (+$15 each)',
min: 0,
max: 100,
defaultValue: 0
}, '1fr');
});
// Wedding Package (conditional)
const weddingSection = form.addSubform('weddingPackage', {
title: '๐ Wedding Package',
isVisible: () => sessionSection.dropdown('category')?.value() === 'event'
});
weddingSection.addRow(row => {
row.addCheckbox('isWedding', {
label: 'This is a wedding or engagement',
defaultValue: false
});
});
weddingSection.addRow(row => {
row.addRadioButton('weddingPackage', {
label: 'Wedding Coverage',
options: [
{ id: 'none', name: 'Not applicable' },
{ id: 'wedding-basic', name: 'Essential - 6 hours, 1 photographer ($2,500)' },
{ id: 'wedding-premium', name: 'Premium - 10 hours, 2 photographers ($4,500)' }
],
defaultValue: 'none',
orientation: 'vertical',
isVisible: () => weddingSection.checkbox('isWedding')?.value() === true
});
});
weddingSection.addRow(row => {
row.addCheckboxList('weddingAddons', {
label: 'Wedding Add-ons',
options: [
{ id: 'engagement', name: 'Include Engagement Session (+$350)' },
{ id: 'secondShooter', name: 'Add Second Photographer (+$500)' }
],
orientation: 'vertical',
isVisible: () => weddingSection.checkbox('isWedding')?.value() === true
});
});
// Location Section
const locationSection = form.addSubform('location', { title: '๐ Location & Travel' });
locationSection.addRow(row => {
row.addRadioButton('locationType', {
label: 'Session Location',
options: [
{ id: 'studio', name: 'Studio (No travel fee)' },
{ id: 'local', name: 'Local (within 25 miles)' },
{ id: 'travel', name: 'Travel Required (25+ miles)' }
],
defaultValue: 'studio',
orientation: 'vertical'
});
});
locationSection.addRow(row => {
row.addInteger('travelMiles', {
label: 'Distance (miles)',
min: 25,
max: 200,
defaultValue: 30,
isVisible: () => locationSection.radioButton('locationType')?.value() === 'travel'
}, '1fr');
});
// Deliverables Section
const deliverablesSection = form.addSubform('deliverables', { title: '๐ผ๏ธ Prints & Products' });
deliverablesSection.addRow(row => {
row.addCheckboxList('printProducts', {
label: 'Print Packages',
options: [
{ id: 'printSmall', name: 'Print Package - Small (+$150)' },
{ id: 'printLarge', name: 'Print Package - Large (+$350)' },
{ id: 'canvas', name: '16x20 Canvas Print (+$200)' },
{ id: 'album', name: 'Photo Album - 20 pages (+$400)' }
],
orientation: 'vertical'
}, '1fr');
row.addCheckboxList('digitalOptions', {
label: 'Digital Options',
options: [
{ id: 'allDigital', name: 'All Images (Full Gallery) (+$300)' },
{ id: 'rushDelivery', name: 'Rush Delivery - 48 hours (+$200)' }
],
orientation: 'vertical'
}, '1fr');
});
// Add-ons Section
const addonsSection = form.addSubform('addons', { title: 'โจ Additional Services' });
addonsSection.addRow(row => {
row.addCheckboxList('services', {
label: 'Extra Services',
options: [
{ id: 'makeupArtist', name: 'Hair & Makeup Artist (+$150)' },
{ id: 'wardrobeStylist', name: 'Wardrobe Styling (+$100)' },
{ id: 'retouching', name: 'Advanced Retouching (+$10/image)' },
{ id: 'videoclip', name: 'Behind-the-Scenes Video (+$250)' }
],
orientation: 'vertical'
});
});
form.addSpacer({ height: 20, showLine: true, lineStyle: 'dashed' });
// Quote Summary Section
const summarySection = form.addSubform('summary', { title: '๐ฐ Investment Summary', isCollapsible: false });
const getBaseSessionCost = () => {
const isWeddingEvent = weddingSection.checkbox('isWedding')?.value();
const weddingPackage = weddingSection.radioButton('weddingPackage')?.value();
if (isWeddingEvent && weddingPackage && weddingPackage !== 'none') {
return sessionRates[weddingPackage] || 2500;
}
const sessionLength = sessionSection.radioButton('sessionLength')?.value() || 'standard';
const category = sessionSection.dropdown('category')?.value() || 'portrait';
const baseRate = sessionRates[sessionLength] || 300;
const categoryMultiplier = sessionTypeMultipliers[category] || 1;
return Math.round(baseRate * categoryMultiplier);
};
const getTravelFee = () => {
const locationType = locationSection.radioButton('locationType')?.value();
if (locationType === 'studio') return 0;
if (locationType === 'local') return 25;
const miles = locationSection.integer('travelMiles')?.value() || 30;
return Math.round((miles - 25) * 0.65) + 25; // $0.65/mile after 25 miles + $25 base
};
const getWeddingAddonsCost = () => {
const addons = weddingSection.checkboxList('weddingAddons')?.value() || [];
let total = 0;
if (addons.includes('engagement')) total += 350;
if (addons.includes('secondShooter')) total += 500;
return total;
};
const getProductsCost = () => {
const prints = deliverablesSection.checkboxList('printProducts')?.value() || [];
const digital = deliverablesSection.checkboxList('digitalOptions')?.value() || [];
let total = 0;
if (prints.includes('printSmall')) total += 150;
if (prints.includes('printLarge')) total += 350;
if (prints.includes('canvas')) total += 200;
if (prints.includes('album')) total += 400;
if (digital.includes('allDigital')) total += 300;
if (digital.includes('rushDelivery')) total += 200;
return total;
};
const getServicesCost = () => {
const services = addonsSection.checkboxList('services')?.value() || [];
let total = 0;
if (services.includes('makeupArtist')) total += 150;
if (services.includes('wardrobeStylist')) total += 100;
if (services.includes('videoclip')) total += 250;
// Retouching cost depends on number of images
if (services.includes('retouching')) {
const sessionLength = sessionSection.radioButton('sessionLength')?.value() || 'standard';
const baseImages: Record<string, number> = {
'mini': 10, 'standard': 25, 'extended': 50, 'half-day': 100, 'full-day': 200
};
const images = baseImages[sessionLength] || 25;
const additionalImages = sessionSection.integer('additionalImages')?.value() || 0;
total += (images + additionalImages) * 10;
}
return total;
};
summarySection.addRow(row => {
row.addPriceDisplay('sessionCost', {
label: 'Session Fee',
computedValue: () => {
let cost = getBaseSessionCost();
// Additional hours
const additionalHours = sessionSection.integer('additionalHours')?.value() || 0;
cost += additionalHours * 150;
// Additional images
const additionalImages = sessionSection.integer('additionalImages')?.value() || 0;
cost += additionalImages * 15;
return cost;
},
variant: 'default'
}, '1fr');
row.addPriceDisplay('travelFee', {
label: 'Travel Fee',
computedValue: getTravelFee,
variant: 'default',
prefix: '+'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('weddingExtras', {
label: 'Wedding Add-ons',
computedValue: getWeddingAddonsCost,
variant: 'default',
prefix: '+',
isVisible: () => weddingSection.checkbox('isWedding')?.value() === true
}, '1fr');
row.addPriceDisplay('productsTotal', {
label: 'Prints & Products',
computedValue: getProductsCost,
variant: 'default',
prefix: '+'
}, '1fr');
});
summarySection.addRow(row => {
row.addPriceDisplay('servicesTotal', {
label: 'Additional Services',
computedValue: getServicesCost,
variant: 'default',
prefix: '+'
});
});
const finalSection = form.addSubform('final', {
title: '๐งพ Summary',
isCollapsible: false,
sticky: 'bottom'
});
finalSection.addRow(row => {
row.addPriceDisplay('totalInvestment', {
label: 'Total Investment',
computedValue: () => {
let total = getBaseSessionCost();
// Additional hours and images
const additionalHours = sessionSection.integer('additionalHours')?.value() || 0;
total += additionalHours * 150;
const additionalImages = sessionSection.integer('additionalImages')?.value() || 0;
total += additionalImages * 15;
// Travel
total += getTravelFee();
// Wedding extras
total += getWeddingAddonsCost();
// Products
total += getProductsCost();
// Services
total += getServicesCost();
return Math.round(total);
},
variant: 'large'
});
});
finalSection.addRow(row => {
row.addTextPanel('deposit', {
computedValue: () => {
const isWeddingEvent = weddingSection.checkbox('isWedding')?.value();
if (isWeddingEvent) {
return '50% deposit required to reserve your date.';
}
return '$100 session fee due at booking.';
},
customStyles: { 'font-size': '0.9rem', 'color': '#059669' }
});
});
finalSection.addRow(row => {
row.addTextPanel('disclaimer', {
computedValue: () => 'Final quote provided after consultation.',
customStyles: { 'font-size': '0.85rem', 'color': '#94a3b8', 'font-style': 'italic' }
});
});
form.configureSubmitButton({
label: 'Book Consultation'
});
}