Compare commits

..
2 Commits
Author SHA1 Message Date
oliver 80a52ed790 u 2026-09-03 14:42:37 -03:00
oliver b299bf3627 filter 2026-09-03 13:23:11 -03:00
3 changed files with 266 additions and 77 deletions
+100 -17
View File
@@ -7,7 +7,8 @@ let allData = [];
let filteredData = []; let filteredData = [];
let currentSort = { column: null, direction: null }; let currentSort = { column: null, direction: null };
let filters = {}; let filters = {};
let globalSearch = ''; let columnTextFilters = {};
let hideOkFilter = false;
let apiCredentials = null; let apiCredentials = null;
// API Configuration // API Configuration
@@ -140,14 +141,21 @@ async function loadData() {
// =================================== // ===================================
function applyFilters() { function applyFilters() {
let result = filteredData.filter(item => { let result = [...filteredData];
if (!globalSearch) return true;
const searchLower = globalSearch.toLowerCase(); // Apply column text filters
return Object.values(item).some(value => Object.entries(columnTextFilters).forEach(([column, searchText]) => {
value !== null && value.toString().toLowerCase().includes(searchLower) if (searchText && searchText.trim()) {
); const searchLower = searchText.toLowerCase().trim();
result = result.filter(item => {
const value = item[column];
return value !== null && value !== undefined &&
String(value).toLowerCase().includes(searchLower);
});
}
}); });
// Apply checkbox filters
Object.entries(filters).forEach(([column, selectedValues]) => { Object.entries(filters).forEach(([column, selectedValues]) => {
if (selectedValues.length > 0) { if (selectedValues.length > 0) {
result = result.filter(item => result = result.filter(item =>
@@ -156,12 +164,25 @@ function applyFilters() {
} }
}); });
// Apply "hide OK" filter
if (hideOkFilter) {
result = result.filter(item => {
const remark = item.Remark || '';
return !String(remark).trim().toLowerCase().includes('ok');
});
}
renderTable(result); renderTable(result);
updateStats(result.length); updateStats(result.length);
console.group('%c Table View', 'background: #06b6d4; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;'); console.group('%c Table View', 'background: #06b6d4; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;');
console.log('%c Rendering', 'font-weight: bold;', result.length, 'records'); console.log('%c Rendering', 'font-weight: bold;', result.length, 'records');
console.log('%c View range:', 'color: #64748b;', '1-', result.length); console.log('%c Active text filters:', Object.entries(columnTextFilters)
.filter(([_, v]) => v && v.trim())
.map(([k, v]) => `${k}: "${v}"`), 'color: #64748b;');
if (hideOkFilter) {
console.log('%c Filter:', 'color: #10b981;', 'Hide OK remarks is ON');
}
console.groupEnd(); console.groupEnd();
} }
@@ -292,9 +313,9 @@ function getRemarkIcon(remark) {
// Recheck Action // Recheck Action
// =================================== // ===================================
async function handleRecheck(orderId, isChecked) { async function handleRecheck(id, isChecked) {
console.group('%c Recheck', 'background: #6366f1; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;'); console.group('%c Recheck', 'background: #6366f1; color: white; padding: 4px 8px; border-radius: 4px; font-weight: bold;');
console.log('%c Order ID:', 'font-weight: bold;', orderId); console.log('%c ID:', 'font-weight: bold;', id);
console.log('%c Status:', isChecked ? 'color: #10b981' : 'color: #94a3b8', isChecked ? 'Checked' : 'Unchecked'); console.log('%c Status:', isChecked ? 'color: #10b981' : 'color: #94a3b8', isChecked ? 'Checked' : 'Unchecked');
try { try {
@@ -307,7 +328,7 @@ async function handleRecheck(orderId, isChecked) {
} : {}) } : {})
}, },
body: JSON.stringify({ body: JSON.stringify({
Order_ID: orderId, ID: id,
recheck: isChecked recheck: isChecked
}) })
}); });
@@ -461,11 +482,63 @@ function setupClickOutside() {
}); });
} }
function handleGlobalSearch(search) { function handleColumnFilter(input) {
globalSearch = search; const column = input.dataset.column;
const value = input.value;
if (value) {
columnTextFilters[column] = value;
input.parentElement.classList.add('filter-active');
} else {
delete columnTextFilters[column];
input.parentElement.classList.remove('filter-active');
}
applyFilters(); applyFilters();
} }
function clearAllFilters() {
// Clear column text filters
columnTextFilters = {};
// Clear checkbox filters
filters = {};
// Reset hide OK filter
hideOkFilter = false;
updateHideOkButton();
// Clear all input fields
document.querySelectorAll('.column-filter').forEach(input => {
input.value = '';
input.parentElement.classList.remove('filter-active');
});
// Reset filter indicators
document.querySelectorAll('.filter-indicator').forEach(indicator => {
indicator.classList.remove('active');
});
applyFilters();
}
function toggleHideOkFilter() {
hideOkFilter = !hideOkFilter;
updateHideOkButton();
applyFilters();
}
function updateHideOkButton() {
const btn = document.getElementById('hideOkBtn');
if (!btn) return;
if (hideOkFilter) {
btn.style.boxShadow = '0 0 0 2px var(--danger)';
} else {
btn.style.boxShadow = 'none';
}
}
// =================================== // ===================================
// Statistics // Statistics
// =================================== // ===================================
@@ -519,7 +592,7 @@ function showCredentialPrompt() {
</div> </div>
</div> </div>
<div class="form-actions"> <div class="form-actions">
<button type="button" class="btn-secondary" onclick="closeCredentialPrompt()">Cancel</button> <button type="button" class="btn-secondary" id="cancelAuthBtn">Cancel</button>
<button type="submit" class="btn-primary">Connect</button> <button type="submit" class="btn-primary">Connect</button>
</div> </div>
</form> </form>
@@ -533,12 +606,22 @@ function showCredentialPrompt() {
`; `;
document.body.appendChild(prompt); document.body.appendChild(prompt);
document.getElementById('credentialForm').addEventListener('submit', handleCredentials); const form = document.getElementById('credentialForm');
document.querySelector('.credential-overlay').addEventListener('click', function(e) { form.addEventListener('submit', handleCredentials);
if (e.target === this) {
const cancelBtn = document.getElementById('cancelAuthBtn');
if (cancelBtn) {
cancelBtn.addEventListener('click', closeCredentialPrompt);
}
const overlay = document.querySelector('#credentialPrompt .credential-overlay');
if (overlay) {
overlay.addEventListener('click', function(e) {
if (e.target === overlay) {
closeCredentialPrompt(); closeCredentialPrompt();
} }
}); });
}
} }
function closeCredentialPrompt() { function closeCredentialPrompt() {
+66 -11
View File
@@ -39,10 +39,12 @@
<button class="refresh-btn" id="authBtn" onclick="toggleAuth()" style="background: var(--secondary);"> <button class="refresh-btn" id="authBtn" onclick="toggleAuth()" style="background: var(--secondary);">
<i class="fas fa-key"></i> <span id="authBtnText">Enter Credentials</span> <i class="fas fa-key"></i> <span id="authBtnText">Enter Credentials</span>
</button> </button>
<div class="search-box"> <button class="refresh-btn" id="hideOkBtn" onclick="toggleHideOkFilter()" style="background: var(--success);">
<i class="fas fa-search"></i> <i class="fas fa-check-circle"></i> <span>Hide OK Remarks</span>
<input type="text" id="globalSearch" placeholder="Search orders, countries, VAT numbers..." oninput="handleGlobalSearch(this.value)"> </button>
</div> <button class="refresh-btn" onclick="clearAllFilters()" style="background: var(--warning);">
<i class="fas fa-filter"></i> <span>Clear Filters</span>
</button>
</div> </div>
<div class="table-container"> <div class="table-container">
@@ -58,7 +60,14 @@
<thead> <thead>
<tr> <tr>
<th> <th>
Month <div class="th-label">Month</div>
<div class="th-controls">
<input type="text"
class="column-filter"
placeholder="Filter Month"
data-column="Month"
oninput="handleColumnFilter(this)">
<div class="th-actions">
<div class="filter-indicator" onclick="toggleFilter('Month')"> <div class="filter-indicator" onclick="toggleFilter('Month')">
<i class="fas fa-filter"></i> <i class="fas fa-filter"></i>
</div> </div>
@@ -66,9 +75,18 @@
<i class="fas fa-arrow-up"></i> <i class="fas fa-arrow-up"></i>
<i class="fas fa-arrow-down"></i> <i class="fas fa-arrow-down"></i>
</div> </div>
</div>
</div>
</th> </th>
<th> <th>
Odoo AR Nr <div class="th-label">Odoo AR Nr</div>
<div class="th-controls">
<input type="text"
class="column-filter"
placeholder="Filter Odoo AR Nr"
data-column="Odoo_AR_Nr"
oninput="handleColumnFilter(this)">
<div class="th-actions">
<div class="filter-indicator" onclick="toggleFilter('Odoo_AR_Nr')"> <div class="filter-indicator" onclick="toggleFilter('Odoo_AR_Nr')">
<i class="fas fa-filter"></i> <i class="fas fa-filter"></i>
</div> </div>
@@ -76,9 +94,18 @@
<i class="fas fa-arrow-up"></i> <i class="fas fa-arrow-up"></i>
<i class="fas fa-arrow-down"></i> <i class="fas fa-arrow-down"></i>
</div> </div>
</div>
</div>
</th> </th>
<th> <th>
Order ID <div class="th-label">Order ID</div>
<div class="th-controls">
<input type="text"
class="column-filter"
placeholder="Filter Order ID"
data-column="Order_ID"
oninput="handleColumnFilter(this)">
<div class="th-actions">
<div class="filter-indicator" onclick="toggleFilter('Order_ID')"> <div class="filter-indicator" onclick="toggleFilter('Order_ID')">
<i class="fas fa-filter"></i> <i class="fas fa-filter"></i>
</div> </div>
@@ -86,9 +113,18 @@
<i class="fas fa-arrow-up"></i> <i class="fas fa-arrow-up"></i>
<i class="fas fa-arrow-down"></i> <i class="fas fa-arrow-down"></i>
</div> </div>
</div>
</div>
</th> </th>
<th> <th>
Country <div class="th-label">Country</div>
<div class="th-controls">
<input type="text"
class="column-filter"
placeholder="Filter Country"
data-column="Country_Name"
oninput="handleColumnFilter(this)">
<div class="th-actions">
<div class="filter-indicator" onclick="toggleFilter('Country_Name')"> <div class="filter-indicator" onclick="toggleFilter('Country_Name')">
<i class="fas fa-filter"></i> <i class="fas fa-filter"></i>
</div> </div>
@@ -96,9 +132,13 @@
<i class="fas fa-arrow-up"></i> <i class="fas fa-arrow-up"></i>
<i class="fas fa-arrow-down"></i> <i class="fas fa-arrow-down"></i>
</div> </div>
</div>
</div>
</th> </th>
<th> <th>
VAT <div class="th-label">VAT</div>
<div class="th-controls">
<div class="th-actions">
<div class="filter-indicator" onclick="toggleFilter('VAT')"> <div class="filter-indicator" onclick="toggleFilter('VAT')">
<i class="fas fa-filter"></i> <i class="fas fa-filter"></i>
</div> </div>
@@ -106,9 +146,18 @@
<i class="fas fa-arrow-up"></i> <i class="fas fa-arrow-up"></i>
<i class="fas fa-arrow-down"></i> <i class="fas fa-arrow-down"></i>
</div> </div>
</div>
</div>
</th> </th>
<th> <th>
Remark <div class="th-label">Remark</div>
<div class="th-controls">
<input type="text"
class="column-filter"
placeholder="Filter Remark"
data-column="Remark"
oninput="handleColumnFilter(this)">
<div class="th-actions">
<div class="filter-indicator" onclick="toggleFilter('Remark')"> <div class="filter-indicator" onclick="toggleFilter('Remark')">
<i class="fas fa-filter"></i> <i class="fas fa-filter"></i>
</div> </div>
@@ -116,9 +165,13 @@
<i class="fas fa-arrow-up"></i> <i class="fas fa-arrow-up"></i>
<i class="fas fa-arrow-down"></i> <i class="fas fa-arrow-down"></i>
</div> </div>
</div>
</div>
</th> </th>
<th> <th>
Recheck <div class="th-label">Recheck</div>
<div class="th-controls">
<div class="th-actions">
<div class="filter-indicator" onclick="toggleFilter('recheck')"> <div class="filter-indicator" onclick="toggleFilter('recheck')">
<i class="fas fa-filter"></i> <i class="fas fa-filter"></i>
</div> </div>
@@ -126,6 +179,8 @@
<i class="fas fa-arrow-up"></i> <i class="fas fa-arrow-up"></i>
<i class="fas fa-arrow-down"></i> <i class="fas fa-arrow-down"></i>
</div> </div>
</div>
</div>
</th> </th>
</tr> </tr>
</thead> </thead>
+55 -4
View File
@@ -211,8 +211,7 @@ thead {
} }
th { th {
padding: 1rem 1.5rem; padding: 0.5rem;
text-align: left;
font-weight: 600; font-weight: 600;
color: var(--dark); color: var(--dark);
border-bottom: 2px solid var(--border); border-bottom: 2px solid var(--border);
@@ -220,10 +219,62 @@ th {
top: 0; top: 0;
background: var(--light); background: var(--light);
z-index: 10; z-index: 10;
min-width: 130px;
vertical-align: top;
white-space: nowrap;
display: table-cell;
} }
th:hover { th .th-controls {
background: var(--hover); display: inline-flex;
align-items: center;
gap: 0.35rem;
flex-wrap: nowrap;
flex: 1;
min-width: 0;
}
th .th-label {
font-size: 0.8rem;
font-weight: 700;
color: var(--dark);
white-space: nowrap;
display: inline-flex;
align-items: center;
margin-right: 0.35rem;
flex-shrink: 0;
}
.column-filter {
width: 90px;
padding: 0.35rem 0.5rem;
border: 1px solid var(--border);
border-radius: 4px;
font-size: 0.8rem;
transition: border-color 0.2s ease, box-shadow 0.2s ease;
background: white;
}
.column-filter:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.1);
}
.column-filter::placeholder {
color: #94a3b8;
}
th.filter-active .column-filter {
border-color: var(--primary);
background-color: rgba(99, 102, 241, 0.05);
}
.th-actions {
display: flex;
align-items: center;
gap: 0.35rem;
flex-shrink: 0;
} }
th .sort-indicator { th .sort-indicator {