168 lines
5.2 KiB
TypeScript
168 lines
5.2 KiB
TypeScript
import { getHistory } from './history.ts';
|
|
import { execute } from './sqlite-manager.ts';
|
|
|
|
export interface HistoryViewCallbacks {
|
|
onBack: () => void;
|
|
}
|
|
|
|
let allRecords: any[] = [];
|
|
let showBadOnly = false;
|
|
|
|
const BAD_MODES = new Set(['hard', 'none']);
|
|
|
|
/**
|
|
* Показывает страницу истории повторений для указанной колоды.
|
|
*/
|
|
export async function renderHistoryView(
|
|
container: HTMLElement,
|
|
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>
|
|
`;
|
|
|
|
document.getElementById('btnHistoryBack')?.addEventListener('click', () => {
|
|
callbacks.onBack();
|
|
});
|
|
|
|
document.getElementById('btnFilterAll')?.addEventListener('click', () => {
|
|
showBadOnly = false;
|
|
updateFilterButtons();
|
|
renderRows(container);
|
|
});
|
|
|
|
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 = `всего записей: ${allRecords.length}`;
|
|
}
|
|
|
|
renderRows(container);
|
|
} catch (err) {
|
|
console.error('[History] Ошибка загрузки:', err);
|
|
const contentEl = document.getElementById('history-content');
|
|
if (contentEl) {
|
|
contentEl.innerHTML = '<div style="text-align:center;padding:2rem;color:var(--error);">Ошибка загрузки истории</div>';
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
try {
|
|
const placeholders = wordIds.map(() => '?').join(',');
|
|
const rows: any[] = await execute(
|
|
`SELECT id, key FROM words WHERE id IN (${placeholders})`,
|
|
wordIds,
|
|
);
|
|
for (const row of rows) {
|
|
map.set(row[0], row[1]);
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|
|
return map;
|
|
}
|
|
|
|
function escapeHtml(text: string): string {
|
|
const div = document.createElement('div');
|
|
div.textContent = text;
|
|
return div.innerHTML;
|
|
} |