|
301 | 301 |
|
302 | 302 | function renderTable() { |
303 | 303 | const wrap = document.getElementById('tableWrap'); |
| 304 | + wrap.replaceChildren(); |
304 | 305 | if (!entries.length) { |
305 | | - wrap.innerHTML = `<div class="empty-state"><div class="em-icon">📋</div><p>No scores logged yet.<br>Enter your first practice test score above to start tracking.<br><span>Tip: log at least 2 scores to see trends and domain weaknesses.</span></p></div>`; |
| 306 | + const emptyState = document.createElement('div'); |
| 307 | + emptyState.className = 'empty-state'; |
| 308 | + |
| 309 | + const icon = document.createElement('div'); |
| 310 | + icon.className = 'em-icon'; |
| 311 | + icon.textContent = '📋'; |
| 312 | + |
| 313 | + const copy = document.createElement('p'); |
| 314 | + copy.append('No scores logged yet.'); |
| 315 | + copy.append(document.createElement('br')); |
| 316 | + copy.append('Enter your first practice test score above to start tracking.'); |
| 317 | + copy.append(document.createElement('br')); |
| 318 | + |
| 319 | + const tip = document.createElement('span'); |
| 320 | + tip.textContent = 'Tip: log at least 2 scores to see trends and domain weaknesses.'; |
| 321 | + copy.append(tip); |
| 322 | + |
| 323 | + emptyState.append(icon, copy); |
| 324 | + wrap.appendChild(emptyState); |
306 | 325 | return; |
307 | 326 | } |
| 327 | + |
308 | 328 | const sorted = [...entries].sort((a,b) => b.id - a.id); |
309 | | - let rows = sorted.map(e => { |
310 | | - const s = e.score; |
311 | | - const tag = !s ? '' : s >= 750 ? '<span class="tag tag-pass">PASS</span>' : s >= 700 ? '<span class="tag tag-close">CLOSE</span>' : '<span class="tag tag-fail">FAIL</span>'; |
312 | | - const domCells = e.d.map((v,i) => `<td style="color:${DOMAIN_META[i].color}">${v !== null ? v+'%' : '—'}</td>`).join(''); |
313 | | - return `<tr> |
314 | | - <td style="color:var(--dim)">${e.date}</td> |
315 | | - <td>${e.source}</td> |
316 | | - <td class="score-cell">${s ? s + ' ' + tag : '—'}</td> |
317 | | - ${domCells} |
318 | | - <td><button class="del-btn" onclick="deleteEntry(${e.id})">✕</button></td> |
319 | | - </tr>`; |
320 | | - }).join(''); |
| 329 | + const table = document.createElement('table'); |
| 330 | + const thead = document.createElement('thead'); |
| 331 | + const headRow = document.createElement('tr'); |
| 332 | + const headers = [ |
| 333 | + { label: 'Date' }, |
| 334 | + { label: 'Source' }, |
| 335 | + { label: 'Score' }, |
| 336 | + { label: 'D1', weight: '31%' }, |
| 337 | + { label: 'D2', weight: '26%' }, |
| 338 | + { label: 'D3', weight: '20%' }, |
| 339 | + { label: 'D4', weight: '12%' }, |
| 340 | + { label: 'D5', weight: '11%' }, |
| 341 | + { label: '' }, |
| 342 | + ]; |
| 343 | + |
| 344 | + headers.forEach(({ label, weight }) => { |
| 345 | + const th = document.createElement('th'); |
| 346 | + th.append(label); |
| 347 | + if (weight) { |
| 348 | + const small = document.createElement('small'); |
| 349 | + small.style.opacity = '.5'; |
| 350 | + small.textContent = ` ${weight}`; |
| 351 | + th.appendChild(small); |
| 352 | + } |
| 353 | + headRow.appendChild(th); |
| 354 | + }); |
| 355 | + |
| 356 | + thead.appendChild(headRow); |
| 357 | + table.appendChild(thead); |
| 358 | + |
| 359 | + const tbody = document.createElement('tbody'); |
| 360 | + sorted.forEach(e => { |
| 361 | + const row = document.createElement('tr'); |
| 362 | + |
| 363 | + const dateCell = document.createElement('td'); |
| 364 | + dateCell.style.color = 'var(--dim)'; |
| 365 | + dateCell.textContent = e.date; |
| 366 | + row.appendChild(dateCell); |
| 367 | + |
| 368 | + const sourceCell = document.createElement('td'); |
| 369 | + sourceCell.textContent = e.source; |
| 370 | + row.appendChild(sourceCell); |
| 371 | + |
| 372 | + const scoreCell = document.createElement('td'); |
| 373 | + scoreCell.className = 'score-cell'; |
| 374 | + if (e.score) { |
| 375 | + scoreCell.append(`${e.score} `); |
| 376 | + const tag = document.createElement('span'); |
| 377 | + tag.className = e.score >= 750 ? 'tag tag-pass' : e.score >= 700 ? 'tag tag-close' : 'tag tag-fail'; |
| 378 | + tag.textContent = e.score >= 750 ? 'PASS' : e.score >= 700 ? 'CLOSE' : 'FAIL'; |
| 379 | + scoreCell.appendChild(tag); |
| 380 | + } else { |
| 381 | + scoreCell.textContent = '—'; |
| 382 | + } |
| 383 | + row.appendChild(scoreCell); |
| 384 | + |
| 385 | + e.d.forEach((v, i) => { |
| 386 | + const domainCell = document.createElement('td'); |
| 387 | + domainCell.style.color = DOMAIN_META[i].color; |
| 388 | + domainCell.textContent = v !== null ? `${v}%` : '—'; |
| 389 | + row.appendChild(domainCell); |
| 390 | + }); |
| 391 | + |
| 392 | + const actionCell = document.createElement('td'); |
| 393 | + const deleteButton = document.createElement('button'); |
| 394 | + deleteButton.className = 'del-btn'; |
| 395 | + deleteButton.type = 'button'; |
| 396 | + deleteButton.textContent = '✕'; |
| 397 | + deleteButton.addEventListener('click', () => deleteEntry(e.id)); |
| 398 | + actionCell.appendChild(deleteButton); |
| 399 | + row.appendChild(actionCell); |
| 400 | + |
| 401 | + tbody.appendChild(row); |
| 402 | + }); |
321 | 403 |
|
322 | | - wrap.innerHTML = `<table> |
323 | | - <thead><tr> |
324 | | - <th>Date</th><th>Source</th><th>Score</th> |
325 | | - <th>D1<small style="opacity:.5"> 31%</small></th> |
326 | | - <th>D2<small style="opacity:.5"> 26%</small></th> |
327 | | - <th>D3<small style="opacity:.5"> 20%</small></th> |
328 | | - <th>D4<small style="opacity:.5"> 12%</small></th> |
329 | | - <th>D5<small style="opacity:.5"> 11%</small></th> |
330 | | - <th></th> |
331 | | - </tr></thead> |
332 | | - <tbody>${rows}</tbody> |
333 | | - </table>`; |
| 404 | + table.appendChild(tbody); |
| 405 | + wrap.appendChild(table); |
334 | 406 | } |
335 | 407 |
|
336 | 408 | function renderAnalysis() { |
|
0 commit comments