Update Filament Dryer
This commit is contained in:
@@ -46,9 +46,74 @@ checkUserRole(['admin']);
|
|||||||
<!--start page wrapper -->
|
<!--start page wrapper -->
|
||||||
<div class="page-wrapper">
|
<div class="page-wrapper">
|
||||||
<div class="page-content">
|
<div class="page-content">
|
||||||
<!--start page content -->
|
|
||||||
|
<!-- Start Page Content -->
|
||||||
|
|
||||||
|
<!-- Start Drying Modal -->
|
||||||
|
<div class="modal fade" id="startDryingModal" tabindex="-1" aria-labelledby="startDryingModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="startDryingModalLabel">Start Drying</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<form id="dryerControlForm">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="filamentPreset" class="form-label">Filament Preset</label>
|
||||||
|
<select class="form-select" id="filamentPreset" name="filamentPreset" required>
|
||||||
|
<option value="">Select Filament Type</option>
|
||||||
|
<option value="PLA" data-temp="50">PLA</option>
|
||||||
|
<option value="ABS" data-temp="70">ABS</option>
|
||||||
|
<option value="PETG" data-temp="65">PETG</option>
|
||||||
|
<option value="Nylon" data-temp="80">Nylon</option>
|
||||||
|
<option value="TPU" data-temp="45">TPU</option>
|
||||||
|
<option value="Custom" data-temp="">Custom</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="dryingTime" class="form-label">Drying Time (Hours)</label>
|
||||||
|
<input type="number" class="form-control" id="dryingTime" name="dryingTime" min="1" max="24" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="temperature" class="form-label">Drying Temperature (°C)</label>
|
||||||
|
<input type="number" class="form-control" id="temperature" name="temperature" min="30" max="100" required>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<button type="submit" class="btn btn-success">Start Drying</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Stop Drying Modal -->
|
||||||
|
<div class="modal fade" id="stopDryingModal" tabindex="-1" aria-labelledby="stopDryingModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h5 class="modal-title" id="stopDryingModalLabel">Stop Drying</h5>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<p>Are you sure you want to stop the drying process?</p>
|
||||||
|
<div class="d-flex justify-content-between">
|
||||||
|
<button type="button" class="btn btn-danger" id="confirmStopDrying">Yes, Stop Drying</button>
|
||||||
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Page Content -->
|
||||||
<div class="container mt-4">
|
<div class="container mt-4">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
<!-- Dryer Status Cards -->
|
||||||
<div class="col-lg-4">
|
<div class="col-lg-4">
|
||||||
<div class="card bg-danger radius-10 overflow-hidden">
|
<div class="card bg-danger radius-10 overflow-hidden">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -86,12 +151,13 @@ checkUserRole(['admin']);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Left Column - Charts -->
|
<!-- Left Column - Charts -->
|
||||||
<div class="col-lg-7">
|
<div class="col-lg-7">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<!-- Temperature Chart -->
|
<!-- Temperature Chart -->
|
||||||
<div class="col-lg-12 mb-4">
|
<div class="col-lg-12 mb-0">
|
||||||
<div class="card radius-10 overflow-hidden">
|
<div class="card radius-10 overflow-hidden">
|
||||||
<div class="card-body d-flex align-items-center justify-content-between">
|
<div class="card-body d-flex align-items-center justify-content-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -103,7 +169,7 @@ checkUserRole(['admin']);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Humidity Chart -->
|
<!-- Humidity Chart -->
|
||||||
<div class="col-lg-12 mb-4">
|
<div class="col-lg-12 mb-0">
|
||||||
<div class="card radius-10 overflow-hidden">
|
<div class="card radius-10 overflow-hidden">
|
||||||
<div class="card-body d-flex align-items-center justify-content-between">
|
<div class="card-body d-flex align-items-center justify-content-between">
|
||||||
<div>
|
<div>
|
||||||
@@ -117,61 +183,40 @@ checkUserRole(['admin']);
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Right Column - Dryer Control Form -->
|
<!-- Right Column - Dryer Control Card with Dropdown and Chart -->
|
||||||
<div class="col-lg-5">
|
<div class="col-lg-5">
|
||||||
<div class="card radius-10 overflow-hidden">
|
<div class="card rounded-4 mb-0">
|
||||||
<div class="card-header bg-primary text-white">
|
|
||||||
<h5 class="mb-0">Filament Dryer Control</h5>
|
|
||||||
</div>
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form id="dryerControlForm">
|
<div class="d-flex align-items-start justify-content-between mb-0">
|
||||||
<div class="mb-3">
|
<div class="">
|
||||||
<label for="filamentPreset" class="form-label">Filament Preset</label>
|
<h6 class="mb-0">Dryer Control</h6>
|
||||||
<select class="form-select" id="filamentPreset" name="filamentPreset" required>
|
|
||||||
<option value="">Select Filament Type</option>
|
|
||||||
<option value="PLA" data-temp="50">PLA</option>
|
|
||||||
<option value="ABS" data-temp="70">ABS</option>
|
|
||||||
<option value="PETG" data-temp="65">PETG</option>
|
|
||||||
<option value="Nylon" data-temp="80">Nylon</option>
|
|
||||||
<option value="TPU" data-temp="45">TPU</option>
|
|
||||||
<option value="Custom" data-temp="">Custom</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div class="dropdown">
|
||||||
<div class="mb-3">
|
<a href="javascript:;" class="dropdown-toggle-nocaret more-options dropdown-toggle" data-bs-toggle="dropdown">
|
||||||
<label for="dryingTime" class="form-label">Drying Time (Hours)</label>
|
<i class='bx bx-dots-vertical-rounded'></i>
|
||||||
<input type="number" class="form-control" id="dryingTime" name="dryingTime" min="1" max="24" required>
|
</a>
|
||||||
</div>
|
<ul class="dropdown-menu">
|
||||||
|
<li><a class="dropdown-item" href="javascript:;" data-bs-toggle="modal" data-bs-target="#startDryingModal">Start Drying</a></li>
|
||||||
<div class="mb-3">
|
<li><a class="dropdown-item" href="javascript:;" data-bs-toggle="modal" data-bs-target="#stopDryingModal">Stop Drying</a></li>
|
||||||
<label for="temperature" class="form-label">Drying Temperature (°C)</label>
|
</ul>
|
||||||
<input type="number" class="form-control" id="temperature" name="temperature" min="30" max="100" required>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="d-flex justify-content-between">
|
|
||||||
<button type="submit" class="btn btn-success">Start Drying</button>
|
|
||||||
<button type="button" class="btn btn-danger" id="dryerOff">Turn Off Dryer</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Progress Bar Section -->
|
<div class="chart-container2">
|
||||||
<div class="card mt-4 radius-10 overflow-hidden">
|
<div id="chartProgress"></div>
|
||||||
<div class="card-header bg-primary text-white d-flex justify-content-between align-items-center">
|
</div>
|
||||||
<h5 class="mb-0">Drying Progress</h5>
|
<div class="text-center">
|
||||||
|
<div class="">
|
||||||
|
<h4 id="dryingTimeDisplay" class="mb-1">Loadig..</h4>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
|
||||||
<div class="progress mb-2">
|
|
||||||
<div class="progress-bar bg-success" id="dryingProgress" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100"></div>
|
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-0">Time Set: <span id="dryingTimeDisplay">--</span></p>
|
|
||||||
<p class="mb-0">Remaining Time: <span id="remainingTimeDisplay">--</span></p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!--end page content -->
|
|
||||||
|
|
||||||
|
<!-- End Page Content -->
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -213,10 +258,13 @@ checkUserRole(['admin']);
|
|||||||
$(".data-attributes span").peity("donut")
|
$(".data-attributes span").peity("donut")
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.addEventListener("DOMContentLoaded", function () {
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
const ESP32_IP = 'https://filamentdry.hickmeister.uk'; // Replace with your ESP32 IP
|
const ESP32_IP = 'https://filamentdry.hickmeister.uk'; // Replace with your ESP32 IP
|
||||||
let tempChart, humidityChart;
|
let tempChart, humidityChart;
|
||||||
|
let progressChart; // Declare a variable for the chart instance
|
||||||
|
const startDryingModal = new bootstrap.Modal(document.getElementById('startDryingModal'));
|
||||||
|
const stopDryingModal = new bootstrap.Modal(document.getElementById('stopDryingModal'));
|
||||||
|
|
||||||
// Fetch ESP32 Status Data
|
// Fetch ESP32 Status Data
|
||||||
function fetchDryerStatus() {
|
function fetchDryerStatus() {
|
||||||
@@ -232,7 +280,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
updateTemperatureCard(data);
|
updateTemperatureCard(data);
|
||||||
updateHumidityCard(data);
|
updateHumidityCard(data);
|
||||||
updateStatusCards(data);
|
updateStatusCards(data);
|
||||||
updateProgressBar(data); // Update progress bar and time displays
|
updateChartProgress(data); // Update chart for drying progress
|
||||||
} else {
|
} else {
|
||||||
console.error("Dryer offline or no data available");
|
console.error("Dryer offline or no data available");
|
||||||
showOfflineStatus();
|
showOfflineStatus();
|
||||||
@@ -267,27 +315,31 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
.then(data => {
|
.then(data => {
|
||||||
console.log('Dryer control updated:', data);
|
console.log('Dryer control updated:', data);
|
||||||
fetchDryerStatus(); // Refresh status after control update
|
fetchDryerStatus(); // Refresh status after control update
|
||||||
|
if (dryerOn) {
|
||||||
|
startDryingModal.hide();
|
||||||
|
} else {
|
||||||
|
stopDryingModal.hide();
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
console.error('Failed to update dryer control:', error);
|
console.error('Failed to update dryer control:', error);
|
||||||
|
alert('Failed to update dryer control. Check your connection.');
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Prevent Form Submission Refresh
|
// Handle Form Submission for Starting Drying
|
||||||
const dryerControlForm = document.getElementById("dryerControlForm");
|
const dryerControlForm = document.getElementById("dryerControlForm");
|
||||||
if (dryerControlForm) {
|
|
||||||
dryerControlForm.addEventListener("submit", function (event) {
|
dryerControlForm.addEventListener("submit", function (event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
|
||||||
// Collect form data
|
// Collect form data
|
||||||
const formData = new FormData(dryerControlForm);
|
const formData = new FormData(dryerControlForm);
|
||||||
const dryerOn = true; // Always true when starting the dryer
|
const dryerOn = true; // Always true when starting the dryer
|
||||||
const filamentPreset = formData.get("filamentPreset");
|
|
||||||
const dryingTimeInHours = parseFloat(formData.get("dryingTime"));
|
const dryingTimeInHours = parseFloat(formData.get("dryingTime"));
|
||||||
const targetTemperature = parseFloat(formData.get("temperature"));
|
const targetTemperature = parseFloat(formData.get("temperature"));
|
||||||
|
|
||||||
// Validate Input
|
// Validate Input
|
||||||
if (!filamentPreset || isNaN(dryingTimeInHours) || isNaN(targetTemperature)) {
|
if (isNaN(dryingTimeInHours) || isNaN(targetTemperature)) {
|
||||||
alert("Please complete all required fields.");
|
alert("Please complete all required fields.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -298,21 +350,20 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
// Control Dryer
|
// Control Dryer
|
||||||
controlDryer(dryerOn, targetTemperature, dryingTimeInMinutes);
|
controlDryer(dryerOn, targetTemperature, dryingTimeInMinutes);
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Handle Turn Off Dryer
|
// Handle Stop Dryer
|
||||||
const dryerOffButton = document.getElementById("dryerOff");
|
document.getElementById("stopDryingModal").addEventListener("click", function () {
|
||||||
if (dryerOffButton) {
|
stopDryingModal.show();
|
||||||
dryerOffButton.addEventListener("click", function () {
|
});
|
||||||
|
|
||||||
|
document.getElementById("confirmStopDrying").addEventListener("click", function() {
|
||||||
controlDryer(false, 0, 0); // Turn off the dryer
|
controlDryer(false, 0, 0); // Turn off the dryer
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-Fill Temperature Input Based on Filament Preset
|
// Auto-Fill Temperature Input Based on Filament Preset
|
||||||
const filamentPresetSelect = document.getElementById("filamentPreset");
|
const filamentPresetSelect = document.getElementById("filamentPreset");
|
||||||
const temperatureInput = document.getElementById("temperature");
|
const temperatureInput = document.getElementById("temperature");
|
||||||
|
|
||||||
if (filamentPresetSelect && temperatureInput) {
|
|
||||||
filamentPresetSelect.addEventListener("change", function () {
|
filamentPresetSelect.addEventListener("change", function () {
|
||||||
const selectedOption = filamentPresetSelect.options[filamentPresetSelect.selectedIndex];
|
const selectedOption = filamentPresetSelect.options[filamentPresetSelect.selectedIndex];
|
||||||
const presetTemperature = selectedOption.getAttribute("data-temp");
|
const presetTemperature = selectedOption.getAttribute("data-temp");
|
||||||
@@ -323,49 +374,108 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
temperatureInput.value = ""; // Clear the temperature input for "Custom"
|
temperatureInput.value = ""; // Clear the temperature input for "Custom"
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Update Progress Bar and Time Display
|
// Update chart progress
|
||||||
function updateProgressBar(data) {
|
function updateChartProgress(data) {
|
||||||
const progressBar = document.getElementById("dryingProgress");
|
const chartProgressElement = document.getElementById("chartProgress");
|
||||||
const dryingTimeEl = document.getElementById("dryingTimeDisplay"); // Total drying time display
|
|
||||||
const remainingTimeDisplay = document.getElementById("remainingTimeDisplay"); // Remaining time display
|
|
||||||
|
|
||||||
const dryingTime = data.dryingTime || 0; // Total drying time in minutes
|
const dryingTime = data.dryingTime || 0; // Total drying time in minutes
|
||||||
const remainingTime = data.remainingTime || 0; // Remaining time in minutes
|
const remainingTime = data.remainingTime || 0; // Remaining time in minutes
|
||||||
|
|
||||||
// Reset progress bar if the dryer is turned off
|
// Convert remaining time and total drying time to h:mm format
|
||||||
if (!data.dryerOn) {
|
const remainingTimeFormatted = convertToHoursAndMinutes(remainingTime);
|
||||||
if (progressBar) {
|
const dryingTimeFormatted = convertToHoursAndMinutes(dryingTime);
|
||||||
progressBar.style.width = "0%";
|
|
||||||
progressBar.setAttribute("aria-valuenow", 0);
|
const progressPercent = dryingTime > 0 ? ((dryingTime - remainingTime) / dryingTime) * 100 : 0; // Calculate the percentage of drying progress
|
||||||
|
|
||||||
|
// Initialize the chart if it doesn't exist yet
|
||||||
|
if (!progressChart) {
|
||||||
|
const options = {
|
||||||
|
series: [progressPercent],
|
||||||
|
chart: {
|
||||||
|
height: 385,
|
||||||
|
type: 'radialBar',
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
}
|
}
|
||||||
if (dryingTimeEl) {
|
},
|
||||||
dryingTimeEl.innerText = "Total: 0h 0m";
|
plotOptions: {
|
||||||
|
radialBar: {
|
||||||
|
hollow: {
|
||||||
|
margin: 0,
|
||||||
|
size: '80%',
|
||||||
|
background: 'transparent',
|
||||||
|
},
|
||||||
|
track: {
|
||||||
|
background: 'rgba(0, 0, 0, 0.1)',
|
||||||
|
strokeWidth: '67%',
|
||||||
|
},
|
||||||
|
dataLabels: {
|
||||||
|
show: true,
|
||||||
|
name: {
|
||||||
|
offsetY: -10,
|
||||||
|
show: false,
|
||||||
|
},
|
||||||
|
value: {
|
||||||
|
offsetY: 10,
|
||||||
|
color: 'rgba(13, 160, 8, 0.82)',
|
||||||
|
fontSize: '20px',
|
||||||
|
show: true,
|
||||||
|
formatter: function () {
|
||||||
|
return remainingTimeFormatted; // Display remaining time in the center
|
||||||
}
|
}
|
||||||
if (remainingTimeDisplay) {
|
|
||||||
remainingTimeDisplay.innerText = "Remaining: 0h 0m";
|
|
||||||
}
|
}
|
||||||
return; // Exit early as no further updates are needed
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fill: {
|
||||||
|
type: 'gradient',
|
||||||
|
gradient: {
|
||||||
|
shade: 'dark',
|
||||||
|
type: 'horizontal',
|
||||||
|
shadeIntensity: 02,
|
||||||
|
gradientToColors: ['#44FF00'],
|
||||||
|
opacityFrom: 1,
|
||||||
|
opacityTo: 1,
|
||||||
|
stops: [0, 100]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
colors: ["#289600"],
|
||||||
|
stroke: {
|
||||||
|
lineCap: 'round'
|
||||||
|
},
|
||||||
|
labels: ['Drying Progress'],
|
||||||
|
};
|
||||||
|
|
||||||
|
progressChart = new ApexCharts(chartProgressElement, options);
|
||||||
|
progressChart.render();
|
||||||
|
} else {
|
||||||
|
progressChart.updateSeries([progressPercent]);
|
||||||
|
|
||||||
|
// Dynamically update the remaining time in the center
|
||||||
|
progressChart.updateOptions({
|
||||||
|
plotOptions: {
|
||||||
|
radialBar: {
|
||||||
|
dataLabels: {
|
||||||
|
value: {
|
||||||
|
formatter: function () {
|
||||||
|
return remainingTimeFormatted; // Dynamically update the remaining time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update progress bar when the dryer is on
|
// Display drying time below the chart
|
||||||
if (progressBar && dryingTime > 0) {
|
const timeSetElement = document.getElementById('dryingTimeDisplay');
|
||||||
const progressPercent = ((dryingTime - remainingTime) / dryingTime) * 100;
|
if (timeSetElement) {
|
||||||
progressBar.style.width = `${progressPercent}%`;
|
timeSetElement.innerText = `Time Set: ${dryingTimeFormatted}`;
|
||||||
progressBar.setAttribute("aria-valuenow", progressPercent.toFixed(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update time displays
|
|
||||||
if (dryingTimeEl) {
|
|
||||||
dryingTimeEl.innerText = `Total: ${convertToHoursAndMinutes(dryingTime)}`;
|
|
||||||
}
|
|
||||||
if (remainingTimeDisplay) {
|
|
||||||
remainingTimeDisplay.innerText = `Remaining: ${convertToHoursAndMinutes(remainingTime)}`;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert minutes to hours and minutes
|
// Convert minutes to hours and minutes (e.g., 90 minutes -> 1h 30m)
|
||||||
function convertToHoursAndMinutes(minutes) {
|
function convertToHoursAndMinutes(minutes) {
|
||||||
const hours = Math.floor(minutes / 60);
|
const hours = Math.floor(minutes / 60);
|
||||||
const mins = minutes % 60;
|
const mins = minutes % 60;
|
||||||
@@ -384,6 +494,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
series: [{ name: "Temperature", data: tempHistory }],
|
series: [{ name: "Temperature", data: tempHistory }],
|
||||||
chart: {
|
chart: {
|
||||||
type: 'area',
|
type: 'area',
|
||||||
|
color: '#1df00a',
|
||||||
height: 150,
|
height: 150,
|
||||||
toolbar: { show: false },
|
toolbar: { show: false },
|
||||||
sparkline: { enabled: true },
|
sparkline: { enabled: true },
|
||||||
@@ -440,7 +551,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update Status Cards
|
// Update Status Cards (Dryer, Heater, Fan)
|
||||||
function updateStatusCards(data) {
|
function updateStatusCards(data) {
|
||||||
const dryerStatusCard = document.querySelector('.card:nth-child(1)');
|
const dryerStatusCard = document.querySelector('.card:nth-child(1)');
|
||||||
const heaterStatusCard = document.getElementById('heaterRelayStatus');
|
const heaterStatusCard = document.getElementById('heaterRelayStatus');
|
||||||
@@ -474,7 +585,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
const dryerStatusCard = document.querySelector('.card:nth-child(1)');
|
const dryerStatusCard = document.querySelector('.card:nth-child(1)');
|
||||||
const heaterStatusCard = document.getElementById('heaterRelayStatus');
|
const heaterStatusCard = document.getElementById('heaterRelayStatus');
|
||||||
const fanStatusCard = document.getElementById('fanRelayStatus');
|
const fanStatusCard = document.getElementById('fanRelayStatus');
|
||||||
const progressBar = document.getElementById("dryingProgress");
|
|
||||||
|
|
||||||
if (dryerStatusCard) {
|
if (dryerStatusCard) {
|
||||||
dryerStatusCard.classList.remove("bg-success");
|
dryerStatusCard.classList.remove("bg-success");
|
||||||
@@ -493,11 +603,6 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
fanStatusCard.classList.add("bg-danger");
|
fanStatusCard.classList.add("bg-danger");
|
||||||
fanStatusCard.querySelector("h5").innerText = "Offline";
|
fanStatusCard.querySelector("h5").innerText = "Offline";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progressBar) {
|
|
||||||
progressBar.style.width = "0%";
|
|
||||||
progressBar.setAttribute("aria-valuenow", 0);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fetch data every 10 seconds
|
// Fetch data every 10 seconds
|
||||||
@@ -505,7 +610,8 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
setInterval(fetchDryerStatus, 10000);
|
setInterval(fetchDryerStatus, 10000);
|
||||||
});
|
});
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
@@ -146,7 +146,21 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
const filamentData = data.data[filament];
|
const filamentData = data.data[filament];
|
||||||
const prices = filamentData.prices.map(entry => parseFloat(entry.price));
|
const prices = filamentData.prices.map(entry => parseFloat(entry.price));
|
||||||
const timestamps = filamentData.prices.map(entry => entry.recordedAt);
|
const timestamps = filamentData.prices.map(entry => entry.recordedAt);
|
||||||
const latestPrice = prices[prices.length - 1] || 0;
|
let latestPrice = prices[prices.length - 1] || 0;
|
||||||
|
|
||||||
|
// Adjust price for voucher
|
||||||
|
const currentDiscount = filamentData.currentDiscount || {};
|
||||||
|
const voucher = currentDiscount.voucher || {};
|
||||||
|
if (voucher.value > 0) {
|
||||||
|
if (voucher.type === 'percentage') {
|
||||||
|
latestPrice -= (latestPrice * voucher.value) / 100; // Apply percentage voucher
|
||||||
|
} else if (voucher.type === 'fixed') {
|
||||||
|
latestPrice -= voucher.value; // Subtract fixed voucher value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure price doesn't go below zero
|
||||||
|
latestPrice = Math.max(latestPrice, 0);
|
||||||
|
|
||||||
// Find the last distinct price (where the price actually changed)
|
// Find the last distinct price (where the price actually changed)
|
||||||
let lastDistinctPrice = latestPrice;
|
let lastDistinctPrice = latestPrice;
|
||||||
@@ -175,10 +189,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
const amazonUrl = filamentData.amazonUrl || '#';
|
const amazonUrl = filamentData.amazonUrl || '#';
|
||||||
|
|
||||||
// Extract discount details
|
// Extract discount details
|
||||||
const currentDiscount = filamentData.currentDiscount || {};
|
|
||||||
const voucher = currentDiscount.voucher || {};
|
|
||||||
const discount = currentDiscount.discount || {};
|
const discount = currentDiscount.discount || {};
|
||||||
|
|
||||||
let discountText = '';
|
let discountText = '';
|
||||||
if (voucher.value > 0) {
|
if (voucher.value > 0) {
|
||||||
discountText = voucher.type === 'percentage'
|
discountText = voucher.type === 'percentage'
|
||||||
@@ -201,7 +212,7 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
<div>
|
<div>
|
||||||
<a href="${amazonUrl}" target="_blank" class="mb-0 filament-name">${filament}</a>
|
<a href="${amazonUrl}" target="_blank" class="mb-0 filament-name">${filament}</a>
|
||||||
<h5 class="mb-0">
|
<h5 class="mb-0">
|
||||||
£${latestPrice} ${priceChangeIndicator}
|
£${latestPrice.toFixed(2)} ${priceChangeIndicator}
|
||||||
</h5>
|
</h5>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -262,10 +273,11 @@ document.addEventListener("DOMContentLoaded", function () {
|
|||||||
console.error("Error fetching filament data:", error);
|
console.error("Error fetching filament data:", error);
|
||||||
});
|
});
|
||||||
|
|
||||||
showSkeletonLoader();
|
showSkeletonLoader();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
@@ -9,35 +9,42 @@ include '../src/session_check.php';
|
|||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Fetch all filament prices with the latest discount
|
// Query 1: Fetch filament details and price history
|
||||||
$stmt = $pdo->query("
|
$stmt1 = $pdo->query("
|
||||||
SELECT ft.filamentName,
|
SELECT ft.id AS filamentId,
|
||||||
|
ft.filamentName,
|
||||||
ft.brand,
|
ft.brand,
|
||||||
ft.material,
|
ft.material,
|
||||||
ft.color,
|
ft.color,
|
||||||
ft.amazonUrl,
|
ft.amazonUrl,
|
||||||
fp.price,
|
fp.price,
|
||||||
fp.currentDiscount,
|
|
||||||
fp.recordedAt
|
fp.recordedAt
|
||||||
FROM filamentTracker ft
|
FROM filamentTracker ft
|
||||||
JOIN filamentPriceHistory fp ON ft.id = fp.filamentId
|
JOIN filamentPriceHistory fp ON ft.id = fp.filamentId
|
||||||
JOIN (
|
ORDER BY ft.filamentName ASC, fp.recordedAt ASC;
|
||||||
SELECT filamentId, recordedAt
|
|
||||||
FROM (
|
|
||||||
SELECT filamentId, recordedAt,
|
|
||||||
ROW_NUMBER() OVER (PARTITION BY filamentId ORDER BY recordedAt DESC) as rn
|
|
||||||
FROM filamentPriceHistory
|
|
||||||
) ranked
|
|
||||||
WHERE rn <= 380
|
|
||||||
) filtered ON fp.filamentId = filtered.filamentId AND fp.recordedAt = filtered.recordedAt
|
|
||||||
ORDER BY ft.filamentName, fp.recordedAt ASC
|
|
||||||
");
|
");
|
||||||
|
$filaments = $stmt1->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
$filaments = $stmt->fetchAll(PDO::FETCH_ASSOC);
|
// Query 2: Fetch the latest discount for each filament
|
||||||
|
$stmt2 = $pdo->query("
|
||||||
|
SELECT filamentId,
|
||||||
|
JSON_OBJECT(
|
||||||
|
'discount', JSON_OBJECT('value', MAX(JSON_EXTRACT(currentDiscount, '$.discount.value')), 'type', 'percentage'),
|
||||||
|
'voucher', JSON_OBJECT('value', MAX(JSON_EXTRACT(currentDiscount, '$.voucher.value')), 'type', MAX(JSON_UNQUOTE(JSON_EXTRACT(currentDiscount, '$.voucher.type'))))
|
||||||
|
) AS currentDiscount
|
||||||
|
FROM filamentPriceHistory
|
||||||
|
GROUP BY filamentId;
|
||||||
|
");
|
||||||
|
$discounts = $stmt2->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
// Create a mapping of filamentId to discounts
|
||||||
|
$discountMap = [];
|
||||||
|
foreach ($discounts as $discount) {
|
||||||
|
$discountMap[$discount['filamentId']] = json_decode($discount['currentDiscount'], true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Format data for response
|
||||||
$result = [];
|
$result = [];
|
||||||
|
|
||||||
// Format data for charts (grouped by filament)
|
|
||||||
foreach ($filaments as $filament) {
|
foreach ($filaments as $filament) {
|
||||||
$name = $filament['filamentName'];
|
$name = $filament['filamentName'];
|
||||||
|
|
||||||
@@ -48,15 +55,15 @@ try {
|
|||||||
'color' => $filament['color'],
|
'color' => $filament['color'],
|
||||||
'amazonUrl' => $filament['amazonUrl'],
|
'amazonUrl' => $filament['amazonUrl'],
|
||||||
'prices' => [],
|
'prices' => [],
|
||||||
'currentDiscount' => $filament['currentDiscount'] ? json_decode($filament['currentDiscount'], true) : [
|
'currentDiscount' => $discountMap[$filament['filamentId']] ?? [
|
||||||
'discount' => ['value' => 0, 'type' => 'none'],
|
'discount' => ['value' => 0, 'type' => 'none'],
|
||||||
'voucher' => ['value' => 0, 'type' => 'none']
|
'voucher' => ['value' => 0, 'type' => 'none']
|
||||||
] // Decode JSON format and provide fallback
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
$result[$name]['prices'][] = [
|
$result[$name]['prices'][] = [
|
||||||
'price' => $filament['price'],
|
'price' => (float)$filament['price'],
|
||||||
'recordedAt' => $filament['recordedAt']
|
'recordedAt' => $filament['recordedAt']
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user