const React = require("react"); const { Box, Text, useInput, useApp } = require("ink"); const { useAppState } = require("../../providers/AppProvider.jsx"); const TextInput = require("ink-text-input").default; /** * Scheduling Screen Component * Interface for configuring scheduled price update operations * Requirements: 7.3, 7.4 */ const SchedulingScreen = () => { const { appState, navigateBack, updateConfiguration } = useAppState(); const { exit } = useApp(); // Form fields configuration const formFields = [ { id: "scheduledTime", label: "Scheduled Execution Time", placeholder: "2023-12-25T15:30:00", description: "When to run the operation (ISO 8601 format)", validator: (value) => { if (!value || value.trim() === "") return "Scheduled time is required"; try { const date = new Date(value); if (isNaN(date.getTime())) return "Invalid date format"; if (date <= new Date()) return "Scheduled time must be in the future"; return null; } catch (error) { return "Invalid date format"; } }, }, { id: "scheduleType", label: "Schedule Type", placeholder: "one-time", description: "Type of schedule", type: "select", options: [ { value: "one-time", label: "One-time" }, { value: "daily", label: "Daily" }, { value: "weekly", label: "Weekly" }, { value: "monthly", label: "Monthly" }, ], }, { id: "scheduleTime", label: "Schedule Time", placeholder: "15:30", description: "Time of day for recurring schedules", validator: (value) => { if (!value || value.trim() === "") return "Time is required"; const timeRegex = /^([01]?[0-9]|2[0-3]):[0-5][0-9]$/; if (!timeRegex.test(value)) return "Invalid time format (HH:MM)"; return null; }, }, ]; // State for form inputs const [formValues, setFormValues] = React.useState( formFields.reduce((acc, field) => { acc[field.id] = appState.configuration[field.id] || ""; return acc; }, {}) ); const [errors, setErrors] = React.useState({}); const [focusedField, setFocusedField] = React.useState(0); const [showValidation, setShowValidation] = React.useState(false); const [nextRunTime, setNextRunTime] = React.useState(null); // Validate all fields const validateForm = () => { const newErrors = {}; let isValid = true; formFields.forEach((field) => { const error = field.validator(formValues[field.id]); if (error) { newErrors[field.id] = error; isValid = false; } }); setErrors(newErrors); return isValid; }; // Calculate next run time const calculateNextRunTime = () => { if (!formValues.scheduledTime) return null; try { const scheduledDate = new Date(formValues.scheduledTime); const now = new Date(); // Format for display const options = { year: "numeric", month: "long", day: "numeric", hour: "2-digit", minute: "2-digit", second: "2-digit", }; return scheduledDate.toLocaleString(undefined, options); } catch (error) { return null; } }; // Handle keyboard input useInput((input, key) => { if (key.escape) { // Go back to main menu navigateBack(); } else if (key.tab || (key.tab && key.shift)) { // Navigate between fields if (key.shift) { // Shift+Tab - previous field setFocusedField((prev) => prev > 0 ? prev - 1 : formFields.length - 1 ); } else { // Tab - next field setFocusedField((prev) => prev < formFields.length - 1 ? prev + 1 : 0 ); } } else if (key.return || key.enter) { // Handle Enter key if (focusedField === formFields.length - 1) { // Last field (Save button) - save configuration handleSave(); } else { // Move to next field setFocusedField((prev) => prev < formFields.length - 1 ? prev + 1 : 0 ); } } else if (key.upArrow) { // Navigate up setFocusedField((prev) => (prev > 0 ? prev - 1 : formFields.length - 1)); } else if (key.downArrow) { // Navigate down setFocusedField((prev) => (prev < formFields.length - 1 ? prev + 1 : 0)); } }); // Handle input changes const handleInputChange = (fieldId, value) => { setFormValues((prev) => ({ ...prev, [fieldId]: value, })); // Clear error when user starts typing if (errors[fieldId]) { setErrors((prev) => { const newErrors = { ...prev }; delete newErrors[fieldId]; return newErrors; }); } // Update next run time when scheduled time changes if (fieldId === "scheduledTime") { setNextRunTime(calculateNextRunTime()); } }; // Handle save configuration const handleSave = () => { if (validateForm()) { // Update configuration const config = { ...formValues, isScheduled: true, }; updateConfiguration(config); navigateBack(); } else { // Show validation errors setShowValidation(true); } }; // Handle test schedule const handleTestSchedule = () => { // Validate required fields const requiredFields = ["scheduledTime"]; const tempErrors = {}; let hasError = false; requiredFields.forEach((fieldId) => { const field = formFields.find((f) => f.id === fieldId); const error = field.validator(formValues[fieldId]); if (error) { tempErrors[fieldId] = error; hasError = true; } }); if (hasError) { setErrors(tempErrors); setShowValidation(true); return; } // Calculate and display next run time setNextRunTime(calculateNextRunTime()); }; return React.createElement( Box, { flexDirection: "column", padding: 2, flexGrow: 1 }, // Header React.createElement( Box, { flexDirection: "column", marginBottom: 2 }, React.createElement(Text, { bold: true, color: "cyan" }, "⏰ Scheduling"), React.createElement( Text, { color: "gray" }, "Configure when to run price update operations" ) ), // Schedule information React.createElement( Box, { borderStyle: "single", borderColor: "blue", paddingX: 1, paddingY: 1, marginBottom: 2, }, React.createElement( Box, { flexDirection: "column" }, React.createElement( Text, { bold: true, color: "blue" }, "Schedule Information:" ), React.createElement( Text, null, ` Current Mode: ${appState.configuration.operationMode.toUpperCase()}` ), React.createElement( Text, null, ` Target Tag: ${appState.configuration.targetTag || "Not set"}` ), React.createElement( Text, null, ` Store: ${appState.configuration.shopDomain || "Not set"}` ) ) ), // Form fields React.createElement( Box, { flexDirection: "column", marginBottom: 2 }, formFields.map((field, index) => { const isFocused = focusedField === index; const hasError = errors[field.id]; const currentValue = formValues[field.id]; return React.createElement( Box, { key: field.id, borderStyle: "single", borderColor: hasError ? "red" : isFocused ? "blue" : "gray", paddingX: 1, paddingY: 1, marginBottom: 1, flexDirection: "column", }, React.createElement( Box, { flexDirection: "row", alignItems: "center", marginBottom: 1 }, React.createElement( Text, { bold: true, color: isFocused ? "blue" : "white", }, `${field.label}:` ), React.createElement( Box, { flexGrow: 1 }, React.createElement( Text, { color: "gray" }, ` ${field.description}` ) ) ), React.createElement( Box, { flexDirection: "row" }, field.type === "select" ? React.createElement( Box, { flexDirection: "column" }, field.options.map((option, optIndex) => React.createElement( Box, { key: optIndex, borderStyle: formValues[field.id] === option.value ? "single" : "none", borderColor: "blue", paddingX: 2, paddingY: 0.5, marginBottom: 0.5, backgroundColor: formValues[field.id] === option.value ? "blue" : undefined, }, React.createElement( Text, { color: formValues[field.id] === option.value ? "white" : "gray", }, option.label ) ) ) ) : React.createElement(TextInput, { value: currentValue, placeholder: field.placeholder, mask: null, showCursor: isFocused, focus: isFocused, onChange: (value) => handleInputChange(field.id, value), style: { color: hasError ? "red" : isFocused ? "blue" : "white", bold: isFocused, }, }) ), hasError && React.createElement( Text, { color: "red", italic: true }, ` Error: ${errors[field.id]}` ) ); }) ), // Next run time display nextRunTime && React.createElement( Box, { borderStyle: "single", borderColor: "green", paddingX: 1, paddingY: 1, marginBottom: 2, }, React.createElement( Box, { flexDirection: "column" }, React.createElement( Text, { bold: true, color: "green" }, "Next Scheduled Run:" ), React.createElement(Text, { color: "green" }, ` ${nextRunTime}`) ) ), // Action buttons React.createElement( Box, { flexDirection: "row", justifyContent: "space-between" }, React.createElement( Box, { flexDirection: "column", width: "48%" }, React.createElement( Box, { borderStyle: "single", borderColor: "gray", paddingX: 2, paddingY: 1, alignItems: "center", backgroundColor: focusedField === formFields.length ? "yellow" : undefined, }, React.createElement( Text, { color: focusedField === formFields.length ? "black" : "white", bold: true, }, focusedField === formFields.length ? "Testing..." : "Test Schedule" ) ), React.createElement( Text, { color: "gray", italic: true, marginTop: 0.5 }, "Calculate next run time" ) ), React.createElement( Box, { flexDirection: "column", width: "48%" }, React.createElement( Box, { borderStyle: "single", borderColor: "green", paddingX: 2, paddingY: 1, alignItems: "center", backgroundColor: focusedField === formFields.length - 1 ? "green" : undefined, }, React.createElement( Text, { color: "white", bold: true, }, "Save & Exit" ) ), React.createElement( Text, { color: "gray", italic: true, marginTop: 0.5 }, "Save schedule and return to menu" ) ) ), // Instructions React.createElement( Box, { flexDirection: "column", marginTop: 2, borderTopStyle: "single", borderColor: "gray", paddingTop: 2, }, React.createElement(Text, { color: "gray" }, "Navigation:"), React.createElement(Text, { color: "gray" }, " ↑/↓ - Navigate fields"), React.createElement( Text, { color: "gray" }, " Tab/Shift+Tab - Next/Previous field" ), React.createElement(Text, { color: "gray" }, " Enter - Save/Next field"), React.createElement(Text, { color: "gray" }, " Esc - Back to menu") ), // Validation message showValidation && Object.keys(errors).length > 0 && React.createElement( Box, { flexDirection: "column", marginTop: 2, padding: 1, borderStyle: "single", borderColor: "red", }, React.createElement( Text, { color: "red", bold: true }, "Validation Errors:" ), React.createElement( Text, { color: "red" }, "Please fix the errors before saving." ) ) ); }; module.exports = SchedulingScreen;