Commit 831930da authored by Aravind RK's avatar Aravind RK

feat : Combined all the evaluators evaluation and put it in a pdf

parent 9caafb31
......@@ -221,9 +221,13 @@
const interviews = await Interview.find({ candidateId: userId })
.populate('interviewerId', 'name email role')
.populate('assignedInterviewers', 'name email')
.populate('assignedHRs', 'name email')
.populate('assignedPMs', 'name email')
.populate('createdBy', 'name')
.populate('decidedBy', 'name')
.populate('quizzes.quizId', 'title category difficulty timer totalQuestions')
.populate('evaluations.evaluatorId', 'name email role')
.sort({ createdAt: -1 });
res.json({ user, interviews });
......
Initial chunk files | Names  |  Raw size | Estimated transfer size
chunk-MIRC6W6E.js  | -  | 246.95 kB |  67.80 kB
styles-4EG7DNWJ.css | styles  |  11.07 kB |  776 bytes
main-PZJZRLKZ.js  | main  |  2.51 kB |  802 bytes
chunk-RTK3EC7A.js  | -  |  1.35 kB |  548 bytes
   | Initial total  | 261.89 kB |  69.92 kB
Lazy chunk files  | Names  |  Raw size | Estimated transfer size
chunk-ZWSSD7ZD.js  | -  |  29.23 kB |  6.50 kB
chunk-F5TCWNSW.js  | generate-quiz  |  14.44 kB |  3.89 kB
chunk-T4IU55DP.js  | take-quiz  |  13.50 kB |  3.73 kB
chunk-GPZWIEOZ.js  | results  |  12.05 kB |  3.13 kB
chunk-D642GFQ6.js  | submission-detail |  11.32 kB |  3.04 kB
chunk-7KH36PB3.js  | profile  |  11.30 kB |  3.03 kB
chunk-JIQZZPSH.js  | user-history  |  10.56 kB |  2.90 kB
chunk-NDRVNEE6.js  | dashboard  |  9.60 kB |  2.70 kB
chunk-5TGG3WZ6.js  | users  |  9.40 kB |  2.56 kB
chunk-RPGMXBIO.js  | register  |  8.49 kB |  2.47 kB
chunk-JD4GHZ43.js  | login  |  7.61 kB |  2.33 kB
chunk-DX5QOEI4.js  | dashboard  |  7.39 kB |  1.99 kB
chunk-CJBJHMWZ.js  | -  |  1.25 kB |  412 bytes
Application bundle generation complete. [3.660 seconds] - 2026-04-07T13:08:03.882Z
▲ [WARNING] src/app/pages/admin/users/users.css exceeded maximum budget. Budget 4.00 kB was not met by 283 bytes with a total of 4.28 kB.
▲ [WARNING] src/app/pages/admin/submission-detail/submission-detail.css exceeded maximum budget. Budget 4.00 kB was not met by 746 bytes with a total of 4.75 kB.
▲ [WARNING] src/app/pages/admin/user-history/user-history.css exceeded maximum budget. Budget 4.00 kB was not met by 582 bytes with a total of 4.58 kB.
▲ [WARNING] src/app/pages/student/results/results.css exceeded maximum budget. Budget 4.00 kB was not met by 1.11 kB with a total of 5.12 kB.
▲ [WARNING] src/app/pages/student/dashboard/dashboard.css exceeded maximum budget. Budget 4.00 kB was not met by 364 bytes with a total of 4.36 kB.
▲ [WARNING] src/app/pages/admin/generate-quiz/generate-quiz.css exceeded maximum budget. Budget 4.00 kB was not met by 1.71 kB with a total of 5.71 kB.
▲ [WARNING] src/app/pages/student/profile/profile.css exceeded maximum budget. Budget 4.00 kB was not met by 937 bytes with a total of 4.94 kB.
▲ [WARNING] src/app/pages/student/take-quiz/take-quiz.css exceeded maximum budget. Budget 4.00 kB was not met by 1.05 kB with a total of 5.05 kB.

Output location: C:\Users\Ramesh\Aravind\Studies\intern\Evaluation_App3\Frontend\dist\quiz-app
> quiz-app@0.0.0 build
> ng build
❯ Building...
✔ Building...
Initial chunk files | Names  |  Raw size | Estimated transfer size
chunk-MIRC6W6E.js  | -  | 246.95 kB |  67.80 kB
styles-4EG7DNWJ.css | styles  |  11.07 kB |  776 bytes
main-PZJZRLKZ.js  | main  |  2.51 kB |  802 bytes
chunk-RTK3EC7A.js  | -  |  1.35 kB |  548 bytes
   | Initial total  | 261.89 kB |  69.92 kB
