Filtering bad

This commit is contained in:
2026-07-05 13:34:23 +03:00
parent c184945e95
commit 064c39924b
3 changed files with 119 additions and 52 deletions
+1 -1
View File
@@ -23,4 +23,4 @@ dist-ssr
*.sln
*.sw?
jwl.tar.gz
/.jwl
/jwl
+99 -51
View File
@@ -5,6 +5,11 @@ export interface HistoryViewCallbacks {
onBack: () => void;
}
let allRecords: any[] = [];
let showBadOnly = false;
const BAD_MODES = new Set(['hard', 'none']);
/**
* Показывает страницу истории повторений для указанной колоды.
*/
@@ -13,77 +18,53 @@ export async function renderHistoryView(
setId: number,
callbacks: HistoryViewCallbacks,
): Promise<void> {
showBadOnly = false;
container.innerHTML = `
<div class="history-header">
<button id="btnHistoryBack" class="btn" style="flex:none; padding:0.4rem 0.8rem;">← Назад</button>
<span style="font-size:0.85rem;color:var(--text);">Загрузка...</span>
</div>
<div class="history-filters">
<button id="btnFilterAll" class="btn btn-filter active">📊 Все записи</button>
<button id="btnFilterBad" class="btn btn-filter">🔴 Только плохие</button>
</div>
<div class="history-card">
<div id="history-content" style="text-align:center;padding:2rem;opacity:0.6;">Загрузка истории...</div>
</div>
`;
console.log('[History] Загрузка истории для set_id:', setId);
document.getElementById('btnHistoryBack')?.addEventListener('click', () => {
callbacks.onBack();
});
try {
const records = await getHistory(setId, 2000);
console.log('[History] Получено записей:', records.length, records);
document.getElementById('btnFilterAll')?.addEventListener('click', () => {
showBadOnly = false;
updateFilterButtons();
renderRows(container);
});
// Получаем слова для отображения
const wordIds = [...new Set(records.map((r) => r.word_id))];
document.getElementById('btnFilterBad')?.addEventListener('click', () => {
showBadOnly = true;
updateFilterButtons();
renderRows(container);
});
try {
allRecords = await getHistory(setId, 2000);
const wordIds = [...new Set(allRecords.map((r) => r.word_id))];
const wordMap = await getWordMap(wordIds);
// Сохраняем wordMap для renderRows
(window as any).__wordMap = wordMap;
const headerSpan = container.querySelector('.history-header span');
if (headerSpan) {
headerSpan.textContent = `записей: ${records.length}`;
headerSpan.textContent = `всего записей: ${allRecords.length}`;
}
const contentEl = document.getElementById('history-content');
if (!contentEl) return;
if (records.length === 0) {
contentEl.innerHTML = '<div style="text-align:center;padding:2rem;opacity:0.6;">История пуста</div>';
return;
}
contentEl.innerHTML = '';
for (const record of records) {
const word = wordMap.get(record.word_id);
const wordText = word ? word : `#${record.word_id}`;
const date = new Date(record.timestamp);
const dateStr = date.toLocaleString('ru-RU', {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
const modeLabels: Record<string, string> = {
easy: '🟢 Легко',
ok: '🟡 Нормально',
hard: '🟠 Тяжело',
none: '🔴 Не знаю',
};
const row = document.createElement('div');
row.className = 'history-row';
row.innerHTML = `
<div class="history-row-word">${escapeHtml(wordText)}</div>
<div class="history-row-mode">${modeLabels[record.mode] || record.mode}</div>
<div class="history-row-scores">
<span class="history-score-before">${record.score_before.toFixed(1)}</span>
<span class="history-score-arrow">→</span>
<span class="history-score-after">${record.score_after.toFixed(1)}</span>
</div>
<div class="history-row-date">${dateStr}</div>
`;
contentEl.appendChild(row);
}
renderRows(container);
} catch (err) {
console.error('[History] Ошибка загрузки:', err);
const contentEl = document.getElementById('history-content');
@@ -93,6 +74,74 @@ export async function renderHistoryView(
}
}
function updateFilterButtons() {
const btnAll = document.getElementById('btnFilterAll');
const btnBad = document.getElementById('btnFilterBad');
if (btnAll && btnBad) {
btnAll.classList.toggle('active', !showBadOnly);
btnBad.classList.toggle('active', showBadOnly);
}
}
function renderRows(container: HTMLElement) {
const contentEl = document.getElementById('history-content');
if (!contentEl) return;
const wordMap = (window as any).__wordMap as Map<number, string> || new Map();
const headerSpan = container.querySelector('.history-header span');
let filtered = allRecords;
if (showBadOnly) {
filtered = allRecords.filter((r) => BAD_MODES.has(r.mode));
}
if (headerSpan) {
headerSpan.textContent = showBadOnly
? `плохих: ${filtered.length} / ${allRecords.length}`
: `всего записей: ${allRecords.length}`;
}
if (filtered.length === 0) {
contentEl.innerHTML = '<div style="text-align:center;padding:2rem;opacity:0.6;">Нет записей</div>';
return;
}
contentEl.innerHTML = '';
for (const record of filtered) {
const word = wordMap.get(record.word_id);
const wordText = word ? word : `#${record.word_id}`;
const date = new Date(record.timestamp);
const dateStr = date.toLocaleString('ru-RU', {
day: '2-digit',
month: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
const modeLabels: Record<string, string> = {
easy: '🟢 Легко',
ok: '🟡 Нормально',
hard: '🟠 Тяжело',
none: '🔴 Не знаю',
};
const row = document.createElement('div');
row.className = 'history-row';
row.innerHTML = `
<div class="history-row-word">${escapeHtml(wordText)}</div>
<div class="history-row-mode">${modeLabels[record.mode] || record.mode}</div>
<div class="history-row-scores">
<span class="history-score-before">${record.score_before.toFixed(1)}</span>
<span class="history-score-arrow">→</span>
<span class="history-score-after">${record.score_after.toFixed(1)}</span>
</div>
<div class="history-row-date">${dateStr}</div>
`;
contentEl.appendChild(row);
}
}
async function getWordMap(wordIds: number[]): Promise<Map<number, string>> {
const map = new Map<number, string>();
if (wordIds.length === 0) return map;
@@ -104,7 +153,6 @@ async function getWordMap(wordIds: number[]): Promise<Map<number, string>> {
wordIds,
);
for (const row of rows) {
// rows — массив массивов [id, key]
map.set(row[0], row[1]);
}
} catch {
+19
View File
@@ -426,6 +426,25 @@ body {
padding: 0.5rem 0;
}
.history-filters {
display: flex;
gap: 0.5rem;
padding: 0.25rem 0;
}
.btn-filter {
flex: 1;
font-size: 0.8rem;
padding: 0.5rem 0.75rem;
min-height: 40px;
}
.btn-filter.active {
background: var(--accent-light);
border-color: var(--accent);
color: var(--accent);
}
.history-card {
flex: 1;
background: var(--card-bg);