File size: 4,781 Bytes
392f1be
 
a17d02d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392f1be
a17d02d
 
 
 
 
 
 
 
 
 
 
 
 
392f1be
a17d02d
392f1be
a17d02d
 
 
 
 
 
 
 
 
 
392f1be
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
import { testAPI } from '../api/api'

function escapeHtml(value) {
  return String(value ?? '').replace(/[&<>"']/g, ch => ({
    '&': '&amp;',
    '<': '&lt;',
    '>': '&gt;',
    '"': '&quot;',
    "'": '&#39;',
  }[ch]))
}

function formatDate(value) {
  if (!value) return '-'
  return new Date(value).toLocaleString('en-IN')
}

export function buildResultHtml(result) {
  const rows = (result.answers || []).map((answer, idx) => {
    const status = answer.is_correct === true ? 'Correct' : answer.is_correct === false ? 'Wrong' : 'Skipped'
    const options = (answer.options || []).map((option, i) => {
      const letter = 'ABCD'[i] || `${i + 1}`
      return `<li><strong>${letter}.</strong> ${escapeHtml(option)}</li>`
    }).join('')

    return `
      <section class="question">
        <div class="q-head">
          <strong>Q${idx + 1}</strong>
          <span class="${status.toLowerCase()}">${status}</span>
          <span>${escapeHtml(answer.marks_awarded)} marks</span>
        </div>
        <p>${escapeHtml(answer.question_text)}</p>
        ${options ? `<ol>${options}</ol>` : ''}
        <div class="answers">
          <span>Your answer: <strong>${escapeHtml(answer.selected_answer || 'Skipped')}</strong></span>
          <span>Correct answer: <strong>${escapeHtml(answer.correct_answer)}</strong></span>
          <span>Time: <strong>${escapeHtml(answer.time_spent_seconds)}s</strong></span>
        </div>
      </section>`
  }).join('')

  return `<!doctype html>
<html>
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />
  <title>${escapeHtml(result.test_title)} result</title>
  <style>
    body { font-family: Arial, sans-serif; margin: 32px; color: #111827; line-height: 1.45; }
    header { border-bottom: 1px solid #d1d5db; margin-bottom: 20px; padding-bottom: 16px; }
    h1 { margin: 0 0 8px; font-size: 24px; }
    .summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 10px; margin-top: 16px; }
    .metric { border: 1px solid #d1d5db; border-radius: 8px; padding: 10px; }
    .metric strong { display: block; font-size: 20px; }
    .question { border: 1px solid #d1d5db; border-radius: 8px; padding: 14px; margin: 12px 0; break-inside: avoid; }
    .q-head, .answers { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; }
    .q-head { justify-content: space-between; margin-bottom: 8px; }
    .correct { color: #15803d; }
    .wrong { color: #b91c1c; }
    .skipped { color: #64748b; }
    ol { margin: 8px 0; padding-left: 20px; }
  </style>
</head>
<body>
  <header>
    <h1>${escapeHtml(result.test_title)}</h1>
    <div>Attempt ${escapeHtml(result.attempt_number)} · Practice attempt · ${escapeHtml(formatDate(result.submitted_at))}</div>
    <div class="summary">
      <div class="metric"><span>Score</span><strong>${escapeHtml(result.score?.toFixed?.(2) ?? result.score)} / ${escapeHtml(result.total_marks)}</strong></div>
      <div class="metric"><span>Percentage</span><strong>${escapeHtml(Math.round(result.percentage))}%</strong></div>
      <div class="metric"><span>Correct</span><strong>${escapeHtml(result.correct)}</strong></div>
      <div class="metric"><span>Wrong</span><strong>${escapeHtml(result.incorrect)}</strong></div>
      <div class="metric"><span>Skipped</span><strong>${escapeHtml(result.skipped)}</strong></div>
    </div>
  </header>
  ${rows}
</body>
</html>`
}

export function downloadPracticeResultHtml(result) {
  const slug = (result.test_title || 'result').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
  const suffix = 'practice-result'
  const blob = new Blob([buildResultHtml(result)], { type: 'text/html;charset=utf-8' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = `${slug || 'test'}-${suffix}.html`
  document.body.appendChild(link)
  link.click()
  link.remove()
  setTimeout(() => URL.revokeObjectURL(url), 0)
}

export async function downloadResultReport(result) {
  if (!result || result.attempt_id == null || String(result.attempt_id).startsWith('practice-')) {
    return downloadPracticeResultHtml(result || {})
  }
  const res = await testAPI.downloadResultHtml(result.attempt_id)
  const slug = (result.test_title || 'result').toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
  const suffix = 'result'
  const blob = res.data instanceof Blob ? res.data : new Blob([res.data], { type: 'text/html' })
  const url = URL.createObjectURL(blob)
  const link = document.createElement('a')
  link.href = url
  link.download = `${slug || 'test'}-${suffix}.html`
  document.body.appendChild(link)
  link.click()
  link.remove()
  setTimeout(() => URL.revokeObjectURL(url), 0)
}