export function citizenSatisfactionSurvey(form: FormTs) {
// Citizen Service Satisfaction Survey - Government/Public Sector Feedback
// Demonstrates: Multi-page wizard, CES, StarRating, ThumbRating, MatrixQuestion, Slider, conditional visibility
// ============================================
// HEADER
// ============================================
form.addRow(row => {
row.addTextPanel('header', {
label: 'Help Us Serve You Better',
computedValue: () => 'Your feedback helps improve public services for everyone in our community.',
customStyles: {
background: 'linear-gradient(135deg, #1e40af 0%, #3b82f6 100%)',
color: 'white',
padding: '28px',
borderRadius: '12px',
textAlign: 'center'
}
});
});
// ============================================
// MULTI-PAGE WIZARD
// ============================================
const pages = form.addPages('surveyPages', { heightMode: 'current-page' });
// ----------------------------------------
// PAGE 1: Service Information
// ----------------------------------------
const page1 = pages.addPage('serviceInfo');
page1.addRow(row => {
row.addTextPanel('page1Title', {
label: 'Step 1 of 3: Service Information',
customStyles: {
fontSize: '18px',
fontWeight: 'bold',
color: '#1e40af',
marginBottom: '16px'
}
});
});
page1.addRow(row => {
row.addDropdown('serviceType', {
label: 'What type of service did you receive today?',
placeholder: 'Select a service...',
options: [
{ id: 'license-registration', name: 'License & Registration' },
{ id: 'permits', name: 'Permits & Inspections' },
{ id: 'taxes', name: 'Tax Services' },
{ id: 'utilities', name: 'Utility Services' },
{ id: 'social-services', name: 'Social Services' },
{ id: 'records', name: 'Records & Documents' },
{ id: 'planning', name: 'Planning & Zoning' },
{ id: 'other', name: 'Other Service' }
],
isRequired: true
});
});
page1.addRow(row => {
row.addTextbox('otherServiceType', {
label: 'Please specify the service',
placeholder: 'Describe the service you received...',
isVisible: () => page1.dropdown('serviceType')?.value() === 'other',
isRequired: () => page1.dropdown('serviceType')?.value() === 'other'
});
});
page1.addRow(row => {
row.addRadioButton('visitChannel', {
label: 'How did you access this service?',
options: [
{ id: 'in-person', name: 'In-person visit' },
{ id: 'online', name: 'Online/Website' },
{ id: 'phone', name: 'Phone call' },
{ id: 'email', name: 'Email' },
{ id: 'mail', name: 'Mail' }
],
orientation: 'vertical',
isRequired: true
});
});
page1.addSpacer({ height: '24px' });
page1.addRow(row => {
row.addButton('nextPage1', {
label: 'Continue to Service Experience →',
onClick: () => pages.goToPage('experience')
});
});
// ----------------------------------------
// PAGE 2: Service Experience
// ----------------------------------------
const page2 = pages.addPage('experience');
page2.addRow(row => {
row.addTextPanel('page2Title', {
label: 'Step 2 of 3: Your Experience',
customStyles: {
fontSize: '18px',
fontWeight: 'bold',
color: '#1e40af',
marginBottom: '16px'
}
});
});
// Customer Effort Score
const cesSection = page2.addSubform('cesSection', {
customStyles: () => {
const cesValue = cesSection.ratingScale('effortScore')?.value();
if (cesValue !== null && cesValue !== undefined) {
if (cesValue >= 6) return { backgroundColor: '#dcfce7', padding: '16px', borderRadius: '8px' };
if (cesValue >= 4) return { backgroundColor: '#fef9c3', padding: '16px', borderRadius: '8px' };
return { backgroundColor: '#fee2e2', padding: '16px', borderRadius: '8px' };
}
return { backgroundColor: '#f8fafc', padding: '16px', borderRadius: '8px' };
}
});
cesSection.addRow(row => {
row.addRatingScale('effortScore', {
preset: 'ces',
label: 'How easy was it to get the service you needed today?',
lowLabel: 'Very Difficult',
highLabel: 'Very Easy',
size: 'lg',
alignment: 'center',
isRequired: true
});
});
page2.addSpacer({ height: '16px' });
// Wait Time
page2.addRow(row => {
row.addSlider('waitTime', {
label: () => {
const channel = page1.radioButton('visitChannel')?.value();
if (channel === 'in-person') return 'How long did you wait to be served? (minutes)';
if (channel === 'phone') return 'How long were you on hold? (minutes)';
return 'How long did the process take? (minutes)';
},
min: 0,
max: 120,
step: 5,
showValue: true,
unit: 'min',
defaultValue: 15,
isVisible: () => {
const channel = page1.radioButton('visitChannel')?.value();
return channel === 'in-person' || channel === 'phone';
}
});
});
page2.addRow(row => {
row.addThumbRating('waitAcceptable', {
label: () => {
const wait = page2.slider('waitTime')?.value() || 0;
return `Was the ${wait}-minute wait acceptable?`;
},
showLabels: true,
upLabel: 'Yes, acceptable',
downLabel: 'No, too long',
size: 'lg',
alignment: 'center',
isVisible: () => {
const channel = page1.radioButton('visitChannel')?.value();
return channel === 'in-person' || channel === 'phone';
}
});
});
page2.addSpacer({ height: '16px' });
// Service Quality Matrix
page2.addRow(row => {
row.addMatrixQuestion('serviceQuality', {
label: 'Please rate the following aspects of your experience:',
rows: [
{ id: 'professionalism', label: 'Staff professionalism', isRequired: true },
{ id: 'knowledge', label: 'Staff knowledge' },
{ id: 'clarity', label: 'Clarity of information provided' },
{ id: 'efficiency', label: 'Process efficiency' },
{ id: 'accessibility', label: 'Accessibility of service' }
],
columns: [
{ id: 'poor', label: 'Poor' },
{ id: 'fair', label: 'Fair' },
{ id: 'good', label: 'Good' },
{ id: 'very-good', label: 'Very Good' },
{ id: 'excellent', label: 'Excellent' }
],
striped: true,
fullWidth: true
});
});
page2.addSpacer({ height: '24px' });
page2.addRow(row => {
row.addButton('backPage2', {
label: '← Back',
onClick: () => pages.goToPage('serviceInfo')
}, '120px');
row.addEmpty('1fr');
row.addButton('nextPage2', {
label: 'Continue to Feedback →',
onClick: () => pages.goToPage('feedback')
}, '200px');
});
// ----------------------------------------
// PAGE 3: Additional Feedback
// ----------------------------------------
const page3 = pages.addPage('feedback');
page3.addRow(row => {
row.addTextPanel('page3Title', {
label: 'Step 3 of 3: Your Feedback',
customStyles: {
fontSize: '18px',
fontWeight: 'bold',
color: '#1e40af',
marginBottom: '16px'
}
});
});
// Outcome satisfaction
page3.addRow(row => {
row.addStarRating('overallSatisfaction', {
label: 'Overall, how satisfied are you with the service you received?',
maxStars: 5,
size: 'xl',
alignment: 'center',
showConfettiOnMax: true
});
});
page3.addRow(row => {
row.addThumbRating('issueResolved', {
label: 'Was your issue or request resolved?',
showLabels: true,
upLabel: 'Yes, resolved',
downLabel: 'No, unresolved',
size: 'lg',
alignment: 'center'
});
});
page3.addSpacer({ height: '16px' });
// Unresolved issue follow-up
const unresolvedSection = page3.addSubform('unresolvedSection', {
title: 'Help Us Resolve Your Issue',
isVisible: () => page3.thumbRating('issueResolved')?.value() === 'down',
customStyles: { backgroundColor: '#fef3c7', padding: '16px', borderRadius: '8px' }
});
unresolvedSection.addRow(row => {
row.addTextarea('unresolvedDetails', {
label: 'Please describe what was left unresolved',
placeholder: 'Help us understand what we can do to resolve your issue...',
rows: 3,
autoExpand: true,
isRequired: true
});
});
unresolvedSection.addRow(row => {
row.addCheckbox('requestCallback', {
label: 'I would like someone to contact me about this issue'
});
});
unresolvedSection.addRow(row => {
row.addTextbox('callbackPhone', {
label: 'Phone number',
placeholder: '(555) 123-4567',
isVisible: () => unresolvedSection.checkbox('requestCallback')?.value() === true,
isRequired: () => unresolvedSection.checkbox('requestCallback')?.value() === true
}, '1fr');
row.addEmail('callbackEmail', {
label: 'Email address',
placeholder: 'your@email.com',
isVisible: () => unresolvedSection.checkbox('requestCallback')?.value() === true
}, '1fr');
});
// Positive highlights
const positiveSection = page3.addSubform('positiveSection', {
title: 'What Did We Do Well?',
isVisible: () => {
const ces = cesSection.ratingScale('effortScore')?.value();
const sat = page3.starRating('overallSatisfaction')?.value();
return (ces !== null && ces !== undefined && ces >= 5) || (sat !== null && sat !== undefined && sat >= 4);
},
customStyles: { backgroundColor: '#dcfce7', padding: '16px', borderRadius: '8px' }
});
positiveSection.addRow(row => {
row.addSuggestionChips('positives', {
label: 'Select what went well:',
suggestions: [
{ id: 'friendly-staff', name: 'Friendly staff' },
{ id: 'quick-service', name: 'Quick service' },
{ id: 'clear-info', name: 'Clear information' },
{ id: 'easy-process', name: 'Easy process' },
{ id: 'clean-facility', name: 'Clean facility' },
{ id: 'helpful-website', name: 'Helpful website' },
{ id: 'good-hours', name: 'Convenient hours' },
{ id: 'parking', name: 'Easy parking' }
],
alignment: 'center'
});
});
// Improvement suggestions
page3.addSpacer({ height: '16px' });
page3.addRow(row => {
row.addTextarea('suggestions', {
label: 'Do you have any suggestions for improving our services?',
placeholder: 'Your ideas help us serve the community better...',
rows: 3,
autoExpand: true
});
});
page3.addSpacer({ height: '24px' });
page3.addRow(row => {
row.addButton('backPage3', {
label: '← Back',
onClick: () => pages.goToPage('experience')
}, '120px');
});
// ============================================
// SUMMARY SECTION
// ============================================
const summarySection = form.addSubform('summary', {
title: 'Feedback Summary',
isVisible: () => page3.starRating('overallSatisfaction')?.value() !== null
});
summarySection.addRow(row => {
row.addTextPanel('summaryContent', {
computedValue: () => {
const serviceType = page1.dropdown('serviceType')?.value();
const channel = page1.radioButton('visitChannel')?.value();
const ces = cesSection.ratingScale('effortScore')?.value();
const satisfaction = page3.starRating('overallSatisfaction')?.value();
const resolved = page3.thumbRating('issueResolved')?.value();
if (!satisfaction) return '';
const serviceLabels: Record<string, string> = {
'license-registration': 'License & Registration',
'permits': 'Permits & Inspections',
'taxes': 'Tax Services',
'utilities': 'Utility Services',
'social-services': 'Social Services',
'records': 'Records & Documents',
'planning': 'Planning & Zoning',
'other': 'Other Service'
};
const channelLabels: Record<string, string> = {
'in-person': 'In-person',
'online': 'Online',
'phone': 'Phone',
'email': 'Email',
'mail': 'Mail'
};
let summary = '📋 Citizen Feedback Summary\n';
summary += `${'═'.repeat(28)}\n\n`;
summary += `🏛️ Service: ${serviceLabels[serviceType || ''] || 'Not specified'}\n`;
summary += `📍 Channel: ${channelLabels[channel || ''] || 'Not specified'}\n`;
if (ces !== null && ces !== undefined) {
const cesEmoji = ces >= 6 ? '✅' : ces >= 4 ? '⚠️' : '❌';
summary += `\n${cesEmoji} Effort Score: ${ces}/7`;
if (ces >= 6) summary += ' (Easy)';
else if (ces >= 4) summary += ' (Moderate)';
else summary += ' (Difficult)';
}
summary += `\n⭐ Overall Satisfaction: ${satisfaction}/5 stars`;
if (resolved) {
summary += `\n${resolved === 'up' ? '✅ Issue Resolved' : '⚠️ Issue Unresolved'}`;
}
return summary;
},
customStyles: () => {
const satisfaction = page3.starRating('overallSatisfaction')?.value();
const baseStyles = {
padding: '16px',
borderRadius: '8px',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
fontSize: '14px'
};
if (satisfaction && satisfaction >= 4) {
return { ...baseStyles, backgroundColor: '#dbeafe', borderLeft: '4px solid #3b82f6' };
} else if (satisfaction && satisfaction >= 3) {
return { ...baseStyles, backgroundColor: '#fef9c3', borderLeft: '4px solid #eab308' };
} else if (satisfaction) {
return { ...baseStyles, backgroundColor: '#fee2e2', borderLeft: '4px solid #ef4444' };
}
return baseStyles;
}
});
});
// ============================================
// FORM CONFIGURATION
// ============================================
form.configureSubmitButton({
label: 'Submit Feedback',
isVisible: () => page3.starRating('overallSatisfaction')?.value() !== null
});
form.configureCompletionScreen({
type: 'text',
title: 'Thank You for Your Feedback!',
message: 'Your input helps us improve public services for all citizens. We review all feedback and use it to make our services more accessible and efficient.'
});
}