Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
H
Hire-Guru
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vigneswaran Shanmugam
Hire-Guru
Commits
d8e24864
Commit
d8e24864
authored
May 26, 2026
by
Aravind RK
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat : added file type for uploading
parent
e01f6a70
Changes
5
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
324 additions
and
129 deletions
+324
-129
Backend/server.js
Backend/server.js
+2
-2
Frontend/src/app/pages/admin/user-history/user-history.ts
Frontend/src/app/pages/admin/user-history/user-history.ts
+104
-80
Frontend/src/app/pages/hr/user-history/user-history.ts
Frontend/src/app/pages/hr/user-history/user-history.ts
+154
-30
Frontend/src/app/pages/staff/profile/profile.html
Frontend/src/app/pages/staff/profile/profile.html
+2
-0
Frontend/src/app/pages/staff/profile/profile.ts
Frontend/src/app/pages/staff/profile/profile.ts
+62
-17
No files found.
Backend/server.js
View file @
d8e24864
...
@@ -43,8 +43,8 @@
...
@@ -43,8 +43,8 @@
allowedHeaders
:
[
'
Content-Type
'
,
'
Authorization
'
]
allowedHeaders
:
[
'
Content-Type
'
,
'
Authorization
'
]
}));
}));
app
.
use
(
express
.
json
());
app
.
use
(
express
.
json
(
{
limit
:
'
10mb
'
}
));
app
.
use
(
express
.
urlencoded
({
extended
:
true
}));
app
.
use
(
express
.
urlencoded
({
extended
:
true
,
limit
:
'
10mb
'
}));
app
.
use
(
cookieParser
());
app
.
use
(
cookieParser
());
// Serve static files from uploads folder (for resumes, etc)
// Serve static files from uploads folder (for resumes, etc)
...
...
Frontend/src/app/pages/admin/user-history/user-history.ts
View file @
d8e24864
...
@@ -191,6 +191,17 @@ export class UserHistoryComponent implements OnInit {
...
@@ -191,6 +191,17 @@ export class UserHistoryComponent implements OnInit {
const
q2Name
=
q2
?.
quizId
?.
title
||
q2
?.
title
||
''
;
const
q2Name
=
q2
?.
quizId
?.
title
||
q2
?.
title
||
''
;
const
q2Score
=
(
q2
?.
score
!=
null
)
?
`
${
q2
.
score
}
/
${
q2
.
totalMarks
}
`
:
''
;
const
q2Score
=
(
q2
?.
score
!=
null
)
?
`
${
q2
.
score
}
/
${
q2
.
totalMarks
}
`
:
''
;
const
_lcm
:
Record
<
string
,
string
>
=
{
easy
:
'
BEG
'
,
medium
:
'
INT
'
,
hard
:
'
ADV
'
};
const
makeQuizId
=
(
quiz
:
any
,
idx
:
number
):
string
=>
{
if
(
!
quiz
)
return
''
;
const
qd
=
quiz
.
quizId
||
{};
const
topic
=
(
qd
.
category
||
'
General
'
).
replace
(
/
\s
+/g
,
'
_
'
);
const
code
=
_lcm
[(
qd
.
difficulty
||
'
easy
'
).
toLowerCase
()]
||
'
BEG
'
;
return
`Q_
${
topic
}
_
${
code
}
_
${
String
(
idx
+
1
).
padStart
(
3
,
'
0
'
)}
`
;
};
const
q1Id
=
makeQuizId
(
q1
,
0
);
const
q2Id
=
makeQuizId
(
q2
,
1
);
const
evals
=
interview
.
evaluations
||
[];
const
evals
=
interview
.
evaluations
||
[];
const
ivEval
=
evals
.
find
((
e
:
any
)
=>
e
.
evaluatorRole
===
'
interviewer
'
);
const
ivEval
=
evals
.
find
((
e
:
any
)
=>
e
.
evaluatorRole
===
'
interviewer
'
);
const
pmEval
=
evals
.
find
((
e
:
any
)
=>
e
.
evaluatorRole
===
'
pm
'
);
const
pmEval
=
evals
.
find
((
e
:
any
)
=>
e
.
evaluatorRole
===
'
pm
'
);
...
@@ -228,28 +239,29 @@ export class UserHistoryComponent implements OnInit {
...
@@ -228,28 +239,29 @@ export class UserHistoryComponent implements OnInit {
<title>ITL Evaluation -
${
candidateName
}
</title><style>
<title>ITL Evaluation -
${
candidateName
}
</title><style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
*,*::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;}
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
;}
.page-wrap{max-width:
820px;margin:0 auto;border:1px solid #bbb;box-shadow: 0 4px 12px rgba(0,0,0,0.1)
;}
.header{background:#4472C4;display:flex;align-items:center;justify-content:space-between;padding:10px 16px;}
.header{background:#4472C4;display:flex;align-items:center;justify-content:space-between;padding:10px 16px;}
.header h1{color:#fff;font-size:2
6
px;font-weight:bold;}
.header h1{color:#fff;font-size:2
4
px;font-weight:bold;}
.logo-wrap img{height:52px;object-fit:contain;}
.logo-wrap img{height:52px;object-fit:contain;}
.info-table{width:100%;border-collapse:collapse;}
.info-table{width:100%;border-collapse:collapse;}
.info-table td{border:1px solid #bbb;padding:
6px 10
px;vertical-align:middle;}
.info-table td{border:1px solid #bbb;padding:
8px 12
px;vertical-align:middle;}
.info-table .label{font-weight:bold;background:#f
ff;width:16
0px;white-space:nowrap;}
.info-table .label{font-weight:bold;background:#f
5f5f5;width:17
0px;white-space:nowrap;}
.info-table .value{background:#fff;color:#
444;font-style:italic
;}
.info-table .value{background:#fff;color:#
222
;}
.comments-section{border:1px solid #bbb;border-top:none;padding:1
0px 16px
;}
.comments-section{border:1px solid #bbb;border-top:none;padding:1
2px 16px;background:#fff
;}
.comments-label{font-weight:bold;margin-bottom:6px;}
.comments-label{font-weight:bold;margin-bottom:6px;
color:#111;
}
.comments-text{min-height:
6
5px;font-size:13px;font-family:Arial,sans-serif;color:#333;line-height:1.5;padding:4px 0;}
.comments-text{min-height:
5
5px;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-row{border:1px solid #bbb;border-top:none;padding:8px 16px;display:flex;align-items:center;gap:20px;flex-wrap:wrap;
background:#fcfcfc;
}
.rec-label{font-weight:bold;white-space:nowrap;}
.rec-label{font-weight:bold;white-space:nowrap;}
.sig-row{
.sig-row{
border:1px solid #bbb;
border:1px solid #bbb;
border-top:none;
border-top:none;
padding:1
4px 16px 10
px;
padding:1
2px 16
px;
display:flex;
display:flex;
justify-content:space-between;
justify-content:space-between;
align-items:flex-end;
align-items:flex-end;
gap:40px;
gap:40px;
background:#fff;
page-break-inside: avoid;
page-break-inside: avoid;
break-inside: avoid;
break-inside: avoid;
}
}
...
@@ -264,42 +276,76 @@ body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-s
...
@@ -264,42 +276,76 @@ body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-s
.sig-field label{
.sig-field label{
font-weight:bold;
font-weight:bold;
white-space:nowrap;
white-space:nowrap;
font-size:1
4
px;
font-size:1
3
px;
}
}
.signature-box{
.signature-box{
border-bottom:
2
px solid #222;
border-bottom:
1.5
px solid #222;
width:1
65
px;
width:1
80
px;
height:
26
px;
height:
30
px;
display:flex;
display:flex;
justify-content:center;
justify-content:center;
align-items:flex-end;
align-items:flex-end;
padding-bottom:
4
px;
padding-bottom:
3
px;
}
}
.signature-box img{
.signature-box img{
max-height:
18
px;
max-height:
26
px;
max-width:
9
0px;
max-width:
16
0px;
object-fit:contain;
object-fit:contain;
}
}
.date-box{
.date-box{
border-bottom:
2
px solid #222;
border-bottom:
1.5
px solid #222;
width:1
3
0px;
width:1
4
0px;
height:2
2
px;
height:2
4
px;
display:flex;
display:flex;
align-items:flex-end;
align-items:flex-end;
padding-bottom:
3
px;
padding-bottom:
2
px;
font-style:italic;
font-style:italic;
color:#
444
;
color:#
333
;
}
}
.actions{margin-top:20px;display:flex;justify-content:center;gap:10px;}
.btn{font-size:14px;font-family:Arial,sans-serif;padding:8px 24px;border-radius:4px;cursor:pointer;border:1px solid #3360b0;font-weight:bold;}
.btn-print{background:#4472C4;color:#fff;}
@media print {
.actions{margin-top:16px;display:flex;justify-content:flex-end;gap:10px;}
@page {
.btn{font-size:13px;font-family:Arial,sans-serif;padding:7px 18px;border-radius:3px;cursor:pointer;border:1px solid #aaa;}
size: A4 portrait;
.btn-print{background:#4472C4;color:#fff;border-color:#3360b0;}
margin: 8mm 12mm;
@media print{body{padding:0;}.actions{display:none;}}
}
body {
padding: 0;
font-size: 13.5px;
background: #fff;
}
.page-wrap {
width: 100%;
max-width: 100%;
margin: 0;
border: 1px solid #bbb;
box-shadow: none;
}
.info-table td {
padding: 7px 10px;
}
.comments-section {
padding: 10px 14px;
}
.comments-text {
min-height: 52px;
}
.rec-row {
padding: 8px 14px;
}
.sig-row {
padding: 10px 14px;
}
.actions {
display: none;
}
}
</style></head><body>
</style></head><body>
<div class="page-wrap">
<div class="page-wrap">
<div class="header">
<div class="header">
...
@@ -320,11 +366,11 @@ body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-s
...
@@ -320,11 +366,11 @@ body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-s
<td class="label"><strong>Interviewer:</strong></td><td class="value">
${
interviewerNames
}
</td>
<td class="label"><strong>Interviewer:</strong></td><td class="value">
${
interviewerNames
}
</td>
</tr>
</tr>
<tr>
<tr>
<td class="label">General Aptitude Test QP
Set</td><td class="value">
${
q1Name
}
</td>
<td class="label">General Aptitude Test QP
ID</td><td class="value">
${
q1Id
}
</td>
<td class="label"><strong>General Aptitude Test Score</strong></td><td class="value">
${
q1Score
}
</td>
<td class="label"><strong>General Aptitude Test Score</strong></td><td class="value">
${
q1Score
}
</td>
</tr>
</tr>
<tr>
<tr>
<td class="label">Technical MCQ Test QP
Set</td><td class="value">
${
q2Name
}
</td>
<td class="label">Technical MCQ Test QP
ID</td><td class="value">
${
q2Id
}
</td>
<td class="label"><strong>Technical MCQ Test Score</strong></td><td class="value">
${
q2Score
}
</td>
<td class="label"><strong>Technical MCQ Test Score</strong></td><td class="value">
${
q2Score
}
</td>
</tr>
</tr>
</tbody></table>
</tbody></table>
...
@@ -334,21 +380,16 @@ body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-s
...
@@ -334,21 +380,16 @@ body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-s
<div class="comments-text">
${
ivEval
?.
comments
||
''
}
</div>
<div class="comments-text">
${
ivEval
?.
comments
||
''
}
</div>
</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec1
'
,
ivEval
?.
recommendation
||
''
)}
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec1
'
,
ivEval
?.
recommendation
||
''
)}
</div>
<div class="sig-row">
<div class="sig-row">
<div class="sig-field">
<div class="sig-field">
<label>Evaluator's Signature:</label>
<label>Evaluator's Signature:</label>
<div class="signature-box">
<div class="signature-box">
${
getSigHtml
(
ivEval
,
ivName
)}
</div>
${
getSigHtml
(
ivEval
,
ivName
)}
</div>
</div>
</div>
<div class="sig-field" style="flex:0.55;">
<label>Date:</label>
<div class="sig-field" style="flex:0.55;">
<div class="date-box">
${
fmtDate
(
ivEval
?.
date
)}
</div>
<label>Date:</label>
<div class="date-box">
${
fmtDate
(
ivEval
?.
date
)}
</div>
</div>
</div>
</div>
</div>
<div class="comments-section">
<div class="comments-section">
<div class="comments-label">Project Manager Comments (
${
pmName
}
):</div>
<div class="comments-label">Project Manager Comments (
${
pmName
}
):</div>
...
@@ -356,64 +397,47 @@ body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-s
...
@@ -356,64 +397,47 @@ body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-s
</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec2
'
,
pmEval
?.
recommendation
||
''
)}
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec2
'
,
pmEval
?.
recommendation
||
''
)}
</div>
<div class="sig-row">
<div class="sig-row">
<div class="sig-field">
<div class="sig-field">
<label>Evaluator's Signature:</label>
<label>Evaluator's Signature:</label>
<div class="signature-box">
<div class="signature-box">
${
getSigHtml
(
pmEval
,
pmName
)}
</div>
${
getSigHtml
(
pmEval
,
pmName
)}
</div>
</div>
</div>
<div class="sig-field" style="flex:0.55;">
<label>Date:</label>
<div class="sig-field" style="flex:0.55;">
<div class="date-box">
${
fmtDate
(
pmEval
?.
date
)}
</div>
<label>Date:</label>
<div class="date-box">
${
fmtDate
(
pmEval
?.
date
)}
</div>
</div>
</div>
</div>
</div>
<div class="comments-section">
<div class="comments-section">
<div class="comments-label">HR Comments (
${
hrName
}
):</div>
<div class="comments-label">HR Comments (
${
hrName
}
):</div>
<div class="comments-text" style="min-height:52px;">
${
hrEval
?.
comments
||
''
}
</div>
<div class="comments-text" style="min-height:52px;">
${
hrEval
?.
comments
||
''
}
</div>
</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec3
'
,
hrEval
?.
recommendation
||
''
)}
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec3
'
,
hrEval
?.
recommendation
||
''
)}
</div>
<div class="sig-row">
<div class="sig-row">
<div class="sig-field">
<div class="sig-field">
<label>Evaluator's Signature:</label>
<label>Evaluator's Signature:</label>
<div class="signature-box">
<div class="signature-box">
${
getSigHtml
(
hrEval
,
hrName
)}
</div>
${
getSigHtml
(
hrEval
,
hrName
)}
</div>
</div>
</div>
<div class="sig-field" style="flex:0.55;">
<label>Date:</label>
<div class="sig-field" style="flex:0.55;">
<div class="date-box">
${
fmtDate
(
hrEval
?.
date
)}
</div>
<label>Date:</label>
<div class="date-box">
${
fmtDate
(
hrEval
?.
date
)}
</div>
</div>
</div>
</div>
</div>
<div class="comments-section">
<div class="comments-section">
<div class="comments-label">Overall Comments (
${
adminEval
?.
evaluatorId
?.
name
}
):</div>
<div class="comments-label">Overall Comments (
${
adminEval
?.
evaluatorId
?.
name
||
adminName
}
):</div>
<div class="comments-text" style="min-height:52px;">
${
adminEval
?.
comments
||
''
}
</div>
<div class="comments-text" style="min-height:52px;">
${
adminEval
?.
comments
||
''
}
</div>
</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec-overall
'
,
overallRec
)}
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec-overall
'
,
overallRec
)}
</div>
<div class="sig-row">
<div class="sig-row">
<div class="sig-field">
<div class="sig-field">
<label>Evaluator's Signature:</label>
<label>Evaluator's Signature:</label>
<div class="signature-box">
<div class="signature-box">
${
getSigHtml
(
adminEval
,
adminName
)}
</div>
${
getSigHtml
(
adminEval
,
adminName
)}
</div>
<div class="sig-field" style="flex:0.55;">
<label>Date:</label>
<div class="date-box">
${
fmtDate
(
adminEval
?.
date
)}
</div>
</div>
</div>
</div>
</div>
<div class="sig-field" style="flex:0.55;">
<label>Date:</label>
<div class="date-box">
${
fmtDate
(
adminEval
?.
date
)}
</div>
</div>
</div>
</div>
</div>
<div class="actions">
<div class="actions">
<button class="btn btn-print" onclick="window.print()">🖶 Print / Save as PDF</button>
<button class="btn btn-print" onclick="window.print()">🖶 Print / Save as PDF</button>
...
...
Frontend/src/app/pages/hr/user-history/user-history.ts
View file @
d8e24864
...
@@ -191,6 +191,17 @@ export class HRUserHistoryComponent implements OnInit {
...
@@ -191,6 +191,17 @@ export class HRUserHistoryComponent implements OnInit {
const
q2Name
=
q2
?.
quizId
?.
title
||
q2
?.
title
||
''
;
const
q2Name
=
q2
?.
quizId
?.
title
||
q2
?.
title
||
''
;
const
q2Score
=
(
q2
?.
score
!=
null
)
?
`
${
q2
.
score
}
/
${
q2
.
totalMarks
}
`
:
''
;
const
q2Score
=
(
q2
?.
score
!=
null
)
?
`
${
q2
.
score
}
/
${
q2
.
totalMarks
}
`
:
''
;
const
_lcm
:
Record
<
string
,
string
>
=
{
easy
:
'
BEG
'
,
medium
:
'
INT
'
,
hard
:
'
ADV
'
};
const
makeQuizId
=
(
quiz
:
any
,
idx
:
number
):
string
=>
{
if
(
!
quiz
)
return
''
;
const
qd
=
quiz
.
quizId
||
{};
const
topic
=
(
qd
.
category
||
'
General
'
).
replace
(
/
\s
+/g
,
'
_
'
);
const
code
=
_lcm
[(
qd
.
difficulty
||
'
easy
'
).
toLowerCase
()]
||
'
BEG
'
;
return
`Q_
${
topic
}
_
${
code
}
_
${
String
(
idx
+
1
).
padStart
(
3
,
'
0
'
)}
`
;
};
const
q1Id
=
makeQuizId
(
q1
,
0
);
const
q2Id
=
makeQuizId
(
q2
,
1
);
const
evals
=
interview
.
evaluations
||
[];
const
evals
=
interview
.
evaluations
||
[];
const
ivEval
=
evals
.
find
((
e
:
any
)
=>
e
.
evaluatorRole
===
'
interviewer
'
);
const
ivEval
=
evals
.
find
((
e
:
any
)
=>
e
.
evaluatorRole
===
'
interviewer
'
);
const
pmEval
=
evals
.
find
((
e
:
any
)
=>
e
.
evaluatorRole
===
'
pm
'
);
const
pmEval
=
evals
.
find
((
e
:
any
)
=>
e
.
evaluatorRole
===
'
pm
'
);
...
@@ -229,32 +240,117 @@ export class HRUserHistoryComponent implements OnInit {
...
@@ -229,32 +240,117 @@ export class HRUserHistoryComponent implements OnInit {
<title>ITL Evaluation -
${
candidateName
}
</title><style>
<title>ITL Evaluation -
${
candidateName
}
</title><style>
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0;}
*,*::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;}
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
;}
.page-wrap{max-width:
820px;margin:0 auto;border:1px solid #bbb;box-shadow: 0 4px 12px rgba(0,0,0,0.1)
;}
.header{background:#4472C4;display:flex;align-items:center;justify-content:space-between;padding:10px 16px;}
.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
;}
.header h1{color:#fff;font-size:
24px;font-weight:bold
;}
.logo-wrap img{height:52px;object-fit:contain;}
.logo-wrap img{height:52px;object-fit:contain;}
.info-table{width:100%;border-collapse:collapse;}
.info-table{width:100%;border-collapse:collapse;}
.info-table td{border:1px solid #bbb;padding:
6px 10
px;vertical-align:middle;}
.info-table td{border:1px solid #bbb;padding:
8px 12
px;vertical-align:middle;}
.info-table .label{font-weight:bold;background:#f
ff;width:16
0px;white-space:nowrap;}
.info-table .label{font-weight:bold;background:#f
5f5f5;width:17
0px;white-space:nowrap;}
.info-table .value{background:#fff;color:#
444;font-style:italic
;}
.info-table .value{background:#fff;color:#
222
;}
.comments-section{border:1px solid #bbb;border-top:none;padding:1
0px 16px
;}
.comments-section{border:1px solid #bbb;border-top:none;padding:1
2px 16px;background:#fff
;}
.comments-label{font-weight:bold;margin-bottom:6px;}
.comments-label{font-weight:bold;margin-bottom:6px;
color:#111;
}
.comments-text{min-height:
6
5px;font-size:13px;font-family:Arial,sans-serif;color:#333;line-height:1.5;padding:4px 0;}
.comments-text{min-height:
5
5px;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-row{border:1px solid #bbb;border-top:none;padding:8px 16px;display:flex;align-items:center;gap:20px;flex-wrap:wrap;
background:#fcfcfc;
}
.rec-label{font-weight:bold;white-space:nowrap;}
.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-row{
.sig-field label{font-weight:bold;white-space:nowrap;}
border:1px solid #bbb;
.sig-line{border-bottom:1px solid #000;min-width:200px;height:18px;font-style:italic;color:#555;font-size:13px;}
border-top:none;
.sig-line-date{min-width:140px;}
padding:12px 16px;
.actions{margin-top:16px;display:flex;justify-content:flex-end;gap:10px;}
display:flex;
.btn{font-size:13px;font-family:Arial,sans-serif;padding:7px 18px;border-radius:3px;cursor:pointer;border:1px solid #aaa;}
justify-content:space-between;
.btn-print{background:#4472C4;color:#fff;border-color:#3360b0;}
align-items:flex-end;
@media print{body{padding:0;}.actions{display:none;}}
gap:40px;
background:#fff;
page-break-inside: avoid;
break-inside: avoid;
}
.sig-field{
display:flex;
align-items:flex-end;
gap:10px;
flex:1;
}
.sig-field label{
font-weight:bold;
white-space:nowrap;
font-size:13px;
}
.signature-box{
border-bottom:1.5px solid #222;
width:180px;
height:30px;
display:flex;
justify-content:center;
align-items:flex-end;
padding-bottom:3px;
}
.signature-box img{
max-height:26px;
max-width:160px;
object-fit:contain;
}
.date-box{
border-bottom:1.5px solid #222;
width:140px;
height:24px;
display:flex;
align-items:flex-end;
padding-bottom:2px;
font-style:italic;
color:#333;
}
.actions{margin-top:20px;display:flex;justify-content:center;gap:10px;}
.btn{font-size:14px;font-family:Arial,sans-serif;padding:8px 24px;border-radius:4px;cursor:pointer;border:1px solid #3360b0;font-weight:bold;}
.btn-print{background:#4472C4;color:#fff;}
@media print {
@page {
size: A4 portrait;
margin: 8mm 12mm;
}
body {
padding: 0;
font-size: 13.5px;
background: #fff;
}
.page-wrap {
width: 100%;
max-width: 100%;
margin: 0;
border: 1px solid #bbb;
box-shadow: none;
}
.info-table td {
padding: 7px 10px;
}
.comments-section {
padding: 10px 14px;
}
.comments-text {
min-height: 52px;
}
.rec-row {
padding: 8px 14px;
}
.sig-row {
padding: 10px 14px;
}
.actions {
display: none;
}
}
</style></head><body>
</style></head><body>
<div class="page-wrap">
<div class="page-wrap">
<div class="header">
<div class="header">
<h1>
Intern Interview
Evaluation Form</h1>
<h1>
Candidate
Evaluation Form</h1>
<div class="logo-wrap"><img src="
${
ITL_LOGO
}
" alt="ITL Logo"/></div>
<div class="logo-wrap"><img src="
${
ITL_LOGO
}
" alt="ITL Logo"/></div>
</div>
</div>
<table class="info-table"><tbody>
<table class="info-table"><tbody>
...
@@ -271,49 +367,77 @@ body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-s
...
@@ -271,49 +367,77 @@ body{font-family:Arial,sans-serif;background:#fff;color:#000;padding:24px;font-s
<td class="label"><strong>Interviewer:</strong></td><td class="value">
${
interviewerNames
}
</td>
<td class="label"><strong>Interviewer:</strong></td><td class="value">
${
interviewerNames
}
</td>
</tr>
</tr>
<tr>
<tr>
<td class="label">General Aptitude Test QP
Set</td><td class="value">
${
q1Name
}
</td>
<td class="label">General Aptitude Test QP
ID</td><td class="value">
${
q1Id
}
</td>
<td class="label"><strong>General Aptitude Test Score</strong></td><td class="value">
${
q1Score
}
</td>
<td class="label"><strong>General Aptitude Test Score</strong></td><td class="value">
${
q1Score
}
</td>
</tr>
</tr>
<tr>
<tr>
<td class="label">Technical MCQ Test QP
Set</td><td class="value">
${
q2Name
}
</td>
<td class="label">Technical MCQ Test QP
ID</td><td class="value">
${
q2Id
}
</td>
<td class="label"><strong>Technical MCQ Test Score</strong></td><td class="value">
${
q2Score
}
</td>
<td class="label"><strong>Technical MCQ Test Score</strong></td><td class="value">
${
q2Score
}
</td>
</tr>
</tr>
</tbody></table>
</tbody></table>
<div class="comments-section" style="margin-top:0;">
<div class="comments-section" style="margin-top:0;">
<div class="comments-label">Interviewer's Comments (
${
ivName
}
):</div>
<div class="comments-label">Interviewer's Comments (
${
ivName
}
):</div>
<div class="comments-text">
${
ivEval
?.
comments
||
''
}
</div>
<div class="comments-text">
${
ivEval
?.
comments
||
''
}
</div>
</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec1
'
,
ivEval
?.
recommendation
||
''
)}
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec1
'
,
ivEval
?.
recommendation
||
''
)}
</div>
<div class="sig-row">
<div class="sig-row">
<div class="sig-field"><label>Evaluator's Signature:</label><div class="sig-line" style="text-align:center;">
${
getSigHtml
(
ivEval
,
ivName
)}
</div></div>
<div class="sig-field">
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">
${
fmtDate
(
ivEval
?.
date
)}
</div></div>
<label>Evaluator's Signature:</label>
<div class="signature-box">
${
getSigHtml
(
ivEval
,
ivName
)}
</div>
</div>
<div class="sig-field" style="flex:0.55;">
<label>Date:</label>
<div class="date-box">
${
fmtDate
(
ivEval
?.
date
)}
</div>
</div>
</div>
</div>
<div class="comments-section">
<div class="comments-section">
<div class="comments-label">Project Manager Comments (
${
pmName
}
):</div>
<div class="comments-label">Project Manager Comments (
${
pmName
}
):</div>
<div class="comments-text">
${
pmEval
?.
comments
||
''
}
</div>
<div class="comments-text">
${
pmEval
?.
comments
||
''
}
</div>
</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec2
'
,
pmEval
?.
recommendation
||
''
)}
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec2
'
,
pmEval
?.
recommendation
||
''
)}
</div>
<div class="sig-row">
<div class="sig-row">
<div class="sig-field"><label>Evaluator's Signature:</label><div class="sig-line" style="text-align:center;">
${
getSigHtml
(
pmEval
,
pmName
)}
</div></div>
<div class="sig-field">
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">
${
fmtDate
(
pmEval
?.
date
)}
</div></div>
<label>Evaluator's Signature:</label>
<div class="signature-box">
${
getSigHtml
(
pmEval
,
pmName
)}
</div>
</div>
<div class="sig-field" style="flex:0.55;">
<label>Date:</label>
<div class="date-box">
${
fmtDate
(
pmEval
?.
date
)}
</div>
</div>
</div>
</div>
<div class="comments-section">
<div class="comments-section">
<div class="comments-label">HR Comments (
${
hrName
}
):</div>
<div class="comments-label">HR Comments (
${
hrName
}
):</div>
<div class="comments-text" style="min-height:52px;">
${
hrEval
?.
comments
||
''
}
</div>
<div class="comments-text" style="min-height:52px;">
${
hrEval
?.
comments
||
''
}
</div>
</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec3
'
,
hrEval
?.
recommendation
||
''
)}
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec3
'
,
hrEval
?.
recommendation
||
''
)}
</div>
<div class="sig-row">
<div class="sig-row">
<div class="sig-field"><label>Evaluator's Signature:</label><div class="sig-line" style="text-align:center;">
${
getSigHtml
(
hrEval
,
hrName
)}
</div></div>
<div class="sig-field">
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">
${
fmtDate
(
hrEval
?.
date
)}
</div></div>
<label>Evaluator's Signature:</label>
<div class="signature-box">
${
getSigHtml
(
hrEval
,
hrName
)}
</div>
</div>
<div class="sig-field" style="flex:0.55;">
<label>Date:</label>
<div class="date-box">
${
fmtDate
(
hrEval
?.
date
)}
</div>
</div>
</div>
</div>
<div class="comments-section">
<div class="comments-section">
<div class="comments-label">Overall Comments (
${
adminName
}
):</div>
<div class="comments-label">Overall Comments (
${
adminName
}
):</div>
<div class="comments-text" style="min-height:52px;">
${
adminEval
?.
comments
||
''
}
</div>
<div class="comments-text" style="min-height:52px;">
${
adminEval
?.
comments
||
''
}
</div>
</div>
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec-overall
'
,
overallRec
)}
</div>
<div class="rec-row"><span class="rec-label">Recommendation:</span>
${
buildCheckboxes
(
'
rec-overall
'
,
overallRec
)}
</div>
<div class="sig-row">
<div class="sig-row">
<div class="sig-field"><label>Evaluator's Signature:</label><div class="sig-line" style="text-align:center;">
${
getSigHtml
(
adminEval
,
adminName
)}
</div></div>
<div class="sig-field">
<div class="sig-field"><label>Date:</label><div class="sig-line sig-line-date">
${
fmtDate
(
adminEval
?.
date
)}
</div></div>
<label>Evaluator's Signature:</label>
<div class="signature-box">
${
getSigHtml
(
adminEval
,
adminName
)}
</div>
</div>
<div class="sig-field" style="flex:0.55;">
<label>Date:</label>
<div class="date-box">
${
fmtDate
(
adminEval
?.
date
)}
</div>
</div>
</div>
</div>
</div>
</div>
<div class="actions">
<div class="actions">
...
...
Frontend/src/app/pages/staff/profile/profile.html
View file @
d8e24864
...
@@ -47,6 +47,7 @@
...
@@ -47,6 +47,7 @@
<input
type=
"file"
accept=
"image/*"
style=
"display: none;"
(change)=
"uploadSignature($event)"
>
<input
type=
"file"
accept=
"image/*"
style=
"display: none;"
(change)=
"uploadSignature($event)"
>
</label>
</label>
</div>
</div>
<p
style=
"margin-top: 8px; font-size: 12px; color: #666;"
>
Valid formats: JPG, PNG, GIF, WebP (Max 5MB)
</p>
} @else {
} @else {
<div
class=
"empty-state"
style=
"padding: 40px 20px;"
>
<div
class=
"empty-state"
style=
"padding: 40px 20px;"
>
<span
class=
"material-symbols-rounded"
>
draw
</span>
<span
class=
"material-symbols-rounded"
>
draw
</span>
...
@@ -57,6 +58,7 @@
...
@@ -57,6 +58,7 @@
@else {
<span
class=
"material-symbols-rounded"
>
upload
</span>
Upload Signature }
@else {
<span
class=
"material-symbols-rounded"
>
upload
</span>
Upload Signature }
<input
type=
"file"
accept=
"image/*"
style=
"display: none;"
(change)=
"uploadSignature($event)"
>
<input
type=
"file"
accept=
"image/*"
style=
"display: none;"
(change)=
"uploadSignature($event)"
>
</label>
</label>
<p
style=
"margin-top: 8px; font-size: 12px; color: #666;"
>
Valid formats: JPG, PNG, GIF, WebP (Max 5MB)
</p>
</div>
</div>
}
}
</div>
</div>
...
...
Frontend/src/app/pages/staff/profile/profile.ts
View file @
d8e24864
...
@@ -85,27 +85,72 @@ export class StaffProfileComponent implements OnInit {
...
@@ -85,27 +85,72 @@ export class StaffProfileComponent implements OnInit {
const
file
=
event
.
target
.
files
[
0
];
const
file
=
event
.
target
.
files
[
0
];
if
(
!
file
)
return
;
if
(
!
file
)
return
;
// Validate file type
const
allowedTypes
=
[
'
image/jpeg
'
,
'
image/jpg
'
,
'
image/png
'
,
'
image/gif
'
,
'
image/webp
'
];
if
(
!
allowedTypes
.
includes
(
file
.
type
))
{
alert
(
'
Please upload an image file (JPG, PNG, GIF, or WebP)
'
);
return
;
}
// Validate file size (max 5MB before compression)
if
(
file
.
size
>
5
*
1024
*
1024
)
{
alert
(
'
File size must be less than 5MB
'
);
return
;
}
const
reader
=
new
FileReader
();
const
reader
=
new
FileReader
();
reader
.
onload
=
()
=>
{
reader
.
onload
=
(
e
:
any
)
=>
{
const
base64String
=
reader
.
result
as
string
;
const
img
=
new
Image
();
this
.
isUploadingSignature
.
set
(
true
);
img
.
onload
=
()
=>
{
// Resize to signature-appropriate dimensions and compress as JPEG
this
.
authService
.
uploadSignature
({
signature
:
base64String
}).
subscribe
({
const
MAX_WIDTH
=
600
;
next
:
(
res
)
=>
{
const
MAX_HEIGHT
=
200
;
this
.
isUploadingSignature
.
set
(
false
);
let
width
=
img
.
width
;
if
(
res
.
signature
)
{
let
height
=
img
.
height
;
// Update user signal
this
.
user
.
update
(
u
=>
({
...
u
,
signature
:
res
.
signature
}));
// Scale down proportionally if needed
}
if
(
width
>
MAX_WIDTH
)
{
},
height
=
Math
.
round
((
height
*
MAX_WIDTH
)
/
width
);
error
:
(
err
)
=>
{
width
=
MAX_WIDTH
;
this
.
isUploadingSignature
.
set
(
false
);
}
alert
(
err
.
error
?.
message
||
'
Error uploading signature
'
);
if
(
height
>
MAX_HEIGHT
)
{
width
=
Math
.
round
((
width
*
MAX_HEIGHT
)
/
height
);
height
=
MAX_HEIGHT
;
}
}
});
const
canvas
=
document
.
createElement
(
'
canvas
'
);
canvas
.
width
=
width
;
canvas
.
height
=
height
;
const
ctx
=
canvas
.
getContext
(
'
2d
'
)
!
;
// White background so transparent PNGs look clean
ctx
.
fillStyle
=
'
#ffffff
'
;
ctx
.
fillRect
(
0
,
0
,
width
,
height
);
ctx
.
drawImage
(
img
,
0
,
0
,
width
,
height
);
// Export as JPEG at 80% quality (much smaller than raw PNG base64)
const
base64String
=
canvas
.
toDataURL
(
'
image/jpeg
'
,
0.8
);
this
.
isUploadingSignature
.
set
(
true
);
this
.
authService
.
uploadSignature
({
signature
:
base64String
}).
subscribe
({
next
:
(
res
)
=>
{
this
.
isUploadingSignature
.
set
(
false
);
if
(
res
.
signature
)
{
this
.
user
.
update
(
u
=>
({
...
u
,
signature
:
res
.
signature
}));
}
},
error
:
(
err
)
=>
{
this
.
isUploadingSignature
.
set
(
false
);
alert
(
err
.
error
?.
message
||
'
Error uploading signature. Please try again.
'
);
}
});
};
img
.
onerror
=
()
=>
{
alert
(
'
Failed to load image. Please try a different file.
'
);
};
img
.
src
=
e
.
target
.
result
;
};
};
reader
.
onerror
=
()
=>
{
reader
.
onerror
=
()
=>
{
alert
(
'
Failed to read file
'
);
alert
(
'
Failed to read file
. Please try again.
'
);
};
};
reader
.
readAsDataURL
(
file
);
reader
.
readAsDataURL
(
file
);
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment