diff --git a/static/css/styles.css b/static/css/styles.css index 9c5dced..4520130 100644 --- a/static/css/styles.css +++ b/static/css/styles.css @@ -58,8 +58,8 @@ --content-text: white; /* Form elements */ - --input-bg: #3b3b53; - --input-border: 1px solid #4b5563; + --input-bg: #2e2e42; + --input-border: 2px solid #6b7280; --input-text: white; --label-color: #e2e8f0; @@ -1084,18 +1084,24 @@ html { .enhanced-search-input { width: 100%; padding: 0.75rem 2.5rem 0.75rem 1rem; - border: 2px solid var(--input-border); + border: var(--input-border); border-radius: 0.5rem; - background: var(--input-bg); + background-color: var(--input-bg); color: var(--input-text); font-size: 1rem; - transition: border-color 0.2s ease; + transition: border-color 0.2s ease, box-shadow 0.2s ease; + box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05); } .enhanced-search-input:focus { outline: none; border-color: var(--btn-primary-bg); - box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1); + box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.5); +} + +.enhanced-search-input::placeholder { + color: var(--label-color); + opacity: 0.8; } .search-clear-btn { @@ -1117,11 +1123,23 @@ html { } .search-loading { - display: flex; + display: none; align-items: center; gap: 0.75rem; margin-top: 1rem; + padding: 0.75rem; + background: var(--content-bg); + border: 1px solid var(--input-border); + border-radius: 0.25rem; color: var(--content-text); + font-weight: 500; +} + +/* HTMX will add htmx-request class to the indicator element directly */ +.search-loading.htmx-request, +#search-loading.htmx-request { + display: flex !important; + opacity: 1 !important; } .loading-spinner { @@ -1189,10 +1207,9 @@ html { } .refresh-board-btn, -.load-recent-btn { +.load-recent-btn, +.clear-board-btn { padding: 0.5rem 1rem; - background: var(--btn-success-bg); - color: white; border: none; border-radius: 0.25rem; cursor: pointer; @@ -1200,11 +1217,26 @@ html { transition: background-color 0.2s ease; } +.refresh-board-btn, +.load-recent-btn { + background: var(--btn-success-bg); + color: white; +} + .refresh-board-btn:hover, .load-recent-btn:hover { background: var(--btn-success-hover); } +.clear-board-btn { + background: var(--btn-warning-bg); + color: white; +} + +.clear-board-btn:hover { + background: #dc2626; +} + .invoice-kanban-board { display: grid; grid-template-columns: repeat(6, 1fr); @@ -1532,4 +1564,172 @@ html { opacity: 0.7; max-width: 400px; line-height: 1.5; +} + +/* Invoice Details Modal */ +.invoice-details-modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1001; + opacity: 0; + transition: opacity 0.3s ease; +} + +.invoice-details-modal.show { + opacity: 1; +} + +.invoice-details-modal .modal-content { + background: var(--content-bg); + border-radius: 0.5rem; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3); + max-width: 500px; + width: 90%; + max-height: 80vh; + overflow-y: auto; + transform: scale(0.9); + transition: transform 0.3s ease; +} + +.invoice-details-modal.show .modal-content { + transform: scale(1); +} + +.invoice-details-modal .modal-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 1rem 1.5rem; + border-bottom: 1px solid var(--content-border); +} + +.invoice-details-modal .modal-header h3 { + margin: 0; + color: var(--dashboard-header-color); +} + +.invoice-details-modal .modal-close { + background: none; + border: none; + font-size: 1.5rem; + cursor: pointer; + color: var(--label-color); + padding: 0; + width: 30px; + height: 30px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: background-color 0.2s ease; +} + +.invoice-details-modal .modal-close:hover { + background: var(--input-border); +} + +.invoice-details-modal .modal-body { + padding: 1.5rem; +} + +.invoice-detail-grid { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.detail-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 0; + border-bottom: 1px solid var(--input-border); +} + +.detail-row:last-child { + border-bottom: none; +} + +.detail-label { + font-weight: bold; + color: var(--label-color); + min-width: 120px; +} + +.detail-value { + color: var(--content-text); + text-align: right; + flex: 1; +} + +.total-amount { + font-weight: bold; + font-size: 1.1rem; + color: var(--btn-success-bg); +} + +.status-badge { + padding: 0.25rem 0.75rem; + border-radius: 1rem; + font-size: 0.875rem; + font-weight: bold; + text-transform: uppercase; +} + +.status-badge.status-draft { + background: var(--btn-primary-bg); + color: white; +} + +.status-badge.status-ok { + background: var(--btn-success-bg); + color: white; +} + +.status-badge.status-pending_accounting { + background: var(--btn-caution-bg); + color: var(--text-color); +} + +.status-badge.status-processed { + background: var(--btn-primary-bg); + color: white; +} + +.status-badge.status-failed { + background: var(--btn-warning-bg); + color: white; +} + +.status-badge.status-void { + background: var(--btn-disabled); + color: white; +} + +.invoice-details-modal .modal-footer { + padding: 1rem 1.5rem; + border-top: 1px solid var(--content-border); + display: flex; + justify-content: flex-end; +} + +.btn-secondary { + padding: 0.5rem 1rem; + background: var(--card-bg); + color: var(--content-text); + border: 1px solid var(--input-border); + border-radius: 0.25rem; + cursor: pointer; + transition: background-color 0.2s ease; +} + +.btn-secondary:hover { + background: var(--input-border); } \ No newline at end of file diff --git a/static/js/invoices.js b/static/js/invoices.js index 56ef2cd..2127abc 100644 --- a/static/js/invoices.js +++ b/static/js/invoices.js @@ -41,21 +41,37 @@ function extractInvoiceDataFromSearchResults() { const resultsDiv = document.getElementById('invoice-search-results'); const invoiceCards = resultsDiv.querySelectorAll('.invoice-card'); - lastSearchResults = []; + // Store new search results separately + const newSearchResults = []; invoiceCards.forEach(card => { const invoiceData = extractInvoiceDataFromCard(card); if (invoiceData) { - lastSearchResults.push(invoiceData); + newSearchResults.push(invoiceData); console.log('Extracted invoice data:', invoiceData); } }); - console.log(`Captured ${lastSearchResults.length} invoices from search results`); + console.log(`Captured ${newSearchResults.length} new invoices from search results`); + + // Add new results to existing results (avoid duplicates) + newSearchResults.forEach(newInvoice => { + const exists = lastSearchResults.some(existing => + existing.id === newInvoice.id || + existing.invoiceNumber === newInvoice.invoiceNumber + ); - // If we're currently in board view, update it immediately + if (!exists) { + lastSearchResults.push(newInvoice); + console.log(`Added new invoice to collection: ${newInvoice.id}`); + } else { + console.log(`Invoice already exists in collection: ${newInvoice.id}`); + } + }); + + // If we're currently in board view, add the new invoices to the board if (currentView === 'board') { - populateBoardWithSearchResults(); + addNewInvoicesToBoard(newSearchResults); } } @@ -160,13 +176,11 @@ function clearSearch() { `; - // Clear board if currently in board view - if (currentView === 'board') { - clearBoard(); - } + // Don't clear board when clearing search - let users keep their board intact + console.log('Search cleared, but board preserved'); } -// View switching functionality +// View switching functionality (modified to preserve board) function switchView(viewName) { const searchView = document.getElementById('search-view'); const boardView = document.getElementById('board-view'); @@ -183,10 +197,13 @@ function switchView(viewName) { currentView = viewName; - // If switching to board view, populate it with search results + // If switching to board view, only populate if board is empty if (viewName === 'board') { - if (lastSearchResults.length > 0) { + const existingCards = document.querySelectorAll('.board-invoice-card'); + if (existingCards.length === 0 && lastSearchResults.length > 0) { populateBoardWithSearchResults(); + } else if (existingCards.length > 0) { + console.log(`Board already has ${existingCards.length} invoices, keeping them`); } else { checkBoardState(); } @@ -195,6 +212,24 @@ function switchView(viewName) { console.log(`Switched to ${viewName} view`); } +// Populate board with search results (modified to be explicit about clearing) +function populateBoardWithSearchResults() { + console.log(`Populating board with ${lastSearchResults.length} search results`); + + // Clear existing board content + clearBoard(); + + // Add each search result to the board + lastSearchResults.forEach(invoiceData => { + addInvoiceToBoard(invoiceData); + }); + + // Debug the board state after population + debugBoardState(); + + showMessage(`Added ${lastSearchResults.length} invoices to board`, 'success'); +} + // Clear all invoices from the board function clearBoard() { document.querySelectorAll('.invoice-drop-zone').forEach(zone => { @@ -214,24 +249,8 @@ function clearBoard() { // Update counts updateColumnCounts(); -} - -// Populate board with search results -function populateBoardWithSearchResults() { - console.log(`Populating board with ${lastSearchResults.length} search results`); - - // Clear existing board content - clearBoard(); - - // Add each search result to the board - lastSearchResults.forEach(invoiceData => { - addInvoiceToBoard(invoiceData); - }); - - // Debug the board state after population - debugBoardState(); - showMessage(`Added ${lastSearchResults.length} invoices to board`, 'success'); + console.log('Board cleared'); } // Board functionality @@ -263,7 +282,7 @@ function loadRecentInvoices() { // For now, show a helpful message setTimeout(() => { showBoardLoading(false); - showMessage('Search for invoices in the Search View to populate the board', 'info'); + showMessage('Sorry, this feature is not yet implemented', 'info'); }, 1000); } @@ -630,18 +649,111 @@ function createBoardCardHTML(invoice) {
`; } -// Invoice details modal (placeholder) +// Invoice details modal function showInvoiceDetails(invoiceId) { console.log(`Showing details for invoice ${invoiceId}`); - // TODO: Implement invoice details modal - showMessage(`Details for invoice ${invoiceId}`, 'info'); + + // Find the invoice data in our stored data + let invoiceData = boardInvoices.get(invoiceId); + + // If not found in board, try searching in lastSearchResults + if (!invoiceData) { + invoiceData = lastSearchResults.find(inv => + inv.id === invoiceId || inv.id === invoiceId.toString() || + inv.invoiceNumber === invoiceId + ); + } + + if (!invoiceData) { + showMessage('Invoice details not found', 'error'); + return; + } + + // Create and show modal + const modal = createInvoiceDetailsModal(invoiceData); + document.body.appendChild(modal); + + // Show modal with animation + setTimeout(() => modal.classList.add('show'), 10); + + // Close modal when clicking outside or on close button + modal.addEventListener('click', function (e) { + if (e.target === modal || e.target.classList.contains('modal-close')) { + closeInvoiceDetailsModal(modal); + } + }); +} + +function createInvoiceDetailsModal(invoiceData) { + const modal = document.createElement('div'); + modal.className = 'invoice-details-modal'; + + modal.innerHTML = ` + + `; + + return modal; +} + +function closeInvoiceDetailsModal(modal) { + modal.classList.remove('show'); + setTimeout(() => { + if (document.body.contains(modal)) { + document.body.removeChild(modal); + } + }, 300); } // Utility function for showing messages @@ -713,4 +825,44 @@ function debugBoardState() { } // Make debug function globally accessible for browser console -window.debugBoardState = debugBoardState; \ No newline at end of file +window.debugBoardState = debugBoardState; + +// Add new invoices to board without clearing existing ones +function addNewInvoicesToBoard(newInvoices) { + console.log(`Adding ${newInvoices.length} new invoices to existing board`); + + let addedCount = 0; + newInvoices.forEach(invoiceData => { + // Check if invoice is already on the board + const existingCard = document.getElementById(`board-invoice-${invoiceData.id}`); + if (!existingCard) { + addInvoiceToBoard(invoiceData); + addedCount++; + } else { + console.log(`Invoice ${invoiceData.id} already on board, skipping`); + } + }); + + if (addedCount > 0) { + showMessage(`Added ${addedCount} new invoices to board`, 'success'); + } else if (newInvoices.length > 0) { + showMessage(`All ${newInvoices.length} invoices already on board`, 'info'); + } +} + +// Clear board with confirmation +function clearBoardAndConfirm() { + const existingCards = document.querySelectorAll('.board-invoice-card'); + + if (existingCards.length === 0) { + showMessage('Board is already empty', 'info'); + return; + } + + const confirmed = confirm(`Are you sure you want to clear all ${existingCards.length} invoices from the board? This will not affect the invoices themselves, only remove them from the board view.`); + + if (confirmed) { + clearBoard(); + showMessage('Board cleared successfully', 'success'); + } +} \ No newline at end of file diff --git a/templates/invoices.html b/templates/invoices.html index acf6511..b316187 100644 --- a/templates/invoices.html +++ b/templates/invoices.html @@ -53,10 +53,13 @@💡 Tip: Search for invoices in the Search View first, then switch - to Board View to see them organized by status.
+💡 Tip: Search for invoices in the Search View to add them to the + board. Invoices persist when switching between views.