modified layout and made it so you can switch between card modals and keep the pricing chart
This commit is contained in:
@@ -2,7 +2,6 @@ import Chart from 'chart.js/auto';
|
||||
|
||||
const CONDITIONS = ["Near Mint", "Lightly Played", "Moderately Played", "Heavily Played", "Damaged"];
|
||||
|
||||
// Match the $tiers colors from your SCSS exactly
|
||||
const CONDITION_COLORS = {
|
||||
"Near Mint": { active: 'rgba(156, 204, 102, 1)', muted: 'rgba(156, 204, 102, 0.67)' },
|
||||
"Lightly Played": { active: 'rgba(211, 225, 86, 1)', muted: 'rgba(211, 225, 86, 0.67)' },
|
||||
@@ -11,7 +10,7 @@ const CONDITION_COLORS = {
|
||||
"Damaged": { active: 'rgba(255, 167, 36, 1)', muted: 'rgba(255, 167, 36, 0.67)' },
|
||||
};
|
||||
|
||||
const RANGE_DAYS = { '1m': 30, '3m': 90 };
|
||||
const RANGE_DAYS = { '1m': 30, '3m': 90, '6m': 180, '1y': 365, 'all': Infinity };
|
||||
|
||||
let chartInstance = null;
|
||||
let allHistory = [];
|
||||
@@ -24,8 +23,19 @@ function formatDate(dateStr) {
|
||||
return d.toLocaleDateString('en-US', { month: 'short', day: 'numeric' });
|
||||
}
|
||||
|
||||
function setEmptyState(isEmpty) {
|
||||
const modal = document.getElementById('cardModal');
|
||||
const empty = modal?.querySelector('#priceHistoryEmpty');
|
||||
const canvasWrapper = empty?.nextElementSibling;
|
||||
if (!empty || !canvasWrapper) return;
|
||||
empty.classList.toggle('d-none', !isEmpty);
|
||||
canvasWrapper.classList.toggle('d-none', isEmpty);
|
||||
}
|
||||
|
||||
function buildChartData(history, rangeKey) {
|
||||
const cutoff = new Date(Date.now() - RANGE_DAYS[rangeKey] * 86_400_000);
|
||||
const cutoff = RANGE_DAYS[rangeKey] === Infinity
|
||||
? new Date(0)
|
||||
: new Date(Date.now() - RANGE_DAYS[rangeKey] * 86_400_000);
|
||||
|
||||
const filtered = history.filter(r => new Date(r.calculatedAt) >= cutoff);
|
||||
|
||||
@@ -40,17 +50,22 @@ function buildChartData(history, rangeKey) {
|
||||
lookup[row.condition][row.calculatedAt] = Number(row.marketPrice);
|
||||
}
|
||||
|
||||
// Check specifically whether the active condition has any data points
|
||||
const activeConditionDates = allDates.filter(
|
||||
date => lookup[activeCondition]?.[date] != null
|
||||
);
|
||||
const activeConditionHasData = activeConditionDates.length > 0;
|
||||
|
||||
const datasets = CONDITIONS.map(condition => {
|
||||
const isActive = condition === activeCondition;
|
||||
const colors = CONDITION_COLORS[condition];
|
||||
const data = allDates.map(date => lookup[condition]?.[date] ?? null);
|
||||
|
||||
return {
|
||||
label: condition,
|
||||
data,
|
||||
borderColor: isActive ? colors.active : colors.muted,
|
||||
borderWidth: isActive ? 2.5 : 1,
|
||||
pointRadius: isActive ? 3 : 0,
|
||||
borderWidth: isActive ? 2 : 1,
|
||||
pointRadius: isActive ? 2.5 : 0,
|
||||
pointHoverRadius: isActive ? 5 : 3,
|
||||
pointBackgroundColor: isActive ? colors.active : colors.muted,
|
||||
tension: 0.3,
|
||||
@@ -60,12 +75,20 @@ function buildChartData(history, rangeKey) {
|
||||
};
|
||||
});
|
||||
|
||||
return { labels, datasets };
|
||||
return { labels, datasets, hasData: allDates.length > 0, activeConditionHasData };
|
||||
}
|
||||
|
||||
function updateChart() {
|
||||
if (!chartInstance) return;
|
||||
const { labels, datasets } = buildChartData(allHistory, activeRange);
|
||||
const { labels, datasets, hasData, activeConditionHasData } = buildChartData(allHistory, activeRange);
|
||||
|
||||
// Show empty state if no data at all, or if the active condition specifically has no data
|
||||
if (!hasData || !activeConditionHasData) {
|
||||
setEmptyState(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setEmptyState(false);
|
||||
chartInstance.data.labels = labels;
|
||||
chartInstance.data.datasets = datasets;
|
||||
chartInstance.update('none');
|
||||
@@ -85,11 +108,18 @@ function initPriceChart(canvas) {
|
||||
}
|
||||
|
||||
if (!allHistory.length) {
|
||||
console.warn('No price history data for this card');
|
||||
setEmptyState(true);
|
||||
return;
|
||||
}
|
||||
|
||||
const { labels, datasets } = buildChartData(allHistory, activeRange);
|
||||
const { labels, datasets, hasData, activeConditionHasData } = buildChartData(allHistory, activeRange);
|
||||
|
||||
if (!hasData || !activeConditionHasData) {
|
||||
setEmptyState(true);
|
||||
return;
|
||||
}
|
||||
|
||||
setEmptyState(false);
|
||||
|
||||
chartInstance = new Chart(canvas.getContext('2d'), {
|
||||
type: 'line',
|
||||
@@ -151,64 +181,54 @@ function initPriceChart(canvas) {
|
||||
});
|
||||
}
|
||||
|
||||
function setupObserver() {
|
||||
function initFromCanvas(canvas) {
|
||||
activeCondition = "Near Mint";
|
||||
activeRange = '1m';
|
||||
const modal = document.getElementById('cardModal');
|
||||
if (!modal) {
|
||||
console.error('cardModal element not found');
|
||||
return;
|
||||
}
|
||||
modal?.querySelectorAll('.price-range-btn').forEach(b => {
|
||||
b.classList.toggle('active', b.dataset.range === '1m');
|
||||
});
|
||||
initPriceChart(canvas);
|
||||
}
|
||||
|
||||
const observer = new MutationObserver(() => {
|
||||
const canvas = document.getElementById('priceHistoryChart');
|
||||
if (!canvas) return;
|
||||
function setup() {
|
||||
const modal = document.getElementById('cardModal');
|
||||
if (!modal) return;
|
||||
|
||||
// Disconnect immediately so tab switches don't retrigger initPriceChart
|
||||
observer.disconnect();
|
||||
|
||||
activeCondition = "Near Mint";
|
||||
activeRange = '1m';
|
||||
document.querySelectorAll('.price-range-btn').forEach(b => {
|
||||
b.classList.toggle('active', b.dataset.range === '1m');
|
||||
});
|
||||
|
||||
initPriceChart(canvas);
|
||||
modal.addEventListener('card-modal:swapped', () => {
|
||||
const canvas = modal.querySelector('#priceHistoryChart');
|
||||
if (canvas) initFromCanvas(canvas);
|
||||
});
|
||||
|
||||
observer.observe(modal, { childList: true, subtree: true });
|
||||
|
||||
modal.addEventListener('hidden.bs.modal', () => {
|
||||
if (chartInstance) {
|
||||
chartInstance.destroy();
|
||||
chartInstance = null;
|
||||
}
|
||||
if (chartInstance) { chartInstance.destroy(); chartInstance = null; }
|
||||
allHistory = [];
|
||||
// Reconnect so the next card open is detected
|
||||
observer.observe(modal, { childList: true, subtree: true });
|
||||
});
|
||||
|
||||
document.addEventListener('shown.bs.tab', (e) => {
|
||||
if (!modal.contains(e.target)) return;
|
||||
const target = e.target?.getAttribute('data-bs-target');
|
||||
const conditionMap = {
|
||||
'#nav-nm': 'Near Mint',
|
||||
'#nav-lp': 'Lightly Played',
|
||||
'#nav-mp': 'Moderately Played',
|
||||
'#nav-hp': 'Heavily Played',
|
||||
'#nav-dmg': 'Damaged',
|
||||
};
|
||||
if (target && conditionMap[target]) {
|
||||
activeCondition = conditionMap[target];
|
||||
updateChart();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const btn = e.target?.closest('.price-range-btn');
|
||||
if (!btn || !modal.contains(btn)) return;
|
||||
modal.querySelectorAll('.price-range-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
activeRange = btn.dataset.range ?? '1m';
|
||||
updateChart();
|
||||
});
|
||||
}
|
||||
|
||||
document.addEventListener('shown.bs.tab', (e) => {
|
||||
const target = e.target?.getAttribute('data-bs-target');
|
||||
const conditionMap = {
|
||||
'#nav-nm': 'Near Mint',
|
||||
'#nav-lp': 'Lightly Played',
|
||||
'#nav-mp': 'Moderately Played',
|
||||
'#nav-hp': 'Heavily Played',
|
||||
'#nav-dmg': 'Damaged',
|
||||
};
|
||||
if (target && conditionMap[target]) {
|
||||
activeCondition = conditionMap[target];
|
||||
updateChart();
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('click', (e) => {
|
||||
const btn = e.target?.closest('.price-range-btn');
|
||||
if (!btn) return;
|
||||
document.querySelectorAll('.price-range-btn').forEach(b => b.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
activeRange = btn.dataset.range ?? '1m';
|
||||
updateChart();
|
||||
});
|
||||
|
||||
setupObserver();
|
||||
setup();
|
||||
Reference in New Issue
Block a user