-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathstage-view.js
More file actions
213 lines (191 loc) · 10.8 KB
/
Copy pathstage-view.js
File metadata and controls
213 lines (191 loc) · 10.8 KB
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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// ══════════════════════════════════════════════════
// RENDER STAGE VIEW (works for any stage, any mode)
// ══════════════════════════════════════════════════
const TYPE_BADGE = { theory: 'exam-regular', practical: 'exam-lab', audit: 'exam-arrear' };
const SEM_PALETTE = ['accent', 'accent2', 'accent3', 'accent4'];
let showMarksDetail = false;
let currentStageId = null;
function toggleInternalMarks() {
showMarksDetail = !showMarksDetail;
if (currentStageId) renderStageView(currentStageId);
}
function renderStageView(stageId) {
const stage = getStage(stageId);
const wrap = document.getElementById('stage-content');
if (!stage) { wrap.innerHTML = ''; return; }
currentStageId = stageId;
const def = STAGE_TYPES[stage.type] || STAGE_TYPES.custom;
// CET stages don't have subjects/terms — separate, simpler view.
if (stage.mode === 'cet') {
document.getElementById('stage-title').innerHTML = `${def.icon} ${escHtml(stageLabel(stage))} <em>Entrance Exam</em>`;
document.getElementById('stage-summary').textContent = 'Conversion score & rank';
document.getElementById('stage-controls').innerHTML = `
<button class="btn btn-solid" onclick="openEditCetModal('${stage.id}')"><i class="bi bi-pencil-fill"></i> Edit Details</button>
<button class="btn btn-danger" onclick="confirmDeleteStage('${stage.id}')"><i class="bi bi-trash3-fill"></i> Delete Stage</button>
`;
wrap.classList.remove('hide-marks');
wrap.innerHTML = renderCetStageBody(stage);
return;
}
const subs = stageSubjects(stageId);
const avg = stageAvg(subs);
const total = stageTotal(stageId);
const gpa = stageGPA(stageId);
document.getElementById('stage-title').innerHTML = `${def.icon} ${escHtml(stageLabel(stage))} <em>${stage.mode === 'annual' ? 'Annual' : 'Semester-wise'}</em>`;
document.getElementById('stage-summary').textContent = `${subs.length} subject${subs.length !== 1 ? 's' : ''} · ${avg}% overall · Total ${fmt2(total.scored)}/${fmt2(total.max)} · CGPA ${fmt2(gpa)}`;
const controls = document.getElementById('stage-controls');
controls.innerHTML = `
<button class="btn btn-ghost" onclick="toggleInternalMarks()"><i class="bi bi-eye${showMarksDetail ? '-slash' : ''}-fill"></i> ${showMarksDetail ? 'Hide' : 'Show'} Marks</button>
${stage.mode === 'semester' ? `<button class="btn btn-ghost" onclick="addTerm('${stage.id}')"><i class="bi bi-plus-lg"></i> Add Semester</button>` : ''}
<button class="btn btn-solid" onclick="openAddSubjectModal('${stage.id}', '${stage.terms[0]?.id || ''}')"><i class="bi bi-plus-lg"></i> Add Subject</button>
<button class="btn btn-danger" onclick="confirmDeleteStage('${stage.id}')"><i class="bi bi-trash3-fill"></i> Delete Stage</button>
`;
wrap.classList.toggle('hide-marks', !showMarksDetail);
if (stage.mode === 'annual') {
const term = stage.terms[0];
wrap.innerHTML = subs.length
? subjectTable(stage, term.id, termSubjects(stage.id, term.id), 0)
: emptyState('No subjects recorded yet.', 'Add your first subject to begin');
return;
}
// semester mode
if (!stage.terms.length) {
wrap.innerHTML = emptyState('No semesters yet.', 'Click "Add Semester" to begin');
return;
}
wrap.innerHTML = stage.terms.map((term, idx) => {
const tSubs = termSubjects(stage.id, term.id);
const tAvg = termAvg(stage.id, term.id);
const tTotal = termTotal(stage.id, term.id);
const tGpa = termGPA(stage.id, term.id);
return `
<div class="semester-block" id="term-block-${term.id}">
<div class="semester-header" id="term-hdr-${term.id}" onclick="toggleTerm('${term.id}')">
<div style="display:flex;align-items:center;gap:0.75rem;">
<span class="collapse-icon"><i class="bi bi-chevron-down"></i></span>
<span class="semester-label">${escHtml(term.label)}</span>
<span style="font-family:var(--font-mono);font-size:0.6rem;color:var(--text-dim);">${tSubs.length} subjects</span>
</div>
<div style="display:flex;align-items:center;gap:0.75rem;">
<span style="font-family:var(--font-mono);font-size:0.68rem;color:var(--text-dim);">${fmt2(tTotal.scored)}/${fmt2(tTotal.max)}</span>
<span style="font-family:var(--font-mono);font-size:0.68rem;color:var(--text-dim);">GPA ${fmt2(tGpa)}</span>
<span class="semester-avg">${tAvg}%</span>
<button class="btn btn-ghost btn-sm" onclick="event.stopPropagation();openAddSubjectModal('${stage.id}','${term.id}')"><i class="bi bi-plus-lg"></i> Subject</button>
<button class="btn btn-danger btn-sm" onclick="event.stopPropagation();confirmDeleteTerm('${stage.id}','${term.id}')"><i class="bi bi-x-lg"></i></button>
</div>
</div>
<div class="semester-body" id="term-body-${term.id}">
${tSubs.length ? subjectTable(stage, term.id, tSubs, idx) : `<div class="empty-state" style="margin-bottom:1rem;"><div class="empty-state-text" style="font-size:0.9rem;">No subjects in this semester</div></div>`}
</div>
</div>`;
}).join('');
}
function renderCetStageBody(stage) {
const c = stage.cet || {};
const hasData = c.conversion || c.rank || c.categoryRank;
if (!hasData) {
return emptyState('No CET details yet.', 'Click "Edit Details" to add your conversion score and rank');
}
return `<div class="subject-card-grid" style="--card-accent:var(--accent);--card-tint:var(--accent-glow);">
<div class="subject-card">
<div class="subject-card-head">
<div class="subject-card-name">Previous Stage Conversion</div>
<span class="exam-badge exam-regular">score</span>
</div>
<div class="subject-card-stats">
<div class="sc-stat sc-internal" style="grid-column:1 / -1;">
<span class="sc-stat-label">Conversion Score</span>
<div class="sc-stat-value" style="font-size:1.4rem;font-weight:700;color:var(--text-primary);">${c.conversion ? fmt2(c.conversion) + ' / 100' : '—'}</div>
</div>
</div>
</div>
<div class="subject-card">
<div class="subject-card-head">
<div class="subject-card-name">Current Ranking</div>
<span class="exam-badge exam-regular">rank</span>
</div>
<div class="subject-card-stats">
<div class="sc-stat sc-internal">
<span class="sc-stat-label">Overall Rank</span>
<div class="sc-stat-value" style="font-size:1.4rem;font-weight:700;color:var(--text-primary);">${c.rank ? '#' + escHtml(String(c.rank)) : '—'}</div>
</div>
<div class="sc-stat sc-external">
<span class="sc-stat-label">Category Rank</span>
<div class="sc-stat-value" style="font-size:1.4rem;font-weight:700;color:var(--text-primary);">${c.categoryRank ? '#' + escHtml(String(c.categoryRank)) : '—'}</div>
</div>
</div>
</div>
</div>`;
}
function emptyState(text, sub) {
return `<div class="empty-state"><div class="empty-state-icon"><i class="bi bi-journal-bookmark"></i></div><div class="empty-state-text">${text}</div><div class="empty-state-sub">${sub}</div></div>`;
}
function marksCell(label, m) {
const min = +m.min || 0, max = +m.max || 0, ob = +m.obtained || 0;
if (!max) return `<span style="color:var(--text-dim);font-size:0.72rem;">—</span>`;
const below = ob < min;
return `<div class="comp-pill"><span class="comp-pill-name">${label}${min ? ` (min ${fmt2(min)})` : ''}</span><span class="comp-pill-score" style="${below ? 'color:var(--red);' : ''}">${fmt2(ob)}/${fmt2(max)}</span></div>`;
}
// Component pill showing only name + obtained marks (no min/max)
function compPill(name, obtained) {
return `<div class="comp-pill"><span class="comp-pill-name">${escHtml(name || 'Internal')}</span><span class="comp-pill-score">${fmt2(obtained)}</span></div>`;
}
function internalCell(subj) {
const comps = internalComponents(subj);
const { min, max } = internalMinMax(subj);
const parts = [];
if (comps.length) parts.push(...comps.map(c => compPill(c.name, c.obtained)));
if (max) {
const totalObt = comps.reduce((s, c) => s + (+c.obtained || 0), 0);
const below = totalObt < min;
parts.push(`<div class="comp-pill"><span class="comp-pill-name">Final IA${min ? ` (min ${fmt2(min)})` : ''}</span><span class="comp-pill-score" style="${below ? 'color:var(--red);' : ''}">${fmt2(totalObt)}/${fmt2(max)}</span></div>`);
}
if (!parts.length) return `<span style="color:var(--text-dim);font-size:0.72rem;">—</span>`;
return parts.join('');
}
function subjectTable(stage, termIdForAdd, subs, colorIdx = 0) {
const c = SEM_PALETTE[colorIdx % SEM_PALETTE.length];
const cards = subs.map(s => {
const t = calcSubjectTotal(s);
const p = pct(t.scored, t.max);
const passed = subjectPass(s);
const typeCls = TYPE_BADGE[s.subjectType] || 'exam-regular';
const resultHtml = s.subjectType === 'audit'
? `<span class="sc-result" style="color:${passed ? 'var(--accent-light)' : 'var(--red)'};">${passed ? 'Satisfactory' : 'Not Satisfactory'}</span>`
: `<span class="sc-result" style="color:${passed ? 'var(--accent-light)' : 'var(--red)'};">${grade(p)}${passed ? '' : ' · Fail'}</span>`;
return `<div class="subject-card">
<div class="subject-card-head">
<div class="subject-card-name">${escHtml(s.name)}</div>
<span class="exam-badge ${typeCls}">${s.subjectType}</span>
</div>
<div class="subject-card-stats">
<div class="sc-stat sc-internal">
<span class="sc-stat-label">Internal</span>
<div class="sc-stat-value">${internalCell(s)}</div>
</div>
<div class="sc-stat sc-external">
<span class="sc-stat-label">External</span>
<div class="sc-stat-value">${marksCell('External', s.external || {})}</div>
</div>
</div>
<div class="subject-card-footer">
<div class="sc-footer-left">
<span class="marks-badge">${fmt2(t.scored)}/${fmt2(t.max)}</span>
<span class="pct-badge ${pctClass(p)}">${p}%</span>
${resultHtml}
</div>
<div class="action-cell">
<button class="btn btn-ghost btn-sm" onclick="openEditSubjectModal('${stage.id}','${s.id}')"><i class="bi bi-pencil-fill"></i> Edit</button>
<button class="btn btn-danger btn-sm" onclick="confirmDelete('${stage.id}','${s.id}')"><i class="bi bi-trash3-fill"></i></button>
</div>
</div>
</div>`;
}).join('');
return `<div class="subject-card-grid" style="--card-accent:var(--${c});--card-tint:var(--${c}-glow);">${cards}</div>`;
}
function toggleTerm(termId) {
const hdr = document.getElementById('term-hdr-' + termId);
const body = document.getElementById('term-body-' + termId);
hdr.classList.toggle('collapsed');
body.classList.toggle('collapsed');
}