export function publicTransitFeedbackSurvey(form: FormTs) {
// Public Transit Experience Survey
// Demonstrates: StarRating, EmojiRating, MatrixQuestion, Slider, ThumbRating, conditional visibility, dynamic labels
// ============================================
// HEADER
// ============================================
form.addRow(row => {
row.addTextPanel('header', {
label: 'Share Your Transit Experience',
computedValue: () => 'Your feedback helps us improve public transportation for everyone.',
customStyles: {
backgroundColor: '#1e40af',
color: 'white',
padding: '28px',
borderRadius: '12px',
textAlign: 'center'
}
});
});
// ============================================
// SECTION 1: Trip Details
// ============================================
const tripSection = form.addSubform('trip', {
title: 'About Your Trip'
});
tripSection.addRow(row => {
row.addDropdown('transitType', {
label: 'Type of transit used',
options: [
{ id: 'bus', name: 'Bus' },
{ id: 'metro', name: 'Metro/Subway' },
{ id: 'tram', name: 'Tram/Light Rail' },
{ id: 'train', name: 'Commuter Train' },
{ id: 'ferry', name: 'Ferry' },
{ id: 'multiple', name: 'Multiple modes' }
],
isRequired: true
}, '1fr');
row.addTextbox('routeLine', {
label: () => {
const type = tripSection.dropdown('transitType')?.value();
if (type === 'bus') return 'Bus route number';
if (type === 'metro') return 'Metro line';
if (type === 'tram') return 'Tram line';
if (type === 'train') return 'Train line/service';
if (type === 'ferry') return 'Ferry route';
return 'Route/Line';
},
placeholder: 'e.g., Line 5, Route 42',
isVisible: () => tripSection.dropdown('transitType')?.value() !== null
}, '1fr');
});
tripSection.addRow(row => {
row.addDropdown('tripPurpose', {
label: 'Purpose of this trip',
options: [
{ id: 'commute', name: 'Commute to work/school' },
{ id: 'business', name: 'Business/appointments' },
{ id: 'shopping', name: 'Shopping/errands' },
{ id: 'leisure', name: 'Leisure/recreation' },
{ id: 'medical', name: 'Medical appointment' },
{ id: 'other', name: 'Other' }
]
}, '1fr');
row.addDropdown('tripFrequency', {
label: 'How often do you use public transit?',
options: [
{ id: 'daily', name: 'Daily' },
{ id: 'weekly', name: 'Several times a week' },
{ id: 'monthly', name: 'Few times a month' },
{ id: 'rarely', name: 'Rarely' },
{ id: 'first', name: 'First time' }
]
}, '1fr');
});
tripSection.addRow(row => {
row.addDatepicker('tripDate', {
label: 'Date of trip',
maxDate: () => new Date().toISOString()
}, '1fr');
row.addTimepicker('tripTime', {
label: 'Approximate departure time'
}, '1fr');
});
// ============================================
// SECTION 2: Reliability & Punctuality
// ============================================
const reliabilitySection = form.addSubform('reliability', {
title: 'Reliability & Punctuality',
isVisible: () => tripSection.dropdown('transitType')?.value() !== null
});
reliabilitySection.addRow(row => {
row.addEmojiRating('overallExperience', {
label: 'How was your overall experience on this trip?',
preset: 'satisfaction',
size: 'lg',
alignment: 'center'
});
});
reliabilitySection.addRow(row => {
row.addRadioButton('onTime', {
label: 'Was the service on time?',
options: [
{ id: 'early', name: 'Arrived early' },
{ id: 'ontime', name: 'On time (within 5 min)' },
{ id: 'slight', name: 'Slightly late (5-15 min)' },
{ id: 'late', name: 'Significantly late (15+ min)' },
{ id: 'cancelled', name: 'Service was cancelled' }
],
orientation: 'vertical',
isRequired: true
});
});
reliabilitySection.addRow(row => {
row.addSlider('waitTime', {
label: 'How long did you wait at the stop/station?',
min: 0,
max: 60,
step: 5,
defaultValue: 10,
showValue: true,
unit: ' min'
});
});
// Delay follow-up
reliabilitySection.addRow(row => {
row.addRadioButton('delayInfo', {
label: 'Were you informed about the delay?',
options: [
{ id: 'yes-accurate', name: 'Yes, with accurate timing' },
{ id: 'yes-inaccurate', name: 'Yes, but timing was wrong' },
{ id: 'no', name: 'No information provided' }
],
orientation: 'vertical',
isVisible: () => {
const onTime = reliabilitySection.radioButton('onTime')?.value();
return onTime === 'slight' || onTime === 'late' || onTime === 'cancelled';
}
});
});
// ============================================
// SECTION 3: Vehicle & Station Quality
// ============================================
const qualitySection = form.addSubform('quality', {
title: 'Vehicle & Station Quality',
isVisible: () => reliabilitySection.emojiRating('overallExperience')?.value() !== null
});
qualitySection.addRow(row => {
row.addMatrixQuestion('vehicleQuality', {
label: 'Rate the following aspects of the vehicle:',
rows: [
{ id: 'cleanliness', label: 'Cleanliness', isRequired: true },
{ id: 'comfort', label: 'Seating comfort', isRequired: true },
{ id: 'temperature', label: 'Temperature/AC/Heating', isRequired: false },
{ id: 'crowding', label: 'Crowding level', isRequired: true },
{ id: 'announcements', label: 'Announcements/signage', isRequired: false }
],
columns: [
{ id: 'excellent', label: 'Excellent' },
{ id: 'good', label: 'Good' },
{ id: 'fair', label: 'Fair' },
{ id: 'poor', label: 'Poor' }
],
striped: true,
fullWidth: true
});
});
qualitySection.addSpacer();
qualitySection.addRow(row => {
row.addMatrixQuestion('stationQuality', {
label: 'Rate the stop/station where you boarded:',
rows: [
{ id: 'clean', label: 'Cleanliness', isRequired: true },
{ id: 'shelter', label: 'Weather shelter', isRequired: false },
{ id: 'seating', label: 'Seating availability', isRequired: false },
{ id: 'lighting', label: 'Lighting', isRequired: false },
{ id: 'info', label: 'Schedule/info displays', isRequired: true }
],
columns: [
{ id: 'excellent', label: 'Excellent' },
{ id: 'good', label: 'Good' },
{ id: 'fair', label: 'Fair' },
{ id: 'poor', label: 'Poor' },
{ id: 'na', label: 'N/A' }
],
striped: true,
fullWidth: true
});
});
// ============================================
// SECTION 4: Safety & Staff
// ============================================
const safetySection = form.addSubform('safety', {
title: 'Safety & Staff',
isVisible: () => qualitySection.matrixQuestion('vehicleQuality')?.areAllRequiredRowsAnswered() ?? false
});
safetySection.addRow(row => {
row.addStarRating('safetyRating', {
label: 'How safe did you feel during your trip?',
maxStars: 5,
size: 'lg',
alignment: 'center',
showCounter: true
}, '1fr');
row.addStarRating('driverRating', {
label: () => {
const type = tripSection.dropdown('transitType')?.value();
if (type === 'metro' || type === 'train') return 'Rate the train operator/conductor';
if (type === 'ferry') return 'Rate the ferry crew';
return 'Rate the driver';
},
maxStars: 5,
size: 'lg',
alignment: 'center',
showCounter: true
}, '1fr');
});
safetySection.addRow(row => {
row.addCheckboxList('safetyIssues', {
label: 'Did you experience any safety concerns? (Select all that apply)',
options: [
{ id: 'none', name: 'No concerns' },
{ id: 'driving', name: 'Aggressive/unsafe driving' },
{ id: 'harassment', name: 'Harassment from other passengers' },
{ id: 'theft', name: 'Theft or attempted theft' },
{ id: 'dark', name: 'Poorly lit areas' },
{ id: 'emergency', name: 'Emergency equipment issues' },
{ id: 'other', name: 'Other safety issue' }
],
orientation: 'vertical'
});
});
safetySection.addRow(row => {
row.addTextarea('safetyDetails', {
label: 'Please describe the safety issue:',
placeholder: 'Provide details to help us address the issue...',
rows: 3,
isVisible: () => {
const issues = safetySection.checkboxList('safetyIssues')?.value() || [];
return issues.length > 0 && !issues.includes('none');
}
});
});
// ============================================
// SECTION 5: Accessibility
// ============================================
const accessSection = form.addSubform('accessibility', {
title: 'Accessibility',
isVisible: () => safetySection.starRating('safetyRating')?.value() !== null
});
accessSection.addRow(row => {
row.addRadioButton('needsAccessibility', {
label: 'Do you use accessibility features?',
options: [
{ id: 'yes', name: 'Yes' },
{ id: 'no', name: 'No' },
{ id: 'sometimes', name: 'Sometimes / for someone I travel with' }
],
orientation: 'horizontal'
});
});
accessSection.addRow(row => {
row.addStarRating('accessibilityRating', {
label: 'How would you rate the accessibility of the service?',
maxStars: 5,
size: 'md',
alignment: 'center',
isVisible: () => {
const needs = accessSection.radioButton('needsAccessibility')?.value();
return needs === 'yes' || needs === 'sometimes';
}
});
});
accessSection.addRow(row => {
row.addCheckboxList('accessIssues', {
label: 'What accessibility issues did you encounter?',
options: [
{ id: 'none', name: 'No issues' },
{ id: 'ramp', name: 'Ramp/lift not working' },
{ id: 'space', name: 'No wheelchair space available' },
{ id: 'audio', name: 'Audio announcements unclear' },
{ id: 'visual', name: 'Visual displays hard to read' },
{ id: 'gap', name: 'Gap between platform and vehicle' },
{ id: 'stairs', name: 'Stairs without alternatives' }
],
orientation: 'vertical',
isVisible: () => {
const needs = accessSection.radioButton('needsAccessibility')?.value();
return needs === 'yes' || needs === 'sometimes';
}
});
});
// ============================================
// SECTION 6: Value & Recommendations
// ============================================
const valueSection = form.addSubform('value', {
title: 'Value & Recommendations',
isVisible: () => accessSection.radioButton('needsAccessibility')?.value() !== null
});
valueSection.addRow(row => {
row.addRatingScale('fareValue', {
preset: 'likert-5',
label: 'The fare represents good value for money',
lowLabel: 'Strongly disagree',
highLabel: 'Strongly agree',
alignment: 'center'
});
});
valueSection.addRow(row => {
row.addThumbRating('wouldRecommend', {
label: 'Would you recommend public transit to others?',
showLabels: true,
upLabel: 'Yes, I would',
downLabel: 'No, I would not',
size: 'lg',
alignment: 'center'
});
});
valueSection.addRow(row => {
row.addSuggestionChips('improvements', {
label: 'What would most improve your transit experience? (Select up to 3)',
suggestions: [
{ id: 'frequency', name: 'More frequent service' },
{ id: 'reliability', name: 'Better reliability' },
{ id: 'cleaner', name: 'Cleaner vehicles' },
{ id: 'safety', name: 'Improved safety' },
{ id: 'info', name: 'Real-time information' },
{ id: 'coverage', name: 'More routes/coverage' },
{ id: 'fare', name: 'Lower fares' },
{ id: 'comfort', name: 'More comfortable seating' }
],
max: 3,
alignment: 'center'
});
});
// ============================================
// SECTION 7: Additional Comments
// ============================================
const commentsSection = form.addSubform('comments', {
title: 'Additional Feedback',
isVisible: () => valueSection.thumbRating('wouldRecommend')?.value() !== null
});
commentsSection.addSpacer();
commentsSection.addRow(row => {
row.addTextarea('additionalComments', {
label: 'Any other comments or suggestions?',
placeholder: 'Share your thoughts on how we can improve...',
rows: 4,
autoExpand: true
});
});
commentsSection.addRow(row => {
row.addCheckbox('contactMe', {
label: 'I would like to be contacted about my feedback'
});
});
commentsSection.addRow(row => {
row.addEmail('contactEmail', {
label: 'Email address',
placeholder: 'your@email.com',
isVisible: () => commentsSection.checkbox('contactMe')?.value() === true,
isRequired: () => commentsSection.checkbox('contactMe')?.value() === true
});
});
// ============================================
// SECTION 8: Summary
// ============================================
const summarySection = form.addSubform('summary', {
title: 'Feedback Summary',
isVisible: () => commentsSection.checkbox('contactMe')?.value() !== null
});
summarySection.addRow(row => {
row.addTextPanel('summaryContent', {
computedValue: () => {
const transitType = tripSection.dropdown('transitType')?.value();
const overallExp = reliabilitySection.emojiRating('overallExperience')?.value();
const onTime = reliabilitySection.radioButton('onTime')?.value();
const safetyRating = safetySection.starRating('safetyRating')?.value();
const driverRating = safetySection.starRating('driverRating')?.value();
const fareValue = valueSection.ratingScale('fareValue')?.value();
const recommend = valueSection.thumbRating('wouldRecommend')?.value();
if (!transitType) return '';
const typeLabels: Record<string, string> = {
'bus': 'Bus',
'metro': 'Metro/Subway',
'tram': 'Tram/Light Rail',
'train': 'Commuter Train',
'ferry': 'Ferry',
'multiple': 'Multiple Modes'
};
const expLabels: Record<string, string> = {
'very-bad': 'Very Unhappy',
'bad': 'Unhappy',
'neutral': 'Neutral',
'good': 'Happy',
'excellent': 'Very Happy'
};
const onTimeLabels: Record<string, string> = {
'early': 'Early',
'ontime': 'On time',
'slight': 'Slightly late',
'late': 'Significantly late',
'cancelled': 'Cancelled'
};
let summary = `🚌 Transit Feedback Summary\n`;
summary += `${'═'.repeat(28)}\n\n`;
summary += `🎫 Mode: ${typeLabels[transitType] || transitType}\n`;
if (overallExp) {
summary += `😊 Experience: ${expLabels[overallExp] || overallExp}\n`;
}
if (onTime) {
const icon = onTime === 'ontime' || onTime === 'early' ? '✅' : '⏰';
summary += `${icon} Punctuality: ${onTimeLabels[onTime]}\n`;
}
if (safetyRating) {
summary += `🛡️ Safety: ${safetyRating}/5 stars\n`;
}
if (driverRating) {
summary += `👤 Staff: ${driverRating}/5 stars\n`;
}
if (fareValue) {
const valueLabels: Record<number, string> = {
1: 'Poor value',
2: 'Below average',
3: 'Fair value',
4: 'Good value',
5: 'Excellent value'
};
summary += `💰 Value: ${valueLabels[fareValue]}\n`;
}
if (recommend) {
summary += `\n${recommend === 'up' ? '👍 Would recommend' : '👎 Would not recommend'}`;
}
return summary;
},
customStyles: () => {
const exp = reliabilitySection.emojiRating('overallExperience')?.value();
const baseStyles = {
padding: '16px',
borderRadius: '8px',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
fontSize: '14px'
};
if (exp === 'excellent' || exp === 'good') {
return { ...baseStyles, backgroundColor: '#d1fae5', borderLeft: '4px solid #10b981' };
} else if (exp === 'neutral') {
return { ...baseStyles, backgroundColor: '#fef3c7', borderLeft: '4px solid #f59e0b' };
} else if (exp === 'bad' || exp === 'very-bad') {
return { ...baseStyles, backgroundColor: '#fee2e2', borderLeft: '4px solid #ef4444' };
}
return { ...baseStyles, backgroundColor: '#f1f5f9' };
}
});
});
// ============================================
// FORM CONFIGURATION
// ============================================
form.configureSubmitButton({
label: 'Submit Feedback',
isVisible: () => tripSection.dropdown('transitType')?.value() !== null
});
form.configureCompletionScreen({
type: 'text',
title: 'Thank You for Your Feedback!',
message: 'Your input helps us improve public transportation for all riders. Together, we can make transit better for everyone.'
});
}