Lazy chunk files  | Names  |  Raw size | Estimated transfer size
chunk-ZWSSD7ZD.js  | -  |  29.23 kB |  6.50 kB
chunk-F5TCWNSW.js  | generate-quiz  |  14.44 kB |  3.89 kB
chunk-T4IU55DP.js  | take-quiz  |  13.50 kB |  3.73 kB
chunk-GPZWIEOZ.js  | results  |  12.05 kB |  3.13 kB
chunk-D642GFQ6.js  | submission-detail |  11.32 kB |  3.04 kB
chunk-7KH36PB3.js  | profile  |  11.30 kB |  3.03 kB
chunk-JIQZZPSH.js  | user-history  |  10.56 kB |  2.90 kB
chunk-NDRVNEE6.js  | dashboard  |  9.60 kB |  2.70 kB
chunk-5TGG3WZ6.js  | users  |  9.40 kB |  2.56 kB
chunk-RPGMXBIO.js  | register  |  8.49 kB |  2.47 kB
chunk-JD4GHZ43.js  | login  |  7.61 kB |  2.33 kB
chunk-DX5QOEI4.js  | dashboard  |  7.39 kB |  1.99 kB
chunk-CJBJHMWZ.js  | -  |  1.25 kB |  412 bytes
Application bundle generation complete. [3.462 seconds] - 2026-04-07T13:09:03.363Z
Output location: C:\Users\Ramesh\Aravind\Studies\intern\Evaluation_App3\Frontend\dist\quiz-app
Binary files a/Frontend/build_output.txt and /dev/null differ
......@@ -116,7 +116,7 @@
<span>Select a candidate to evaluate</span>
</div>
@for (m of g.members; track m._id) {
<div class="qep-row" [class.qep-done]="!needsEvaluation(m)">
<div class="qep-row" [class.qep-done]="!['quiz_phase', 'coding_phase'].includes(m.status) && !needsEvaluation(m)">
<div class="qep-candidate">
<div class="qep-avatar" [ngClass]="getStatusClass(m.status)">
{{ m.candidateId?.name?.charAt(0)?.toUpperCase() }}
......@@ -127,7 +127,12 @@
</div>
</div>
<div class="qep-action">
@if (needsEvaluation(m)) {
@if (['quiz_phase', 'coding_phase'].includes(m.status)) {
<span class="qep-pending-label" style="color:var(--text-muted); font-size:12px; font-weight:500; display:flex; align-items:center; gap:4px;">
<span class="material-symbols-rounded" style="font-size:16px;">hourglass_empty</span>
Pending
</span>
} @else if (needsEvaluation(m)) {
<button class="btn-evaluate" (click)="openMemberDetail(m._id, $event)">
<span class="material-symbols-rounded">rate_review</span>
Evaluate
......
......@@ -116,7 +116,7 @@
<span>Select a candidate to evaluate</span>
</div>
@for (m of g.members; track m._id) {
<div class="qep-row" [class.qep-done]="!needsEvaluation(m)">
<div class="qep-row" [class.qep-done]="!['quiz_phase', 'coding_phase'].includes(m.status) && !needsEvaluation(m)">
<div class="qep-candidate">
<div class="qep-avatar" [ngClass]="getStatusClass(m.status)">
{{ m.candidateId?.name?.charAt(0)?.toUpperCase() }}
......@@ -127,7 +127,12 @@
</div>
</div>
<div class="qep-action">
@if (needsEvaluation(m)) {
@if (['quiz_phase', 'coding_phase'].includes(m.status)) {
<span class="qep-pending-label" style="color:var(--text-muted); font-size:12px; font-weight:500; display:flex; align-items:center; gap:4px;">
<span class="material-symbols-rounded" style="font-size:16px;">hourglass_empty</span>
Pending
</span>
} @else if (needsEvaluation(m)) {
<button class="btn-evaluate" (click)="openMemberDetail(m._id, $event)">
<span class="material-symbols-rounded">rate_review</span>
Evaluate
......
......@@ -116,7 +116,7 @@
<span>Select a candidate to evaluate</span>
</div>
@for (m of g.members; track m._id) {
<div class="qep-row" [class.qep-done]="!needsEvaluation(m)">
<div class="qep-row" [class.qep-done]="!['quiz_phase', 'coding_phase'].includes(m.status) && !needsEvaluation(m)">
<div class="qep-candidate">
<div class="qep-avatar" [ngClass]="getStatusClass(m.status)">
{{ m.candidateId?.name?.charAt(0)?.toUpperCase() }}
......@@ -127,7 +127,12 @@
</div>
</div>
<div class="qep-action">
@if (needsEvaluation(m)) {
@if (['quiz_phase', 'coding_phase'].includes(m.status)) {
<span class="qep-pending-label" style="color:var(--text-muted); font-size:12px; font-weight:500; display:flex; align-items:center; gap:4px;">
<span class="material-symbols-rounded" style="font-size:16px;">hourglass_empty</span>
Pending
</span>
} @else if (needsEvaluation(m)) {
<button class="btn-evaluate" (click)="openMemberDetail(m._id, $event)">
<span class="material-symbols-rounded">rate_review</span>
Evaluate
......@@ -268,7 +273,7 @@
<span class="material-symbols-rounded">quiz</span> Quiz Configuration
@if (quizSets.length > 0) {
<button class="btn btn-outline btn-sm" style="margin-left: auto;" (click)="addMoreSet()">
<span class="material-symbols-rounded">add</span> Add Set
<span class="material-symbols-rounded">add</span> Add Quiz
</button>
}
</div>
......@@ -276,7 +281,7 @@
@if (quizSets.length === 0 && !showQuizSetup) {
<div class="quiz-empty-hint">
<span class="material-symbols-rounded">quiz</span>
<p>No quiz sets configured yet.</p>
<p>No quizzes configured yet.</p>
<button class="btn btn-primary" (click)="showQuizSetup = true">
<span class="material-symbols-rounded">add</span> Add Quiz
</button>
......@@ -286,7 +291,7 @@
@if (showQuizSetup) {
<div class="quiz-setup-prompt">
<span class="material-symbols-rounded">help_outline</span>
<span>How many quiz sets?</span>
<span>How many quizzes?</span>
<input type="number" min="1" max="10" class="sets-count-input"
[(ngModel)]="pendingSetsCount" placeholder="e.g. 2">
<button class="btn btn-primary btn-sm" (click)="confirmSetsCount()">Confirm</button>
......@@ -299,11 +304,11 @@
<div class="quiz-set-block">
<div class="quiz-set-header">
<div class="quiz-set-title">
<span class="set-badge">Set {{ si + 1 }}</span>
<span class="set-badge">Quiz {{ si + 1 }}</span>
@if (isSingleQuizSet(set)) {
<span class="set-note">📌 Assigned to all candidates</span>
} @else if (isMultiQuizSet(set)) {
<span class="set-note">🎯 {{ validEntries(set).length }} quizzes · {{ set.mode === 'random' ? '🎲 Random' : 'Direct' }} assignment</span>
<span class="set-note">🎯 {{ validEntries(set).length }} sets · {{ set.mode === 'random' ? '🎲 Random' : 'Direct' }} assignment</span>
}
</div>
<button class="icon-btn danger" (click)="removeQuizSet(si)">
......@@ -315,7 +320,7 @@
@for (entry of set.quizEntries; track $index; let ei = $index) {
<div class="quiz-entry-row">
<select class="form-input" [(ngModel)]="entry.quizId">
<option value="">— Select quiz</option>
<option value="">— Select set</option>
@for (q of getAvailableQuizzes(si, ei); track q._id) {
<option [value]="q._id">{{ q.title }}</option>
}
......@@ -327,7 +332,7 @@
}
<button class="btn btn-ghost btn-sm" (click)="addQuizToSet(si)">
<span class="material-symbols-rounded">add</span> Add quiz to Set {{ si + 1 }}
<span class="material-symbols-rounded">add</span> Add set to Quiz {{ si + 1 }}
</button>
<!-- Assignment mode — only shown when multiple quizzes -->
......
......@@ -127,7 +127,7 @@ export class GroupInterviewComponent implements OnInit {
this.hrs.set(staff.filter((s: any) => s.role === 'hr'));
}
});
this.quizService.getAdminQuizzes().subscribe({ next: r => this.quizzes.set(r.quizzes || []) });
this.quizService.getQuizzes().subscribe({ next: r => this.quizzes.set(r.quizzes || []) });
this.showCreateModal.set(true);
}
......
......@@ -119,9 +119,16 @@
</td>
<td class="date-cell">{{ interview.dateOfInterview | date:'dd MMM yyyy' }}</td>
<td>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
<button class="view-btn" (click)="selectInterview(interview)">
View Quizzes <span class="material-symbols-rounded" style="font-size:16px;vertical-align:middle;">arrow_forward</span>
</button>
@if (hasEvaluations(interview)) {
<button class="view-btn" style="background:var(--accent,#4472C4);color:#fff;border-color:#3360b0;" (click)="downloadEvaluationForm(interview)">
<span class="material-symbols-rounded" style="font-size:16px;vertical-align:middle;">download</span> Eval Form
</button>
}
</div>
</td>
</tr>
}
......
......@@ -164,4 +164,161 @@ export class UserHistoryComponent implements OnInit {
if (!resumePath) return '';
return `http://localhost:5000${resumePath}`;
}
hasEvaluations(interview: any): boolean {
return (interview.evaluations?.length || 0) > 0;
}
downloadEvaluationForm(interview: any): void {
const u = this.user();
const candidateName = u?.name || '';
const dateStr = interview.dateOfInterview
? new Date(interview.dateOfInterview).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
: '';
const position = interview.position || '';
const source = interview.source || '';
const techStack = interview.techStack || '';
const interviewerNames = (interview.assignedInterviewers || [])
.map((i: any) => i.name || '').filter(Boolean).join(', ')
|| interview.interviewerId?.name || 'Unassigned';
const quizzes = interview.quizzes || [];
const q1 = quizzes[0];
const q2 = quizzes[1];
const q1Name = q1?.quizId?.title || q1?.title || '';
const q1Score = (q1?.score != null) ? `${q1.score}/${q1.totalMarks}` : '';
const q2Name = q2?.quizId?.title || q2?.title || '';
const q2Score = (q2?.score != null) ? `${q2.score}/${q2.totalMarks}` : '';
const evals = interview.evaluations || [];
const ivEval = evals.find((e: any) => e.evaluatorRole === 'interviewer');
const pmEval = evals.find((e: any) => e.evaluatorRole === 'pm');
const hrEval = evals.find((e: any) => e.evaluatorRole === 'hr');
const adminEval = evals.find((e: any) => e.evaluatorRole === 'admin');
const recMap: Record<string, string> = {
offer: 'Offer/Hire as Intern', on_hold: 'On Hold',
rejected: 'Rejected', '2nd_round': '2nd Round'
};
const buildCheckboxes = (groupName: string, rec: string): string =>
['offer', 'on_hold', 'rejected', '2nd_round'].map(val => {
const checked = rec === val ? 'checked' : '';
const label = val === '2nd_round' ? '2<sup>nd</sup> Round' : recMap[val];
return `<label style="display:flex;align-items:center;gap:5px;font-size:13px;"><input type="checkbox" name="${groupName}" ${checked} style="width:14px;height:14px;accent-color:#4472C4;"> ${label}</label>`;
}).join('');
const fmtDate = (d: any) => d ? new Date(d).toLocaleDateString('en-GB') : '';
const ivName = ivEval?.evaluatorId?.name || 'Interviewer';
const pmName = pmEval?.evaluatorId?.name || 'Project Manager';
const hrName = hrEval?.evaluatorId?.name || 'HR';
const overallRec = adminEval?.recommendation || interview.finalDecision || '';
const ITL_LOGO = 'data:image/gif;base64,R0lGODlhtgBCAPcAAPqsTtTimLXVfMHah/y6Yf7QfP/snt7oov/ikf/ejb/f8b/a6r/Y6L/W5vqvUtrmnvu2XP7Ebf/UgPqwVPu4XsXciv7Jc9jv+r/q+Mzfkefsq7/n9+HppfigP//SftDhlc7gk+jy2vqyV8rejvidO9jknPuzWarQcvmpSviiQv3ky/3lzLTUev3jyfeZNveXM//XhP7Mdv7KdP7Gb7nZjr3Yg7jWf6rPcfH24v/nmePqp9Pil8Pbif7Hcf3BabvXgbPUeaXObPaTL3+83b/e7kCbzQCFzNXV1oKCg6urrODg4Orq6t/u9/X19e/3+2JiZJ/N5iCLxIrN8ZTR8nd3eQCDyp7V9BCCwKja9svLy21tb7Hd98/m8rnh+MDAwcHl+X/C5a/V6jCTyXC02ZeXmGCs1Y/F4le86wCg4AB3uuDy+0W46QCAxgB/wQCd3wCk4X/I8ABztgB6vwBnqABjpABvsQGr5ABcnQCn4xOv5nLE7i206AB9wgBfoGXA7ABqrKGhokCh1gCCyFhYWra2t1Ck0c/o9fmoSRCNz0Ck2f29ZPqtUP7NePu1W/3o0IyMjvmrTf7Cav3AZ/3p0f/bifmnSP2+ZfmqTL/c7fL3477ZhM3gkv3nz7/d7rDTd6/Sdv7q07/o99Xu+r/e77/f78Tr+f7Da/3mzfqxVvmlRfquUdLhlvmmR8jdjf7Pesrs+dzw+/u0WdDt+fihQLzakq3RdPikRPmkRf/Wg/3nzvy7Yv748cLk+LTXirbYjMjn+feaN+vur/mlRt/v+Pu5YPy8Y+vvsP/ml3C54Mnejv2/Zp/R7Mfn+Ofy2fu3XvPzuP/ciurz3LLTeO/14KDMZ9Xr5v3p0v3q0vqwU/u2W/7Qe8/glNzmn7zi96LNav7Bae314P/YhqjPb8Tbif7Fbcjeje3wsf7Oec7p+vmqSzCa0hCKyxCLzfD24bXf9tPs+/ibOdXjmfiePP3o0fDxtPaQK0Ci1+Xrqv7Icf/ZiOz03e303qfObrrakAB6vP///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjEgNjQuMTQwOTQ5LCAyMDEwLzEyLzA3LTEwOjU3OjAxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InV1aWQ6M0VFQTQ2RTUwQjUxRTIxMTg3NjBBREQ5OUJDNDQyNkYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RkQzNEU5MjI5NDI2MTFFMzlFMzJCNTI0NkY5NTYwMUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RkQzNEU5MjE5NDI2MTFFMzlFMzJCNTI0NkY5NTYwMUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgSWxsdXN0cmF0b3IgQ1M1Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ODYxQjI3MzY3Mjc2RTMxMUEzQjZERTY4M0Y2RDE5QTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ODYxQjI3MzY3Mjc2RTMxMUEzQjZERTY4M0Y2RDE5QTEiLz4gPGRjOnRpdGxlPiA8cmRmOkFsdD4gPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij5JVExfQkNfQVc8L3JkZjpsaT4gPC9yZGY6QWx0PiA8L2RjOnRpdGxlPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAAAAAAALAAAAAC2AEIAAAj/AP8JHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEMO3NWihYoVK06dysXJkct5k0TKnEkzY4t6Ql64gCevQwpbwiodGlqzqNGjCVXkBEbCZ6pKKC5BArBoEdKrWGuqeMF0li1WKCAtcjABlQgRWdOq7bgCmLxZtw5dWoRNhIlGEJxRWMu3L8VTJGalOgRJFapYEIgRKKZIkd/HkBfm6hAXgAMRjSjosiTJRyRTkUOLFsgpRSVIlyEQUOTDFLkZ+PCNnv3YEat0qkSothSJXA8ZMRgxot187aRDi1A1IsB7hoUYrgp48ICxiPUxBK1r314kTJiDULiL/9eOHTz5hOGtcynOcNIlByYoKPo2QwajAhIk4IKB0Z//IgT5J+CAAkZBREFDEKiggAAeJMaATCCUoH8HsqeQNQCgko0ukkRgwX0SwJAPJdD091+ACyoIBUETpjhggwUxQaAZEgpYoYUIXaOKCc4oEkkPMWgTIiUJIICAif7A+M+L2ykYoUAtRjHeeQaZQaAYNVKIY0KgOBALMcp4eI4HuBCJwDE5IKnkgAU5McaAZQzU4hASPUjgkwjauCVCoEywnCTkyOCKBOEUmYMBBqiJon8HteiPnALSCREXApYhII0GtXjjnlcxuOijBjkx4Hr/zBnRm/5xcYV/UTSqJ6dYef86EJsHFfGqqRBFweo/lqaa6auwIiWrQLQaZKuWpUY6qYDYhcHsr8iO9sW01FZr7bVfXGDQM/QYowEH3MTzwQjjaGKDNCfQktCwSwqI0LH+VNhiEUPUa6+9B6Ea7z+i7ppntKJ1IfDABBdssMDvFGROMBrocMADO4DQCg81COBJuuueOKu7td7qYrEErerPFQMVAiyUJ4e2xcust+zyyyyjQ5AG9zj8gLgZVDDADwJ8ckM/GSf56bsef8xoQc76F6dAUFT6776zYSH11FRXbTXVvwzEAQcPx7PKJuXwYC4QtfADNELsgpxd0UYb1Ks/3wnU78hPbxqaFXjnrffefO//zYtA3DxQgtcgjKAzzxcHQUPQa3JsLNuSNiSyP06s7V/cKAMc2hScd+7556CD3s0/8QSwwwebJKNzDTaQLY43vjA+dMfI4spQ0/5dwZ2uSrOYcmRSBC/88MQXb7w722yTwQitrG4DCz4HQU0vsm98tEG8Q237QiYbPXLlmUM9mhrkl2//...AAAAAAAAA7';
const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/>
<title>ITL Evaluation - ${candidateName}</title><style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-size:13px;}
.page-wrap{max-width:780px;margin:0 auto;border:1px solid #ccc;}
.header{background:#4472C4;display:flex;align-items:center;justify-content:space-between;padding:10px 16px;}
.header h1{color:#fff;font-size:16px;font-weight:bold;font-style:italic;text-decoration:underline;}
.logo-wrap img{height:52px;object-fit:contain;}
.info-table{width:100%;border-collapse:collapse;}
.info-table td{border:1px solid #bbb;padding:6px 10px;vertical-align:middle;}
.info-table .label{font-weight:bold;background:#fff;width:160px;white-space:nowrap;}
.info-table .value{background:#fff;color:#444;font-style:italic;}
.comments-section{border:1px solid #bbb;border-top:none;padding:10px 16px;}
.comments-label{font-weight:bold;margin-bottom:6px;}
.comments-text{min-height:65px;font-size:13px;font-family:Arial,sans-serif;color:#333;line-height:1.5;padding:4px 0;}
.rec-row{border:1px solid #bbb;border-top:none;padding:8px 16px;display:flex;align-items:center;gap:20px;flex-wrap:wrap;}
.rec-label{font-weight:bold;white-space:nowrap;}
.sig-row{border:1px solid #bbb;border-top:none;padding:8px 16px;display:flex;align-items:flex-end;gap:40px;}
.sig-field{display:flex;align-items:flex-end;gap:8px;}
.sig-field label{font-weight:bold;white-space:nowrap;}
.sig-line{border-bottom:1px solid #000;min-width:200px;height:18px;font-style:italic;color:#555;font-size:13px;}
.sig-line-date{min-width:140px;}
.actions{margin-top:16px;display:flex;justify-content:flex-end;gap:10px;}
.btn{font-size:13px;font-family:Arial,sans-serif;padding:7px 18px;border-radius:3px;cursor:pointer;border:1px solid #aaa;}
.btn-print{background:#4472C4;color:#fff;border-color:#3360b0;}
@media print{body{padding:0;}.actions{display:none;}}
</style></head><body>
<div class="page-wrap">
<div class="header">
<h1>Intern Interview Evaluation Form</h1>
<div class="logo-wrap"><img src="${ITL_LOGO}" alt="ITL Logo"/></div>
</div>
<table class="info-table"><tbody>
<tr>
<td class="label">Candidate Name:</td><td class="value">${candidateName}</td>
<td class="label">Date of Interview:</td><td class="value">${dateStr}</td>
</tr>
<tr>
<td class="label">Position:</td><td class="value">${position}</td>
<td class="label">Source:</td><td class="value">${source}</td>
</tr>
<tr>
<td class="label">Tech Stack:</td><td class="value">${techStack}</td>
<td class="label"><strong>Interviewer:</strong></td><td class="value">${interviewerNames}</td>
</tr>
<tr>
<td class="label">General Aptitude Test QP Set</td><td class="value">${q1Name}</td>
<td class="label"><strong>General Aptitude Test Score</strong></td><td class="value">${q1Score}</td>
</tr>
<tr>
<td class="label">Technical MCQ Test QP Set</td><td class="value">${q2Name}</td>
<td class="label"><strong>Technical MCQ Test Score</strong></td><td class="value">${q2Score}</td>
</tr>
</tbody></table>
<div class="comments-section" style="margin-top:0;">
<div class="comments-label">Interviewer's Comments (${ivName}):</div>
<div class="comments-text">${ivEval?.comments || ''}</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>${buildCheckboxes('rec1', ivEval?.recommendation || '')}</div>
<div class="sig-row">
<div class="sig-field"><label>Evaluator's Signature:</label><div class="sig-line">${ivName}</div></div>
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">${fmtDate(ivEval?.date)}</div></div>
</div>
<div class="comments-section">
<div class="comments-label">Project Manager Comments (${pmName}):</div>
<div class="comments-text">${pmEval?.comments || ''}</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>${buildCheckboxes('rec2', pmEval?.recommendation || '')}</div>
<div class="sig-row">
<div class="sig-field"><label>Evaluator's Signature:</label><div class="sig-line">${pmName}</div></div>
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">${fmtDate(pmEval?.date)}</div></div>
</div>
<div class="comments-section">
<div class="comments-label">HR Comments (${hrName}):</div>
<div class="comments-text" style="min-height:52px;">${hrEval?.comments || ''}</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>${buildCheckboxes('rec3', hrEval?.recommendation || '')}</div>
<div class="sig-row">
<div class="sig-field"><label>Evaluator's Signature:</label><div class="sig-line">${hrName}</div></div>
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">${fmtDate(hrEval?.date)}</div></div>
</div>
<div class="comments-section"><div class="comments-label">Overall Recommendation:</div></div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>${buildCheckboxes('rec-overall', overallRec)}</div>
<div class="sig-row">
<div class="sig-field"><label>Authorized Signature:</label><div class="sig-line">${adminEval?.evaluatorId?.name || ''}</div></div>
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">${fmtDate(adminEval?.date)}</div></div>
</div>
</div>
<div class="actions">
<button class="btn btn-print" onclick="window.print()">&#128438; Print / Save as PDF</button>
</div>
</body></html>`;
const win = window.open('', '_blank');
if (win) {
win.document.write(html);
win.document.close();
win.focus();
}
}
}
......@@ -116,7 +116,7 @@
<span>Select a candidate to evaluate</span>
</div>
@for (m of g.members; track m._id) {
<div class="qep-row" [class.qep-done]="!needsEvaluation(m)">
<div class="qep-row" [class.qep-done]="!['quiz_phase', 'coding_phase'].includes(m.status) && !needsEvaluation(m)">
<div class="qep-candidate">
<div class="qep-avatar" [ngClass]="getStatusClass(m.status)">
{{ m.candidateId?.name?.charAt(0)?.toUpperCase() }}
......@@ -127,7 +127,12 @@
</div>
</div>
<div class="qep-action">
@if (needsEvaluation(m)) {
@if (['quiz_phase', 'coding_phase'].includes(m.status)) {
<span class="qep-pending-label" style="color:var(--text-muted); font-size:12px; font-weight:500; display:flex; align-items:center; gap:4px;">
<span class="material-symbols-rounded" style="font-size:16px;">hourglass_empty</span>
Pending
</span>
} @else if (needsEvaluation(m)) {
<button class="btn-evaluate" (click)="openMemberDetail(m._id, $event)">
<span class="material-symbols-rounded">rate_review</span>
Evaluate
......@@ -269,7 +274,7 @@
<span class="material-symbols-rounded">quiz</span> Quiz Configuration
@if (quizSets.length > 0) {
<button class="btn btn-outline btn-sm" style="margin-left: auto;" (click)="addMoreSet()">
<span class="material-symbols-rounded">add</span> Add Set
<span class="material-symbols-rounded">add</span> Add Quiz
</button>
}
</div>
......@@ -277,7 +282,7 @@
@if (quizSets.length === 0 && !showQuizSetup) {
<div class="quiz-empty-hint">
<span class="material-symbols-rounded">quiz</span>
<p>No quiz sets configured yet.</p>
<p>No quizzes configured yet.</p>
<button class="btn btn-primary" (click)="showQuizSetup = true">
<span class="material-symbols-rounded">add</span> Add Quiz
</button>
......@@ -287,7 +292,7 @@
@if (showQuizSetup) {
<div class="quiz-setup-prompt">
<span class="material-symbols-rounded">help_outline</span>
<span>How many quiz sets?</span>
<span>How many quizzes?</span>
<input type="number" min="1" max="10" class="sets-count-input"
[(ngModel)]="pendingSetsCount" placeholder="e.g. 2">
<button class="btn btn-primary btn-sm" (click)="confirmSetsCount()">Confirm</button>
......@@ -300,11 +305,11 @@
<div class="quiz-set-block">
<div class="quiz-set-header">
<div class="quiz-set-title">
<span class="set-badge">Set {{ si + 1 }}</span>
<span class="set-badge">Quiz {{ si + 1 }}</span>
@if (isSingleQuizSet(set)) {
<span class="set-note">📌 Assigned to all candidates</span>
} @else if (isMultiQuizSet(set)) {
<span class="set-note">🎯 {{ validEntries(set).length }} quizzes · {{ set.mode === 'random' ? '🎲 Random' : 'Direct' }} assignment</span>
<span class="set-note">🎯 {{ validEntries(set).length }} sets · {{ set.mode === 'random' ? '🎲 Random' : 'Direct' }} assignment</span>
}
</div>
<button class="icon-btn danger" (click)="removeQuizSet(si)">
......@@ -316,7 +321,7 @@
@for (entry of set.quizEntries; track $index; let ei = $index) {
<div class="quiz-entry-row">
<select class="form-input" [(ngModel)]="entry.quizId">
<option value="">— Select quiz</option>
<option value="">— Select set</option>
@for (q of getAvailableQuizzes(si, ei); track q._id) {
<option [value]="q._id">{{ q.title }}</option>
}
......@@ -328,7 +333,7 @@
}
<button class="btn btn-ghost btn-sm" (click)="addQuizToSet(si)">
<span class="material-symbols-rounded">add</span> Add quiz to Set {{ si + 1 }}
<span class="material-symbols-rounded">add</span> Add set to Quiz {{ si + 1 }}
</button>
<!-- Assignment mode — only shown when multiple quizzes -->
......
......@@ -127,7 +127,7 @@ export class HRGroupInterviewComponent implements OnInit {
this.hrs.set(staff.filter((s: any) => s.role === 'hr'));
}
});
this.quizService.getAdminQuizzes().subscribe({ next: r => this.quizzes.set(r.quizzes || []) });
this.quizService.getQuizzes().subscribe({ next: r => this.quizzes.set(r.quizzes || []) });
this.showCreateModal.set(true);
}
......
......@@ -119,9 +119,16 @@
</td>
<td class="date-cell">{{ interview.dateOfInterview | date:'dd MMM yyyy' }}</td>
<td>
<div style="display:flex;gap:8px;align-items:center;flex-wrap:wrap;">
<button class="view-btn" (click)="selectInterview(interview)">
View Quizzes <span class="material-symbols-rounded" style="font-size:16px;vertical-align:middle;">arrow_forward</span>
</button>
@if (hasEvaluations(interview)) {
<button class="view-btn" style="background:var(--accent,#4472C4);color:#fff;border-color:#3360b0;" (click)="downloadEvaluationForm(interview)">
<span class="material-symbols-rounded" style="font-size:16px;vertical-align:middle;">download</span> Eval Form
</button>
}
</div>
</td>
</tr>
}
......
......@@ -164,4 +164,157 @@ export class HRUserHistoryComponent implements OnInit {
if (!resumePath) return '';
return `http://localhost:5000${resumePath}`;
}
hasEvaluations(interview: any): boolean {
return (interview.evaluations?.length || 0) > 0;
}
downloadEvaluationForm(interview: any): void {
const u = this.user();
const candidateName = u?.name || '';
const dateStr = interview.dateOfInterview
? new Date(interview.dateOfInterview).toLocaleDateString('en-GB', { day: '2-digit', month: 'short', year: 'numeric' })
: '';
const position = interview.position || '';
const source = interview.source || '';
const techStack = interview.techStack || '';
const interviewerNames = (interview.assignedInterviewers || [])
.map((i: any) => i.name || '').filter(Boolean).join(', ')
|| interview.interviewerId?.name || 'Unassigned';
const quizzes = interview.quizzes || [];
const q1 = quizzes[0];
const q2 = quizzes[1];
const q1Name = q1?.quizId?.title || q1?.title || '';
const q1Score = (q1?.score != null) ? `${q1.score}/${q1.totalMarks}` : '';
const q2Name = q2?.quizId?.title || q2?.title || '';
const q2Score = (q2?.score != null) ? `${q2.score}/${q2.totalMarks}` : '';
const evals = interview.evaluations || [];
const ivEval = evals.find((e: any) => e.evaluatorRole === 'interviewer');
const pmEval = evals.find((e: any) => e.evaluatorRole === 'pm');
const hrEval = evals.find((e: any) => e.evaluatorRole === 'hr');
const adminEval = evals.find((e: any) => e.evaluatorRole === 'admin');
const recMap: Record<string, string> = {
offer: 'Offer/Hire as Intern', on_hold: 'On Hold',
rejected: 'Rejected', '2nd_round': '2nd Round'
};
const buildCheckboxes = (groupName: string, rec: string): string =>
['offer', 'on_hold', 'rejected', '2nd_round'].map(val => {
const checked = rec === val ? 'checked' : '';
const label = val === '2nd_round' ? '2<sup>nd</sup> Round' : recMap[val];
return `<label style="display:flex;align-items:center;gap:5px;font-size:13px;"><input type="checkbox" name="${groupName}" ${checked} style="width:14px;height:14px;accent-color:#4472C4;"> ${label}</label>`;
}).join('');
const fmtDate = (d: any) => d ? new Date(d).toLocaleDateString('en-GB') : '';
const ivName = ivEval?.evaluatorId?.name || 'Interviewer';
const pmName = pmEval?.evaluatorId?.name || 'Project Manager';
const hrName = hrEval?.evaluatorId?.name || 'HR';
const overallRec = adminEval?.recommendation || interview.finalDecision || '';
const ITL_LOGO = 'data:image/gif;base64,R0lGODlhtgBCAPcAAPqsTtTimLXVfMHah/y6Yf7QfP/snt7oov/ikf/ejb/f8b/a6r/Y6L/W5vqvUtrmnvu2XP7Ebf/UgPqwVPu4XsXciv7Jc9jv+r/q+Mzfkefsq7/n9+HppfigP//SftDhlc7gk+jy2vqyV8rejvidO9jknPuzWarQcvmpSviiQv3ky/3lzLTUev3jyfeZNveXM//XhP7Mdv7KdP7Gb7nZjr3Yg7jWf6rPcfH24v/nmePqp9Pil8Pbif7Hcf3BabvXgbPUeaXObPaTL3+83b/e7kCbzQCFzNXV1oKCg6urrODg4Orq6t/u9/X19e/3+2JiZJ/N5iCLxIrN8ZTR8nd3eQCDyp7V9BCCwKja9svLy21tb7Hd98/m8rnh+MDAwcHl+X/C5a/V6jCTyXC02ZeXmGCs1Y/F4le86wCg4AB3uuDy+0W46QCAxgB/wQCd3wCk4X/I8ABztgB6vwBnqABjpABvsQGr5ABcnQCn4xOv5nLE7i206AB9wgBfoGXA7ABqrKGhokCh1gCCyFhYWra2t1Ck0c/o9fmoSRCNz0Ck2f29ZPqtUP7NePu1W/3o0IyMjvmrTf7Cav3AZ/3p0f/bifmnSP2+ZfmqTL/c7fL3477ZhM3gkv3nz7/d7rDTd6/Sdv7q07/o99Xu+r/e77/f78Tr+f7Da/3mzfqxVvmlRfquUdLhlvmmR8jdjf7Pesrs+dzw+/u0WdDt+fihQLzakq3RdPikRPmkRf/Wg/3nzvy7Yv748cLk+LTXirbYjMjn+feaN+vur/mlRt/v+Pu5YPy8Y+vvsP/ml3C54Mnejv2/Zp/R7Mfn+Ofy2fu3XvPzuP/ciurz3LLTeO/14KDMZ9Xr5v3p0v3q0vqwU/u2W/7Qe8/glNzmn7zi96LNav7Bae314P/YhqjPb8Tbif7Fbcjeje3wsf7Oec7p+vmqSzCa0hCKyxCLzfD24bXf9tPs+/ibOdXjmfiePP3o0fDxtPaQK0Ci1+Xrqv7Icf/ZiOz03e303qfObrrakAB6vP///yH/C1hNUCBEYXRhWE1QPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS4wLWMwNjEgNjQuMTQwOTQ5LCAyMDEwLzEyLzA3LTEwOjU3OjAxICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InV1aWQ6M0VFQTQ2RTUwQjUxRTIxMTg3NjBBREQ5OUJDNDQyNkYiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RkQzNEU5MjI5NDI2MTFFMzlFMzJCNTI0NkY5NTYwMUYiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RkQzNEU5MjE5NDI2MTFFMzlFMzJCNTI0NkY5NTYwMUYiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgSWxsdXN0cmF0b3IgQ1M1Ij4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6ODYxQjI3MzY3Mjc2RTMxMUEzQjZERTY4M0Y2RDE5QTEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6ODYxQjI3MzY3Mjc2RTMxMUEzQjZERTY4M0Y2RDE5QTEiLz4gPGRjOnRpdGxlPiA8cmRmOkFsdD4gPHJkZjpsaSB4bWw6bGFuZz0ieC1kZWZhdWx0Ij5JVExfQkNfQVc8L3JkZjpsaT4gPC9yZGY6QWx0PiA8L2RjOnRpdGxlPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PgH//v38+/r5+Pf29fTz8vHw7+7t7Ovq6ejn5uXk4+Lh4N/e3dzb2tnY19bV1NPS0dDPzs3My8rJyMfGxcTDwsHAv769vLu6ubi3trW0s7KxsK+urayrqqmop6alpKOioaCfnp2cm5qZmJeWlZSTkpGQj46NjIuKiYiHhoWEg4KBgH9+fXx7enl4d3Z1dHNycXBvbm1sa2ppaGdmZWRjYmFgX15dXFtaWVhXVlVUU1JRUE9OTUxLSklIR0ZFRENCQUA/Pj08Ozo5ODc2NTQzMjEwLy4tLCsqKSgnJiUkIyIhIB8eHRwbGhkYFxYVFBMSERAPDg0MCwoJCAcGBQQDAgEAACH5BAAAAAAALAAAAAC2AEIAAAj/AP8JHEiwoMGDCBMqXMiwocOHECNKnEixosWLGDNq3Mixo8ePIEMO3NWihYoVK06dysXJkct5k0TKnEkzY4t6Ql64gCevQwpbwiodGlqzqNGjCVXkBEbCZ6pKKC5BArBoEdKrWGuqeMF0li1WKCAtcjABlQgRWdOq7bgCmLxZtw5dWoRNhIlGEJxRWMu3L8VTJGalOgRJFapYEIgRKKZIkd/HkBfm6hAXgAMRjSjosiTJRyRTkUOLFsgpRSVIlyEQUOTDFLkZ+PCNnv3YEat0qkSothSJXA8ZMRgxot187aRDi1A1IsB7hoUYrgp48ICxiPUxBK1r314kTJiDULiL/9eOHTz5hOGtcynOcNIlByYoKPo2QwajAhIk4IKB0Z//IgT5J+CAAkZBREFDEKiggAAeJMaATCCUoH8HsqeQNQCgko0ukkRgwX0SwJAPJdD091+ACyoIBUETpjhggwUxQaAZEgpYoYUIXaOKCc4oEkkPMWgTIiUJIICAif7A+M+L2ykYoUAtRjHeeQaZQaAYNVKIY0KgOBALMcp4eI4HuBCJwDE5IKnkgAU5McaAZQzU4hASPUjgkwjauCVCoEywnCTkyOCKBOEUmYMBBqiJon8HteiPnALSCREXApYhII0GtXjjnlcxuOijBjkx4Hr/zBnRm/5xcYV/UTSqJ6dYef86EJsHFfGqqRBFweo/lqaa6auwIiWrQLQaZKuWpUY6qYDYhcHsr8iO9sW01FZr7bVfXGDQM/QYowEH3MTzwQjjaGKDNCfQktCwSwqI0LH+VNhiEUPUa6+9B6Ea7z+i7ppntKJ1IfDABBdssMDvFGROMBrocMADO4DQCg81COBJuuueOKu7td7qYrEErerPFQMVAiyUJ4e2xcust+zyyyyjQ5AG9zj8gLgZVDDADwJ8ckM/GSf56bsef8xoQc76F6dAUFT6776zYSH11FRXbTXVvwzEAQcPx7PKJuXwYC4QtfADNELsgpxd0UYb1Ks/3wnU78hPbxqaFXjnrffefO//zYtA3DxQgtcgjKAzzxcHQUPQa3JsLNuSNiSyP06s7V/cKAMc2hScd+7556CD3s0/8QSwwwebJKNzDTaQLY43vjA+dMfI4spQ0/5dwZ2uSrOYcmRSBC/88MQXb7w722yTwQitrG4DCz4HQU0vsm98tEG8Q237QiYbPXLlmUM9mhrkl2//...AAAAAAAAA7';
const html = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"/>
<title>ITL Evaluation - ${candidateName}</title><style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-size:13px;}
.page-wrap{max-width:780px;margin:0 auto;border:1px solid #ccc;}
.header{background:#4472C4;display:flex;align-items:center;justify-content:space-between;padding:10px 16px;}
.header h1{color:#fff;font-size:16px;font-weight:bold;font-style:italic;text-decoration:underline;}
.logo-wrap img{height:52px;object-fit:contain;}
.info-table{width:100%;border-collapse:collapse;}
.info-table td{border:1px solid #bbb;padding:6px 10px;vertical-align:middle;}
.info-table .label{font-weight:bold;background:#fff;width:160px;white-space:nowrap;}
.info-table .value{background:#fff;color:#444;font-style:italic;}
.comments-section{border:1px solid #bbb;border-top:none;padding:10px 16px;}
.comments-label{font-weight:bold;margin-bottom:6px;}
.comments-text{min-height:65px;font-size:13px;font-family:Arial,sans-serif;color:#333;line-height:1.5;padding:4px 0;}
.rec-row{border:1px solid #bbb;border-top:none;padding:8px 16px;display:flex;align-items:center;gap:20px;flex-wrap:wrap;}
.rec-label{font-weight:bold;white-space:nowrap;}
.sig-row{border:1px solid #bbb;border-top:none;padding:8px 16px;display:flex;align-items:flex-end;gap:40px;}
.sig-field{display:flex;align-items:flex-end;gap:8px;}
.sig-field label{font-weight:bold;white-space:nowrap;}
.sig-line{border-bottom:1px solid #000;min-width:200px;height:18px;font-style:italic;color:#555;font-size:13px;}
.sig-line-date{min-width:140px;}
.actions{margin-top:16px;display:flex;justify-content:flex-end;gap:10px;}
.btn{font-size:13px;font-family:Arial,sans-serif;padding:7px 18px;border-radius:3px;cursor:pointer;border:1px solid #aaa;}
.btn-print{background:#4472C4;color:#fff;border-color:#3360b0;}
@media print{body{padding:0;}.actions{display:none;}}
</style></head><body>
<div class="page-wrap">
<div class="header">
<h1>Intern Interview Evaluation Form</h1>
<div class="logo-wrap"><img src="${ITL_LOGO}" alt="ITL Logo"/></div>
</div>
<table class="info-table"><tbody>
<tr>
<td class="label">Candidate Name:</td><td class="value">${candidateName}</td>
<td class="label">Date of Interview:</td><td class="value">${dateStr}</td>
</tr>
<tr>
<td class="label">Position:</td><td class="value">${position}</td>
<td class="label">Source:</td><td class="value">${source}</td>
</tr>
<tr>
<td class="label">Tech Stack:</td><td class="value">${techStack}</td>
<td class="label"><strong>Interviewer:</strong></td><td class="value">${interviewerNames}</td>
</tr>
<tr>
<td class="label">General Aptitude Test QP Set</td><td class="value">${q1Name}</td>
<td class="label"><strong>General Aptitude Test Score</strong></td><td class="value">${q1Score}</td>
</tr>
<tr>
<td class="label">Technical MCQ Test QP Set</td><td class="value">${q2Name}</td>
<td class="label"><strong>Technical MCQ Test Score</strong></td><td class="value">${q2Score}</td>
</tr>
</tbody></table>
<div class="comments-section" style="margin-top:0;">
<div class="comments-label">Interviewer's Comments (${ivName}):</div>
<div class="comments-text">${ivEval?.comments || ''}</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>${buildCheckboxes('rec1', ivEval?.recommendation || '')}</div>
<div class="sig-row">
<div class="sig-field"><label>Evaluator's Signature:</label><div class="sig-line">${ivName}</div></div>
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">${fmtDate(ivEval?.date)}</div></div>
</div>
<div class="comments-section">
<div class="comments-label">Project Manager Comments (${pmName}):</div>
<div class="comments-text">${pmEval?.comments || ''}</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>${buildCheckboxes('rec2', pmEval?.recommendation || '')}</div>
<div class="sig-row">
<div class="sig-field"><label>Evaluator's Signature:</label><div class="sig-line">${pmName}</div></div>
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">${fmtDate(pmEval?.date)}</div></div>
</div>
<div class="comments-section">
<div class="comments-label">HR Comments (${hrName}):</div>
<div class="comments-text" style="min-height:52px;">${hrEval?.comments || ''}</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>${buildCheckboxes('rec3', hrEval?.recommendation || '')}</div>
<div class="sig-row">
<div class="sig-field"><label>Evaluator's Signature:</label><div class="sig-line">${hrName}</div></div>
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">${fmtDate(hrEval?.date)}</div></div>
</div>
<div class="comments-section"><div class="comments-label">Overall Recommendation:</div></div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>${buildCheckboxes('rec-overall', overallRec)}</div>
<div class="sig-row">
<div class="sig-field"><label>Authorized Signature:</label><div class="sig-line">${adminEval?.evaluatorId?.name || ''}</div></div>
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">${fmtDate(adminEval?.date)}</div></div>
</div>
</div>
<div class="actions">
<button class="btn btn-print" onclick="window.print()">&#128438; Print / Save as PDF</button>
</div>
</body></html>`;
const win = window.open('', '_blank');
if (win) {
win.document.write(html);
win.document.close();
win.focus();
}
}
}
......@@ -214,6 +214,10 @@
return this.adminUrl;
}
getQuizzes(): Observable<any> {
return this.http.get(`${this.getBaseUrl()}/quizzes`);
}
createGroup(name: string): Observable<any> {
return this.http.post(`${this.getBaseUrl()}/groups`, { name });
}
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Intern Interview Evaluation Form</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: Arial, sans-serif;
background: #fff;
color: #000;
padding: 24px;
font-size: 13px;
}
.page-wrap {
max-width: 780px;
margin: 0 auto;
border: 1px solid #ccc;
}
/* ── HEADER ── */
.header {
background: #4472C4;
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 16px;
}
.header h1 {
color: #fff;
font-size: 16px;
font-weight: bold;
font-style: italic;
text-decoration: underline;
}
.logo-wrap img {
height: 52px;
object-fit: contain;
}
/* ── INFO TABLE ── */
.info-table {
width: 100%;
border-collapse: collapse;
}
.info-table td {
border: 1px solid #bbb;
padding: 6px 10px;
vertical-align: middle;
}
.info-table .label {
font-weight: bold;
background: #fff;
width: 160px;
white-space: nowrap;
}
.info-table .value {
background: #fff;
color: #555;
font-style: italic;
width: 160px;
}
.info-table input[type="text"] {
border: none;
outline: none;
width: 100%;
font-size: 13px;
font-style: italic;
color: #555;
background: transparent;
}
/* ── COMMENTS SECTION ── */
.comments-section {
border: 1px solid #bbb;
border-top: none;
padding: 10px 16px;
}
.comments-label {
font-weight: bold;
margin-bottom: 6px;
}
.comments-section textarea {
width: 100%;
height: 72px;
border: none;
resize: none;
outline: none;
font-size: 13px;
font-family: Arial, sans-serif;
}
/* ── RECOMMENDATION ROW ── */
.rec-row {
border: 1px solid #bbb;
border-top: none;
padding: 8px 16px;
display: flex;
align-items: center;
gap: 20px;
flex-wrap: wrap;
}
.rec-row .rec-label {
font-weight: bold;
white-space: nowrap;
}
.rec-option {
display: flex;
align-items: center;
gap: 5px;
cursor: pointer;
font-size: 13px;
}
.rec-option input[type="checkbox"] {
width: 14px;
height: 14px;
accent-color: #4472C4;
cursor: pointer;
}
/* ── SIGNATURE ROW ── */
.sig-row {
border: 1px solid #bbb;
border-top: none;
padding: 8px 16px;
display: flex;
align-items: flex-end;
gap: 40px;
}
.sig-field {
display: flex;
align-items: flex-end;
gap: 8px;
}
.sig-field label {
font-weight: bold;
white-space: nowrap;
}
.sig-line {
border-bottom: 1px solid #000;
min-width: 200px;
height: 18px;
}
.sig-line-date {
min-width: 140px;
}
.sig-field input[type="text"] {
border: none;
border-bottom: 1px solid #000;
outline: none;
min-width: 200px;
font-size: 13px;
padding: 0 2px;
background: transparent;
}
.sig-field input.date-input {
min-width: 140px;
}
/* ── SECTION DIVIDER ── */
.section-gap {
height: 8px;
background: #fff;
border-left: 1px solid #bbb;
border-right: 1px solid #bbb;
}
/* ── ACTIONS ── */
.actions {
margin-top: 16px;
display: flex;
justify-content: flex-end;
gap: 10px;
}
.btn {
font-size: 13px;
font-family: Arial, sans-serif;
padding: 7px 18px;
border-radius: 3px;
cursor: pointer;
border: 1px solid #aaa;
}
.btn-clear {
background: #f5f5f5;
color: #333;
}
.btn-print {
background: #4472C4;
color: #fff;
border-color: #3360b0;
}
@media print {
body { padding: 0; }
.actions { display: none; }
}
</style>
</head>
<body>
<div class="page-wrap">
<!-- HEADER -->
<div class="header">
<h1>Intern Interview Evaluation Form</h1>
<div class="logo-wrap">
<img src="./logo.jpeg" alt="ITL Logo" />
</div>
</div>
<!-- INFO TABLE -->
<table class="info-table">
<tbody>
<tr>
<td class="label">Candidate Name:</td>
<td class="value"><input type="text" placeholder="Click here to enter text." /></td>
<td class="label">Date of Interview:</td>
<td class="value"><input type="text" placeholder="Click here to enter text." /></td>
</tr>
<tr>
<td class="label">Position:</td>
<td class="value"><input type="text" placeholder="Click here to enter text." /></td>
<td class="label">Source</td>
<td class="value"><input type="text" placeholder="Click here to enter text." /></td>
</tr>
<tr>
<td class="label">Tech Stack:</td>
<td class="value"><input type="text" placeholder="Click here to enter text." /></td>
<td class="label"><strong>Interviewer:</strong></td>
<td class="value"><input type="text" placeholder="Click here to enter text." /></td>
</tr>
<tr>
<td class="label">General Aptitude Test QP Set</td>
<td class="value"><input type="text" placeholder="Click here to enter text." /></td>
<td class="label"><strong>General Aptitude Test Score</strong></td>
<td class="value"><input type="text" placeholder="Click here to enter text." /></td>
</tr>
<tr>
<td class="label">Technical MCQ Test QP Set</td>
<td class="value"><input type="text" placeholder="Click here to enter text." /></td>
<td class="label"><strong>Technical MCQ Test Score</strong></td>
<td class="value"><input type="text" placeholder="Click here to enter text." /></td>
</tr>
</tbody>
</table>
<!-- INTERVIEWER COMMENTS -->
<div class="comments-section" style="margin-top:0;">
<div class="comments-label">Interviewer's Comments (Interviewer Name):</div>
<textarea placeholder=""></textarea>
</div>
<div class="rec-row">
<span class="rec-label">Recommendation:</span>
<label class="rec-option"><input type="checkbox" name="rec1" /> Offer/Hire as Intern</label>
<label class="rec-option"><input type="checkbox" name="rec1" /> On Hold</label>
<label class="rec-option"><input type="checkbox" name="rec1" /> Rejected</label>
<label class="rec-option"><input type="checkbox" name="rec1" /> 2<sup>nd</sup> Round</label>
</div>
<div class="sig-row">
<div class="sig-field">
<label>Evaluator's Signature:</label>
<input type="text" style="min-width:200px;" />
</div>
<div class="sig-field">
<label>Date:</label>
<input type="text" class="date-input" style="min-width:140px;" />
</div>
</div>
<!-- PROJECT MANAGER COMMENTS -->
<div class="comments-section">
<div class="comments-label">Project Manager Comments (Project Manager Name):</div>
<textarea placeholder=""></textarea>
</div>
<div class="rec-row">
<span class="rec-label">Recommendation:</span>
<label class="rec-option"><input type="checkbox" name="rec2" /> Offer/Hire as Intern</label>
<label class="rec-option"><input type="checkbox" name="rec2" /> On Hold</label>
<label class="rec-option"><input type="checkbox" name="rec2" /> Rejected</label>
<label class="rec-option"><input type="checkbox" name="rec2" /> 2<sup>nd</sup> Round</label>
</div>
<div class="sig-row">
<div class="sig-field">
<label>Evaluator's Signature:</label>
<input type="text" style="min-width:200px;" />
</div>
<div class="sig-field">
<label>Date:</label>
<input type="text" class="date-input" style="min-width:140px;" />
</div>
</div>
<!-- HR COMMENTS -->
<div class="comments-section">
<div class="comments-label">HR Comments (HR Name):</div>
<textarea style="height:52px;" placeholder=""></textarea>
</div>
<div class="rec-row">
<span class="rec-label">Recommendation:</span>
<label class="rec-option"><input type="checkbox" name="rec3" /> Offer/Hire as Intern</label>
<label class="rec-option"><input type="checkbox" name="rec3" /> On Hold</label>
<label class="rec-option"><input type="checkbox" name="rec3" /> Rejected</label>
<label class="rec-option"><input type="checkbox" name="rec3" /> 2<sup>nd</sup> Round</label>
</div>
<div class="sig-row">
<div class="sig-field">
<label>Evaluator's Signature:</label>
<input type="text" style="min-width:200px;" />
</div>
<div class="sig-field">
<label>Date:</label>
<input type="text" class="date-input" style="min-width:140px;" />
</div>
</div>
<!-- OVERALL RECOMMENDATION -->
<div class="comments-section">
<div class="comments-label">Overall Recommendation:</div>
</div>
<div class="rec-row">
<span class="rec-label">Recommendation:</span>
<label class="rec-option"><input type="checkbox" name="rec-overall" /> Offer/Hire as Intern</label>
<label class="rec-option"><input type="checkbox" name="rec-overall" /> On Hold</label>
<label class="rec-option"><input type="checkbox" name="rec-overall" /> Rejected</label>
<label class="rec-option"><input type="checkbox" name="rec-overall" /> 2<sup>nd</sup> Round</label>
</div>
<div class="sig-row">
<div class="sig-field">
<label>Authorized Signature:</label>
<input type="text" style="min-width:200px;" />
</div>
<div class="sig-field">
<label>Date:</label>
<input type="text" class="date-input" style="min-width:140px;" />
</div>
</div>
</div><!-- /.page-wrap -->
<!-- ACTIONS -->
<div class="actions">
<button class="btn btn-clear" onclick="clearForm()">Clear Form</button>
<button class="btn btn-print" onclick="window.print()">🖨 Print / Save as PDF</button>
</div>
<script>
function clearForm() {
if (!confirm('Clear all fields? This cannot be undone.')) return;
document.querySelectorAll('input[type="text"], textarea').forEach(el => el.value = '');
document.querySelectorAll('input[type="checkbox"]').forEach(el => el.checked = false);
}
</script>
</body>
</html>
logo.jpeg

10.5 KB

Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment