inventory dashboard setup

This commit is contained in:
Zach Harding
2026-03-25 08:42:17 -04:00
parent db12844dea
commit 9975db20cb
6 changed files with 1657 additions and 191 deletions

View File

@@ -2,51 +2,97 @@ import * as bootstrap from 'bootstrap';
window.bootstrap = bootstrap;
import 'bootstrap/dist/js/bootstrap.bundle.min.js';
// trap browser back and close the modal if open
const cardModal = document.getElementById('cardModal');
const loadingMsg = cardModal.innerHTML;
// Push a new history state when the modal is shown
cardModal.addEventListener('shown.bs.modal', () => {
history.pushState({ modalOpen: true }, null, '#cardModal');
});
// Listen for the browser's back button (popstate event)
window.addEventListener('popstate', (e) => {
if (cardModal.classList.contains('show')) {
const modalInstance = bootstrap.Modal.getInstance(cardModal);
if (modalInstance) {
modalInstance.hide();
document.addEventListener('DOMContentLoaded', () => {
// Initialize all Bootstrap modals
document.querySelectorAll('.modal').forEach(modalEl => {
bootstrap.Modal.getOrCreateInstance(modalEl);
});
// Initialize tooltips
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
if (!el._tooltipInstance) {
el._tooltipInstance = new bootstrap.Tooltip(el, { container: 'body' });
}
}
});
// Trigger a back navigation when the modal is closed via its native controls (X, backdrop click)
cardModal.addEventListener('hide.bs.modal', () => {
cardModal.innerHTML = loadingMsg;
if (history.state && history.state.modalOpen) {
history.back();
}
});
});
// ---------------- DASHBOARD LOGIC ----------------
const toggleBtn = document.getElementById("toggleViewBtn");
const gridView = document.getElementById("gridView");
const tableView = document.getElementById("tableView");
const searchInput = document.getElementById("inventorySearch");
const tbody = document.getElementById("inventoryRows");
import { Tooltip } from "bootstrap";
// Initialize all tooltips globally
const initTooltips = () => {
document.querySelectorAll('[data-bs-toggle="tooltip"]').forEach(el => {
if (!el._tooltipInstance) {
el._tooltipInstance = new Tooltip(el, {
container: 'body', // ensures tooltip is appended to body, important for modals
});
if(toggleBtn && gridView && tableView && tbody) {
// TOGGLE GRID/TABLE
toggleBtn.addEventListener("click", () => {
if(gridView.style.display !== "none") {
gridView.style.display = "none";
tableView.style.display = "block";
toggleBtn.textContent = "Switch to Grid View";
} else {
gridView.style.display = "block";
tableView.style.display = "none";
toggleBtn.textContent = "Switch to Table View";
}
});
};
// Run on page load
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initTooltips);
} else {
initTooltips();
// SEARCH FILTER
if(searchInput) {
searchInput.addEventListener("input", e => {
const term = e.target.value.toLowerCase();
[...tbody.querySelectorAll("tr")].forEach(row => {
row.style.display = row.textContent.toLowerCase().includes(term) ? "" : "none";
});
});
}
// SORTING
document.querySelectorAll("th[data-key]").forEach(th => {
let sortAsc = true;
th.addEventListener("click", () => {
const key = th.dataset.key;
const indexMap = {name:0,set:1,condition:2,qty:3,price:4,market:5,gain:6};
const idx = indexMap[key];
const rows = [...tbody.querySelectorAll("tr")];
rows.sort((a,b) => {
let aText = a.children[idx].textContent.replace(/\$|,/g,'').toLowerCase();
let bText = b.children[idx].textContent.replace(/\$|,/g,'').toLowerCase();
if(!isNaN(aText) && !isNaN(bText)) return sortAsc ? aText-bText : bText-aText;
return sortAsc ? aText.localeCompare(bText) : bText.localeCompare(aText);
});
sortAsc = !sortAsc;
tbody.innerHTML="";
rows.forEach(r => tbody.appendChild(r));
});
});
// INLINE EDITING + GAIN/LOSS UPDATE
tbody.addEventListener("input", e => {
const row = e.target.closest("tr");
if(!row) return;
const priceCell = row.querySelector(".editable-price");
const qtyCell = row.querySelector(".editable-qty");
const marketCell = row.children[5];
const gainCell = row.querySelector(".gain");
if(e.target.classList.contains("editable-price")) {
e.target.textContent = e.target.textContent.replace(/[^\d.]/g,"");
}
if(e.target.classList.contains("editable-qty")) {
e.target.textContent = e.target.textContent.replace(/\D/g,"");
}
const price = parseFloat(priceCell.textContent) || 0;
const qty = parseInt(qtyCell.textContent) || 0;
const market = parseFloat(marketCell.textContent) || 0;
const gain = market - price;
gainCell.textContent = (gain>=0 ? "+" : "-") + Math.abs(gain);
gainCell.className = gain>=0 ? "gain text-success" : "gain text-danger";
});
}
// Optional: observe DOM changes for dynamically added tooltips (e.g., modals loaded later)
const observer = new MutationObserver(() => initTooltips());
observer.observe(document.body, { childList: true, subtree: true });
});