Filtering bad
This commit is contained in:
+1
-1
@@ -23,4 +23,4 @@ dist-ssr
|
|||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
jwl.tar.gz
|
jwl.tar.gz
|
||||||
/.jwl
|
/jwl
|
||||||
+99
-51
@@ -5,6 +5,11 @@ export interface HistoryViewCallbacks {
|
|||||||
onBack: () => void;
|
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,
|
setId: number,
|
||||||
callbacks: HistoryViewCallbacks,
|
callbacks: HistoryViewCallbacks,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
showBadOnly = false;
|
||||||
|
|
||||||
container.innerHTML = `
|
container.innerHTML = `
|
||||||
<div class="history-header">
|
<div class="history-header">
|
||||||
<button id="btnHistoryBack" class="btn" style="flex:none; padding:0.4rem 0.8rem;">← Назад</button>
|
<button id="btnHistoryBack" class="btn" style="flex:none; padding:0.4rem 0.8rem;">← Назад</button>
|
||||||
<span style="font-size:0.85rem;color:var(--text);">Загрузка...</span>
|
<span style="font-size:0.85rem;color:var(--text);">Загрузка...</span>
|
||||||
</div>
|
</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 class="history-card">
|
||||||
<div id="history-content" style="text-align:center;padding:2rem;opacity:0.6;">Загрузка истории...</div>
|
<div id="history-content" style="text-align:center;padding:2rem;opacity:0.6;">Загрузка истории...</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
console.log('[History] Загрузка истории для set_id:', setId);
|
|
||||||
|
|
||||||
document.getElementById('btnHistoryBack')?.addEventListener('click', () => {
|
document.getElementById('btnHistoryBack')?.addEventListener('click', () => {
|
||||||
callbacks.onBack();
|
callbacks.onBack();
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
document.getElementById('btnFilterAll')?.addEventListener('click', () => {
|
||||||
const records = await getHistory(setId, 2000);
|
showBadOnly = false;
|
||||||
console.log('[History] Получено записей:', records.length, records);
|
updateFilterButtons();
|
||||||
|
renderRows(container);
|
||||||
|
});
|
||||||
|
|
||||||
// Получаем слова для отображения
|
document.getElementById('btnFilterBad')?.addEventListener('click', () => {
|
||||||
const wordIds = [...new Set(records.map((r) => r.word_id))];
|
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);
|
const wordMap = await getWordMap(wordIds);
|
||||||
|
|
||||||
|
// Сохраняем wordMap для renderRows
|
||||||
|
(window as any).__wordMap = wordMap;
|
||||||
|
|
||||||
const headerSpan = container.querySelector('.history-header span');
|
const headerSpan = container.querySelector('.history-header span');
|
||||||
if (headerSpan) {
|
if (headerSpan) {
|
||||||
headerSpan.textContent = `записей: ${records.length}`;
|
headerSpan.textContent = `всего записей: ${allRecords.length}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
const contentEl = document.getElementById('history-content');
|
renderRows(container);
|
||||||
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);
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('[History] Ошибка загрузки:', err);
|
console.error('[History] Ошибка загрузки:', err);
|
||||||
const contentEl = document.getElementById('history-content');
|
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>> {
|
async function getWordMap(wordIds: number[]): Promise<Map<number, string>> {
|
||||||
const map = new Map<number, string>();
|
const map = new Map<number, string>();
|
||||||
if (wordIds.length === 0) return map;
|
if (wordIds.length === 0) return map;
|
||||||
@@ -104,7 +153,6 @@ async function getWordMap(wordIds: number[]): Promise<Map<number, string>> {
|
|||||||
wordIds,
|
wordIds,
|
||||||
);
|
);
|
||||||
for (const row of rows) {
|
for (const row of rows) {
|
||||||
// rows — массив массивов [id, key]
|
|
||||||
map.set(row[0], row[1]);
|
map.set(row[0], row[1]);
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -426,6 +426,25 @@ body {
|
|||||||
padding: 0.5rem 0;
|
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 {
|
.history-card {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
background: var(--card-bg);
|
background: var(--card-bg);
|
||||||
|
|||||||
Reference in New Issue
Block a user