/** * Configuration utilities for handling settings and form submissions */ class ConfigHandler { constructor(options = {}) { this.options = { apiEndpoint: options.apiEndpoint || "/config/api/update_config", ...options, }; } /** * Update configuration via API * @param {object} configData - Configuration data to send * @returns {Promise} API response promise */ async updateConfig(configData) { try { const response = await fetch(this.options.apiEndpoint, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify(configData), }); const data = await response.json(); if (data.success) { showFlashMessage( data.message || "Configuration updated successfully!", "success" ); } else { const errorMessage = data.updates?.[0]?.message || data.message || "Error updating configuration"; showFlashMessage(errorMessage, "error"); } return data; } catch (error) { console.error("Error updating configuration:", error); showFlashMessage("Network error occurred", "error"); throw error; } } /** * Update volume configuration * @param {number} volume - New volume value */ async updateVolume(volume) { return this.updateConfig({ volume: volume }); } /** * Update schedule configuration * @param {object} schedule - Schedule configuration object */ async updateSchedule(schedule) { return this.updateConfig({ schedule: schedule }); } /** * Create an Alpine.js data object for schedule management * Reads configuration from JSON script tag in the template * @returns {object} Alpine.js data object */ createScheduleManager() { const self = this; // Read configuration from JSON script tag const configElement = document.getElementById("schedule-config"); const config = configElement ? JSON.parse(configElement.textContent) : {}; const initialSchedule = config.initialSchedule || {}; const volume = config.totalVolume || 0; return { schedule: { ...initialSchedule }, volume: volume, selectedHours: [], newWeight: 1.0, volumeValue: volume, isDragging: false, dragOperation: null, formatHour(h) { return String(h).padStart(2, "0") + ":00"; }, async updateVolume() { try { const data = await self.updateVolume(this.volumeValue); if (data.success) { this.volume = parseFloat(this.volumeValue); } } catch (error) { // Error handling is done in updateConfig } }, getBackgroundStyle(hour) { const weight = parseFloat(this.schedule[hour]); const maxWeight = 2.5; // Normalize weight (0.0 to 1.0) const t = Math.min(weight / maxWeight, 1.0); // Interpolate HSL lightness: 95% (light) to 30% (dark) const lightness = 95 - t * 65; const backgroundColor = `hsl(210, 10%, ${lightness}%)`; const textColor = t > 0.65 ? "white" : "black"; return { backgroundColor, color: textColor, }; }, startDrag(event, hour) { event.preventDefault(); this.isDragging = true; this.dragOperation = this.isSelected(hour) ? "remove" : "add"; this.toggleSelect(hour); }, dragSelect(hour) { if (!this.isDragging) return; const selected = this.isSelected(hour); if (this.dragOperation === "add" && !selected) { this.selectedHours.push(hour); } else if (this.dragOperation === "remove" && selected) { this.selectedHours = this.selectedHours.filter((h) => h !== hour); } }, endDrag() { this.isDragging = false; }, toggleSelect(hour) { if (this.isSelected(hour)) { this.selectedHours = this.selectedHours.filter((h) => h !== hour); } else { this.selectedHours.push(hour); } }, isSelected(hour) { return this.selectedHours.includes(hour); }, applyWeight() { this.selectedHours.forEach((hour) => { this.schedule[hour] = parseFloat(this.newWeight).toFixed(1); }); this.selectedHours = []; }, getTotalWeight() { return Object.values(this.schedule).reduce( (sum, w) => sum + parseFloat(w), 0 ); }, getPapersPerHour(hour) { const total = this.getTotalWeight(); if (total === 0) return 0; return ( (parseFloat(this.schedule[hour]) / total) * this.volume ).toFixed(1); }, async saveSchedule() { try { await self.updateSchedule(this.schedule); } catch (error) { // Error handling is done in updateConfig } }, }; } } /** * Global instance for easy access */ window.configHandler = new ConfigHandler();