Price Report Improvements
Some checks failed
Deploy to Server / deploy (push) Failing after 2m0s

This commit is contained in:
2025-11-03 17:12:21 -05:00
parent 338bdf838b
commit a451ff262a
3 changed files with 97 additions and 4 deletions

View File

@@ -41,14 +41,16 @@ jobs:
# Reset any local changes (e.g., package-lock.json, build artifacts) and sync to origin/main
git fetch --prune origin
git reset --hard origin/main
git clean -fdx
git clean -fdx -e .env -e .env.*
# Install Node.js deps and build without modifying lockfile
"$NPM" ci --no-audit --no-fund
"$NPM" run build
# Ensure systemd service exists and restart
sudo -n systemctl restart train-id
if systemctl list-unit-files | grep -q "${SERVICE}.service"; then
sudo systemctl restart "$SERVICE"
else
sudo systemctl start "$SERVICE"
echo "Warning: ${SERVICE}.service not found; start your process manager manually."
fi
EOF

View File

@@ -77,11 +77,17 @@
<section id="tab-prices" class="card" style="display:none;">
<h3>Price Report</h3>
<div style="margin-bottom:16px; font-size:1.2em; font-weight:bold;">
Total Value: <span id="totalPrice">$0.00</span>
</div>
<div class="actions">
<button id="updatePrices">Update Prices</button>
</div>
<div style="display:flex; gap:16px; align-items:flex-start; flex-wrap:wrap;">
<canvas id="pricePie" width="360" height="360" style="border:1px solid #eee; border-radius:8px;"></canvas>
<div style="position:relative;">
<canvas id="pricePie" width="360" height="360" style="border:1px solid #eee; border-radius:8px; cursor:pointer;"></canvas>
<div id="pieTooltip" style="position:absolute; display:none; background:rgba(0,0,0,0.8); color:white; padding:8px; border-radius:4px; pointer-events:none; white-space:nowrap; z-index:1000;"></div>
</div>
<div style="flex:1; min-width:280px;">
<table id="skuPrices">
<thead>
@@ -115,6 +121,9 @@
const skuTbody = document.querySelector('#skuPrices tbody');
const updatePricesBtn = $('#updatePrices');
const pricePie = /** @type {HTMLCanvasElement} */ (document.getElementById('pricePie'));
const totalPriceEl = $('#totalPrice');
const pieTooltip = $('#pieTooltip');
let pieSliceData = []; // Store slice data for hover detection
function showTab(tab) {
if (tab === 'items') {
@@ -172,6 +181,7 @@
const ctx = pricePie.getContext('2d');
if (!ctx) return;
ctx.clearRect(0, 0, pricePie.width, pricePie.height);
pieSliceData = [];
const total = items.reduce((sum, it) => sum + (it.totalValue || 0), 0);
if (total <= 0) {
ctx.fillStyle = '#666';
@@ -186,6 +196,15 @@
const angle = (val / total) * Math.PI * 2;
const end = start + angle;
const hue = (i * 47) % 360;
// Store slice data for hover detection
pieSliceData.push({
start,
end,
item: it,
hue
});
ctx.beginPath();
ctx.moveTo(cx, cy);
ctx.arc(cx, cy, r, start, end);
@@ -196,10 +215,76 @@
});
}
// Handle pie chart hover
pricePie.addEventListener('mousemove', (e) => {
const rect = pricePie.getBoundingClientRect();
const x = e.clientX - rect.left - pricePie.width / 2;
const y = e.clientY - rect.top - pricePie.height / 2;
const distance = Math.sqrt(x * x + y * y);
const cx = pricePie.width / 2;
const cy = pricePie.height / 2;
const r = Math.min(cx, cy) - 8;
if (distance > r) {
pieTooltip.style.display = 'none';
return;
}
// Calculate angle from center (atan2 gives angle from positive x-axis, clockwise)
let angle = Math.atan2(y, x);
// Normalize to 0-2π and adjust for pie starting at top (-Math.PI/2)
// Add Math.PI/2 to rotate so top is 0, then normalize to 0-2π
angle = angle + Math.PI / 2;
if (angle < 0) angle += 2 * Math.PI;
if (angle >= 2 * Math.PI) angle -= 2 * Math.PI;
// Find which slice contains this angle
for (const slice of pieSliceData) {
// Slices start at -Math.PI/2 (top), normalize to 0-2π
let sliceStart = slice.start + Math.PI / 2;
let sliceEnd = slice.end + Math.PI / 2;
// Normalize to 0-2π
if (sliceStart < 0) sliceStart += 2 * Math.PI;
if (sliceEnd < 0) sliceEnd += 2 * Math.PI;
if (sliceStart >= 2 * Math.PI) sliceStart -= 2 * Math.PI;
if (sliceEnd >= 2 * Math.PI) sliceEnd -= 2 * Math.PI;
let contains = false;
if (sliceStart <= sliceEnd) {
// Normal case: slice doesn't wrap around
contains = angle >= sliceStart && angle <= sliceEnd;
} else {
// Slice wraps around 0 (e.g., starts at 350° and ends at 10°)
contains = angle >= sliceStart || angle <= sliceEnd;
}
if (contains) {
const it = slice.item;
pieTooltip.textContent = `ID: ${it.id || 'N/A'} | ${it.description || 'No name'} | $${it.totalValue.toFixed(2)}`;
pieTooltip.style.display = 'block';
pieTooltip.style.left = (e.clientX - rect.left + 10) + 'px';
pieTooltip.style.top = (e.clientY - rect.top + 10) + 'px';
return;
}
}
pieTooltip.style.display = 'none';
});
pricePie.addEventListener('mouseleave', () => {
pieTooltip.style.display = 'none';
});
async function loadPriceReport() {
const res = await fetch('/api/price-report');
const data = await res.json();
// table of skus
// Calculate and display total price
const total = data.items.reduce((sum, it) => sum + (it.totalValue || 0), 0);
totalPriceEl.textContent = `$${total.toFixed(2)}`;
// table of skus (already sorted by ID from API)
skuTbody.innerHTML = '';
for (const s of data.skus) {
const tr = document.createElement('tr');

View File

@@ -277,7 +277,13 @@ router.get('/price-report', async (_req, res) => {
};
}
}
const skuList = Object.values(skuListMap).sort((a, b) => (a.sku || '').localeCompare(b.sku || ''));
const skuList = Object.values(skuListMap).sort((a, b) => {
// Sort by ID first (null IDs go to end), then by SKU
if (a.id === null && b.id === null) return (a.sku || '').localeCompare(b.sku || '');
if (a.id === null) return 1;
if (b.id === null) return -1;
return (a.id || 0) - (b.id || 0);
});
res.json({ items: itemValues, skus: skuList });
} catch (err: any) {
console.error(err);