New Quotator and some major changes to pricing admin
This commit is contained in:
@@ -29,11 +29,23 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Search (only for components) -->
|
||||
<!-- Search and sort (only for components) -->
|
||||
<div id="search-bar" class="mb-4 hidden">
|
||||
<input type="text" id="search-input" placeholder="Поиск по артикулу..."
|
||||
class="w-full px-3 py-2 border rounded"
|
||||
onkeyup="debounceSearch()">
|
||||
<div class="flex gap-4 items-center">
|
||||
<input type="text" id="search-input" placeholder="Поиск по артикулу..."
|
||||
class="flex-1 px-3 py-2 border rounded"
|
||||
onkeyup="debounceSearch()">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm text-gray-500">Сортировка:</span>
|
||||
<select id="sort-field" class="px-2 py-1 border rounded text-sm" onchange="changeSort()">
|
||||
<option value="lot_name">Артикул</option>
|
||||
<option value="popularity_score">Популярность</option>
|
||||
<option value="quote_count">Кол-во котировок</option>
|
||||
<option value="current_price">Цена</option>
|
||||
</select>
|
||||
<button onclick="toggleSortDir()" id="sort-dir-btn" class="px-2 py-1 border rounded text-sm">↑</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="tab-content">
|
||||
@@ -128,6 +140,8 @@ let perPage = 50;
|
||||
let searchTimeout = null;
|
||||
let currentSearch = '';
|
||||
let componentsCache = [];
|
||||
let sortField = 'lot_name';
|
||||
let sortDir = 'asc';
|
||||
|
||||
async function loadTab(tab) {
|
||||
currentTab = tab;
|
||||
@@ -235,20 +249,49 @@ function renderComponents(components, total) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort components locally
|
||||
const sorted = [...components].sort((a, b) => {
|
||||
let aVal, bVal;
|
||||
switch (sortField) {
|
||||
case 'popularity_score':
|
||||
aVal = a.popularity_score || 0;
|
||||
bVal = b.popularity_score || 0;
|
||||
break;
|
||||
case 'quote_count':
|
||||
aVal = a.quote_count || 0;
|
||||
bVal = b.quote_count || 0;
|
||||
break;
|
||||
case 'current_price':
|
||||
aVal = a.current_price || 0;
|
||||
bVal = b.current_price || 0;
|
||||
break;
|
||||
default:
|
||||
aVal = a.lot_name || '';
|
||||
bVal = b.lot_name || '';
|
||||
}
|
||||
if (sortDir === 'asc') {
|
||||
return aVal > bVal ? 1 : aVal < bVal ? -1 : 0;
|
||||
} else {
|
||||
return aVal < bVal ? 1 : aVal > bVal ? -1 : 0;
|
||||
}
|
||||
});
|
||||
|
||||
let html = '<div class="overflow-x-auto"><table class="w-full"><thead class="bg-gray-50"><tr>';
|
||||
html += '<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Артикул</th>';
|
||||
html += '<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Категория</th>';
|
||||
html += '<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Описание</th>';
|
||||
html += '<th class="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase">Кол-во цен</th>';
|
||||
html += '<th class="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase">Популярность</th>';
|
||||
html += '<th class="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase">Кол-во котировок</th>';
|
||||
html += '<th class="px-3 py-2 text-right text-xs font-medium text-gray-500 uppercase">Цена</th>';
|
||||
html += '<th class="px-3 py-2 text-left text-xs font-medium text-gray-500 uppercase">Настройки</th>';
|
||||
html += '</tr></thead><tbody class="divide-y">';
|
||||
|
||||
components.forEach((c, idx) => {
|
||||
sorted.forEach((c, idx) => {
|
||||
const price = c.current_price ? '$' + parseFloat(c.current_price).toLocaleString('en-US', {minimumFractionDigits: 2}) : '—';
|
||||
const category = c.category ? c.category.code : '—';
|
||||
const desc = c.lot && c.lot.lot_description ? c.lot.lot_description : '—';
|
||||
const quoteCount = c.quote_count || 0;
|
||||
const popularity = c.popularity_score ? c.popularity_score.toFixed(2) : '0.00';
|
||||
|
||||
// Build settings summary
|
||||
let settings = [];
|
||||
@@ -268,10 +311,14 @@ function renderComponents(components, total) {
|
||||
settings.push('РУЧН');
|
||||
}
|
||||
|
||||
html += '<tr class="hover:bg-gray-50 cursor-pointer" onclick="openModal(' + idx + ')">';
|
||||
// Find original index in componentsCache
|
||||
const origIdx = componentsCache.findIndex(x => x.lot_name === c.lot_name);
|
||||
|
||||
html += '<tr class="hover:bg-gray-50 cursor-pointer" onclick="openModal(' + origIdx + ')">';
|
||||
html += '<td class="px-3 py-2 text-sm font-mono">' + escapeHtml(c.lot_name) + '</td>';
|
||||
html += '<td class="px-3 py-2 text-sm">' + escapeHtml(category) + '</td>';
|
||||
html += '<td class="px-3 py-2 text-sm text-gray-500 max-w-xs truncate">' + escapeHtml(desc) + '</td>';
|
||||
html += '<td class="px-3 py-2 text-sm text-right">' + popularity + '</td>';
|
||||
html += '<td class="px-3 py-2 text-sm text-right">' + quoteCount + '</td>';
|
||||
html += '<td class="px-3 py-2 text-sm text-right font-medium">' + price + '</td>';
|
||||
html += '<td class="px-3 py-2 text-sm"><span class="text-xs bg-gray-100 px-2 py-1 rounded">' + settings.join(' | ') + '</span></td>';
|
||||
@@ -535,6 +582,17 @@ document.getElementById('price-modal').addEventListener('click', function(e) {
|
||||
}
|
||||
});
|
||||
|
||||
function changeSort() {
|
||||
sortField = document.getElementById('sort-field').value;
|
||||
renderComponents(componentsCache, componentsCache.length);
|
||||
}
|
||||
|
||||
function toggleSortDir() {
|
||||
sortDir = sortDir === 'asc' ? 'desc' : 'asc';
|
||||
document.getElementById('sort-dir-btn').textContent = sortDir === 'asc' ? '↑' : '↓';
|
||||
renderComponents(componentsCache, componentsCache.length);
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadTab('alerts');
|
||||
|
||||
|
||||
Reference in New Issue
Block a user