Update Filament Dryer

This commit is contained in:
Hickmeister
2025-01-15 17:50:57 +00:00
parent cc4e915cf5
commit 053662c2e8
3 changed files with 454 additions and 329 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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']
]; ];
} }