feat(exam): log time spent per scenario in exam report
Signed-off-by: Abhinav Sinha <[email protected]>
This commit is contained in:
@@ -344,12 +344,16 @@ app.post('/api/sessions/:id/submit', (req, res) => {
|
||||
const examProgressMap = {};
|
||||
for (const r of examProgressRows) examProgressMap[r.scenario_id] = r;
|
||||
|
||||
// Also pull time_spent_seconds from global progress (tracked per scenario during the exam)
|
||||
const globalProgress = loadProgress();
|
||||
|
||||
const snapshot = bundleScenarios.map(s => ({
|
||||
id: s.id, title: s.title, weight: s.weight,
|
||||
category: s.category, type: s.type, difficulty: s.difficulty,
|
||||
status: examProgressMap[s.id]?.status || 'not_started',
|
||||
completed_at: examProgressMap[s.id]?.completed_at || null,
|
||||
attempts: examProgressMap[s.id]?.attempts || 0,
|
||||
time_spent_seconds: globalProgress[s.id]?.time_spent_seconds || 0,
|
||||
}));
|
||||
const startedAt = new Date(session.started_at + 'Z');
|
||||
const durationSecs = Math.round((Date.now() - startedAt.getTime()) / 1000);
|
||||
|
||||
@@ -22,6 +22,14 @@ function formatDate(iso) {
|
||||
})
|
||||
}
|
||||
|
||||
function formatRowTime(secs) {
|
||||
if (!secs || secs === 0) return '—'
|
||||
const m = Math.floor(secs / 60)
|
||||
const s = secs % 60
|
||||
if (m > 0) return `${m}m ${s > 0 ? s + 's' : ''}`.trim()
|
||||
return `${s}s`
|
||||
}
|
||||
|
||||
function ScoreRing({ pct, passed, size = 72 }) {
|
||||
const r = (size / 2) - 7
|
||||
const circ = 2 * Math.PI * r
|
||||
@@ -169,6 +177,15 @@ function AttemptDetail({ attempt }) {
|
||||
{s.attempts > 0 && (
|
||||
<span className={styles.rowAttempts}>{s.attempts} attempt{s.attempts > 1 ? 's' : ''}</span>
|
||||
)}
|
||||
<span className={styles.rowTime}>
|
||||
{(s.time_spent_seconds > 0) && (
|
||||
<svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ marginRight: 3, verticalAlign: 'middle', opacity: 0.6 }}>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<polyline points="12 6 12 12 16 14" />
|
||||
</svg>
|
||||
)}
|
||||
{formatRowTime(s.time_spent_seconds)}
|
||||
</span>
|
||||
<span className={styles.rowPts}>
|
||||
{s.status === 'completed' ? s.weight : 0}/{s.weight} pts
|
||||
</span>
|
||||
|
||||
@@ -430,6 +430,15 @@
|
||||
border-radius: 3px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.rowTime {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
flex-shrink: 0;
|
||||
min-width: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.rowPts {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
|
||||
@@ -10,6 +10,14 @@ function formatDuration(secs) {
|
||||
return `${s}s`
|
||||
}
|
||||
|
||||
function formatRowTime(secs) {
|
||||
if (!secs || secs === 0) return '—'
|
||||
const m = Math.floor(secs / 60)
|
||||
const s = secs % 60
|
||||
if (m > 0) return `${m}m ${s > 0 ? s + 's' : ''}`.trim()
|
||||
return `${s}s`
|
||||
}
|
||||
|
||||
const DIFF_COLOR = { Easy: 'var(--green)', Medium: 'var(--amber)', Hard: 'var(--red)' }
|
||||
|
||||
export default function ExamReport({ report, bundle, onClose, onRetry }) {
|
||||
@@ -86,6 +94,15 @@ export default function ExamReport({ report, bundle, onClose, onRetry }) {
|
||||
<span className={styles.rowDiff} style={{ color: DIFF_COLOR[s.difficulty] }}>
|
||||
{s.difficulty}
|
||||
</span>
|
||||
<span className={styles.rowTime}>
|
||||
{s.time_spent_seconds > 0 && (
|
||||
<svg viewBox="0 0 24 24" width="10" height="10" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ marginRight: 3, verticalAlign: 'middle', opacity: 0.6 }}>
|
||||
<circle cx="12" cy="12" r="10" />
|
||||
<polyline points="12 6 12 12 16 14" />
|
||||
</svg>
|
||||
)}
|
||||
{formatRowTime(s.time_spent_seconds)}
|
||||
</span>
|
||||
<span className={styles.rowPts}>{s.status === 'completed' ? s.weight : 0}/{s.weight} pts</span>
|
||||
</div>
|
||||
))}
|
||||
|
||||
@@ -101,6 +101,15 @@
|
||||
.rowIcon { font-size: 14px; flex-shrink: 0; }
|
||||
.rowTitle { flex: 1; }
|
||||
.rowDiff { font-size: 11px; font-weight: 600; flex-shrink: 0; }
|
||||
.rowTime {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--text-3);
|
||||
flex-shrink: 0;
|
||||
min-width: 52px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.rowPts { font-family: var(--mono); font-size: 12px; color: var(--text-3); flex-shrink: 0; min-width: 60px; text-align: right; }
|
||||
|
||||
/* Actions */
|
||||
|
||||
Reference in New Issue
Block a user