export function hotelCheckoutSurvey(form: FormTs) {
// Hotel Check-out Experience Survey
// Demonstrates: RatingScale, StarRating, Slider, ThumbRating, RadioButton, MatrixQuestion, Timepicker
// ============================================
// HEADER
// ============================================
form.addRow(row => {
row.addTextPanel('header', {
label: 'Thank You for Staying With Us!',
computedValue: () => 'Please take a moment to share your checkout experience.',
customStyles: {
backgroundColor: '#1e40af',
color: 'white',
padding: '24px',
borderRadius: '12px',
textAlign: 'center'
}
});
});
// ============================================
// SECTION 1: Checkout Method
// ============================================
const methodSection = form.addSubform('method', {
title: 'Your Checkout'
});
methodSection.addRow(row => {
row.addRadioButton('checkoutMethod', {
label: 'How did you check out today?',
options: [
{ id: 'frontDesk', name: 'Front desk (in person)' },
{ id: 'express', name: 'Express checkout (envelope/drop box)' },
{ id: 'mobile', name: 'Mobile app checkout' },
{ id: 'tvCheckout', name: 'In-room TV checkout' },
{ id: 'other', name: 'Other' }
],
orientation: 'vertical',
isRequired: true
});
});
methodSection.addRow(row => {
row.addTimepicker('checkoutTime', {
label: 'Approximate checkout time',
isVisible: () => methodSection.radioButton('checkoutMethod')?.value() === 'frontDesk'
});
});
// ============================================
// SECTION 2: Wait Time (Front Desk Only)
// ============================================
const waitSection = form.addSubform('wait', {
title: 'Wait Time Experience',
isVisible: () => methodSection.radioButton('checkoutMethod')?.value() === 'frontDesk'
});
waitSection.addRow(row => {
row.addSlider('waitMinutes', {
label: 'How long did you wait in line? (minutes)',
min: 0,
max: 30,
step: 1,
unit: 'min',
showValue: true,
defaultValue: 0
});
});
waitSection.addRow(row => {
row.addStarRating('waitSatisfaction', {
label: 'How satisfied were you with the wait time?',
maxStars: 5,
size: 'md',
alignment: 'center',
isVisible: () => {
const wait = waitSection.slider('waitMinutes')?.value();
return wait !== null && wait !== undefined;
}
});
});
// Dynamic wait time message
waitSection.addRow(row => {
row.addTextPanel('waitMessage', {
isVisible: () => {
const wait = waitSection.slider('waitMinutes')?.value();
return wait !== null && wait !== undefined && wait > 0;
},
computedValue: () => {
const wait = waitSection.slider('waitMinutes')?.value();
if (!wait || wait === 0) return '';
if (wait <= 3) return '✅ Great! That\'s a quick checkout.';
if (wait <= 5) return '👍 Acceptable wait time.';
if (wait <= 10) return '⚠️ We aim to reduce wait times. Thank you for your patience.';
return '😔 We apologize for the long wait. This is not our standard.';
},
customStyles: () => {
const wait = waitSection.slider('waitMinutes')?.value();
const baseStyles = { padding: '10px', borderRadius: '6px', textAlign: 'center', fontSize: '14px' };
if (!wait || wait <= 3) return { ...baseStyles, backgroundColor: '#ecfdf5', color: '#047857' };
if (wait <= 5) return { ...baseStyles, backgroundColor: '#f0fdf4', color: '#15803d' };
if (wait <= 10) return { ...baseStyles, backgroundColor: '#fefce8', color: '#a16207' };
return { ...baseStyles, backgroundColor: '#fef2f2', color: '#b91c1c' };
}
});
});
// ============================================
// SECTION 3: Billing Experience
// ============================================
const billingSection = form.addSubform('billing', {
title: 'Billing & Folio'
});
billingSection.addRow(row => {
row.addThumbRating('billingAccuracy', {
label: 'Was your bill accurate with no unexpected charges?',
showLabels: true,
upLabel: 'Yes, accurate',
downLabel: 'No, issues found',
size: 'lg',
alignment: 'center'
});
});
// Billing issues section
billingSection.addRow(row => {
row.addCheckboxList('billingIssues', {
label: 'What billing issues did you experience?',
options: [
{ id: 'minibar', name: 'Incorrect mini-bar charges' },
{ id: 'room', name: 'Wrong room rate' },
{ id: 'dining', name: 'Restaurant charges error' },
{ id: 'parking', name: 'Parking charges issue' },
{ id: 'spa', name: 'Spa/amenity charges error' },
{ id: 'taxes', name: 'Unexpected taxes/fees' },
{ id: 'other', name: 'Other discrepancy' }
],
orientation: 'vertical',
isVisible: () => billingSection.thumbRating('billingAccuracy')?.value() === 'down'
});
});
billingSection.addRow(row => {
row.addRadioButton('billingResolution', {
label: 'Was the billing issue resolved to your satisfaction?',
options: [
{ id: 'yes', name: 'Yes, fully resolved' },
{ id: 'partial', name: 'Partially resolved' },
{ id: 'no', name: 'No, not resolved' },
{ id: 'pending', name: 'Still pending' }
],
orientation: 'vertical',
isVisible: () => billingSection.thumbRating('billingAccuracy')?.value() === 'down'
});
});
// ============================================
// SECTION 4: Staff Service (Front Desk)
// ============================================
const staffSection = form.addSubform('staff', {
title: 'Front Desk Staff',
isVisible: () => methodSection.radioButton('checkoutMethod')?.value() === 'frontDesk'
});
staffSection.addRow(row => {
row.addMatrixQuestion('staffRating', {
label: 'Please rate the checkout staff:',
rows: [
{ id: 'friendliness', label: 'Friendliness', isRequired: true },
{ id: 'efficiency', label: 'Efficiency', isRequired: true },
{ id: 'helpfulness', label: 'Helpfulness' },
{ id: 'professionalism', label: 'Professionalism' }
],
columns: [
{ id: 'poor', label: 'Poor' },
{ id: 'fair', label: 'Fair' },
{ id: 'good', label: 'Good' },
{ id: 'excellent', label: 'Excellent' }
],
striped: true,
fullWidth: true
});
});
staffSection.addRow(row => {
row.addTextbox('staffName', {
label: 'If you remember the staff member\'s name, please share (optional)',
placeholder: 'Staff name for recognition...'
});
});
// ============================================
// SECTION 5: Express/Mobile Checkout Experience
// ============================================
const expressSection = form.addSubform('express', {
title: 'Express Checkout Experience',
isVisible: () => {
const method = methodSection.radioButton('checkoutMethod')?.value();
return method === 'express' || method === 'mobile' || method === 'tvCheckout';
}
});
expressSection.addRow(row => {
row.addRatingScale('expressEase', {
label: 'How easy was the express checkout process?',
preset: 'ces',
alignment: 'center',
lowLabel: 'Very difficult',
highLabel: 'Very easy'
});
});
expressSection.addRow(row => {
row.addSuggestionChips('expressIssues', {
label: 'Any issues with express checkout?',
suggestions: [
{ id: 'none', name: 'No issues' },
{ id: 'instructions', name: 'Unclear instructions' },
{ id: 'technical', name: 'Technical problems' },
{ id: 'folio', name: 'Couldn\'t review folio' },
{ id: 'receipt', name: 'Receipt not received' },
{ id: 'keyReturn', name: 'Key return unclear' }
],
max: 3,
alignment: 'left'
});
});
// ============================================
// SECTION 6: Overall Checkout
// ============================================
const overallSection = form.addSubform('overall', {
title: 'Overall Checkout Experience'
});
overallSection.addRow(row => {
row.addEmojiRating('overallCheckout', {
label: 'How would you rate your overall checkout experience?',
preset: 'satisfaction',
size: 'lg',
showLabels: true,
alignment: 'center'
});
});
overallSection.addSpacer();
overallSection.addRow(row => {
row.addRatingScale('returnIntent', {
label: 'How likely are you to stay with us again?',
preset: 'nps',
showSegmentColors: true,
showCategoryLabel: true,
alignment: 'center'
});
});
// ============================================
// SECTION 7: Feedback
// ============================================
const feedbackSection = form.addSubform('feedback', {
title: 'Additional Comments',
isVisible: () => overallSection.emojiRating('overallCheckout')?.value() !== null
});
feedbackSection.addRow(row => {
row.addTextarea('comments', {
label: () => {
const checkout = overallSection.emojiRating('overallCheckout')?.value();
if (checkout === 'excellent' || checkout === 'good') return 'Any comments to share about your departure?';
if (checkout === 'neutral') return 'How could we have made checkout better?';
return 'We\'re sorry about your experience. What went wrong?';
},
placeholder: 'Your feedback helps us improve...',
rows: 3,
autoExpand: true
});
});
// ============================================
// SECTION 8: Summary
// ============================================
const summarySection = form.addSubform('summary', {
title: 'Checkout Feedback Summary',
isVisible: () => overallSection.emojiRating('overallCheckout')?.value() !== null
});
summarySection.addRow(row => {
row.addTextPanel('summaryContent', {
computedValue: () => {
const method = methodSection.radioButton('checkoutMethod')?.value();
const wait = waitSection.slider('waitMinutes')?.value();
const billing = billingSection.thumbRating('billingAccuracy')?.value();
const checkout = overallSection.emojiRating('overallCheckout')?.value();
const nps = overallSection.ratingScale('returnIntent')?.npsCategory();
if (!checkout) return '';
const methodLabels: Record<string, string> = {
'frontDesk': 'Front desk',
'express': 'Express checkout',
'mobile': 'Mobile app',
'tvCheckout': 'In-room TV',
'other': 'Other'
};
const checkoutLabels: Record<string, string> = {
'very-bad': '😢 Poor',
'bad': '😕 Below expectations',
'neutral': '😐 Average',
'good': '🙂 Good',
'excellent': '😃 Excellent'
};
let emoji = checkout === 'excellent' || checkout === 'good' ? '✅' : checkout === 'neutral' ? '⚠️' : '❌';
let summary = `${emoji} Checkout Summary\n`;
summary += `${'═'.repeat(25)}\n\n`;
summary += `Method: ${methodLabels[method || ''] || method}\n`;
if (method === 'frontDesk' && wait !== null && wait !== undefined) {
summary += `Wait: ${wait} minutes\n`;
}
if (billing) {
summary += `Billing: ${billing === 'up' ? '✓ Accurate' : '✗ Issues found'}\n`;
}
summary += `\n📊 Overall: ${checkoutLabels[checkout] || checkout}`;
if (nps) {
const npsLabels: Record<string, string> = {
'promoter': ' | 🎉 Likely to return',
'passive': ' | 🤔 May return',
'detractor': ' | 😟 Unlikely to return'
};
summary += npsLabels[nps] || '';
}
return summary;
},
customStyles: () => {
const checkout = overallSection.emojiRating('overallCheckout')?.value();
const baseStyles = {
padding: '16px',
borderRadius: '8px',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
fontSize: '14px'
};
if (checkout === 'excellent' || checkout === 'good') {
return { ...baseStyles, backgroundColor: '#dbeafe', borderLeft: '4px solid #2563eb' };
} else if (checkout === 'neutral') {
return { ...baseStyles, backgroundColor: '#fef3c7', borderLeft: '4px solid #f59e0b' };
} else {
return { ...baseStyles, backgroundColor: '#fee2e2', borderLeft: '4px solid #ef4444' };
}
}
});
});
// ============================================
// FORM CONFIGURATION
// ============================================
form.configureSubmitButton({
label: 'Submit Feedback',
isVisible: () => methodSection.radioButton('checkoutMethod')?.value() !== null
});
form.configureCompletionScreen({
type: 'text',
title: 'Thank You!',
message: 'We appreciate you taking the time to share your checkout experience. Your feedback helps us ensure every guest leaves with a positive final impression. Safe travels!'
});
}