document.addEventListener('DOMContentLoaded', function() { // Event Listeners document.getElementById('createTransactionBtn').addEventListener('click', () => { showTransactionModal(); }); document.getElementById('addVendorBtn').addEventListener('click', () => { const vendorName = prompt('Enter vendor name:'); if (vendorName) { createVendor(vendorName); } }); document.getElementById('addMarketplaceBtn').addEventListener('click', () => { const marketplaceName = prompt('Enter marketplace name:'); if (marketplaceName) { createMarketplace(marketplaceName); } }); document.getElementById('addItemBtn').addEventListener('click', addItem); document.getElementById('transactionType').addEventListener('change', (e) => { const marketplaceSection = document.getElementById('marketplaceSection'); if (e.target.value === 'sale') { marketplaceSection.classList.remove('hidden'); } else { marketplaceSection.classList.add('hidden'); } }); document.getElementById('saveTransactionBtn').addEventListener('click', saveTransaction); // Load initial data loadVendors(); loadMarketplaces(); loadTransactions(); addItem(); // Add first item by default }); // Modal Functions function showTransactionModal() { document.getElementById('createTransactionModal').classList.remove('hidden'); } function closeTransactionModal() { document.getElementById('createTransactionModal').classList.add('hidden'); } // Item Management Functions function addItem() { const itemsContainer = document.getElementById('itemsContainer'); const itemIndex = itemsContainer.children.length; const itemDiv = document.createElement('div'); itemDiv.className = 'item border border-gray-700 rounded-lg p-4'; itemDiv.innerHTML = `
Item ${itemIndex + 1}
${itemIndex > 0 ? '' : ''}
`; itemsContainer.appendChild(itemDiv); // Add event listeners for the new item const productSearch = itemDiv.querySelector('.product-search'); const setEvBtn = itemDiv.querySelector('.set-ev-btn'); const suggestionsDiv = itemDiv.querySelector('.product-suggestions'); // Product search functionality let searchTimeout; productSearch.addEventListener('input', (e) => { clearTimeout(searchTimeout); const searchTerm = e.target.value; if (searchTerm.length < 2) { suggestionsDiv.classList.add('hidden'); return; } searchTimeout = setTimeout(async () => { try { const response = await fetch(`/api/inventory/products/search?q=${encodeURIComponent(searchTerm)}`); const products = await response.json(); suggestionsDiv.innerHTML = products.map(product => `
${product.name}
`).join(''); suggestionsDiv.classList.remove('hidden'); } catch (error) { console.error('Error searching products:', error); } }, 300); }); // Handle suggestion selection suggestionsDiv.addEventListener('click', async (e) => { const selectedProduct = e.target.closest('[data-tcgplayer_product_id]'); if (selectedProduct) { const productId = selectedProduct.dataset.tcgplayer_product_id; productSearch.value = selectedProduct.textContent.trim(); productSearch.dataset.tcgplayer_product_id = productId; suggestionsDiv.classList.add('hidden'); // Check expected value try { const response = await fetch(`/api/inventory/products/${productId}/expected-value`); const expectedValue = await response.json(); if (expectedValue === null || expectedValue === undefined || isNaN(expectedValue)) { setEvBtn.classList.remove('hidden'); } else { setEvBtn.classList.add('hidden'); } } catch (error) { console.error('Error checking expected value:', error); setEvBtn.classList.remove('hidden'); } } }); // Handle expected value setting setEvBtn.addEventListener('click', async () => { const expectedValue = prompt('Enter expected value for this product:'); if (expectedValue && !isNaN(expectedValue)) { try { const response = await fetch('/api/inventory/products/expected-value', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ tcgplayer_product_id: parseInt(productSearch.dataset.tcgplayer_product_id), expected_value: parseFloat(expectedValue) }) }); if (response.ok) { setEvBtn.classList.add('hidden'); } else { const error = await response.json(); alert(`Failed to set expected value: ${error.detail}`); } } catch (error) { console.error('Error setting expected value:', error); alert('Failed to set expected value. Please try again.'); } } }); // Handle item removal const removeBtn = itemDiv.querySelector('.remove-item-btn'); if (removeBtn) { removeBtn.addEventListener('click', () => { itemDiv.remove(); }); } } // API Functions async function loadVendors() { try { const response = await fetch('/api/inventory/vendors'); const vendors = await response.json(); const vendorSelect = document.getElementById('vendorSelect'); // Clear existing options except the first one while (vendorSelect.options.length > 1) { vendorSelect.remove(1); } // Add new options vendors.forEach(vendor => { const option = new Option(vendor.name, vendor.id); vendorSelect.add(option); }); } catch (error) { console.error('Error loading vendors:', error); } } async function loadMarketplaces() { try { const response = await fetch('/api/inventory/marketplaces'); const marketplaces = await response.json(); const marketplaceSelect = document.getElementById('marketplaceSelect'); // Clear existing options except the first one while (marketplaceSelect.options.length > 1) { marketplaceSelect.remove(1); } // Add new options marketplaces.forEach(marketplace => { const option = new Option(marketplace.name, marketplace.id); marketplaceSelect.add(option); }); } catch (error) { console.error('Error loading marketplaces:', error); } } async function createVendor(name) { try { const response = await fetch('/api/inventory/vendors', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ vendor_name: name }) }); if (response.ok) { const newVendor = await response.json(); // Add new vendor to select and select it const vendorSelect = document.getElementById('vendorSelect'); const option = new Option(newVendor.name, newVendor.id); vendorSelect.add(option); vendorSelect.value = newVendor.id; } else { throw new Error('Failed to create vendor'); } } catch (error) { console.error('Error creating vendor:', error); alert('Failed to create vendor. Please try again.'); } } async function createMarketplace(name) { try { const response = await fetch('/api/inventory/marketplaces', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ marketplace_name: name }) }); if (response.ok) { const newMarketplace = await response.json(); // Add new marketplace to select and select it const marketplaceSelect = document.getElementById('marketplaceSelect'); const option = new Option(newMarketplace.name, newMarketplace.id); marketplaceSelect.add(option); marketplaceSelect.value = newMarketplace.id; } else { throw new Error('Failed to create marketplace'); } } catch (error) { console.error('Error creating marketplace:', error); alert('Failed to create marketplace. Please try again.'); } } // Transaction Management Functions let currentPage = 1; let currentLimit = 25; let totalTransactions = 0; // Manabox Files Pagination let currentManaboxPage = 1; let currentManaboxLimit = 5; let totalManaboxFiles = 0; // DOM Elements const transactionsBody = document.getElementById('transactionsBody'); const prevPageBtn = document.getElementById('prevPageBtn'); const nextPageBtn = document.getElementById('nextPageBtn'); const pageInfo = document.getElementById('pageInfo'); const limitSelect = document.getElementById('limitSelect'); const transactionDetailsModal = document.getElementById('transactionDetailsModal'); const transactionDetails = document.getElementById('transactionDetails'); // Event Listeners document.addEventListener('DOMContentLoaded', () => { loadTransactions(); limitSelect.addEventListener('change', (e) => { currentLimit = parseInt(e.target.value); currentPage = 1; loadTransactions(); }); prevPageBtn.addEventListener('click', () => { if (currentPage > 1) { currentPage--; loadTransactions(); } }); nextPageBtn.addEventListener('click', () => { if (currentPage * currentLimit < totalTransactions) { currentPage++; loadTransactions(); } }); }); // Load transactions with pagination async function loadTransactions() { try { const skip = (currentPage - 1) * currentLimit; const response = await fetch(`/api/inventory/transactions?skip=${skip}&limit=${currentLimit}`); const data = await response.json(); totalTransactions = data.total; renderTransactions(data.transactions); updatePaginationControls(); } catch (error) { console.error('Error loading transactions:', error); } } // Render transactions in the table function renderTransactions(transactions) { transactionsBody.innerHTML = ''; transactions.forEach(transaction => { const row = document.createElement('tr'); row.className = 'hover:bg-gray-700 cursor-pointer'; row.onclick = () => showTransactionDetails(transaction); row.innerHTML = ` ${formatDate(transaction.transaction_date)} ${transaction.transaction_type} ${getPartyName(transaction)} $${transaction.transaction_total_amount.toFixed(2)} ${transaction.transaction_notes || ''} `; transactionsBody.appendChild(row); }); } // Update pagination controls function updatePaginationControls() { const totalPages = Math.ceil(totalTransactions / currentLimit); prevPageBtn.disabled = currentPage === 1; nextPageBtn.disabled = currentPage === totalPages; pageInfo.textContent = `Page ${currentPage} of ${totalPages}`; } // Show transaction details modal async function showTransactionDetails(transaction) { try { const response = await fetch(`/api/inventory/transactions/${transaction.id}`); const transactionData = await response.json(); transactionDetails.innerHTML = `

Date

${formatDate(transactionData.transaction_date)}

Type

${transactionData.transaction_type}

${transactionData.transaction_type === 'purchase' ? 'Vendor' : 'Customer'}

${getPartyName(transactionData)}

Total Amount

$${transactionData.transaction_total_amount.toFixed(2)}

Notes

${transactionData.transaction_notes || 'No notes'}

Items

${transactionData.transaction_items.map(item => ` `).join('')}
Item ID Unit Price
${item.inventory_item_id} $${item.unit_price.toFixed(2)}
`; transactionDetailsModal.classList.remove('hidden'); } catch (error) { console.error('Error loading transaction details:', error); } } // Close transaction details modal function closeTransactionDetailsModal() { transactionDetailsModal.classList.add('hidden'); } // Show inventory item details async function showInventoryItemDetails(inventoryItemId) { try { const response = await fetch(`/api/inventory/items/${inventoryItemId}`); const itemData = await response.json(); // Check for open events first if it's not a card let hasOpenEvents = false; if (itemData.item_type !== 'card') { const openEventsResponse = await fetch(`/api/inventory/items/${inventoryItemId}/open-events`); const openEventsData = await openEventsResponse.json(); hasOpenEvents = openEventsData.open_events.length > 0; } // Create a new modal for inventory item details const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'; modal.innerHTML = `

Inventory Item Details

Item ID

${itemData.id}

Physical Item ID

${itemData.physical_item_id}

Cost Basis

$${itemData.cost_basis.toFixed(2)}

Parent ID

${itemData.parent_id || 'None'}

Created At

${formatDate(itemData.created_at)}

Updated At

${formatDate(itemData.updated_at)}

${itemData.listed_price || itemData.recommended_price ? `

Pricing

${itemData.listed_price ? `
Listed Price: $${itemData.listed_price.toFixed(2)}
` : ''} ${itemData.recommended_price ? `
Recommended Price: $${itemData.recommended_price.toFixed(2)}
` : ''}
` : ''}

Product Details

Product ID

${itemData.product.id}

TCGPlayer Product ID

${itemData.product.tcgplayer_product_id}

Name

${itemData.product.name}

Image

${itemData.product.name}

Category

${itemData.product.category_name}

Group

${itemData.product.group_name}

Market Price

$${itemData.product.market_price.toFixed(2)}

TCGPlayer URL

View on TCGPlayer
${itemData.product.category_name === 'Magic' && itemData.item_type !== 'card' && !hasOpenEvents ? `

Box Actions

` : ''}
`; document.body.appendChild(modal); // Only load open events if not a card if (itemData.item_type !== 'card') { await loadOpenEvents(inventoryItemId); } } catch (error) { console.error('Error loading inventory item details:', error); } } // Load open events for an inventory item async function loadOpenEvents(inventoryItemId) { try { const response = await fetch(`/api/inventory/items/${inventoryItemId}/open-events`); const data = await response.json(); const openEventsSection = document.getElementById('openEventsSection'); const openEventsTable = document.getElementById('openEventsTable'); if (!openEventsSection || !openEventsTable) return; if (data.open_events.length === 0) { openEventsSection.classList.add('hidden'); return; } openEventsSection.classList.remove('hidden'); openEventsTable.innerHTML = ` ${data.open_events.map(event => ` `).join('')}
Date Source Item ID Action
${formatDate(event.created_at)} ${event.source_item_id}
`; } catch (error) { console.error('Error loading open events:', error); } } // Show resulting items from an open event async function showOpenEventResultingItems(inventoryItemId, openEventId) { try { const response = await fetch(`/api/inventory/items/${inventoryItemId}/open-events/${openEventId}/resulting-items`); const resultingItems = await response.json(); const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'; modal.innerHTML = `

Resulting Items

${resultingItems.map(item => ` `).join('')}
Card Name Set Market Price Action
${item.product.name} ${item.product.group_name} $${item.product.market_price.toFixed(2)}
`; document.body.appendChild(modal); } catch (error) { console.error('Error loading resulting items:', error); } } // Add new function to handle confirming listings async function confirmListingsForOpenEvent(inventoryItemId, openEventId) { try { const response = await fetch(`/api/inventory/items/${inventoryItemId}/open-events/${openEventId}/confirm-listings`, { method: 'POST', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to confirm listings'); } // Get the filename from the Content-Disposition header const contentDisposition = response.headers.get('content-disposition'); let filename = 'tcgplayer_listings.csv'; if (contentDisposition) { const filenameMatch = contentDisposition.match(/filename=(.+)/); if (filenameMatch) { filename = filenameMatch[1]; } } // Create a blob from the response and download it const blob = await response.blob(); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); window.URL.revokeObjectURL(url); document.body.removeChild(a); // Close the modal const modal = document.querySelector('.fixed'); if (modal) { modal.remove(); } } catch (error) { console.error('Error confirming listings:', error); alert(error.message || 'Failed to confirm listings. Please try again.'); } } // Show open box modal async function showOpenBoxModal(inventoryItemId, physicalItemId) { try { // Fetch Manabox files with pagination const skip = (currentManaboxPage - 1) * currentManaboxLimit; const response = await fetch(`/api/manabox/manabox-file-uploads?skip=${skip}&limit=${currentManaboxLimit}`); const files = await response.json(); // Create modal for selecting Manabox file const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'; modal.innerHTML = `

Select Manabox Files

${files.map(file => ` `).join('')}
Select Source Description Upload Date
${file.file_metadata.source} ${file.file_metadata.description} ${formatDate(file.created_at)}
Page ${currentManaboxPage}
`; document.body.appendChild(modal); } catch (error) { console.error('Error loading Manabox files:', error); } } // Change Manabox page async function changeManaboxPage(inventoryItemId, physicalItemId, newPage) { if (newPage < 1) return; currentManaboxPage = newPage; const modal = document.querySelector('.fixed'); if (modal) { modal.remove(); } await showOpenBoxModal(inventoryItemId, physicalItemId); } // Open box with selected Manabox files async function openBoxWithFiles(inventoryItemId, physicalItemId) { try { // Get selected file IDs const selectedFiles = Array.from(document.querySelectorAll('.manabox-file-checkbox:checked')) .map(checkbox => parseInt(checkbox.value)); if (selectedFiles.length === 0) { alert('Please select at least one Manabox file'); return; } const response = await fetch(`/api/inventory/items/${inventoryItemId}/open`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ inventory_item_id: inventoryItemId, manabox_file_upload_ids: selectedFiles }) }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to open box'); } const openEvent = await response.json(); if (!openEvent) { throw new Error('No data received from server'); } showOpenEventDetails(openEvent); // Close the modal const modal = document.querySelector('.fixed'); if (modal) { modal.remove(); } } catch (error) { console.error('Error opening box:', error); alert(error.message || 'Failed to open box. Please try again.'); } } // Show open event details function showOpenEventDetails(openEvent) { if (!openEvent) { console.error('No open event data provided'); return; } const modal = document.createElement('div'); modal.className = 'fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center'; modal.innerHTML = `

Box Opening Results

Open Event ID

${openEvent.id || 'N/A'}

Date

${openEvent.created_at ? formatDate(openEvent.created_at) : 'N/A'}

Opened Items

${openEvent.source_item_id ? ` ` : ''}
${openEvent.items && openEvent.items.length > 0 ? openEvent.items.map(item => ` `).join('') : `` }
Card Name Set Condition
${item.card_name || 'N/A'} ${item.set_name || 'N/A'} ${item.condition || 'N/A'}
No items found
`; document.body.appendChild(modal); } // Add new function to handle creating listings async function createListingsForOpenEvent(inventoryItemId, openEventId) { try { const response = await fetch(`/api/inventory/items/${inventoryItemId}/open-events/${openEventId}/create-listings`, { method: 'POST', headers: { 'Content-Type': 'application/json', } }); if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Failed to create listings'); } const result = await response.json(); alert('Listings created successfully!'); // Close the modal const modal = document.querySelector('.fixed'); if (modal) { modal.remove(); } } catch (error) { console.error('Error creating listings:', error); alert(error.message || 'Failed to create listings. Please try again.'); } } // Helper functions function formatDate(dateString) { return new Date(dateString).toLocaleString(); } function getPartyName(transaction) { if (transaction.transaction_type === 'purchase') { return transaction.vendor?.name || 'Unknown Vendor'; } else { return transaction.customer?.name || 'Unknown Customer'; } } // Close modal when clicking outside transactionDetailsModal.addEventListener('click', (e) => { if (e.target === transactionDetailsModal) { closeTransactionDetailsModal(); } }); async function saveTransaction() { const form = document.getElementById('transactionForm'); if (!form.checkValidity()) { form.reportValidity(); return; } const transactionType = document.getElementById('transactionType').value; // Collect all items with their data const items = Array.from(document.querySelectorAll('.item')).map(item => { const productId = parseInt(item.querySelector('.product-search').dataset.tcgplayer_product_id); const unitPrice = parseFloat(item.querySelector('.unit-cost').value); const quantity = parseInt(item.querySelector('.quantity').value); const itemType = item.querySelector('.item-type').value; if (isNaN(productId) || isNaN(unitPrice) || isNaN(quantity)) { throw new Error('Invalid item data'); } return { product_id: productId, unit_price: unitPrice, quantity: quantity, item_type: itemType }; }); if (items.length === 0) { alert('Please add at least one item to the transaction'); return; } const transactionData = { transaction_date: document.getElementById('transactionDate').value, transaction_notes: document.getElementById('transactionNotes').value, items: items }; if (transactionType === 'purchase') { const vendorId = parseInt(document.getElementById('vendorSelect').value); if (isNaN(vendorId)) { alert('Please select a vendor'); return; } transactionData.vendor_id = vendorId; } else { const customerId = parseInt(document.getElementById('vendorSelect').value); if (isNaN(customerId)) { alert('Please select a customer'); return; } transactionData.customer_id = customerId; const marketplaceId = document.getElementById('marketplaceSelect').value; if (marketplaceId) { transactionData.marketplace_id = parseInt(marketplaceId); } } try { const response = await fetch(`/api/inventory/transactions/${transactionType}`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(transactionData) }); if (response.ok) { closeTransactionModal(); loadTransactions(); } else { const error = await response.json(); alert(`Failed to save transaction: ${error.detail}`); } } catch (error) { console.error('Error saving transaction:', error); alert('Failed to save transaction. Please try again.'); } }