Added price tracking cards

This commit is contained in:
Hickmeister
2025-01-05 03:39:59 +00:00
parent 5a11305caf
commit d96fa63298
4 changed files with 316 additions and 123 deletions

293
public/viewFilament.php Normal file
View File

@@ -0,0 +1,293 @@
<?php include '../src/session_check.php'; ?>
<html lang="en" data-bs-theme="light">
<head>
<!-- Required meta tags -->
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!--favicon-->
<link rel="icon" href="../assets/images/favicon-32x32.png" type="image/png">
<!--plugins-->
<link href="../assets/plugins/vectormap/jquery-jvectormap-2.0.2.css" rel="stylesheet">
<link href="../assets/plugins/simplebar/css/simplebar.css" rel="stylesheet">
<link href="../assets/plugins/perfect-scrollbar/css/perfect-scrollbar.css" rel="stylesheet">
<link href="../assets/plugins/metismenu/css/metisMenu.min.css" rel="stylesheet">
<!-- loader-->
<link href="../assets/css/pace.min.css" rel="stylesheet"/>
<script src="../assets/js/pace.min.js"></script>
<!-- Bootstrap CSS -->
<link href="../assets/css/bootstrap.min.css" rel="stylesheet">
<link href="../assets/css/bootstrap-extended.css" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;500&display=swap" rel="stylesheet">
<link href="../assets/sass/app.css" rel="stylesheet">
<link href="../assets/css/icons.css" rel="stylesheet">
<link href='https://unpkg.com/boxicons@2.1.4/css/boxicons.min.css' rel='stylesheet'>
<!-- Theme Style CSS -->
<link rel="stylesheet" href="../assets/sass/dark-theme.css">
<link rel="stylesheet" href="../assets/sass/semi-dark.css">
<link rel="stylesheet" href="../assets/sass/bordered-theme.css">
<title>TOD Dashboard</title>
</head>
<body>
<!--wrapper-->
<div class="wrapper">
<!--sidebar wrapper -->
<?php include '../src/nav.php'; ?>
<!--end sidebar wrapper -->
<!--start header -->
<?php include '../src/header.php'; ?>
<!--end header -->
<!--start page wrapper -->
<div class="page-wrapper">
<div class="page-content">
<!--start page content -->
<div class="container">
<div class="row" id="filamentCardContainer">
<!-- Cards will be inserted here dynamically -->
</div>
</div>
<!--end page content -->
</div>
</div>
<!--end page wrapper -->
<!--start overlay-->
<div class="overlay mobile-toggle-icon"></div>
<!--end overlay-->
<!--Start Back To Top Button-->
<a href="javaScript:;" class="back-to-top"><i class='bx bxs-up-arrow-alt'></i></a>
<!--End Back To Top Button-->
<footer class="page-footer">
<p class="mb-0">Copyright © 2024. All right reserved.</p>
</footer>
</div>
<!--end wrapper-->
<!-- search modal -->
<div class="modal" id="SearchModal" tabindex="-1">
<div class="modal-dialog modal-dialog-centered modal-dialog-scrollable modal-fullscreen-md-down">
<div class="modal-content">
<div class="modal-header gap-2">
<div class="position-relative popup-search w-100">
<input class="form-control form-control-lg ps-5 border border-3 border-primary" type="search" placeholder="Search">
<span class="position-absolute top-50 search-show ms-3 translate-middle-y start-0 top-50 fs-4"><i class='bx bx-search'></i></span>
</div>
<button type="button" class="btn-close d-md-none" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
</div>
</div>
</div>
<!-- end search modal -->
<!-- Bootstrap JS -->
<script src="../assets/js/bootstrap.bundle.min.js"></script>
<!--plugins-->
<script src="../assets/js/jquery.min.js"></script>
<script src="../assets/plugins/simplebar/js/simplebar.min.js"></script>
<script src="../assets/plugins/metismenu/js/metisMenu.min.js"></script>
<script src="../assets/plugins/perfect-scrollbar/js/perfect-scrollbar.js"></script>
<script src="../assets/plugins/apexcharts-bundle/js/apexcharts.min.js"></script>
<!--app JS-->
<script src="../assets/js/app.js"></script>
<script src="../assets/js/index.js"></script>
<script src="../assets/plugins/peity/jquery.peity.min.js"></script>
<script>
$(".data-attributes span").peity("donut")
</script>
<script>
document.addEventListener("DOMContentLoaded", function () {
const container = document.getElementById('filamentCardContainer');
// Function to generate random colors
function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
// Skeleton Loader Template
function showSkeletonLoader() {
container.innerHTML = `
<div class="col-lg-4 mb-1">
<div class="card radius-10 overflow-hidden skeleton-loader" style="height: 200px;">
<div class="card-body d-flex align-items-center justify-content-between">
<div>
<div class="skeleton-text mb-2" style="width: 70px; height: 20px;"></div>
<div class="skeleton-text" style="width: 50px; height: 30px;"></div>
</div>
<div class="skeleton-button" style="width: 40px; height: 40px;"></div>
</div>
<div class="skeleton-chart" style="height: 120px;"></div>
</div>
</div>
`;
}
// Debounce function to reduce DOM updates
function debounceRender(func, delay) {
let inDebounce;
return function() {
const context = this, args = arguments;
clearTimeout(inDebounce);
inDebounce = setTimeout(() => func.apply(context, args), delay);
};
}
fetch('../src/filamentTracker/getFilamentPrices.php')
.then(response => response.json())
.then(data => {
if (data.status === 'success') {
container.innerHTML = ''; // Clear Skeleton
Object.keys(data.data).forEach(filament => {
const prices = data.data[filament].prices.map(entry => entry.price);
const timestamps = data.data[filament].prices.map(entry => entry.recordedAt);
const latestPrice = prices[prices.length - 1] || 0;
const previousPrice = prices[prices.length - 2] || latestPrice;
const priceDifference = latestPrice - previousPrice;
let priceChangeIndicator;
if (priceDifference > 0) {
priceChangeIndicator = `<span class="text-danger ms-4">
<i class="bx bx-up-arrow-alt"></i> +£${priceDifference.toFixed(2)}</span>`;
} else if (priceDifference < 0) {
priceChangeIndicator = `<span class="text-success ms-4">
<i class="bx bx-down-arrow-alt"></i> £${Math.abs(priceDifference).toFixed(2)}</span>`;
} else {
priceChangeIndicator = `<span class="text-muted ms-4">
<i class="bx bx-minus"></i> £0.00</span>`;
}
const amazonUrl = data.data[filament].amazonUrl || '#';
const chartId = `chart-${filament.replace(/\s+/g, '-')}`;
const chartColor = getRandomColor();
// Create Card HTML with cart button
const cardHTML = `
<div class="col-lg-4 mb-1">
<div class="card radius-10 overflow-hidden">
<div class="card-body d-flex align-items-center justify-content-between">
<div>
<a href="${amazonUrl}" target="_blank" class="mb-0 filament-name">${filament}</a>
<h5 class="mb-0">
£${latestPrice} ${priceChangeIndicator}
</h5>
</div>
</div>
<div id="${chartId}"></div>
</div>
</div>
`;
container.insertAdjacentHTML('beforeend', cardHTML);
// Observer to Wait for DOM Insertion
const observer = new MutationObserver(debounceRender(() => {
const chartElement = document.getElementById(chartId);
if (chartElement) {
const chartOptions = {
series: [{
name: "Price",
data: prices
}],
chart: {
type: "area",
height: 110,
toolbar: {
show: false
},
zoom: {
enabled: false
},
dropShadow: {
enabled: true,
top: 3,
left: 14,
blur: 4,
opacity: 0.12,
color: chartColor
},
sparkline: {
enabled: true
}
},
markers: {
size: 0,
colors: [chartColor],
strokeColors: "#fff",
strokeWidth: 2,
hover: {
size: 7
}
},
dataLabels: {
enabled: false
},
stroke: {
show: true,
width: 2.4,
curve: "smooth"
},
colors: [chartColor],
xaxis: {
categories: timestamps,
labels: {
show: false
}
},
fill: {
opacity: 1
},
tooltip: {
theme: "dark",
x: {
show: false
},
y: {
formatter: function (value, { dataPointIndex }) {
const date = new Date(timestamps[dataPointIndex]);
return `£${value} - ${date.toLocaleDateString()}`;
}
}
}
};
const chart = new ApexCharts(chartElement, chartOptions);
chart.render();
observer.disconnect();
}
}, 300));
observer.observe(container, { childList: true, subtree: true });
});
} else {
container.innerHTML = '<p class="text-center">No filament data available.</p>';
}
})
.catch(error => {
console.error("Error fetching filament data:", error);
});
showSkeletonLoader();
});
</script>
</body>
</html>