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
7c84e134
Commit
7c84e134
authored
May 28, 2026
by
Aravind RK
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat : CRUD for student
parent
09fd6813
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
1005 additions
and
535 deletions
+1005
-535
Frontend/src/app/pages/admin/users/users.css
Frontend/src/app/pages/admin/users/users.css
+364
-261
Frontend/src/app/pages/admin/users/users.html
Frontend/src/app/pages/admin/users/users.html
+62
-8
Frontend/src/app/pages/admin/users/users.ts
Frontend/src/app/pages/admin/users/users.ts
+80
-1
Frontend/src/app/pages/hr/users/users.css
Frontend/src/app/pages/hr/users/users.css
+363
-260
Frontend/src/app/pages/hr/users/users.html
Frontend/src/app/pages/hr/users/users.html
+56
-4
Frontend/src/app/pages/hr/users/users.ts
Frontend/src/app/pages/hr/users/users.ts
+80
-1
No files found.
Frontend/src/app/pages/admin/users/users.css
View file @
7c84e134
...
@@ -104,23 +104,23 @@ color: var(--text-secondary);
...
@@ -104,23 +104,23 @@ color: var(--text-secondary);
.users-grid
{
.users-grid
{
display
:
grid
;
display
:
grid
;
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
3
20px
,
1
fr
));
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
3
60px
,
1
fr
));
gap
:
16px
;
gap
:
16px
;
}
}
.user-card
{
.user-card
{
display
:
flex
;
display
:
flex
;
align-items
:
center
;
align-items
:
center
;
gap
:
16px
;
gap
:
12px
;
padding
:
20px
;
padding
:
16px
20px
;
background
:
var
(
--bg-card
);
background
:
var
(
--bg-card
);
border
:
1px
solid
var
(
--border-color
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
16px
;
border-radius
:
16px
;
text-decoration
:
none
;
text-decoration
:
none
;
transition
:
all
0.25s
;
transition
:
all
0.25s
;
cursor
:
pointer
;
cursor
:
pointer
;
overflow
:
hidden
;
min-width
:
0
;
}
}
.user-card
:hover
{
.user-card
:hover
{
...
@@ -152,6 +152,8 @@ color: var(--text-primary);
...
@@ -152,6 +152,8 @@ color: var(--text-primary);
align-items
:
center
;
align-items
:
center
;
gap
:
8px
;
gap
:
8px
;
margin-bottom
:
4px
;
margin-bottom
:
4px
;
flex-wrap
:
nowrap
;
overflow
:
hidden
;
}
}
.user-card-info
h4
{
.user-card-info
h4
{
...
@@ -159,6 +161,9 @@ color: var(--text-primary);
...
@@ -159,6 +161,9 @@ color: var(--text-primary);
font-size
:
15px
;
font-size
:
15px
;
font-weight
:
600
;
font-weight
:
600
;
margin
:
0
;
margin
:
0
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
}
.user-card-info
p
{
.user-card-info
p
{
...
@@ -200,15 +205,16 @@ color: var(--text-secondary);
...
@@ -200,15 +205,16 @@ color: var(--text-secondary);
background
:
transparent
;
background
:
transparent
;
border
:
none
;
border
:
none
;
border-radius
:
8px
;
border-radius
:
8px
;
width
:
32px
;
width
:
30px
;
height
:
32px
;
height
:
30px
;
min-width
:
30px
;
display
:
flex
;
display
:
flex
;
align-items
:
center
;
align-items
:
center
;
justify-content
:
center
;
justify-content
:
center
;
color
:
var
(
--text-muted
);
color
:
var
(
--text-muted
);
cursor
:
pointer
;
cursor
:
pointer
;
transition
:
all
0.2s
;
transition
:
all
0.2s
;
margin-left
:
8px
;
flex-shrink
:
0
;
}
}
.action-btn
.material-symbols-rounded
{
.action-btn
.material-symbols-rounded
{
...
@@ -221,13 +227,14 @@ color: var(--text-secondary);
...
@@ -221,13 +227,14 @@ color: var(--text-secondary);
}
}
.level-badge
{
.level-badge
{
padding
:
4px
12px
;
padding
:
3px
10px
;
border-radius
:
20px
;
border-radius
:
20px
;
font-size
:
1
1px
;
font-size
:
1
0px
;
font-weight
:
700
;
font-weight
:
700
;
text-transform
:
uppercase
;
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
letter-spacing
:
0.5px
;
flex-shrink
:
0
;
flex-shrink
:
0
;
white-space
:
nowrap
;
}
}
.level-badge
[
data-level
=
"beginner"
]
{
.level-badge
[
data-level
=
"beginner"
]
{
...
@@ -259,3 +266,99 @@ color: var(--text-secondary);
...
@@ -259,3 +266,99 @@ color: var(--text-secondary);
grid-template-columns
:
1
fr
;
grid-template-columns
:
1
fr
;
}
}
}
}
/* ─── Student Modal Overlay ─────────────────────────────────── */
.student-modal-overlay
{
position
:
fixed
;
inset
:
0
;
z-index
:
1000
;
background
:
rgba
(
0
,
0
,
0
,
0.5
);
backdrop-filter
:
blur
(
4px
);
-webkit-backdrop-filter
:
blur
(
4px
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
24px
;
animation
:
fadeInOverlay
0.2s
ease
;
}
@keyframes
fadeInOverlay
{
from
{
opacity
:
0
;
}
to
{
opacity
:
1
;
}
}
.student-modal-container
{
background
:
var
(
--bg-card
,
#fff
);
border
:
1px
solid
var
(
--border-color
,
#e2e8f0
);
border-radius
:
20px
;
width
:
100%
;
max-width
:
480px
;
box-shadow
:
0
25px
60px
rgba
(
0
,
0
,
0
,
0.25
);
animation
:
slideInModal
0.25s
cubic-bezier
(
0.34
,
1.56
,
0.64
,
1
);
overflow
:
hidden
;
}
@keyframes
slideInModal
{
from
{
transform
:
scale
(
0.92
)
translateY
(
12px
);
opacity
:
0
;
}
to
{
transform
:
scale
(
1
)
translateY
(
0
);
opacity
:
1
;
}
}
.student-modal-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
24px
28px
0
;
}
.student-modal-header
h2
{
font-size
:
20px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
margin
:
0
;
}
.student-modal-close
{
background
:
transparent
;
border
:
none
;
width
:
36px
;
height
:
36px
;
border-radius
:
10px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
color
:
var
(
--text-secondary
);
transition
:
all
0.2s
;
}
.student-modal-close
:hover
{
background
:
var
(
--bg-hover
,
rgba
(
0
,
0
,
0
,
0.06
));
color
:
var
(
--text-primary
);
}
.student-modal-body
{
padding
:
24px
28px
;
display
:
flex
;
flex-direction
:
column
;
gap
:
16px
;
}
.student-modal-footer
{
display
:
flex
;
align-items
:
center
;
justify-content
:
flex-end
;
gap
:
12px
;
padding
:
0
28px
24px
;
}
.form-hint
{
color
:
var
(
--text-secondary
);
font-size
:
12px
;
margin-top
:
4px
;
display
:
block
;
}
.text-primary
:hover
{
background
:
rgba
(
102
,
126
,
234
,
0.1
);
color
:
#667eea
;
}
\ No newline at end of file
Frontend/src/app/pages/admin/users/users.html
View file @
7c84e134
<div
class=
"page-container animate-fade-in"
>
<div
class=
"page-container animate-fade-in"
>
<div
class=
"page-header"
style=
"display: flex; align-items: center; gap: 16px;"
>
<div
class=
"page-header"
style=
"display: flex; align-items: center; justify-content: space-between;"
>
<div
style=
"display: flex; align-items: center; gap: 16px;"
>
<button
class=
"icon-btn"
(click)=
"goBack()"
style=
"background: var(--bg-card); border: 1px solid var(--border-color); padding: 8px; border-radius: 12px; cursor: pointer; color: var(--text-primary); transition: all 0.2s; display: flex; align-items: center; justify-content: center;"
onmouseover=
"this.style.background='var(--bg-hover)'"
onmouseout=
"this.style.background='var(--bg-card)'"
>
<button
class=
"icon-btn"
(click)=
"goBack()"
style=
"background: var(--bg-card); border: 1px solid var(--border-color); padding: 8px; border-radius: 12px; cursor: pointer; color: var(--text-primary); transition: all 0.2s; display: flex; align-items: center; justify-content: center;"
onmouseover=
"this.style.background='var(--bg-hover)'"
onmouseout=
"this.style.background='var(--bg-card)'"
>
<span
class=
"material-symbols-rounded"
>
arrow_back
</span>
<span
class=
"material-symbols-rounded"
>
arrow_back
</span>
</button>
</button>
...
@@ -8,6 +9,10 @@
...
@@ -8,6 +9,10 @@
<p
style=
"margin: 4px 0 0;"
class=
"page-subtitle"
>
View and manage registered students
</p>
<p
style=
"margin: 4px 0 0;"
class=
"page-subtitle"
>
View and manage registered students
</p>
</div>
</div>
</div>
</div>
<button
class=
"btn btn-primary"
(click)=
"openCreateModal()"
>
<span
class=
"material-symbols-rounded"
>
person_add
</span>
Create Student
</button>
</div>
<div
class=
"filter-tabs"
>
<div
class=
"filter-tabs"
>
<button
class=
"tab"
[class.active]=
"showAll()"
(click)=
"toggleFilter(true)"
>
All Students
</button>
<button
class=
"tab"
[class.active]=
"showAll()"
(click)=
"toggleFilter(true)"
>
All Students
</button>
...
@@ -43,7 +48,10 @@
...
@@ -43,7 +48,10 @@
<div
class=
"status-dot"
[class.online]=
"user.isLoggedIn"
title=
"Online Status"
></div>
<div
class=
"status-dot"
[class.online]=
"user.isLoggedIn"
title=
"Online Status"
></div>
<button
class=
"action-btn text-danger ml-auto"
(click)=
"$event.preventDefault(); $event.stopPropagation(); deleteUser(user._id)"
title=
"Delete Student"
>
<button
class=
"action-btn text-primary ml-auto"
style=
"margin-right: 8px;"
(click)=
"$event.preventDefault(); $event.stopPropagation(); openEditModal(user)"
title=
"Edit Student"
>
<span
class=
"material-symbols-rounded"
>
edit
</span>
</button>
<button
class=
"action-btn text-danger"
(click)=
"$event.preventDefault(); $event.stopPropagation(); deleteUser(user._id)"
title=
"Delete Student"
>
<span
class=
"material-symbols-rounded"
>
delete
</span>
<span
class=
"material-symbols-rounded"
>
delete
</span>
</button>
</button>
...
@@ -52,4 +60,50 @@
...
@@ -52,4 +60,50 @@
}
}
</div>
</div>
}
}
</div>
</div>
<!-- ─── Create / Edit Student Modal ───────────────────────────── -->
@if (showUserModal()) {
<div
class=
"student-modal-overlay"
(click)=
"closeModal()"
>
<div
class=
"student-modal-container"
(click)=
"$event.stopPropagation()"
>
<div
class=
"student-modal-header"
>
<h2>
{{ isEditMode() ? 'Edit Student' : 'Create Student' }}
</h2>
<button
class=
"student-modal-close"
(click)=
"closeModal()"
>
<span
class=
"material-symbols-rounded"
>
close
</span>
</button>
</div>
<div
class=
"student-modal-body"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Full Name *
</label>
<input
class=
"form-input"
[(ngModel)]=
"userForm.name"
placeholder=
"Enter student's full name"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Email Address *
</label>
<input
class=
"form-input"
type=
"email"
[(ngModel)]=
"userForm.email"
placeholder=
"student@example.com"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Password {{ isEditMode() ? '(Optional)' : '*' }}
</label>
<input
class=
"form-input"
type=
"password"
[(ngModel)]=
"userForm.password"
[placeholder]=
"isEditMode() ? 'Leave blank to keep current password' : 'Enter initial password'"
>
@if (isEditMode()) {
<small
class=
"form-hint"
>
Change this only if the student forgot their password.
</small>
}
</div>
</div>
<div
class=
"student-modal-footer"
>
<button
class=
"btn btn-outline"
(click)=
"closeModal()"
>
Cancel
</button>
<button
class=
"btn btn-primary"
(click)=
"saveUser()"
[disabled]=
"savingUser()"
>
@if (savingUser()) {
<div
class=
"spinner spinner-sm"
></div>
Saving...
} @else {
<span
class=
"material-symbols-rounded"
>
save
</span>
{{ isEditMode() ? 'Save Changes' : 'Create Student' }}
}
</button>
</div>
</div>
</div>
}
Frontend/src/app/pages/admin/users/users.ts
View file @
7c84e134
...
@@ -2,13 +2,14 @@ import { Component, OnInit, signal } from '@angular/core';
...
@@ -2,13 +2,14 @@ import { Component, OnInit, signal } from '@angular/core';
import
{
Location
}
from
'
@angular/common
'
;
import
{
Location
}
from
'
@angular/common
'
;
import
{
CommonModule
}
from
'
@angular/common
'
;
import
{
CommonModule
}
from
'
@angular/common
'
;
import
{
RouterLink
}
from
'
@angular/router
'
;
import
{
RouterLink
}
from
'
@angular/router
'
;
import
{
FormsModule
}
from
'
@angular/forms
'
;
import
{
AuthService
}
from
'
../../../services/auth.service
'
;
import
{
AuthService
}
from
'
../../../services/auth.service
'
;
import
{
QuizService
}
from
'
../../../services/quiz.service
'
;
import
{
QuizService
}
from
'
../../../services/quiz.service
'
;
@
Component
({
@
Component
({
selector
:
'
app-admin-users
'
,
selector
:
'
app-admin-users
'
,
standalone
:
true
,
standalone
:
true
,
imports
:
[
CommonModule
,
RouterLink
],
imports
:
[
CommonModule
,
RouterLink
,
FormsModule
],
templateUrl
:
'
./users.html
'
,
templateUrl
:
'
./users.html
'
,
styleUrl
:
'
./users.css
'
styleUrl
:
'
./users.css
'
})
})
...
@@ -17,6 +18,18 @@ export class AdminUsersComponent implements OnInit {
...
@@ -17,6 +18,18 @@ export class AdminUsersComponent implements OnInit {
loading
=
signal
<
boolean
>
(
true
);
loading
=
signal
<
boolean
>
(
true
);
showAll
=
signal
<
boolean
>
(
true
);
showAll
=
signal
<
boolean
>
(
true
);
// Modal State
showUserModal
=
signal
<
boolean
>
(
false
);
isEditMode
=
signal
<
boolean
>
(
false
);
savingUser
=
signal
<
boolean
>
(
false
);
editingUserId
=
signal
<
string
|
null
>
(
null
);
userForm
=
{
name
:
''
,
email
:
''
,
password
:
''
};
constructor
(
public
authService
:
AuthService
,
private
quizService
:
QuizService
,
private
location
:
Location
)
{}
constructor
(
public
authService
:
AuthService
,
private
quizService
:
QuizService
,
private
location
:
Location
)
{}
goBack
():
void
{
goBack
():
void
{
...
@@ -69,6 +82,72 @@ export class AdminUsersComponent implements OnInit {
...
@@ -69,6 +82,72 @@ export class AdminUsersComponent implements OnInit {
}
}
}
}
openCreateModal
():
void
{
this
.
isEditMode
.
set
(
false
);
this
.
editingUserId
.
set
(
null
);
this
.
userForm
=
{
name
:
''
,
email
:
''
,
password
:
''
};
this
.
showUserModal
.
set
(
true
);
}
openEditModal
(
user
:
any
):
void
{
this
.
isEditMode
.
set
(
true
);
this
.
editingUserId
.
set
(
user
.
_id
);
this
.
userForm
=
{
name
:
user
.
name
,
email
:
user
.
email
,
password
:
''
};
this
.
showUserModal
.
set
(
true
);
}
closeModal
():
void
{
this
.
showUserModal
.
set
(
false
);
}
saveUser
():
void
{
if
(
!
this
.
userForm
.
name
||
!
this
.
userForm
.
email
)
{
alert
(
'
Name and email are required
'
);
return
;
}
if
(
!
this
.
isEditMode
()
&&
!
this
.
userForm
.
password
)
{
alert
(
'
Password is required when creating a new student
'
);
return
;
}
this
.
savingUser
.
set
(
true
);
if
(
this
.
isEditMode
()
&&
this
.
editingUserId
())
{
// Edit User
const
payload
:
any
=
{
name
:
this
.
userForm
.
name
,
email
:
this
.
userForm
.
email
};
if
(
this
.
userForm
.
password
)
{
payload
.
password
=
this
.
userForm
.
password
;
}
this
.
quizService
.
editUser
(
this
.
editingUserId
()
!
,
payload
).
subscribe
({
next
:
()
=>
{
this
.
savingUser
.
set
(
false
);
this
.
closeModal
();
this
.
loadUsers
();
},
error
:
(
err
)
=>
{
console
.
error
(
'
Failed to update user
'
,
err
);
alert
(
err
.
error
?.
message
||
'
Failed to update user
'
);
this
.
savingUser
.
set
(
false
);
}
});
}
else
{
// Create User
this
.
authService
.
register
(
this
.
userForm
.
name
,
this
.
userForm
.
email
,
this
.
userForm
.
password
).
subscribe
({
next
:
()
=>
{
this
.
savingUser
.
set
(
false
);
this
.
closeModal
();
this
.
loadUsers
();
},
error
:
(
err
)
=>
{
console
.
error
(
'
Failed to create user
'
,
err
);
alert
(
err
.
error
?.
message
||
'
Failed to create user
'
);
this
.
savingUser
.
set
(
false
);
}
});
}
}
logout
():
void
{
logout
():
void
{
this
.
authService
.
logout
();
this
.
authService
.
logout
();
}
}
...
...
Frontend/src/app/pages/hr/users/users.css
View file @
7c84e134
...
@@ -103,23 +103,23 @@ color: var(--text-secondary);
...
@@ -103,23 +103,23 @@ color: var(--text-secondary);
.users-grid
{
.users-grid
{
display
:
grid
;
display
:
grid
;
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
3
20px
,
1
fr
));
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
3
60px
,
1
fr
));
gap
:
16px
;
gap
:
16px
;
}
}
.user-card
{
.user-card
{
display
:
flex
;
display
:
flex
;
align-items
:
center
;
align-items
:
center
;
gap
:
16px
;
gap
:
12px
;
padding
:
20px
;
padding
:
16px
20px
;
background
:
var
(
--bg-card
);
background
:
var
(
--bg-card
);
border
:
1px
solid
var
(
--border-color
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
16px
;
border-radius
:
16px
;
text-decoration
:
none
;
text-decoration
:
none
;
transition
:
all
0.25s
;
transition
:
all
0.25s
;
cursor
:
pointer
;
cursor
:
pointer
;
overflow
:
hidden
;
min-width
:
0
;
}
}
.user-card
:hover
{
.user-card
:hover
{
...
@@ -151,6 +151,8 @@ color: var(--text-primary);
...
@@ -151,6 +151,8 @@ color: var(--text-primary);
align-items
:
center
;
align-items
:
center
;
gap
:
8px
;
gap
:
8px
;
margin-bottom
:
4px
;
margin-bottom
:
4px
;
flex-wrap
:
nowrap
;
overflow
:
hidden
;
}
}
.user-card-info
h4
{
.user-card-info
h4
{
...
@@ -158,6 +160,9 @@ color: var(--text-primary);
...
@@ -158,6 +160,9 @@ color: var(--text-primary);
font-size
:
15px
;
font-size
:
15px
;
font-weight
:
600
;
font-weight
:
600
;
margin
:
0
;
margin
:
0
;
white-space
:
nowrap
;
overflow
:
hidden
;
text-overflow
:
ellipsis
;
}
}
.user-card-info
p
{
.user-card-info
p
{
...
@@ -199,15 +204,16 @@ color: var(--text-secondary);
...
@@ -199,15 +204,16 @@ color: var(--text-secondary);
background
:
transparent
;
background
:
transparent
;
border
:
none
;
border
:
none
;
border-radius
:
8px
;
border-radius
:
8px
;
width
:
32px
;
width
:
30px
;
height
:
32px
;
height
:
30px
;
min-width
:
30px
;
display
:
flex
;
display
:
flex
;
align-items
:
center
;
align-items
:
center
;
justify-content
:
center
;
justify-content
:
center
;
color
:
var
(
--text-muted
);
color
:
var
(
--text-muted
);
cursor
:
pointer
;
cursor
:
pointer
;
transition
:
all
0.2s
;
transition
:
all
0.2s
;
margin-left
:
8px
;
flex-shrink
:
0
;
}
}
.action-btn
.material-symbols-rounded
{
.action-btn
.material-symbols-rounded
{
...
@@ -219,14 +225,20 @@ color: var(--text-secondary);
...
@@ -219,14 +225,20 @@ color: var(--text-secondary);
color
:
#ef4444
;
color
:
#ef4444
;
}
}
.text-primary
:hover
{
background
:
rgba
(
102
,
126
,
234
,
0.1
);
color
:
#667eea
;
}
.level-badge
{
.level-badge
{
padding
:
4px
12px
;
padding
:
3px
10px
;
border-radius
:
20px
;
border-radius
:
20px
;
font-size
:
1
1px
;
font-size
:
1
0px
;
font-weight
:
700
;
font-weight
:
700
;
text-transform
:
uppercase
;
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
letter-spacing
:
0.5px
;
flex-shrink
:
0
;
flex-shrink
:
0
;
white-space
:
nowrap
;
}
}
.level-badge
[
data-level
=
"beginner"
]
{
.level-badge
[
data-level
=
"beginner"
]
{
...
@@ -258,3 +270,94 @@ color: var(--text-secondary);
...
@@ -258,3 +270,94 @@ color: var(--text-secondary);
grid-template-columns
:
1
fr
;
grid-template-columns
:
1
fr
;
}
}
}
}
/* ─── Student Modal Overlay ─────────────────────────────────── */
.student-modal-overlay
{
position
:
fixed
;
inset
:
0
;
z-index
:
1000
;
background
:
rgba
(
0
,
0
,
0
,
0.5
);
backdrop-filter
:
blur
(
4px
);
-webkit-backdrop-filter
:
blur
(
4px
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
padding
:
24px
;
animation
:
fadeInOverlay
0.2s
ease
;
}
@keyframes
fadeInOverlay
{
from
{
opacity
:
0
;
}
to
{
opacity
:
1
;
}
}
.student-modal-container
{
background
:
var
(
--bg-card
,
#fff
);
border
:
1px
solid
var
(
--border-color
,
#e2e8f0
);
border-radius
:
20px
;
width
:
100%
;
max-width
:
480px
;
box-shadow
:
0
25px
60px
rgba
(
0
,
0
,
0
,
0.25
);
animation
:
slideInModal
0.25s
cubic-bezier
(
0.34
,
1.56
,
0.64
,
1
);
overflow
:
hidden
;
}
@keyframes
slideInModal
{
from
{
transform
:
scale
(
0.92
)
translateY
(
12px
);
opacity
:
0
;
}
to
{
transform
:
scale
(
1
)
translateY
(
0
);
opacity
:
1
;
}
}
.student-modal-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
padding
:
24px
28px
0
;
}
.student-modal-header
h2
{
font-size
:
20px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
margin
:
0
;
}
.student-modal-close
{
background
:
transparent
;
border
:
none
;
width
:
36px
;
height
:
36px
;
border-radius
:
10px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
cursor
:
pointer
;
color
:
var
(
--text-secondary
);
transition
:
all
0.2s
;
}
.student-modal-close
:hover
{
background
:
var
(
--bg-hover
,
rgba
(
0
,
0
,
0
,
0.06
));
color
:
var
(
--text-primary
);
}
.student-modal-body
{
padding
:
24px
28px
;
display
:
flex
;
flex-direction
:
column
;
gap
:
16px
;
}
.student-modal-footer
{
display
:
flex
;
align-items
:
center
;
justify-content
:
flex-end
;
gap
:
12px
;
padding
:
0
28px
24px
;
}
.form-hint
{
color
:
var
(
--text-secondary
);
font-size
:
12px
;
margin-top
:
4px
;
display
:
block
;
}
\ No newline at end of file
Frontend/src/app/pages/hr/users/users.html
View file @
7c84e134
<div
class=
"page-container animate-fade-in"
>
<div
class=
"page-container animate-fade-in"
>
<div
class=
"page-header"
>
<div
class=
"page-header"
style=
"display: flex; align-items: center; justify-content: space-between;"
>
<div>
<h1>
Student Users
</h1>
<h1>
Student Users
</h1>
<p>
View and manage registered students
</p>
<p
class=
"page-subtitle"
>
View and manage registered students
</p>
</div>
<button
class=
"btn btn-primary"
(click)=
"openCreateModal()"
>
<span
class=
"material-symbols-rounded"
>
person_add
</span>
Create Student
</button>
</div>
</div>
<div
class=
"filter-tabs"
>
<div
class=
"filter-tabs"
>
...
@@ -38,7 +43,10 @@
...
@@ -38,7 +43,10 @@
<div
class=
"status-dot"
[class.online]=
"user.isLoggedIn"
title=
"Online Status"
></div>
<div
class=
"status-dot"
[class.online]=
"user.isLoggedIn"
title=
"Online Status"
></div>
<button
class=
"action-btn text-danger ml-auto"
(click)=
"$event.preventDefault(); $event.stopPropagation(); deleteUser(user._id)"
title=
"Delete Student"
>
<button
class=
"action-btn text-primary ml-auto"
style=
"margin-right: 8px;"
(click)=
"$event.preventDefault(); $event.stopPropagation(); openEditModal(user)"
title=
"Edit Student"
>
<span
class=
"material-symbols-rounded"
>
edit
</span>
</button>
<button
class=
"action-btn text-danger"
(click)=
"$event.preventDefault(); $event.stopPropagation(); deleteUser(user._id)"
title=
"Delete Student"
>
<span
class=
"material-symbols-rounded"
>
delete
</span>
<span
class=
"material-symbols-rounded"
>
delete
</span>
</button>
</button>
...
@@ -48,3 +56,47 @@
...
@@ -48,3 +56,47 @@
</div>
</div>
}
}
</div>
</div>
<!-- ─── Create / Edit Student Modal ───────────────────────────── -->
@if (showUserModal()) {
<div
class=
"student-modal-overlay"
(click)=
"closeModal()"
>
<div
class=
"student-modal-container"
(click)=
"$event.stopPropagation()"
>
<div
class=
"student-modal-header"
>
<h2>
{{ isEditMode() ? 'Edit Student' : 'Create Student' }}
</h2>
<button
class=
"student-modal-close"
(click)=
"closeModal()"
>
<span
class=
"material-symbols-rounded"
>
close
</span>
</button>
</div>
<div
class=
"student-modal-body"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Full Name *
</label>
<input
class=
"form-input"
[(ngModel)]=
"userForm.name"
placeholder=
"Enter student's full name"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Email Address *
</label>
<input
class=
"form-input"
type=
"email"
[(ngModel)]=
"userForm.email"
placeholder=
"student@example.com"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Password {{ isEditMode() ? '(Optional)' : '*' }}
</label>
<input
class=
"form-input"
type=
"password"
[(ngModel)]=
"userForm.password"
[placeholder]=
"isEditMode() ? 'Leave blank to keep current password' : 'Enter initial password'"
>
@if (isEditMode()) {
<small
class=
"form-hint"
>
Change this only if the student forgot their password.
</small>
}
</div>
</div>
<div
class=
"student-modal-footer"
>
<button
class=
"btn btn-outline"
(click)=
"closeModal()"
>
Cancel
</button>
<button
class=
"btn btn-primary"
(click)=
"saveUser()"
[disabled]=
"savingUser()"
>
@if (savingUser()) {
<div
class=
"spinner spinner-sm"
></div>
Saving...
} @else {
<span
class=
"material-symbols-rounded"
>
save
</span>
{{ isEditMode() ? 'Save Changes' : 'Create Student' }}
}
</button>
</div>
</div>
</div>
}
Frontend/src/app/pages/hr/users/users.ts
View file @
7c84e134
import
{
Component
,
OnInit
,
signal
}
from
'
@angular/core
'
;
import
{
Component
,
OnInit
,
signal
}
from
'
@angular/core
'
;
import
{
CommonModule
}
from
'
@angular/common
'
;
import
{
CommonModule
}
from
'
@angular/common
'
;
import
{
RouterLink
}
from
'
@angular/router
'
;
import
{
RouterLink
}
from
'
@angular/router
'
;
import
{
FormsModule
}
from
'
@angular/forms
'
;
import
{
AuthService
}
from
'
../../../services/auth.service
'
;
import
{
AuthService
}
from
'
../../../services/auth.service
'
;
import
{
QuizService
}
from
'
../../../services/quiz.service
'
;
import
{
QuizService
}
from
'
../../../services/quiz.service
'
;
@
Component
({
@
Component
({
selector
:
'
app-users
'
,
selector
:
'
app-users
'
,
imports
:
[
CommonModule
,
RouterLink
],
imports
:
[
CommonModule
,
RouterLink
,
FormsModule
],
templateUrl
:
'
./users.html
'
,
templateUrl
:
'
./users.html
'
,
styleUrl
:
'
./users.css
'
,
styleUrl
:
'
./users.css
'
,
})
})
...
@@ -15,6 +16,18 @@ export class Users {
...
@@ -15,6 +16,18 @@ export class Users {
loading
=
signal
<
boolean
>
(
true
);
loading
=
signal
<
boolean
>
(
true
);
showAll
=
signal
<
boolean
>
(
true
);
showAll
=
signal
<
boolean
>
(
true
);
// Modal State
showUserModal
=
signal
<
boolean
>
(
false
);
isEditMode
=
signal
<
boolean
>
(
false
);
savingUser
=
signal
<
boolean
>
(
false
);
editingUserId
=
signal
<
string
|
null
>
(
null
);
userForm
=
{
name
:
''
,
email
:
''
,
password
:
''
};
constructor
(
public
authService
:
AuthService
,
private
quizService
:
QuizService
)
{}
constructor
(
public
authService
:
AuthService
,
private
quizService
:
QuizService
)
{}
ngOnInit
():
void
{
ngOnInit
():
void
{
...
@@ -63,6 +76,72 @@ export class Users {
...
@@ -63,6 +76,72 @@ export class Users {
}
}
}
}
openCreateModal
():
void
{
this
.
isEditMode
.
set
(
false
);
this
.
editingUserId
.
set
(
null
);
this
.
userForm
=
{
name
:
''
,
email
:
''
,
password
:
''
};
this
.
showUserModal
.
set
(
true
);
}
openEditModal
(
user
:
any
):
void
{
this
.
isEditMode
.
set
(
true
);
this
.
editingUserId
.
set
(
user
.
_id
);
this
.
userForm
=
{
name
:
user
.
name
,
email
:
user
.
email
,
password
:
''
};
this
.
showUserModal
.
set
(
true
);
}
closeModal
():
void
{
this
.
showUserModal
.
set
(
false
);
}
saveUser
():
void
{
if
(
!
this
.
userForm
.
name
||
!
this
.
userForm
.
email
)
{
alert
(
'
Name and email are required
'
);
return
;
}
if
(
!
this
.
isEditMode
()
&&
!
this
.
userForm
.
password
)
{
alert
(
'
Password is required when creating a new student
'
);
return
;
}
this
.
savingUser
.
set
(
true
);
if
(
this
.
isEditMode
()
&&
this
.
editingUserId
())
{
// Edit User
const
payload
:
any
=
{
name
:
this
.
userForm
.
name
,
email
:
this
.
userForm
.
email
};
if
(
this
.
userForm
.
password
)
{
payload
.
password
=
this
.
userForm
.
password
;
}
this
.
quizService
.
editUser
(
this
.
editingUserId
()
!
,
payload
).
subscribe
({
next
:
()
=>
{
this
.
savingUser
.
set
(
false
);
this
.
closeModal
();
this
.
loadUsers
();
},
error
:
(
err
)
=>
{
console
.
error
(
'
Failed to update user
'
,
err
);
alert
(
err
.
error
?.
message
||
'
Failed to update user
'
);
this
.
savingUser
.
set
(
false
);
}
});
}
else
{
// Create User
this
.
authService
.
register
(
this
.
userForm
.
name
,
this
.
userForm
.
email
,
this
.
userForm
.
password
).
subscribe
({
next
:
()
=>
{
this
.
savingUser
.
set
(
false
);
this
.
closeModal
();
this
.
loadUsers
();
},
error
:
(
err
)
=>
{
console
.
error
(
'
Failed to create user
'
,
err
);
alert
(
err
.
error
?.
message
||
'
Failed to create user
'
);
this
.
savingUser
.
set
(
false
);
}
});
}
}
logout
():
void
{
logout
():
void
{
this
.
authService
.
logout
();
this
.
authService
.
logout
();
}
}
...
...
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