Browse Source

chore: updated css for better input visibility and changed details icon from eye to a magnifying glass

document-upload-removal-layout-update
nic 8 months ago
parent
commit
c8edfdb56c
  1. 220
      static/css/styles.css
  2. 222
      static/js/invoices.js
  3. 7
      templates/invoices.html
  4. 2
      templates/partials/invoice_board_card.html

220
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);
}

222
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() {
</div>
`;
// 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) {
<div class="board-card-footer">
<div class="status-indicator status-${invoice.status}">${invoice.status}</div>
<button class="card-details-btn" onclick="showInvoiceDetails('${invoice.id}')" title="View Details">
👁
🔍
</button>
</div>
</div>
`;
}
// 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 = `
<div class="modal-content">
<div class="modal-header">
<h3>Invoice Details</h3>
<button class="modal-close" type="button"></button>
</div>
<div class="modal-body">
<div class="invoice-detail-grid">
<div class="detail-row">
<span class="detail-label">Invoice #:</span>
<span class="detail-value">${invoiceData.invoiceNumber || 'N/A'}</span>
</div>
<div class="detail-row">
<span class="detail-label">Invoice ID:</span>
<span class="detail-value">${invoiceData.id || 'N/A'}</span>
</div>
<div class="detail-row">
<span class="detail-label">Status:</span>
<span class="detail-value">
<span class="status-badge status-${invoiceData.status}">${invoiceData.status}</span>
</span>
</div>
<div class="detail-row">
<span class="detail-label">Customer:</span>
<span class="detail-value">${invoiceData.customer ? invoiceData.customer.name : 'N/A'}</span>
</div>
<div class="detail-row">
<span class="detail-label">Job:</span>
<span class="detail-value">${invoiceData.job ? invoiceData.job.name : 'N/A'}</span>
</div>
<div class="detail-row">
<span class="detail-label">Total Price:</span>
<span class="detail-value total-amount">$${invoiceData.totalPrice || '0.00'}</span>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn-secondary modal-close" type="button">Close</button>
</div>
</div>
`;
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;
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');
}
}

7
templates/invoices.html

@ -53,10 +53,13 @@
<div class="board-header">
<h3>Invoice Status Board</h3>
<div class="board-info">
<p class="board-help">💡 <strong>Tip:</strong> Search for invoices in the Search View first, then switch
to Board View to see them organized by status.</p>
<p class="board-help">💡 <strong>Tip:</strong> Search for invoices in the Search View to add them to the
board. Invoices persist when switching between views.</p>
</div>
<div class="board-controls">
<button class="clear-board-btn" onclick="clearBoardAndConfirm()">
🗑️ Clear Board
</button>
<button class="refresh-board-btn" onclick="refreshBoard()">
🔄 Refresh Board
</button>

2
templates/partials/invoice_board_card.html

@ -16,7 +16,7 @@
<div class="board-card-footer">
<div class="status-indicator status-{{.status}}">{{.status}}</div>
<button class="card-details-btn" onclick="showInvoiceDetails('{{.id}}')" title="View Details">
👁️
🔍
</button>
</div>
</div>

Loading…
Cancel
Save