export function tradeServiceFeedback(form: FormTs) {
// Trade Service Feedback Form - Contractor/Tradesperson Review
// Demonstrates: StarRating, EmojiRating, SuggestionChips, RadioButton, conditional sections
// ============================================
// STATE
// ============================================
const overallSatisfied = form.state(true);
// ============================================
// HEADER
// ============================================
form.addRow(row => {
row.addTextPanel('header', {
label: 'Trade Service Feedback',
computedValue: () => 'Help us improve by sharing your experience',
customStyles: {
backgroundColor: '#ea580c',
color: 'white',
padding: '24px',
borderRadius: '12px',
textAlign: 'center'
}
});
});
// ============================================
// SECTION 1: Service Information
// ============================================
const serviceInfo = form.addSubform('serviceInfo', {
title: 'Service Information'
});
serviceInfo.addRow(row => {
row.addDropdown('tradeType', {
label: 'Type of Trade Service',
options: [
{ id: 'plumbing', name: 'Plumbing' },
{ id: 'electrical', name: 'Electrical' },
{ id: 'hvac', name: 'HVAC / Air Conditioning' },
{ id: 'carpentry', name: 'Carpentry' },
{ id: 'painting', name: 'Painting' },
{ id: 'roofing', name: 'Roofing' },
{ id: 'flooring', name: 'Flooring' },
{ id: 'landscaping', name: 'Landscaping' },
{ id: 'general', name: 'General Contractor' },
{ id: 'handyman', name: 'Handyman Services' },
{ id: 'appliance', name: 'Appliance Repair' },
{ id: 'other', name: 'Other' }
],
isRequired: true,
placeholder: 'Select the service type'
}, '1fr');
row.addTextbox('companyName', {
label: 'Company/Contractor Name',
placeholder: 'Enter company or contractor name'
}, '1fr');
});
serviceInfo.addRow(row => {
row.addTextbox('technicianName', {
label: 'Technician Name (if known)',
placeholder: 'Name of the person who performed the service'
}, '1fr');
row.addDatepicker('serviceDate', {
label: 'Date of Service',
isRequired: true,
defaultValue: new Date().toISOString().slice(0, 10)
}, '1fr');
});
serviceInfo.addRow(row => {
row.addTextarea('workDescription', {
label: 'Brief Description of Work Performed',
placeholder: 'Describe the service or repair work...',
rows: 2,
isRequired: true
});
});
// ============================================
// SECTION 2: Punctuality & Communication
// ============================================
const punctualitySection = form.addSubform('punctuality', {
title: 'Punctuality & Communication',
customStyles: { backgroundColor: '#fff7ed', padding: '16px', borderRadius: '8px' }
});
punctualitySection.addRow(row => {
row.addStarRating('arrivalPunctuality', {
label: 'Arrived on time / within scheduled window',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
row.addStarRating('communication', {
label: 'Communication (scheduling, updates)',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
});
punctualitySection.addRow(row => {
row.addRadioButton('appointmentKept', {
label: 'Was the appointment kept as scheduled?',
options: [
{ id: 'on-time', name: 'Yes, on time' },
{ id: 'late-15', name: 'Late by 15-30 minutes' },
{ id: 'late-30', name: 'Late by 30+ minutes' },
{ id: 'rescheduled', name: 'Had to reschedule' },
{ id: 'no-show', name: 'No show / missed appointment' }
],
orientation: 'vertical'
});
});
// ============================================
// SECTION 3: Workmanship & Quality
// ============================================
const qualitySection = form.addSubform('quality', {
title: 'Workmanship & Quality',
customStyles: { backgroundColor: '#f0fdf4', padding: '16px', borderRadius: '8px' }
});
qualitySection.addRow(row => {
row.addStarRating('workQuality', {
label: 'Quality of Work',
maxStars: 5,
size: 'lg',
showCounter: true,
showConfettiOnMax: true
}, '1fr');
row.addStarRating('technicalSkill', {
label: 'Technical Skill & Expertise',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
});
qualitySection.addRow(row => {
row.addMatrixQuestion('qualityDetails', {
label: 'Evaluate specific aspects of the work:',
rows: [
{ id: 'diagnosis', label: 'Accurate problem diagnosis' },
{ id: 'solution', label: 'Effective solution provided' },
{ id: 'completion', label: 'Work completed as promised' },
{ id: 'materials', label: 'Quality of materials used' }
],
columns: [
{ id: 'excellent', label: 'Excellent' },
{ id: 'good', label: 'Good' },
{ id: 'fair', label: 'Fair' },
{ id: 'poor', label: 'Poor' },
{ id: 'na', label: 'N/A' }
],
fullWidth: true
});
});
qualitySection.addRow(row => {
row.addRadioButton('issueResolved', {
label: 'Was your issue fully resolved?',
options: [
{ id: 'yes', name: 'Yes, completely resolved' },
{ id: 'mostly', name: 'Mostly resolved, minor issues remain' },
{ id: 'partial', name: 'Partially resolved' },
{ id: 'no', name: 'No, issue not resolved' }
],
orientation: 'horizontal'
});
});
// ============================================
// SECTION 4: Professionalism
// ============================================
const professionalismSection = form.addSubform('professionalism', {
title: 'Professionalism & Conduct',
customStyles: { backgroundColor: '#eff6ff', padding: '16px', borderRadius: '8px' }
});
professionalismSection.addRow(row => {
row.addStarRating('professionalBehavior', {
label: 'Professional Behavior',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
row.addStarRating('cleanliness', {
label: 'Cleanliness (left area clean)',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
});
professionalismSection.addRow(row => {
row.addSuggestionChips('positiveTraits', {
label: 'What did the technician do well? (select all that apply)',
suggestions: [
{ id: 'courteous', name: 'Courteous & respectful' },
{ id: 'knowledgeable', name: 'Knowledgeable' },
{ id: 'explained', name: 'Explained the work' },
{ id: 'efficient', name: 'Worked efficiently' },
{ id: 'clean', name: 'Cleaned up after' },
{ id: 'protective', name: 'Protected property' },
{ id: 'uniform', name: 'Professional appearance' },
{ id: 'patient', name: 'Patient with questions' }
],
alignment: 'left'
});
});
// ============================================
// SECTION 5: Pricing & Value
// ============================================
const pricingSection = form.addSubform('pricing', {
title: 'Pricing & Value'
});
pricingSection.addRow(row => {
row.addStarRating('priceTransparency', {
label: 'Pricing Transparency',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
row.addStarRating('valueForMoney', {
label: 'Value for Money',
maxStars: 5,
size: 'lg',
showCounter: true
}, '1fr');
});
pricingSection.addRow(row => {
row.addRadioButton('pricingAccuracy', {
label: 'Was the final price as quoted/estimated?',
options: [
{ id: 'exact', name: 'Exact as quoted' },
{ id: 'close', name: 'Within 10% of estimate' },
{ id: 'higher', name: 'Higher than estimated' },
{ id: 'lower', name: 'Lower than estimated' },
{ id: 'no-quote', name: 'No quote provided upfront' }
],
orientation: 'horizontal'
});
});
// ============================================
// SECTION 6: Overall Experience
// ============================================
const overallSection = form.addSubform('overall', {
title: 'Overall Experience'
});
overallSection.addRow(row => {
row.addEmojiRating('overallSatisfaction', {
label: 'Overall, how satisfied are you with this service?',
preset: 'satisfaction',
size: 'lg',
showLabels: true,
alignment: 'center',
onValueChange: (value) => {
const satisfied = value === 'good' || value === 'excellent';
overallSatisfied.set(satisfied);
}
});
});
overallSection.addRow(row => {
row.addRatingScale('recommendScore', {
label: 'How likely are you to recommend this service to others?',
preset: 'nps',
showCategoryLabel: true,
showSegmentColors: true,
alignment: 'center'
});
});
overallSection.addRow(row => {
row.addRadioButton('wouldUseAgain', {
label: 'Would you use this service again?',
options: [
{ id: 'definitely', name: 'Definitely yes' },
{ id: 'probably', name: 'Probably yes' },
{ id: 'maybe', name: 'Maybe' },
{ id: 'probably-not', name: 'Probably not' },
{ id: 'definitely-not', name: 'Definitely not' }
],
orientation: 'horizontal'
});
});
// ============================================
// SECTION 7: Positive Feedback (for satisfied customers)
// ============================================
const positiveSection = form.addSubform('positive', {
title: 'What Went Well',
isVisible: () => overallSatisfied(),
customStyles: { backgroundColor: '#d1fae5', padding: '16px', borderRadius: '8px' }
});
positiveSection.addRow(row => {
row.addTextarea('whatWentWell', {
label: 'What did you like most about the service?',
placeholder: 'Tell us what impressed you...',
rows: 3
});
});
// ============================================
// SECTION 8: Improvement Feedback (for less satisfied customers)
// ============================================
const improvementSection = form.addSubform('improvement', {
title: 'Areas for Improvement',
isVisible: () => !overallSatisfied(),
customStyles: { backgroundColor: '#fee2e2', padding: '16px', borderRadius: '8px' }
});
improvementSection.addRow(row => {
row.addCheckboxList('issueAreas', {
label: 'What areas need improvement? (select all that apply)',
options: [
{ id: 'punctuality', name: 'Punctuality / Scheduling' },
{ id: 'communication', name: 'Communication' },
{ id: 'workmanship', name: 'Quality of work' },
{ id: 'professionalism', name: 'Professionalism' },
{ id: 'cleanliness', name: 'Cleanliness' },
{ id: 'pricing', name: 'Pricing / Transparency' },
{ id: 'other', name: 'Other issues' }
],
orientation: 'vertical'
});
});
improvementSection.addSpacer();
improvementSection.addRow(row => {
row.addTextarea('whatToImprove', {
label: 'Please describe what could be improved:',
placeholder: 'Share specific details about your concerns...',
rows: 4,
isRequired: () => !overallSatisfied()
});
});
// ============================================
// SECTION 9: Additional Comments
// ============================================
const commentsSection = form.addSubform('comments', {
title: 'Additional Comments'
});
commentsSection.addRow(row => {
row.addTextarea('additionalComments', {
label: 'Any other comments or feedback?',
placeholder: 'Share any additional thoughts...',
rows: 3
});
});
commentsSection.addRow(row => {
row.addCheckbox('canContact', {
label: 'You may contact me about my feedback'
});
});
commentsSection.addRow(row => {
row.addEmail('contactEmail', {
label: 'Email address',
placeholder: 'your@email.com',
isVisible: () => commentsSection.checkbox('canContact')?.value() === true
}, '1fr');
row.addTextbox('contactPhone', {
label: 'Phone number (optional)',
placeholder: 'Your phone number',
isVisible: () => commentsSection.checkbox('canContact')?.value() === true
}, '1fr');
});
// ============================================
// SECTION 10: Summary
// ============================================
const summarySection = form.addSubform('summarySection', {
title: 'Feedback Summary',
isVisible: () => {
const satisfaction = overallSection.emojiRating('overallSatisfaction')?.value();
return !!satisfaction;
}
});
summarySection.addRow(row => {
row.addTextPanel('summaryContent', {
computedValue: () => {
const tradeType = serviceInfo.dropdown('tradeType')?.value();
const company = serviceInfo.textbox('companyName')?.value();
const satisfaction = overallSection.emojiRating('overallSatisfaction')?.value();
const nps = overallSection.ratingScale('recommendScore')?.value();
const useAgain = overallSection.radioButton('wouldUseAgain')?.value();
const tradeNames: Record<string, string> = {
'plumbing': 'Plumbing', 'electrical': 'Electrical',
'hvac': 'HVAC', 'carpentry': 'Carpentry',
'painting': 'Painting', 'roofing': 'Roofing',
'general': 'General Contractor', 'handyman': 'Handyman'
};
const satisfactionLabels: Record<string, string> = {
'very-bad': 'Very Dissatisfied',
'bad': 'Dissatisfied',
'neutral': 'Neutral',
'good': 'Satisfied',
'excellent': 'Very Satisfied'
};
let emoji = '';
if (satisfaction === 'excellent') emoji = '';
else if (satisfaction === 'good') emoji = '';
else if (satisfaction === 'neutral') emoji = '';
else emoji = '';
let summary = `${emoji} FEEDBACK SUMMARY\n`;
summary += ''.repeat(30) + '\n\n';
summary += `Service Type: ${tradeNames[tradeType || ''] || tradeType || 'Not specified'}\n`;
if (company) summary += `Company: ${company}\n`;
summary += `\nOverall Satisfaction: ${satisfactionLabels[satisfaction || ''] || 'Not rated'}\n`;
if (nps !== null && nps !== undefined) summary += `Recommendation Score: ${nps}/10\n`;
if (useAgain) {
const useAgainLabels: Record<string, string> = {
'definitely': 'Definitely Yes',
'probably': 'Probably Yes',
'maybe': 'Maybe',
'probably-not': 'Probably Not',
'definitely-not': 'Definitely Not'
};
summary += `Would Use Again: ${useAgainLabels[useAgain] || useAgain}\n`;
}
return summary;
},
customStyles: () => {
const satisfaction = overallSection.emojiRating('overallSatisfaction')?.value();
const baseStyles = {
padding: '16px',
borderRadius: '8px',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
fontSize: '14px'
};
if (satisfaction === 'excellent' || satisfaction === 'good') {
return { ...baseStyles, backgroundColor: '#d1fae5', borderLeft: '4px solid #059669' };
} else if (satisfaction === 'neutral') {
return { ...baseStyles, backgroundColor: '#fef3c7', borderLeft: '4px solid #d97706' };
}
return { ...baseStyles, backgroundColor: '#fee2e2', borderLeft: '4px solid #dc2626' };
}
});
});
// ============================================
// FORM CONFIGURATION
// ============================================
form.configureSubmitButton({
label: 'Submit Feedback'
});
form.configureCompletionScreen({
type: 'text',
title: 'Thank You for Your Feedback!',
message: 'Your feedback helps tradespeople improve their services and helps other customers make informed decisions. We appreciate you taking the time to share your experience.'
});
}