// Travel Reimbursement Form Custom Script Engine // This script provides dynamic form behavior using the FormScriptEngine context // Initialize form on load console.log('Travel Reimbursement Form Script Loaded'); // Auto-calculate total cost when any cost field changes const calculateTotalCost = () => { const transportCost = parseFloat(getField('transport_cost')) || 0; const accommodationCost = parseFloat(getField('accommodation_cost')) || 0; const mealsCost = parseFloat(getField('meals_cost')) || 0; const otherCost = parseFloat(getField('other_cost')) || 0; const totalCost = transportCost + accommodationCost + mealsCost + otherCost; // Update the total cost display field const totalDisplay = document.querySelector('[data-name="total_cost_display"] .formkit-inner'); if (totalDisplay) { totalDisplay.innerHTML = `RM ${totalCost.toFixed(2)}`; } // Show budget warnings if total is high if (totalCost > 5000) { showInfo('High amount claim - may require manager approval'); } return totalCost; }; // Validate email format and domain const validateEmployeeEmail = (email) => { if (!email) return false; // Basic email validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { showError('Please enter a valid email address'); return false; } // Company domain validation (example) const allowedDomains = ['company.com', 'gmail.com', 'outlook.com']; const domain = email.split('@')[1]; if (!allowedDomains.includes(domain)) { showInfo('Please use your company email if available'); } return true; }; // Validate trip dates const validateTripDates = () => { const departureDate = getField('departure_date'); const returnDate = getField('return_date'); if (!departureDate || !returnDate) return true; // Skip if dates not set const departure = new Date(departureDate); const returnDateObj = new Date(returnDate); const today = new Date(); // Check if return date is after departure date if (returnDateObj <= departure) { showError('Return date must be after departure date'); return false; } // Check if trip is too far in the past (more than 3 months) const threeMonthsAgo = new Date(); threeMonthsAgo.setMonth(threeMonthsAgo.getMonth() - 3); if (departure < threeMonthsAgo) { showError('Cannot claim for trips older than 3 months'); return false; } // Check if trip is too far in the future (more than 1 year) const oneYearFromNow = new Date(); oneYearFromNow.setFullYear(oneYearFromNow.getFullYear() + 1); if (departure > oneYearFromNow) { showError('Cannot claim for trips more than 1 year in advance'); return false; } // Calculate and show trip duration const diffTime = Math.abs(returnDateObj - departure); const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1; if (diffDays > 30) { showInfo(`Long trip duration: ${diffDays} days - may require additional documentation`); } else { showSuccess(`Trip duration: ${diffDays} days`); } return true; }; // Show/hide fields based on travel type const handleTravelTypeChange = (travelType) => { console.log('Travel type changed to:', travelType); // Show different guidance based on travel type switch(travelType) { case 'flight': showInfo('Flight travel: Keep boarding passes and receipts. Maximum transport limit: RM 2,000'); break; case 'train': showInfo('Train travel: Keep tickets and receipts. Maximum transport limit: RM 500'); break; case 'car': showInfo('Car travel: Keep fuel receipts and toll receipts. Maximum transport limit: RM 800'); break; case 'bus': showInfo('Bus travel: Keep tickets and receipts. Maximum transport limit: RM 300'); break; } }; // Validate cost fields for reasonable amounts const validateCostField = (fieldName, value) => { const cost = parseFloat(value) || 0; if (cost < 0) { showError(`${fieldName} cannot be negative`); return false; } // Set reasonable limits per field const limits = { 'transport_cost': 5000, 'accommodation_cost': 10000, 'meals_cost': 2000, 'other_cost': 1000 }; if (cost > limits[fieldName]) { showError(`${fieldName.replace('_', ' ')} seems unusually high (RM ${cost}). Please verify the amount.`); return false; } return true; }; // Form validation before submission const validateForm = () => { let isValid = true; const errors = []; // Check required fields const requiredFields = { 'employee_name': 'Employee Name', 'employee_email': 'Employee Email', 'department': 'Department', 'trip_purpose': 'Trip Purpose', 'destination': 'Destination', 'departure_date': 'Departure Date', 'return_date': 'Return Date', 'travel_type': 'Travel Type' }; Object.entries(requiredFields).forEach(([fieldName, displayName]) => { const value = getField(fieldName); if (!value || value.toString().trim() === '') { errors.push(`${displayName} is required`); isValid = false; } }); // Validate email const email = getField('employee_email'); if (email && !validateEmployeeEmail(email)) { isValid = false; } // Validate dates if (!validateTripDates()) { isValid = false; } // Check if at least one cost field has a value > 0 const totalCost = calculateTotalCost(); if (totalCost <= 0) { errors.push('At least one expense amount must be greater than 0'); isValid = false; } // Check receipts confirmation const hasReceipts = getField('has_receipts'); if (!hasReceipts || hasReceipts.length === 0) { errors.push('Please confirm you have all supporting receipts'); isValid = false; } // Show validation results if (errors.length > 0) { showError(`Please fix the following errors:\n• ${errors.join('\n• ')}`); } else { showSuccess('Form validation passed! Ready to submit.'); } return isValid; }; // Set up field change handlers onFieldChange('transport_cost', (newValue) => { if (validateCostField('transport_cost', newValue)) { calculateTotalCost(); } }); onFieldChange('accommodation_cost', (newValue) => { if (validateCostField('accommodation_cost', newValue)) { calculateTotalCost(); } }); onFieldChange('meals_cost', (newValue) => { if (validateCostField('meals_cost', newValue)) { calculateTotalCost(); } }); onFieldChange('other_cost', (newValue) => { if (validateCostField('other_cost', newValue)) { calculateTotalCost(); } }); onFieldChange('employee_email', (newValue) => { if (newValue && newValue.trim() !== '') { validateEmployeeEmail(newValue); } }); onFieldChange('departure_date', () => { setTimeout(validateTripDates, 100); // Small delay to ensure both dates are updated }); onFieldChange('return_date', () => { setTimeout(validateTripDates, 100); }); onFieldChange('travel_type', (newValue) => { if (newValue) { handleTravelTypeChange(newValue); } }); // Auto-format employee name (title case) onFieldChange('employee_name', (newValue) => { if (newValue && typeof newValue === 'string') { const formatted = newValue .toLowerCase() .split(' ') .map(word => word.charAt(0).toUpperCase() + word.slice(1)) .join(' '); if (formatted !== newValue) { setTimeout(() => { setField('employee_name', formatted); }, 100); } } }); // Department suggestions onFieldChange('department', (newValue) => { if (newValue && newValue.length >= 2) { const commonDepartments = [ 'Finance', 'IT', 'Marketing', 'Sales', 'HR', 'Operations', 'Engineering', 'Customer Service', 'Legal', 'Procurement' ]; const suggestions = commonDepartments.filter(dept => dept.toLowerCase().includes(newValue.toLowerCase()) ); if (suggestions.length > 0 && !suggestions.includes(newValue)) { showInfo(`Department suggestions: ${suggestions.join(', ')}`); } } }); // Initialize total cost display on load setTimeout(() => { calculateTotalCost(); }, 1000); // Add form submission validation // Note: This would typically be connected to the form's submit event // For now, we'll expose it as a global function for manual validation window.validateTravelForm = validateForm; console.log('Travel Reimbursement Form Script initialized successfully');