export function timeOffRequestForm(form: FormTs) {
// Time Off Request Form - Employee PTO/Leave Request
// Demonstrates: Datepicker, Dropdown, RadioButton, Computed Values, Multi-page Wizard, Dynamic Styling
// ============================================
// HEADER
// ============================================
form.addRow(row => {
row.addTextPanel('header', {
label: 'Time Off Request',
computedValue: () => 'Submit your leave request for manager approval',
customStyles: {
backgroundColor: '#4f46e5',
color: 'white',
padding: '24px',
borderRadius: '12px',
textAlign: 'center'
}
});
});
// ============================================
// SECTION 1: Employee Information
// ============================================
const employeeSection = form.addSubform('employeeSection', {
title: 'Employee Information'
});
employeeSection.addRow(row => {
row.addTextbox('employeeName', {
label: 'Your Name',
placeholder: 'Full name',
isRequired: true
}, '1fr');
row.addTextbox('employeeId', {
label: 'Employee ID',
placeholder: 'e.g., EMP-1234'
}, '200px');
});
employeeSection.addRow(row => {
row.addDropdown('department', {
label: 'Department',
placeholder: 'Select your department',
options: [
{ id: 'engineering', name: 'Engineering' },
{ id: 'product', name: 'Product' },
{ id: 'design', name: 'Design' },
{ id: 'marketing', name: 'Marketing' },
{ id: 'sales', name: 'Sales' },
{ id: 'hr', name: 'Human Resources' },
{ id: 'finance', name: 'Finance' },
{ id: 'operations', name: 'Operations' },
{ id: 'customer-success', name: 'Customer Success' },
{ id: 'legal', name: 'Legal' },
{ id: 'other', name: 'Other' }
],
isRequired: true
}, '1fr');
row.addTextbox('managerName', {
label: 'Manager Name',
placeholder: 'Your direct manager'
}, '1fr');
});
// ============================================
// SECTION 2: Leave Details
// ============================================
const leaveSection = form.addSubform('leaveSection', {
title: 'Leave Details',
customStyles: { backgroundColor: '#f8fafc', padding: '16px', borderRadius: '8px' }
});
leaveSection.addRow(row => {
row.addRadioButton('leaveType', {
label: 'Type of Leave',
options: [
{ id: 'vacation', name: 'Vacation / PTO' },
{ id: 'sick', name: 'Sick Leave' },
{ id: 'personal', name: 'Personal Day' },
{ id: 'bereavement', name: 'Bereavement' },
{ id: 'jury-duty', name: 'Jury Duty' },
{ id: 'parental', name: 'Parental Leave' },
{ id: 'medical', name: 'Medical Leave' },
{ id: 'unpaid', name: 'Unpaid Leave' },
{ id: 'other', name: 'Other' }
],
orientation: 'vertical',
isRequired: true
});
});
// Show text input for other leave type
leaveSection.addRow(row => {
row.addTextbox('otherLeaveType', {
label: 'Please specify leave type',
placeholder: 'Describe the type of leave',
isVisible: () => leaveSection.radioButton('leaveType')?.value() === 'other',
isRequired: () => leaveSection.radioButton('leaveType')?.value() === 'other'
});
});
// ============================================
// SECTION 3: Date Range
// ============================================
const dateSection = form.addSubform('dateSection', {
title: 'Time Period',
isVisible: () => !!leaveSection.radioButton('leaveType')?.value()
});
dateSection.addRow(row => {
row.addDatepicker('startDate', {
label: 'Start Date',
isRequired: true
}, '1fr');
row.addDatepicker('endDate', {
label: 'End Date',
isRequired: true
}, '1fr');
});
dateSection.addRow(row => {
row.addRadioButton('dayType', {
label: 'Day Type',
options: [
{ id: 'full', name: 'Full Day(s)' },
{ id: 'half-am', name: 'Half Day (Morning)' },
{ id: 'half-pm', name: 'Half Day (Afternoon)' }
],
orientation: 'horizontal'
});
});
// Day count display
dateSection.addRow(row => {
row.addTextPanel('dayCount', {
computedValue: () => {
const start = dateSection.datepicker('startDate')?.value();
const end = dateSection.datepicker('endDate')?.value();
const dayType = dateSection.radioButton('dayType')?.value();
if (!start || !end) return '';
const startDate = new Date(start);
const endDate = new Date(end);
if (endDate < startDate) {
return 'End date must be after start date';
}
// Calculate business days (simplified - doesn't account for holidays)
let days = 0;
const current = new Date(startDate);
while (current <= endDate) {
const dayOfWeek = current.getDay();
if (dayOfWeek !== 0 && dayOfWeek !== 6) {
days++;
}
current.setDate(current.getDate() + 1);
}
if (dayType === 'half-am' || dayType === 'half-pm') {
days = days * 0.5;
}
const dayWord = days === 1 ? 'day' : 'days';
return `Total: ${days} business ${dayWord} requested`;
},
customStyles: () => {
const start = dateSection.datepicker('startDate')?.value();
const end = dateSection.datepicker('endDate')?.value();
if (!start || !end) return { display: 'none' };
const startDate = new Date(start);
const endDate = new Date(end);
if (endDate < startDate) {
return {
backgroundColor: '#fee2e2',
color: '#dc2626',
padding: '12px',
borderRadius: '8px',
textAlign: 'center',
fontWeight: 'bold'
};
}
return {
backgroundColor: '#dbeafe',
color: '#1d4ed8',
padding: '12px',
borderRadius: '8px',
textAlign: 'center',
fontWeight: 'bold'
};
},
isVisible: () => !!dateSection.datepicker('startDate')?.value() && !!dateSection.datepicker('endDate')?.value()
});
});
// ============================================
// SECTION 4: Coverage & Handoff
// ============================================
const coverageSection = form.addSubform('coverageSection', {
title: 'Coverage Arrangements',
isVisible: () => {
const leaveType = leaveSection.radioButton('leaveType')?.value();
const start = dateSection.datepicker('startDate')?.value();
// Only show for vacation/personal and if dates are selected
return (leaveType === 'vacation' || leaveType === 'personal') && !!start;
}
});
coverageSection.addRow(row => {
row.addRadioButton('hasCoverage', {
label: 'Have you arranged coverage for your responsibilities?',
options: [
{ id: 'yes', name: 'Yes, coverage is arranged' },
{ id: 'not-needed', name: 'Not needed - no urgent tasks' },
{ id: 'in-progress', name: 'Working on it' },
{ id: 'need-help', name: 'Need help arranging' }
],
orientation: 'vertical'
});
});
coverageSection.addRow(row => {
row.addTextbox('coveringPerson', {
label: 'Who will cover your responsibilities?',
placeholder: 'Name of colleague(s)',
isVisible: () => coverageSection.radioButton('hasCoverage')?.value() === 'yes'
});
});
coverageSection.addSpacer();
coverageSection.addRow(row => {
row.addTextarea('handoffNotes', {
label: 'Handoff notes (optional)',
placeholder: 'Key tasks, pending items, important contacts...',
rows: 3,
autoExpand: true,
isVisible: () => coverageSection.radioButton('hasCoverage')?.value() === 'yes'
});
});
// ============================================
// SECTION 5: Reason/Details (for specific leave types)
// ============================================
const reasonSection = form.addSubform('reasonSection', {
title: 'Additional Details',
isVisible: () => {
const leaveType = leaveSection.radioButton('leaveType')?.value();
return leaveType === 'medical' || leaveType === 'bereavement' || leaveType === 'other';
}
});
reasonSection.addRow(row => {
row.addTextarea('leaveReason', {
label: () => {
const leaveType = leaveSection.radioButton('leaveType')?.value();
if (leaveType === 'medical') return 'Brief description (medical details are optional and confidential)';
if (leaveType === 'bereavement') return 'Relationship to deceased (optional)';
return 'Additional details';
},
placeholder: 'This information is confidential...',
rows: 2,
autoExpand: true
});
});
// ============================================
// SECTION 6: Contact During Leave
// ============================================
const contactSection = form.addSubform('contactSection', {
title: 'Contact Information',
isVisible: () => !!dateSection.datepicker('startDate')?.value()
});
contactSection.addRow(row => {
row.addRadioButton('emergencyContact', {
label: 'Can you be reached for emergencies during your leave?',
options: [
{ id: 'yes', name: 'Yes - for true emergencies only' },
{ id: 'limited', name: 'Limited availability' },
{ id: 'no', name: 'No - completely unavailable' }
],
orientation: 'vertical'
});
});
contactSection.addRow(row => {
row.addTextbox('emergencyPhone', {
label: 'Emergency Contact Number (optional)',
placeholder: 'Phone number for emergencies',
isVisible: () => {
const contact = contactSection.radioButton('emergencyContact')?.value();
return contact === 'yes' || contact === 'limited';
}
});
});
// ============================================
// SECTION 7: Acknowledgment
// ============================================
const ackSection = form.addSubform('ackSection', {
title: 'Acknowledgment',
isVisible: () => !!dateSection.datepicker('startDate')?.value()
});
ackSection.addRow(row => {
row.addCheckbox('policyAck', {
label: 'I confirm this request complies with company leave policies',
isRequired: true
});
});
ackSection.addRow(row => {
row.addCheckbox('noticeAck', {
label: () => {
const leaveType = leaveSection.radioButton('leaveType')?.value();
if (leaveType === 'vacation' || leaveType === 'personal') {
return 'I understand this request requires manager approval and adequate notice';
}
return 'I understand this request will be reviewed by my manager';
},
isRequired: true
});
});
// ============================================
// SECTION 8: Summary
// ============================================
const summarySection = form.addSubform('summary', {
title: 'Request Summary',
isVisible: () => ackSection.checkbox('policyAck')?.value() === true
});
summarySection.addRow(row => {
row.addTextPanel('summaryContent', {
computedValue: () => {
const name = employeeSection.textbox('employeeName')?.value();
const department = employeeSection.dropdown('department')?.value();
const leaveType = leaveSection.radioButton('leaveType')?.value();
const start = dateSection.datepicker('startDate')?.value();
const end = dateSection.datepicker('endDate')?.value();
const dayType = dateSection.radioButton('dayType')?.value();
const coverage = coverageSection.radioButton('hasCoverage')?.value();
if (!name || !leaveType || !start) return '';
const leaveLabels: Record<string, string> = {
'vacation': 'Vacation / PTO',
'sick': 'Sick Leave',
'personal': 'Personal Day',
'bereavement': 'Bereavement',
'jury-duty': 'Jury Duty',
'parental': 'Parental Leave',
'medical': 'Medical Leave',
'unpaid': 'Unpaid Leave',
'other': 'Other'
};
const deptLabels: Record<string, string> = {
'engineering': 'Engineering',
'product': 'Product',
'design': 'Design',
'marketing': 'Marketing',
'sales': 'Sales',
'hr': 'Human Resources',
'finance': 'Finance',
'operations': 'Operations',
'customer-success': 'Customer Success',
'legal': 'Legal',
'other': 'Other'
};
// Calculate days
let dayCount = 0;
if (start && end) {
const startDate = new Date(start);
const endDate = new Date(end);
const current = new Date(startDate);
while (current <= endDate) {
const dayOfWeek = current.getDay();
if (dayOfWeek !== 0 && dayOfWeek !== 6) {
dayCount++;
}
current.setDate(current.getDate() + 1);
}
if (dayType === 'half-am' || dayType === 'half-pm') {
dayCount = dayCount * 0.5;
}
}
let summary = '📋 Time Off Request\n';
summary += `${'═'.repeat(25)}\n\n`;
summary += `👤 Employee: ${name}\n`;
if (department) {
summary += `🏢 Department: ${deptLabels[department] || department}\n`;
}
summary += `\n📅 Leave Type: ${leaveLabels[leaveType] || leaveType}\n`;
summary += `📆 From: ${start}\n`;
if (end) {
summary += `📆 To: ${end}\n`;
}
if (dayCount > 0) {
const dayWord = dayCount === 1 ? 'day' : 'days';
summary += `\n⏱️ Duration: ${dayCount} business ${dayWord}\n`;
}
if (coverage) {
const coverageLabels: Record<string, string> = {
'yes': '✅ Coverage arranged',
'not-needed': '📝 No coverage needed',
'in-progress': '🔄 Arranging coverage',
'need-help': '❓ Needs assistance'
};
summary += `\n${coverageLabels[coverage] || ''}`;
}
summary += '\n\n⏳ Status: Pending Approval';
return summary;
},
customStyles: {
padding: '16px',
borderRadius: '8px',
backgroundColor: '#f0f9ff',
borderLeft: '4px solid #3b82f6',
whiteSpace: 'pre-wrap',
fontFamily: 'monospace',
fontSize: '14px'
}
});
});
// ============================================
// FORM CONFIGURATION
// ============================================
form.configureSubmitButton({
label: 'Submit Request',
isVisible: () => {
return ackSection.checkbox('policyAck')?.value() === true &&
ackSection.checkbox('noticeAck')?.value() === true;
}
});
form.configureCompletionScreen({
type: 'text',
title: 'Request Submitted Successfully!',
message: 'Your time off request has been submitted and is pending manager approval. You will receive an email notification once your request has been reviewed. For urgent matters, please contact your manager directly.'
});
}