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
Rishikumar
Hire-Guru
Commits
954049ba
Commit
954049ba
authored
May 12, 2026
by
Aravind RK
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Group interview bug have corrected
parent
78c91f3e
Changes
18
Show whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
5630 additions
and
748 deletions
+5630
-748
Backend/models/User.js
Backend/models/User.js
+2
-2
Backend/routes/interview.js
Backend/routes/interview.js
+4
-4
Backend/uploads/1778479566478-635889545.jpeg
Backend/uploads/1778479566478-635889545.jpeg
+0
-0
Backend/uploads/1778488067587-769251812.pdf
Backend/uploads/1778488067587-769251812.pdf
+0
-0
Backend/uploads/1778490006053-347095609.zip
Backend/uploads/1778490006053-347095609.zip
+0
-0
Backend/uploads/1778495154498-888467455.pdf
Backend/uploads/1778495154498-888467455.pdf
+0
-0
Frontend/package-lock.json
Frontend/package-lock.json
+2432
-668
Frontend/src/app/pages/admin/group-interview/group-interview.css
...d/src/app/pages/admin/group-interview/group-interview.css
+117
-8
Frontend/src/app/pages/admin/group-interview/group-interview.html
.../src/app/pages/admin/group-interview/group-interview.html
+317
-47
Frontend/src/app/pages/admin/group-interview/group-interview.ts
...nd/src/app/pages/admin/group-interview/group-interview.ts
+111
-4
Frontend/src/app/pages/admin/individual-interview/individual-interview.html
...ages/admin/individual-interview/individual-interview.html
+3
-3
Frontend/src/app/pages/admin/users/users.html
Frontend/src/app/pages/admin/users/users.html
+2
-2
Frontend/src/app/pages/hr/group-interview/group-interview.css
...tend/src/app/pages/hr/group-interview/group-interview.css
+476
-0
Frontend/src/app/pages/hr/group-interview/group-interview.html
...end/src/app/pages/hr/group-interview/group-interview.html
+748
-1
Frontend/src/app/pages/hr/group-interview/group-interview.ts
Frontend/src/app/pages/hr/group-interview/group-interview.ts
+409
-4
Frontend/src/app/pages/hr/individual-interview/individual-interview.css
...pp/pages/hr/individual-interview/individual-interview.css
+243
-0
Frontend/src/app/pages/hr/individual-interview/individual-interview.html
...p/pages/hr/individual-interview/individual-interview.html
+494
-1
Frontend/src/app/pages/hr/individual-interview/individual-interview.ts
...app/pages/hr/individual-interview/individual-interview.ts
+272
-4
No files found.
Backend/models/User.js
View file @
954049ba
...
@@ -48,8 +48,8 @@ const userSchema = new mongoose.Schema({
...
@@ -48,8 +48,8 @@ const userSchema = new mongoose.Schema({
},
},
level
:
{
level
:
{
type
:
String
,
type
:
String
,
enum
:
[
'
beginner
'
,
'
intermediate
'
,
'
advanced
'
,
'
expert
'
],
enum
:
[
'
Fresher
'
,
'
Intern
'
,
'
Pre final year
'
,
'
Final year
'
],
default
:
'
beginner
'
default
:
'
Fresher
'
},
},
topicsOfInterest
:
[{
topicsOfInterest
:
[{
topic
:
{
type
:
String
,
required
:
true
},
topic
:
{
type
:
String
,
required
:
true
},
...
...
Backend/routes/interview.js
View file @
954049ba
...
@@ -428,9 +428,9 @@ router.put('/:id/evaluate', authorize('admin', 'hr', 'pm', 'interviewer'), async
...
@@ -428,9 +428,9 @@ router.put('/:id/evaluate', authorize('admin', 'hr', 'pm', 'interviewer'), async
// ============================================================
// ============================================================
// @route PUT /api/interview/:id/decision
// @route PUT /api/interview/:id/decision
// @desc Set final decision (accepted/rejected/on_hold/2nd_round)
// @desc Set final decision (accepted/rejected/on_hold/2nd_round)
// @access Admin
only
// @access Admin
, HR
// ============================================================
// ============================================================
router
.
put
(
'
/:id/decision
'
,
authorize
(
'
admin
'
),
async
(
req
,
res
)
=>
{
router
.
put
(
'
/:id/decision
'
,
authorize
(
'
admin
'
,
'
hr
'
),
async
(
req
,
res
)
=>
{
try
{
try
{
const
{
decision
}
=
req
.
body
;
const
{
decision
}
=
req
.
body
;
if
(
!
decision
)
return
res
.
status
(
400
).
json
({
message
:
'
Decision is required
'
});
if
(
!
decision
)
return
res
.
status
(
400
).
json
({
message
:
'
Decision is required
'
});
...
@@ -515,9 +515,9 @@ router.put('/:id/validate-coding', authorize('admin', 'hr', 'pm', 'interviewer')
...
@@ -515,9 +515,9 @@ router.put('/:id/validate-coding', authorize('admin', 'hr', 'pm', 'interviewer')
// ============================================================
// ============================================================
// @route DELETE /api/interview/:id
// @route DELETE /api/interview/:id
// @desc Delete an interview
// @desc Delete an interview
// @access Admin
only
// @access Admin
, HR
// ============================================================
// ============================================================
router
.
delete
(
'
/:id
'
,
authorize
(
'
admin
'
),
async
(
req
,
res
)
=>
{
router
.
delete
(
'
/:id
'
,
authorize
(
'
admin
'
,
'
hr
'
),
async
(
req
,
res
)
=>
{
try
{
try
{
const
interview
=
await
Interview
.
findById
(
req
.
params
.
id
);
const
interview
=
await
Interview
.
findById
(
req
.
params
.
id
);
if
(
!
interview
)
return
res
.
status
(
404
).
json
({
message
:
'
Interview not found
'
});
if
(
!
interview
)
return
res
.
status
(
404
).
json
({
message
:
'
Interview not found
'
});
...
...
Backend/uploads/1778479566478-635889545.jpeg
0 → 100644
View file @
954049ba
69.9 KB
Backend/uploads/1778488067587-769251812.pdf
0 → 100644
View file @
954049ba
File added
Backend/uploads/1778490006053-347095609.zip
0 → 100644
View file @
954049ba
File added
Backend/uploads/1778495154498-888467455.pdf
0 → 100644
View file @
954049ba
File added
Frontend/package-lock.json
View file @
954049ba
...
@@ -925,322 +925,499 @@
...
@@ -925,322 +925,499 @@
"node"
:
">=6.9.0"
"node"
:
">=6.9.0"
}
}
},
},
"node_modules/@esbuild/
win32-x
64"
:
{
"node_modules/@esbuild/
aix-ppc
64"
:
{
"version"
:
"0.27.3"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/
win32-x64/-/win32-x
64-0.27.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/
aix-ppc64/-/aix-ppc
64-0.27.3.tgz"
,
"integrity"
:
"sha512-
4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA
=="
,
"integrity"
:
"sha512-
9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg
=="
,
"cpu"
:
[
"cpu"
:
[
"
x
64"
"
ppc
64"
],
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"optional"
:
true
,
"os"
:
[
"os"
:
[
"
win32
"
"
aix
"
],
],
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
}
}
},
},
"node_modules/@gar/promise-retry"
:
{
"node_modules/@esbuild/android-arm"
:
{
"version"
:
"1.0.3"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz"
,
"integrity"
:
"sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="
,
"integrity"
:
"sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"android"
],
"engines"
:
{
"engines"
:
{
"node"
:
"
^20.17.0 || >=22.9.0
"
"node"
:
"
>=18
"
}
}
},
},
"node_modules/@harperfast/extended-iterable"
:
{
"node_modules/@esbuild/android-arm64"
:
{
"version"
:
"1.0.3"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz"
,
"integrity"
:
"sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw=="
,
"integrity"
:
"sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"Apache-2.0"
,
"license"
:
"MIT"
,
"optional"
:
true
"optional"
:
true
,
"os"
:
[
"android"
],
"engines"
:
{
"node"
:
">=18"
}
},
},
"node_modules/@hono/node-server"
:
{
"node_modules/@esbuild/android-x64"
:
{
"version"
:
"1.19.13"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz"
,
"integrity"
:
"sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ=="
,
"integrity"
:
"sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"android"
],
"engines"
:
{
"engines"
:
{
"node"
:
">=18.14.1"
"node"
:
">=18"
},
"peerDependencies"
:
{
"hono"
:
"^4"
}
}
},
},
"node_modules/@inquirer/ansi"
:
{
"node_modules/@esbuild/darwin-arm64"
:
{
"version"
:
"1.0.2"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz"
,
"integrity"
:
"sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="
,
"integrity"
:
"sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
],
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
}
}
},
},
"node_modules/@inquirer/checkbox"
:
{
"node_modules/@esbuild/darwin-x64"
:
{
"version"
:
"4.3.2"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz"
,
"integrity"
:
"sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA=="
,
"integrity"
:
"sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"optional"
:
true
,
"@inquirer/ansi"
:
"^1.0.2"
,
"os"
:
[
"@inquirer/core"
:
"^10.3.2"
,
"darwin"
"@inquirer/figures"
:
"^1.0.15"
,
],
"@inquirer/type"
:
"^3.0.10"
,
"yoctocolors-cjs"
:
"^2.1.3"
},
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
}
},
},
"node_modules/@inquirer/confirm"
:
{
"node_modules/@esbuild/freebsd-arm64"
:
{
"version"
:
"5.1.21"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz"
,
"integrity"
:
"sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ=="
,
"integrity"
:
"sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"
dependencies"
:
{
"
optional"
:
true
,
"@inquirer/core"
:
"^10.3.2"
,
"os"
:
[
"
@inquirer/type"
:
"^3.0.10
"
"
freebsd
"
}
,
]
,
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
}
},
},
"node_modules/@inquirer/core"
:
{
"node_modules/@esbuild/freebsd-x64"
:
{
"version"
:
"10.3.2"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz"
,
"integrity"
:
"sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A=="
,
"integrity"
:
"sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"optional"
:
true
,
"@inquirer/ansi"
:
"^1.0.2"
,
"os"
:
[
"@inquirer/figures"
:
"^1.0.15"
,
"freebsd"
"@inquirer/type"
:
"^3.0.10"
,
],
"cli-width"
:
"^4.1.0"
,
"mute-stream"
:
"^2.0.0"
,
"signal-exit"
:
"^4.1.0"
,
"wrap-ansi"
:
"^6.2.0"
,
"yoctocolors-cjs"
:
"^2.1.3"
},
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
}
},
},
"node_modules/@inquirer/editor"
:
{
"node_modules/@esbuild/linux-arm"
:
{
"version"
:
"4.2.23"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz"
,
"integrity"
:
"sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ=="
,
"integrity"
:
"sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"optional"
:
true
,
"@inquirer/core"
:
"^10.3.2"
,
"os"
:
[
"@inquirer/external-editor"
:
"^1.0.3"
,
"linux"
"@inquirer/type"
:
"^3.0.10"
],
},
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
}
},
},
"node_modules/@inquirer/expand"
:
{
"node_modules/@esbuild/linux-arm64"
:
{
"version"
:
"4.0.23"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz"
,
"integrity"
:
"sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew=="
,
"integrity"
:
"sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"optional"
:
true
,
"@inquirer/core"
:
"^10.3.2"
,
"os"
:
[
"@inquirer/type"
:
"^3.0.10"
,
"linux"
"yoctocolors-cjs"
:
"^2.1.3"
],
},
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
}
},
},
"node_modules/@inquirer/external-editor"
:
{
"node_modules/@esbuild/linux-ia32"
:
{
"version"
:
"1.0.3"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz"
,
"integrity"
:
"sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA=="
,
"integrity"
:
"sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg=="
,
"cpu"
:
[
"ia32"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"
dependencies"
:
{
"
optional"
:
true
,
"chardet"
:
"^2.1.1"
,
"os"
:
[
"
iconv-lite"
:
"^0.7.0
"
"
linux
"
}
,
]
,
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
}
},
},
"node_modules/@inquirer/figures"
:
{
"node_modules/@esbuild/linux-loong64"
:
{
"version"
:
"1.0.15"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz"
,
"integrity"
:
"sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="
,
"integrity"
:
"sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA=="
,
"cpu"
:
[
"loong64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
}
}
},
},
"node_modules/@inquirer/input"
:
{
"node_modules/@esbuild/linux-mips64el"
:
{
"version"
:
"4.3.1"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz"
,
"integrity"
:
"sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g=="
,
"integrity"
:
"sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw=="
,
"cpu"
:
[
"mips64el"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"
dependencies"
:
{
"
optional"
:
true
,
"@inquirer/core"
:
"^10.3.2"
,
"os"
:
[
"
@inquirer/type"
:
"^3.0.10
"
"
linux
"
}
,
]
,
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
}
},
},
"node_modules/@inquirer/number"
:
{
"node_modules/@esbuild/linux-ppc64"
:
{
"version"
:
"3.0.23"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz"
,
"integrity"
:
"sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg=="
,
"integrity"
:
"sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA=="
,
"cpu"
:
[
"ppc64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"
dependencies"
:
{
"
optional"
:
true
,
"@inquirer/core"
:
"^10.3.2"
,
"os"
:
[
"
@inquirer/type"
:
"^3.0.10
"
"
linux
"
}
,
]
,
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
}
},
},
"peerDependencies"
:
{
"node_modules/@esbuild/linux-riscv64"
:
{
"@types/node"
:
">=18"
"version"
:
"0.27.3"
,
},
"resolved"
:
"https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz"
,
"peerDependenciesMeta"
:
{
"integrity"
:
"sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ=="
,
"@types/node"
:
{
"cpu"
:
[
"optional"
:
true
"riscv64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">=18"
}
}
},
"node_modules/@esbuild/linux-s390x"
:
{
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz"
,
"integrity"
:
"sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw=="
,
"cpu"
:
[
"s390x"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">=18"
}
}
},
},
"node_modules/@inquirer/password"
:
{
"node_modules/@esbuild/linux-x64"
:
{
"version"
:
"4.0.23"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz"
,
"integrity"
:
"sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA=="
,
"integrity"
:
"sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"optional"
:
true
,
"@inquirer/ansi"
:
"^1.0.2"
,
"os"
:
[
"@inquirer/core"
:
"^10.3.2"
,
"linux"
"@inquirer/type"
:
"^3.0.10"
],
"engines"
:
{
"node"
:
">=18"
}
},
},
"node_modules/@esbuild/netbsd-arm64"
:
{
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz"
,
"integrity"
:
"sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"netbsd"
],
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
}
},
},
"peerDependencies"
:
{
"node_modules/@esbuild/netbsd-x64"
:
{
"@types/node"
:
">=18"
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz"
,
"integrity"
:
"sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"netbsd"
],
"engines"
:
{
"node"
:
">=18"
}
},
},
"peerDependenciesMeta"
:
{
"node_modules/@esbuild/openbsd-arm64"
:
{
"@types/node"
:
{
"version"
:
"0.27.3"
,
"optional"
:
true
"resolved"
:
"https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz"
,
"integrity"
:
"sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"openbsd"
],
"engines"
:
{
"node"
:
">=18"
}
}
},
"node_modules/@esbuild/openbsd-x64"
:
{
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz"
,
"integrity"
:
"sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"openbsd"
],
"engines"
:
{
"node"
:
">=18"
}
}
},
},
"node_modules/@inquirer/prompts"
:
{
"node_modules/@esbuild/openharmony-arm64"
:
{
"version"
:
"7.10.1"
,
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz"
,
"integrity"
:
"sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg=="
,
"integrity"
:
"sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"optional"
:
true
,
"@inquirer/checkbox"
:
"^4.3.2"
,
"os"
:
[
"@inquirer/confirm"
:
"^5.1.21"
,
"openharmony"
"@inquirer/editor"
:
"^4.2.23"
,
],
"@inquirer/expand"
:
"^4.0.23"
,
"engines"
:
{
"@inquirer/input"
:
"^4.3.1"
,
"node"
:
">=18"
"@inquirer/number"
:
"^3.0.23"
,
}
"@inquirer/password"
:
"^4.0.23"
,
"@inquirer/rawlist"
:
"^4.1.11"
,
"@inquirer/search"
:
"^3.2.2"
,
"@inquirer/select"
:
"^4.4.2"
},
},
"node_modules/@esbuild/sunos-x64"
:
{
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz"
,
"integrity"
:
"sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"sunos"
],
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
}
},
},
"peerDependencies"
:
{
"node_modules/@esbuild/win32-arm64"
:
{
"@types/node"
:
">=18"
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz"
,
"integrity"
:
"sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
">=18"
}
},
},
"peerDependenciesMeta"
:
{
"node_modules/@esbuild/win32-ia32"
:
{
"@types/node"
:
{
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz"
,
"integrity"
:
"sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q=="
,
"cpu"
:
[
"ia32"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/@esbuild/win32-x64"
:
{
"version"
:
"0.27.3"
,
"resolved"
:
"https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz"
,
"integrity"
:
"sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/@gar/promise-retry"
:
{
"version"
:
"1.0.3"
,
"resolved"
:
"https://registry.npmjs.org/@gar/promise-retry/-/promise-retry-1.0.3.tgz"
,
"integrity"
:
"sha512-GmzA9ckNokPypTg10pgpeHNQe7ph+iIKKmhKu3Ob9ANkswreCx7R3cKmY781K8QK3AqVL3xVh9A42JvIAbkkSA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
"^20.17.0 || >=22.9.0"
}
},
"node_modules/@harperfast/extended-iterable"
:
{
"version"
:
"1.0.3"
,
"resolved"
:
"https://registry.npmjs.org/@harperfast/extended-iterable/-/extended-iterable-1.0.3.tgz"
,
"integrity"
:
"sha512-sSAYhQca3rDWtQUHSAPeO7axFIUJOI6hn1gjRC5APVE1a90tuyT8f5WIgRsFhhWA7htNkju2veB9eWL6YHi/Lw=="
,
"dev"
:
true
,
"license"
:
"Apache-2.0"
,
"optional"
:
true
"optional"
:
true
},
"node_modules/@hono/node-server"
:
{
"version"
:
"1.19.13"
,
"resolved"
:
"https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz"
,
"integrity"
:
"sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=18.14.1"
},
"peerDependencies"
:
{
"hono"
:
"^4"
}
}
},
"node_modules/@inquirer/ansi"
:
{
"version"
:
"1.0.2"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz"
,
"integrity"
:
"sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=18"
}
}
},
},
"node_modules/@inquirer/
rawlist
"
:
{
"node_modules/@inquirer/
checkbox
"
:
{
"version"
:
"4.
1.11
"
,
"version"
:
"4.
3.2
"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/
rawlist/-/rawlist-4.1.11
.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/
checkbox/-/checkbox-4.3.2
.tgz"
,
"integrity"
:
"sha512-
+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw
=="
,
"integrity"
:
"sha512-
VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA
=="
,
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"dependencies"
:
{
"@inquirer/ansi"
:
"^1.0.2"
,
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/figures"
:
"^1.0.15"
,
"@inquirer/type"
:
"^3.0.10"
,
"@inquirer/type"
:
"^3.0.10"
,
"yoctocolors-cjs"
:
"^2.1.3"
"yoctocolors-cjs"
:
"^2.1.3"
},
},
...
@@ -1256,17 +1433,15 @@
...
@@ -1256,17 +1433,15 @@
}
}
}
}
},
},
"node_modules/@inquirer/
search
"
:
{
"node_modules/@inquirer/
confirm
"
:
{
"version"
:
"
3.2.2
"
,
"version"
:
"
5.1.21
"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/
search/-/search-3.2.2
.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/
confirm/-/confirm-5.1.21
.tgz"
,
"integrity"
:
"sha512-
p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA
=="
,
"integrity"
:
"sha512-
KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ
=="
,
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"dependencies"
:
{
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/figures"
:
"^1.0.15"
,
"@inquirer/type"
:
"^3.0.10"
"@inquirer/type"
:
"^3.0.10"
,
"yoctocolors-cjs"
:
"^2.1.3"
},
},
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
...
@@ -1280,17 +1455,20 @@
...
@@ -1280,17 +1455,20 @@
}
}
}
}
},
},
"node_modules/@inquirer/
select
"
:
{
"node_modules/@inquirer/
core
"
:
{
"version"
:
"
4.4
.2"
,
"version"
:
"
10.3
.2"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/
select/-/select-4.4
.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/
core/-/core-10.3
.2.tgz"
,
"integrity"
:
"sha512-
l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w
=="
,
"integrity"
:
"sha512-
43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A
=="
,
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"dependencies"
:
{
"@inquirer/ansi"
:
"^1.0.2"
,
"@inquirer/ansi"
:
"^1.0.2"
,
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/figures"
:
"^1.0.15"
,
"@inquirer/figures"
:
"^1.0.15"
,
"@inquirer/type"
:
"^3.0.10"
,
"@inquirer/type"
:
"^3.0.10"
,
"cli-width"
:
"^4.1.0"
,
"mute-stream"
:
"^2.0.0"
,
"signal-exit"
:
"^4.1.0"
,
"wrap-ansi"
:
"^6.2.0"
,
"yoctocolors-cjs"
:
"^2.1.3"
"yoctocolors-cjs"
:
"^2.1.3"
},
},
"engines"
:
{
"engines"
:
{
...
@@ -1305,12 +1483,17 @@
...
@@ -1305,12 +1483,17 @@
}
}
}
}
},
},
"node_modules/@inquirer/
type
"
:
{
"node_modules/@inquirer/
editor
"
:
{
"version"
:
"
3.0.10
"
,
"version"
:
"
4.2.23
"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/
type/-/type-3.0.10
.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/
editor/-/editor-4.2.23
.tgz"
,
"integrity"
:
"sha512-
BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA
=="
,
"integrity"
:
"sha512-
aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ
=="
,
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/external-editor"
:
"^1.0.3"
,
"@inquirer/type"
:
"^3.0.10"
},
"engines"
:
{
"engines"
:
{
"node"
:
">=18"
"node"
:
">=18"
},
},
...
@@ -1323,9 +1506,251 @@
...
@@ -1323,9 +1506,251 @@
}
}
}
}
},
},
"node_modules/@isaacs/fs-minipass"
:
{
"node_modules/@inquirer/expand"
:
{
"version"
:
"4.0.1"
,
"version"
:
"4.0.23"
,
"resolved"
:
"https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz"
,
"integrity"
:
"sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/type"
:
"^3.0.10"
,
"yoctocolors-cjs"
:
"^2.1.3"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
},
"node_modules/@inquirer/external-editor"
:
{
"version"
:
"1.0.3"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz"
,
"integrity"
:
"sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"chardet"
:
"^2.1.1"
,
"iconv-lite"
:
"^0.7.0"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
},
"node_modules/@inquirer/figures"
:
{
"version"
:
"1.0.15"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz"
,
"integrity"
:
"sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=18"
}
},
"node_modules/@inquirer/input"
:
{
"version"
:
"4.3.1"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz"
,
"integrity"
:
"sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/type"
:
"^3.0.10"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
},
"node_modules/@inquirer/number"
:
{
"version"
:
"3.0.23"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz"
,
"integrity"
:
"sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/type"
:
"^3.0.10"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
},
"node_modules/@inquirer/password"
:
{
"version"
:
"4.0.23"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz"
,
"integrity"
:
"sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@inquirer/ansi"
:
"^1.0.2"
,
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/type"
:
"^3.0.10"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
},
"node_modules/@inquirer/prompts"
:
{
"version"
:
"7.10.1"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz"
,
"integrity"
:
"sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@inquirer/checkbox"
:
"^4.3.2"
,
"@inquirer/confirm"
:
"^5.1.21"
,
"@inquirer/editor"
:
"^4.2.23"
,
"@inquirer/expand"
:
"^4.0.23"
,
"@inquirer/input"
:
"^4.3.1"
,
"@inquirer/number"
:
"^3.0.23"
,
"@inquirer/password"
:
"^4.0.23"
,
"@inquirer/rawlist"
:
"^4.1.11"
,
"@inquirer/search"
:
"^3.2.2"
,
"@inquirer/select"
:
"^4.4.2"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
},
"node_modules/@inquirer/rawlist"
:
{
"version"
:
"4.1.11"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz"
,
"integrity"
:
"sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/type"
:
"^3.0.10"
,
"yoctocolors-cjs"
:
"^2.1.3"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
},
"node_modules/@inquirer/search"
:
{
"version"
:
"3.2.2"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz"
,
"integrity"
:
"sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/figures"
:
"^1.0.15"
,
"@inquirer/type"
:
"^3.0.10"
,
"yoctocolors-cjs"
:
"^2.1.3"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
},
"node_modules/@inquirer/select"
:
{
"version"
:
"4.4.2"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz"
,
"integrity"
:
"sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"@inquirer/ansi"
:
"^1.0.2"
,
"@inquirer/core"
:
"^10.3.2"
,
"@inquirer/figures"
:
"^1.0.15"
,
"@inquirer/type"
:
"^3.0.10"
,
"yoctocolors-cjs"
:
"^2.1.3"
},
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
},
"node_modules/@inquirer/type"
:
{
"version"
:
"3.0.10"
,
"resolved"
:
"https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz"
,
"integrity"
:
"sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"engines"
:
{
"node"
:
">=18"
},
"peerDependencies"
:
{
"@types/node"
:
">=18"
},
"peerDependenciesMeta"
:
{
"@types/node"
:
{
"optional"
:
true
}
}
},
"node_modules/@isaacs/fs-minipass"
:
{
"version"
:
"4.0.1"
,
"resolved"
:
"https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz"
,
"integrity"
:
"sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="
,
"integrity"
:
"sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="
,
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
,
"license"
:
"ISC"
,
...
@@ -1413,26 +1838,110 @@
...
@@ -1413,26 +1838,110 @@
"listr2"
:
"9.0.5"
"listr2"
:
"9.0.5"
}
}
},
},
"node_modules/@lmdb/lmdb-
win32-x
64"
:
{
"node_modules/@lmdb/lmdb-
darwin-arm
64"
:
{
"version"
:
"3.5.1"
,
"version"
:
"3.5.1"
,
"resolved"
:
"https://registry.npmjs.org/@lmdb/lmdb-
win32-x64/-/lmdb-win32-x
64-3.5.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@lmdb/lmdb-
darwin-arm64/-/lmdb-darwin-arm
64-3.5.1.tgz"
,
"integrity"
:
"sha512-
qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg
=="
,
"integrity"
:
"sha512-
tpfN4kKrrMpQ+If1l8bhmoNkECJi0iOu6AEdrTJvWVC+32sLxTARX5Rsu579mPImRP9YFWfWgeRQ5oav7zApQQ
=="
,
"cpu"
:
[
"cpu"
:
[
"
x
64"
"
arm
64"
],
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"optional"
:
true
,
"os"
:
[
"os"
:
[
"
win32
"
"
darwin
"
]
]
},
},
"node_modules/@modelcontextprotocol/sdk"
:
{
"node_modules/@lmdb/lmdb-darwin-x64"
:
{
"version"
:
"1.26.0"
,
"version"
:
"3.5.1"
,
"resolved"
:
"https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.5.1.tgz"
,
"integrity"
:
"sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="
,
"integrity"
:
"sha512-+a2tTfc3rmWhLAolFUWRgJtpSuu+Fw/yjn4rF406NMxhfjbMuiOUTDRvRlMFV+DzyjkwnokisskHbCWkS3Ly5w=="
,
"dev"
:
true
,
"cpu"
:
[
"license"
:
"MIT"
,
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
]
},
"node_modules/@lmdb/lmdb-linux-arm"
:
{
"version"
:
"3.5.1"
,
"resolved"
:
"https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.5.1.tgz"
,
"integrity"
:
"sha512-0EgcE6reYr8InjD7V37EgXcYrloqpxVPINy3ig1MwDSbl6LF/vXTYRH9OE1Ti1D8YZnB35ZH9aTcdfSb5lql2A=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
"node_modules/@lmdb/lmdb-linux-arm64"
:
{
"version"
:
"3.5.1"
,
"resolved"
:
"https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.5.1.tgz"
,
"integrity"
:
"sha512-aoERa5B6ywXdyFeYGQ1gbQpkMkDbEo45qVoXE5QpIRavqjnyPwjOulMkmkypkmsbJ5z4Wi0TBztON8agCTG0Vg=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
"node_modules/@lmdb/lmdb-linux-x64"
:
{
"version"
:
"3.5.1"
,
"resolved"
:
"https://registry.npmjs.org/@lmdb/lmdb-linux-x64/-/lmdb-linux-x64-3.5.1.tgz"
,
"integrity"
:
"sha512-SqNDY1+vpji7bh0sFH5wlWyFTOzjbDOl0/kB5RLLYDAFyd/uw3n7wyrmas3rYPpAW7z18lMOi1yKlTPv967E3g=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
"node_modules/@lmdb/lmdb-win32-arm64"
:
{
"version"
:
"3.5.1"
,
"resolved"
:
"https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.5.1.tgz"
,
"integrity"
:
"sha512-50v0O1Lt37cwrmR9vWZK5hRW0Aw+KEmxJJ75fge/zIYdvNKB/0bSMSVR5Uc2OV9JhosIUyklOmrEvavwNJ8D6w=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
]
},
"node_modules/@lmdb/lmdb-win32-x64"
:
{
"version"
:
"3.5.1"
,
"resolved"
:
"https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.5.1.tgz"
,
"integrity"
:
"sha512-qwosvPyl+zpUlp3gRb7UcJ3H8S28XHCzkv0Y0EgQToXjQP91ZD67EHSCDmaLjtKhe+GVIW5om1KUpzVLA0l6pg=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
]
},
"node_modules/@modelcontextprotocol/sdk"
:
{
"version"
:
"1.26.0"
,
"resolved"
:
"https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz"
,
"integrity"
:
"sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"dependencies"
:
{
"dependencies"
:
{
"@hono/node-server"
:
"^1.19.9"
,
"@hono/node-server"
:
"^1.19.9"
,
"ajv"
:
"^8.17.1"
,
"ajv"
:
"^8.17.1"
,
...
@@ -1468,6 +1977,76 @@
...
@@ -1468,6 +1977,76 @@
}
}
}
}
},
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64"
:
{
"version"
:
"3.0.3"
,
"resolved"
:
"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz"
,
"integrity"
:
"sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64"
:
{
"version"
:
"3.0.3"
,
"resolved"
:
"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz"
,
"integrity"
:
"sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm"
:
{
"version"
:
"3.0.3"
,
"resolved"
:
"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz"
,
"integrity"
:
"sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64"
:
{
"version"
:
"3.0.3"
,
"resolved"
:
"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz"
,
"integrity"
:
"sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-linux-x64"
:
{
"version"
:
"3.0.3"
,
"resolved"
:
"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz"
,
"integrity"
:
"sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64"
:
{
"node_modules/@msgpackr-extract/msgpackr-extract-win32-x64"
:
{
"version"
:
"3.0.3"
,
"version"
:
"3.0.3"
,
"resolved"
:
"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz"
,
...
@@ -1516,125 +2095,437 @@
...
@@ -1516,125 +2095,437 @@
"@napi-rs/nice-win32-x64-msvc"
:
"1.1.1"
"@napi-rs/nice-win32-x64-msvc"
:
"1.1.1"
}
}
},
},
"node_modules/@napi-rs/nice-
win32-x64-msvc
"
:
{
"node_modules/@napi-rs/nice-
android-arm-eabi
"
:
{
"version"
:
"1.1.1"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-
win32-x64-msvc/-/nice-win32-x64-msvc
-1.1.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-
android-arm-eabi/-/nice-android-arm-eabi
-1.1.1.tgz"
,
"integrity"
:
"sha512-
vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ
=="
,
"integrity"
:
"sha512-
kjirL3N6TnRPv5iuHw36wnucNqXAO46dzK9oPb0wj076R5Xm8PfUVA9nAFB5ZNMmfJQJVKACAPd/Z2KYMppthw
=="
,
"cpu"
:
[
"cpu"
:
[
"
x64
"
"
arm
"
],
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"optional"
:
true
,
"os"
:
[
"os"
:
[
"
win32
"
"
android
"
],
],
"engines"
:
{
"engines"
:
{
"node"
:
">= 10"
"node"
:
">= 10"
}
}
},
},
"node_modules/@npmcli/agent"
:
{
"node_modules/@napi-rs/nice-android-arm64"
:
{
"version"
:
"4.0.0"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.1.1.tgz"
,
"integrity"
:
"sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA=="
,
"integrity"
:
"sha512-blG0i7dXgbInN5urONoUCNf+DUEAavRffrO7fZSeoRMJc5qD+BJeNcpr54msPF6qfDD6kzs9AQJogZvT2KD5nw=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"optional"
:
true
,
"agent-base"
:
"^7.1.0"
,
"os"
:
[
"http-proxy-agent"
:
"^7.0.0"
,
"android"
"https-proxy-agent"
:
"^7.0.1"
,
],
"lru-cache"
:
"^11.2.1"
,
"socks-proxy-agent"
:
"^8.0.3"
},
"engines"
:
{
"engines"
:
{
"node"
:
"
^20.17.0 || >=22.9.
0"
"node"
:
"
>= 1
0"
}
}
},
},
"node_modules/@npmcli/agent/node_modules/lru-cache"
:
{
"node_modules/@napi-rs/nice-darwin-arm64"
:
{
"version"
:
"11.3.2"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.1.1.tgz"
,
"integrity"
:
"sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ=="
,
"integrity"
:
"sha512-s/E7w45NaLqTGuOjC2p96pct4jRfo61xb9bU1unM/MJ/RFkKlJyJDx7OJI/O0ll/hrfpqKopuAFDV8yo0hfT7A=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"BlueOak-1.0.0"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
],
"engines"
:
{
"engines"
:
{
"node"
:
"
20 || >=22
"
"node"
:
"
>= 10
"
}
}
},
},
"node_modules/@npmcli/fs"
:
{
"node_modules/@napi-rs/nice-darwin-x64"
:
{
"version"
:
"5.0.0"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.1.1.tgz"
,
"integrity"
:
"sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="
,
"integrity"
:
"sha512-dGoEBnVpsdcC+oHHmW1LRK5eiyzLwdgNQq3BmZIav+9/5WTZwBYX7r5ZkQC07Nxd3KHOCkgbHSh4wPkH1N1LiQ=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"optional"
:
true
,
"semver"
:
"^7.3.5"
"os"
:
[
},
"darwin"
],
"engines"
:
{
"engines"
:
{
"node"
:
"
^20.17.0 || >=22.9.
0"
"node"
:
"
>= 1
0"
}
}
},
},
"node_modules/@npmcli/git"
:
{
"node_modules/@napi-rs/nice-freebsd-x64"
:
{
"version"
:
"7.0.2"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.1.1.tgz"
,
"integrity"
:
"sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg=="
,
"integrity"
:
"sha512-kHv4kEHAylMYmlNwcQcDtXjklYp4FCf0b05E+0h6nDHsZ+F0bDe04U/tXNOqrx5CmIAth4vwfkjjUmp4c4JktQ=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
,
"license"
:
"MIT"
,
"dependencies"
:
{
"optional"
:
true
,
"@gar/promise-retry"
:
"^1.0.0"
,
"os"
:
[
"@npmcli/promise-spawn"
:
"^9.0.0"
,
"freebsd"
"ini"
:
"^6.0.0"
,
],
"lru-cache"
:
"^11.2.1"
,
"npm-pick-manifest"
:
"^11.0.1"
,
"proc-log"
:
"^6.0.0"
,
"semver"
:
"^7.3.5"
,
"which"
:
"^6.0.0"
},
"engines"
:
{
"engines"
:
{
"node"
:
"
^20.17.0 || >=22.9.
0"
"node"
:
"
>= 1
0"
}
}
},
},
"node_modules/@npmcli/git/node_modules/isexe"
:
{
"node_modules/@napi-rs/nice-linux-arm-gnueabihf"
:
{
"version"
:
"4.0.0"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.1.1.tgz"
,
"integrity"
:
"sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="
,
"integrity"
:
"sha512-E1t7K0efyKXZDoZg1LzCOLxgolxV58HCkaEkEvIYQx12ht2pa8hoBo+4OB3qh7e+QiBlp1SRf+voWUZFxyhyqg=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"BlueOak-1.0.0"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"engines"
:
{
"node"
:
">=
2
0"
"node"
:
">=
1
0"
}
}
},
},
"node_modules/@npmcli/git/node_modules/lru-cache"
:
{
"node_modules/@napi-rs/nice-linux-arm64-gnu"
:
{
"version"
:
"11.3.2"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.1.1.tgz"
,
"integrity"
:
"sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ=="
,
"integrity"
:
"sha512-CIKLA12DTIZlmTaaKhQP88R3Xao+gyJxNWEn04wZwC2wmRapNnxCUZkVwggInMJvtVElA+D4ZzOU5sX4jV+SmQ=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"BlueOak-1.0.0"
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"engines"
:
{
"node"
:
"
20 || >=22
"
"node"
:
"
>= 10
"
}
}
},
},
"node_modules/@npmcli/git/node_modules/which"
:
{
"node_modules/@napi-rs/nice-linux-arm64-musl"
:
{
"version"
:
"6.0.1"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/which/-/which-6.0.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.1.1.tgz"
,
"integrity"
:
"sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="
,
"integrity"
:
"sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
,
"libc"
:
[
"dependencies"
:
{
"musl"
"isexe"
:
"^4.0.0"
],
},
"license"
:
"MIT"
,
"bin"
:
{
"optional"
:
true
,
"node-which"
:
"bin/which.js"
"os"
:
[
},
"linux"
],
"engines"
:
{
"engines"
:
{
"node"
:
"
^20.17.0 || >=22.9.
0"
"node"
:
"
>= 1
0"
}
}
},
},
"node_modules/@npmcli/installed-package-contents"
:
{
"node_modules/@napi-rs/nice-linux-ppc64-gnu"
:
{
"version"
:
"4.0.0"
,
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.1.1.tgz"
,
"integrity"
:
"sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA=="
,
"integrity"
:
"sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg=="
,
"cpu"
:
[
"ppc64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10"
}
},
"node_modules/@napi-rs/nice-linux-riscv64-gnu"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.1.1.tgz"
,
"integrity"
:
"sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw=="
,
"cpu"
:
[
"riscv64"
],
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10"
}
},
"node_modules/@napi-rs/nice-linux-s390x-gnu"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.1.1.tgz"
,
"integrity"
:
"sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ=="
,
"cpu"
:
[
"s390x"
],
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10"
}
},
"node_modules/@napi-rs/nice-linux-x64-gnu"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-linux-x64-gnu/-/nice-linux-x64-gnu-1.1.1.tgz"
,
"integrity"
:
"sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10"
}
},
"node_modules/@napi-rs/nice-linux-x64-musl"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-linux-x64-musl/-/nice-linux-x64-musl-1.1.1.tgz"
,
"integrity"
:
"sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10"
}
},
"node_modules/@napi-rs/nice-openharmony-arm64"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-openharmony-arm64/-/nice-openharmony-arm64-1.1.1.tgz"
,
"integrity"
:
"sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"openharmony"
],
"engines"
:
{
"node"
:
">= 10"
}
},
"node_modules/@napi-rs/nice-win32-arm64-msvc"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.1.1.tgz"
,
"integrity"
:
"sha512-uoTb4eAvM5B2aj/z8j+Nv8OttPf2m+HVx3UjA5jcFxASvNhQriyCQF1OB1lHL43ZhW+VwZlgvjmP5qF3+59atA=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
">= 10"
}
},
"node_modules/@napi-rs/nice-win32-ia32-msvc"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.1.1.tgz"
,
"integrity"
:
"sha512-CNQqlQT9MwuCsg1Vd/oKXiuH+TcsSPJmlAFc5frFyX/KkOh0UpBLEj7aoY656d5UKZQMQFP7vJNa1DNUNORvug=="
,
"cpu"
:
[
"ia32"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
">= 10"
}
},
"node_modules/@napi-rs/nice-win32-x64-msvc"
:
{
"version"
:
"1.1.1"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.1.1.tgz"
,
"integrity"
:
"sha512-vB+4G/jBQCAh0jelMTY3+kgFy00Hlx2f2/1zjMoH821IbplbWZOkLiTYXQkygNTzQJTq5cvwBDgn2ppHD+bglQ=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
">= 10"
}
},
"node_modules/@napi-rs/wasm-runtime"
:
{
"version"
:
"1.1.4"
,
"resolved"
:
"https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz"
,
"integrity"
:
"sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"dependencies"
:
{
"@tybys/wasm-util"
:
"^0.10.1"
},
"funding"
:
{
"type"
:
"github"
,
"url"
:
"https://github.com/sponsors/Brooooooklyn"
},
"peerDependencies"
:
{
"@emnapi/core"
:
"^1.7.1"
,
"@emnapi/runtime"
:
"^1.7.1"
}
},
"node_modules/@npmcli/agent"
:
{
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/agent/-/agent-4.0.0.tgz"
,
"integrity"
:
"sha512-kAQTcEN9E8ERLVg5AsGwLNoFb+oEG6engbqAU2P43gD4JEIkNGMHdVQ096FsOAAYpZPB0RSt0zgInKIAS1l5QA=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"agent-base"
:
"^7.1.0"
,
"http-proxy-agent"
:
"^7.0.0"
,
"https-proxy-agent"
:
"^7.0.1"
,
"lru-cache"
:
"^11.2.1"
,
"socks-proxy-agent"
:
"^8.0.3"
},
"engines"
:
{
"node"
:
"^20.17.0 || >=22.9.0"
}
},
"node_modules/@npmcli/agent/node_modules/lru-cache"
:
{
"version"
:
"11.3.2"
,
"resolved"
:
"https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz"
,
"integrity"
:
"sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ=="
,
"dev"
:
true
,
"license"
:
"BlueOak-1.0.0"
,
"engines"
:
{
"node"
:
"20 || >=22"
}
},
"node_modules/@npmcli/fs"
:
{
"version"
:
"5.0.0"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/fs/-/fs-5.0.0.tgz"
,
"integrity"
:
"sha512-7OsC1gNORBEawOa5+j2pXN9vsicaIOH5cPXxoR6fJOmH6/EXpJB2CajXOu1fPRFun2m1lktEFX11+P89hqO/og=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"semver"
:
"^7.3.5"
},
"engines"
:
{
"node"
:
"^20.17.0 || >=22.9.0"
}
},
"node_modules/@npmcli/git"
:
{
"version"
:
"7.0.2"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/git/-/git-7.0.2.tgz"
,
"integrity"
:
"sha512-oeolHDjExNAJAnlYP2qzNjMX/Xi9bmu78C9dIGr4xjobrSKbuMYCph8lTzn4vnW3NjIqVmw/f8BCfouqyJXlRg=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"@gar/promise-retry"
:
"^1.0.0"
,
"@npmcli/promise-spawn"
:
"^9.0.0"
,
"ini"
:
"^6.0.0"
,
"lru-cache"
:
"^11.2.1"
,
"npm-pick-manifest"
:
"^11.0.1"
,
"proc-log"
:
"^6.0.0"
,
"semver"
:
"^7.3.5"
,
"which"
:
"^6.0.0"
},
"engines"
:
{
"node"
:
"^20.17.0 || >=22.9.0"
}
},
"node_modules/@npmcli/git/node_modules/isexe"
:
{
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz"
,
"integrity"
:
"sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="
,
"dev"
:
true
,
"license"
:
"BlueOak-1.0.0"
,
"engines"
:
{
"node"
:
">=20"
}
},
"node_modules/@npmcli/git/node_modules/lru-cache"
:
{
"version"
:
"11.3.2"
,
"resolved"
:
"https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.2.tgz"
,
"integrity"
:
"sha512-wgWa6FWQ3QRRJbIjbsldRJZxdxYngT/dO0I5Ynmlnin8qy7tC6xYzbcJjtN4wHLXtkbVwHzk0C+OejVw1XM+DQ=="
,
"dev"
:
true
,
"license"
:
"BlueOak-1.0.0"
,
"engines"
:
{
"node"
:
"20 || >=22"
}
},
"node_modules/@npmcli/git/node_modules/which"
:
{
"version"
:
"6.0.1"
,
"resolved"
:
"https://registry.npmjs.org/which/-/which-6.0.1.tgz"
,
"integrity"
:
"sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"isexe"
:
"^4.0.0"
},
"bin"
:
{
"node-which"
:
"bin/which.js"
},
"engines"
:
{
"node"
:
"^20.17.0 || >=22.9.0"
}
},
"node_modules/@npmcli/installed-package-contents"
:
{
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/installed-package-contents/-/installed-package-contents-4.0.0.tgz"
,
"integrity"
:
"sha512-yNyAdkBxB72gtZ4GrwXCM0ZUedo9nIbOMKfGjt6Cu6DXf0p8y1PViZAKDC8q8kv/fufx0WTjRBdSlyrvnP7hmA=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"dependencies"
:
{
"npm-bundled"
:
"^5.0.0"
,
"npm-bundled"
:
"^5.0.0"
,
"npm-normalize-package-bin"
:
"^5.0.0"
"npm-normalize-package-bin"
:
"^5.0.0"
...
@@ -1693,153 +2584,1000 @@
...
@@ -1693,153 +2584,1000 @@
"resolved"
:
"https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/isexe/-/isexe-4.0.0.tgz"
,
"integrity"
:
"sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="
,
"integrity"
:
"sha512-FFUtZMpoZ8RqHS3XeXEmHWLA4thH+ZxCv2lOiPIn1Xc7CxrqhWzNSDzD+/chS/zbYezmiwWLdQC09JdQKmthOw=="
,
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"BlueOak-1.0.0"
,
"license"
:
"BlueOak-1.0.0"
,
"engines"
:
{
"engines"
:
{
"node"
:
">=20"
"node"
:
">=20"
}
}
},
"node_modules/@npmcli/promise-spawn/node_modules/which"
:
{
"version"
:
"6.0.1"
,
"resolved"
:
"https://registry.npmjs.org/which/-/which-6.0.1.tgz"
,
"integrity"
:
"sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"isexe"
:
"^4.0.0"
},
"bin"
:
{
"node-which"
:
"bin/which.js"
},
"engines"
:
{
"node"
:
"^20.17.0 || >=22.9.0"
}
},
"node_modules/@npmcli/redact"
:
{
"version"
:
"4.0.0"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz"
,
"integrity"
:
"sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"engines"
:
{
"node"
:
"^20.17.0 || >=22.9.0"
}
},
"node_modules/@npmcli/run-script"
:
{
"version"
:
"10.0.4"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz"
,
"integrity"
:
"sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="
,
"dev"
:
true
,
"license"
:
"ISC"
,
"dependencies"
:
{
"@npmcli/node-gyp"
:
"^5.0.0"
,
"@npmcli/package-json"
:
"^7.0.0"
,
"@npmcli/promise-spawn"
:
"^9.0.0"
,
"node-gyp"
:
"^12.1.0"
,
"proc-log"
:
"^6.0.0"
},
"engines"
:
{
"node"
:
"^20.17.0 || >=22.9.0"
}
},
"node_modules/@oxc-project/types"
:
{
"version"
:
"0.113.0"
,
"resolved"
:
"https://registry.npmjs.org/@oxc-project/types/-/types-0.113.0.tgz"
,
"integrity"
:
"sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"funding"
:
{
"url"
:
"https://github.com/sponsors/Boshen"
}
},
"node_modules/@parcel/watcher"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz"
,
"integrity"
:
"sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="
,
"dev"
:
true
,
"hasInstallScript"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"dependencies"
:
{
"detect-libc"
:
"^2.0.3"
,
"is-glob"
:
"^4.0.3"
,
"node-addon-api"
:
"^7.0.0"
,
"picomatch"
:
"^4.0.3"
},
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
},
"optionalDependencies"
:
{
"@parcel/watcher-android-arm64"
:
"2.5.6"
,
"@parcel/watcher-darwin-arm64"
:
"2.5.6"
,
"@parcel/watcher-darwin-x64"
:
"2.5.6"
,
"@parcel/watcher-freebsd-x64"
:
"2.5.6"
,
"@parcel/watcher-linux-arm-glibc"
:
"2.5.6"
,
"@parcel/watcher-linux-arm-musl"
:
"2.5.6"
,
"@parcel/watcher-linux-arm64-glibc"
:
"2.5.6"
,
"@parcel/watcher-linux-arm64-musl"
:
"2.5.6"
,
"@parcel/watcher-linux-x64-glibc"
:
"2.5.6"
,
"@parcel/watcher-linux-x64-musl"
:
"2.5.6"
,
"@parcel/watcher-win32-arm64"
:
"2.5.6"
,
"@parcel/watcher-win32-ia32"
:
"2.5.6"
,
"@parcel/watcher-win32-x64"
:
"2.5.6"
}
},
"node_modules/@parcel/watcher-android-arm64"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.6.tgz"
,
"integrity"
:
"sha512-YQxSS34tPF/6ZG7r/Ih9xy+kP/WwediEUsqmtf0cuCV5TPPKw/PQHRhueUo6JdeFJaqV3pyjm0GdYjZotbRt/A=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"android"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-arm64"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.6.tgz"
,
"integrity"
:
"sha512-Z2ZdrnwyXvvvdtRHLmM4knydIdU9adO3D4n/0cVipF3rRiwP+3/sfzpAwA/qKFL6i1ModaabkU7IbpeMBgiVEA=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-darwin-x64"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.6.tgz"
,
"integrity"
:
"sha512-HgvOf3W9dhithcwOWX9uDZyn1lW9R+7tPZ4sug+NGrGIo4Rk1hAXLEbcH1TQSqxts0NYXXlOWqVpvS1SFS4fRg=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-freebsd-x64"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.6.tgz"
,
"integrity"
:
"sha512-vJVi8yd/qzJxEKHkeemh7w3YAn6RJCtYlE4HPMoVnCpIXEzSrxErBW5SJBgKLbXU3WdIpkjBTeUNtyBVn8TRng=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"freebsd"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-glibc"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.6.tgz"
,
"integrity"
:
"sha512-9JiYfB6h6BgV50CCfasfLf/uvOcJskMSwcdH1PHH9rvS1IrNy8zad6IUVPVUfmXr+u+Km9IxcfMLzgdOudz9EQ=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.6.tgz"
,
"integrity"
:
"sha512-Ve3gUCG57nuUUSyjBq/MAM0CzArtuIOxsBdQ+ftz6ho8n7s1i9E1Nmk/xmP323r2YL0SONs1EuwqBp2u1k5fxg=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-glibc"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.6.tgz"
,
"integrity"
:
"sha512-f2g/DT3NhGPdBmMWYoxixqYr3v/UXcmLOYy16Bx0TM20Tchduwr4EaCbmxh1321TABqPGDpS8D/ggOTaljijOA=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm64-musl"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.6.tgz"
,
"integrity"
:
"sha512-qb6naMDGlbCwdhLj6hgoVKJl2odL34z2sqkC7Z6kzir8b5W65WYDpLB6R06KabvZdgoHI/zxke4b3zR0wAbDTA=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-glibc"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.6.tgz"
,
"integrity"
:
"sha512-kbT5wvNQlx7NaGjzPFu8nVIW1rWqV780O7ZtkjuWaPUgpv2NMFpjYERVi0UYj1msZNyCzGlaCWEtzc+exjMGbQ=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-x64-musl"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.6.tgz"
,
"integrity"
:
"sha512-1JRFeC+h7RdXwldHzTsmdtYR/Ku8SylLgTU/reMuqdVD7CtLwf0VR1FqeprZ0eHQkO0vqsbvFLXUmYm/uNKJBg=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-arm64"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.6.tgz"
,
"integrity"
:
"sha512-3ukyebjc6eGlw9yRt678DxVF7rjXatWiHvTXqphZLvo7aC5NdEgFufVwjFfY51ijYEWpXbqF5jtrK275z52D4Q=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-ia32"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.6.tgz"
,
"integrity"
:
"sha512-k35yLp1ZMwwee3Ez/pxBi5cf4AoBKYXj00CZ80jUz5h8prpiaQsiRPKQMxoLstNuqe2vR4RNPEAEcjEFzhEz/g=="
,
"cpu"
:
[
"ia32"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-win32-x64"
:
{
"version"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6.tgz"
,
"integrity"
:
"sha512-hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
">= 10.0.0"
},
"funding"
:
{
"type"
:
"opencollective"
,
"url"
:
"https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher/node_modules/node-addon-api"
:
{
"version"
:
"7.1.1"
,
"resolved"
:
"https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz"
,
"integrity"
:
"sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
},
"node_modules/@rolldown/binding-android-arm64"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-vRq9f4NzvbdZavhQbjkJBx7rRebDKYR9zHfO/Wg486+I7bSecdUapzCm5cyXoK+LHokTxgSq7A5baAXUZkIz0w=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"android"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-arm64"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-kFgEvkWLqt3YCgKB5re9RlIrx9bRsvyVUnaTakEpOPuLGzLpLapYxE9BufJNvPg8GjT6mB1alN4yN1NjzoeM8Q=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-darwin-x64"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-JXmaOJGsL/+rsmMfutcDjxWM2fTaVgCHGoXS7nE8Z3c9NAYjGqHvXrAhMUZvMpHS/k7Mg+X7n/MVKb7NYWKKww=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-freebsd-x64"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-ep3Catd6sPnHTM0P4hNEvIv5arnDvk01PfyJIJ+J3wVCG1eEaPo09tvFqdtcaTrkwQy0VWR24uz+cb4IsK53Qw=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"freebsd"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm-gnueabihf"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-LwA5ayKIpnsgXJEwWc3h8wPiS33NMIHd9BhsV92T8VetVAbGe2qXlJwNVDGHN5cOQ22R9uYvbrQir2AB+ntT2w=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-gnu"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-AC1WsGdlV1MtGay/OQ4J9T7GRadVnpYRzTcygV1hKnypbYN20Yh4t6O1Sa2qRBMqv1etulUknqXjc3CTIsBu6A=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-arm64-musl"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-lU+6rgXXViO61B4EudxtVMXSOfiZONR29Sys5VGSetUY7X8mg9FCKIIjcPPj8xNDeYzKl+H8F/qSKOBVFJChCQ=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-gnu"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-DZaN1f0PGp/bSvKhtw50pPsnln4T13ycDq1FrDWRiHmWt1JeW+UtYg9touPFf8yt993p8tS2QjybpzKNTxYEwg=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-linux-x64-musl"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-RnGxwZLN7fhMMAItnD6dZ7lvy+TI7ba+2V54UF4dhaWa/p8I/ys1E73KO6HmPmgz92ZkfD8TXS1IMV8+uhbR9g=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-openharmony-arm64"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-6lcI79+X8klGiGd8yHuTgQRjuuJYNggmEml+RsyN596P23l/zf9FVmJ7K0KVKkFAeYEdg0iMUKyIxiV5vebDNQ=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"openharmony"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-wasm32-wasi"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-wz7ohsKCAIWy91blZ/1FlpPdqrsm1xpcEOQVveWoL6+aSPKL4VUcoYmmzuLTssyZxRpEwzuIxL/GDsvpjaBtOw=="
,
"cpu"
:
[
"wasm32"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"dependencies"
:
{
"@napi-rs/wasm-runtime"
:
"^1.1.1"
},
"engines"
:
{
"node"
:
">=14.0.0"
}
},
"node_modules/@rolldown/binding-win32-arm64-msvc"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-cfiMrfuWCIgsFmcVG0IPuO6qTRHvF7NuG3wngX1RZzc6dU8FuBFb+J3MIR5WrdTNozlumfgL4cvz+R4ozBCvsQ=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/binding-win32-x64-msvc"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09g=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
],
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
"node_modules/@rolldown/pluginutils"
:
{
"version"
:
"1.0.0-rc.4"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.4.tgz"
,
"integrity"
:
"sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ=="
,
"dev"
:
true
,
"license"
:
"MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi"
:
{
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz"
,
"integrity"
:
"sha512-d6FinEBLdIiK+1uACUttJKfgZREXrF0Qc2SmLII7W2AD8FfiZ9Wjd+rD/iRuf5s5dWrr1GgwXCvPqOuDquOowA=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"android"
]
},
"node_modules/@rollup/rollup-android-arm64"
:
{
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.1.tgz"
,
"integrity"
:
"sha512-YjG/EwIDvvYI1YvYbHvDz/BYHtkY4ygUIXHnTdLhG+hKIQFBiosfWiACWortsKPKU/+dUwQQCKQM3qrDe8c9BA=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"android"
]
},
"node_modules/@rollup/rollup-darwin-arm64"
:
{
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.1.tgz"
,
"integrity"
:
"sha512-mjCpF7GmkRtSJwon+Rq1N8+pI+8l7w5g9Z3vWj4T7abguC4Czwi3Yu/pFaLvA3TTeMVjnu3ctigusqWUfjZzvw=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
]
},
"node_modules/@rollup/rollup-darwin-x64"
:
{
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.1.tgz"
,
"integrity"
:
"sha512-haZ7hJ1JT4e9hqkoT9R/19XW2QKqjfJVv+i5AGg57S+nLk9lQnJ1F/eZloRO3o9Scy9CM3wQ9l+dkXtcBgN5Ew=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
]
},
"node_modules/@rollup/rollup-freebsd-arm64"
:
{
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.1.tgz"
,
"integrity"
:
"sha512-czw90wpQq3ZsAVBlinZjAYTKduOjTywlG7fEeWKUA7oCmpA8xdTkxZZlwNJKWqILlq0wehoZcJYfBvOyhPTQ6w=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"freebsd"
]
},
"node_modules/@rollup/rollup-freebsd-x64"
:
{
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.1.tgz"
,
"integrity"
:
"sha512-KVB2rqsxTHuBtfOeySEyzEOB7ltlB/ux38iu2rBQzkjbwRVlkhAGIEDiiYnO2kFOkJp+Z7pUXKyrRRFuFUKt+g=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"freebsd"
]
},
},
"node_modules/@npmcli/promise-spawn/node_modules/which"
:
{
"node_modules/@rollup/rollup-linux-arm-gnueabihf"
:
{
"version"
:
"6.0.1"
,
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/which/-/which-6.0.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.1.tgz"
,
"integrity"
:
"sha512-oGLe46MIrCRqX7ytPUf66EAYvdeMIZYn3WaocqqKZAxrBpkqHfL/qvTyJ/bTk5+AqHCjXmrv3CEWgy368zhRUg=="
,
"integrity"
:
"sha512-L+34Qqil+v5uC0zEubW7uByo78WOCIrBvci69E7sFASRl0X7b/MB6Cqd1lky/CtcSVTydWa2WZwFuWexjS5o6g=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
,
"libc"
:
[
"dependencies"
:
{
"glibc"
"isexe"
:
"^4.0.0"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
},
"bin"
:
{
"node_modules/@rollup/rollup-linux-arm-musleabihf"
:
{
"node-which"
:
"bin/which.js"
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.1.tgz"
,
"integrity"
:
"sha512-n83O8rt4v34hgFzlkb1ycniJh7IR5RCIqt6mz1VRJD6pmhRi0CXdmfnLu9dIUS6buzh60IvACM842Ffb3xd6Gg=="
,
"cpu"
:
[
"arm"
],
"dev"
:
true
,
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
},
"engines"
:
{
"node_modules/@rollup/rollup-linux-arm64-gnu"
:
{
"node"
:
"^20.17.0 || >=22.9.0"
"version"
:
"4.60.1"
,
}
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.1.tgz"
,
"integrity"
:
"sha512-Nql7sTeAzhTAja3QXeAI48+/+GjBJ+QmAH13snn0AJSNL50JsDqotyudHyMbO2RbJkskbMbFJfIJKWA6R1LCJQ=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
},
"node_modules/@npmcli/redact"
:
{
"node_modules/@rollup/rollup-linux-arm64-musl"
:
{
"version"
:
"4.0.0"
,
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/redact/-/redact-4.0.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.1.tgz"
,
"integrity"
:
"sha512-gOBg5YHMfZy+TfHArfVogwgfBeQnKbbGo3pSUyK/gSI0AVu+pEiDVcKlQb0D8Mg1LNRZILZ6XG8I5dJ4KuAd9Q=="
,
"integrity"
:
"sha512-+pUymDhd0ys9GcKZPPWlFiZ67sTWV5UU6zOJat02M1+PiuSGDziyRuI/pPue3hoUwm2uGfxdL+trT6Z9rxnlMA=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
,
"libc"
:
[
"engines"
:
{
"musl"
"node"
:
"^20.17.0 || >=22.9.0"
],
}
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
},
"node_modules/@npmcli/run-script"
:
{
"node_modules/@rollup/rollup-linux-loong64-gnu"
:
{
"version"
:
"10.0.4"
,
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@npmcli/run-script/-/run-script-10.0.4.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.1.tgz"
,
"integrity"
:
"sha512-mGUWr1uMnf0le2TwfOZY4SFxZGXGfm4Jtay/nwAa2FLNAKXUoUwaGwBMNH36UHPtinWfTSJ3nqFQr0091CxVGg=="
,
"integrity"
:
"sha512-VSvgvQeIcsEvY4bKDHEDWcpW4Yw7BtlKG1GUT4FzBUlEKQK0rWHYBqQt6Fm2taXS+1bXvJT6kICu5ZwqKCnvlQ=="
,
"cpu"
:
[
"loong64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"ISC"
,
"libc"
:
[
"dependencies"
:
{
"glibc"
"@npmcli/node-gyp"
:
"^5.0.0"
,
],
"@npmcli/package-json"
:
"^7.0.0"
,
"license"
:
"MIT"
,
"@npmcli/promise-spawn"
:
"^9.0.0"
,
"optional"
:
true
,
"node-gyp"
:
"^12.1.0"
,
"os"
:
[
"proc-log"
:
"^6.0.0"
"linux"
]
},
},
"engines"
:
{
"node_modules/@rollup/rollup-linux-loong64-musl"
:
{
"node"
:
"^20.17.0 || >=22.9.0"
"version"
:
"4.60.1"
,
}
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.1.tgz"
,
"integrity"
:
"sha512-4LqhUomJqwe641gsPp6xLfhqWMbQV04KtPp7/dIp0nzPxAkNY1AbwL5W0MQpcalLYk07vaW9Kp1PBhdpZYYcEw=="
,
"cpu"
:
[
"loong64"
],
"dev"
:
true
,
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
},
"node_modules/@oxc-project/types"
:
{
"node_modules/@rollup/rollup-linux-ppc64-gnu"
:
{
"version"
:
"0.113.0"
,
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@oxc-project/types/-/types-0.113.0.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.1.tgz"
,
"integrity"
:
"sha512-Tp3XmgxwNQ9pEN9vxgJBAqdRamHibi76iowQ38O2I4PMpcvNRQNVsU2n1x1nv9yh0XoTrGFzf7cZSGxmixxrhA=="
,
"integrity"
:
"sha512-tLQQ9aPvkBxOc/EUT6j3pyeMD6Hb8QF2BTBnCQWP/uu1lhc9AIrIjKnLYMEroIz/JvtGYgI9dF3AxHZNaEH0rw=="
,
"cpu"
:
[
"ppc64"
],
"dev"
:
true
,
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"funding"
:
{
"optional"
:
true
,
"url"
:
"https://github.com/sponsors/Boshen"
"os"
:
[
}
"linux"
]
},
},
"node_modules/@parcel/watcher"
:
{
"node_modules/@rollup/rollup-linux-ppc64-musl"
:
{
"version"
:
"2.5.6"
,
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.1.tgz"
,
"integrity"
:
"sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ=="
,
"integrity"
:
"sha512-RMxFhJwc9fSXP6PqmAz4cbv3kAyvD1etJFjTx4ONqFP9DkTkXsAMU4v3Vyc5BgzC+anz7nS/9tp4obsKfqkDHg=="
,
"cpu"
:
[
"ppc64"
],
"dev"
:
true
,
"dev"
:
true
,
"hasInstallScript"
:
true
,
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"optional"
:
true
,
"dependencies"
:
{
"os"
:
[
"detect-libc"
:
"^2.0.3"
,
"linux"
"is-glob"
:
"^4.0.3"
,
]
"node-addon-api"
:
"^7.0.0"
,
"picomatch"
:
"^4.0.3"
},
},
"engines"
:
{
"node_modules/@rollup/rollup-linux-riscv64-gnu"
:
{
"node"
:
">= 10.0.0"
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.1.tgz"
,
"integrity"
:
"sha512-QKgFl+Yc1eEk6MmOBfRHYF6lTxiiiV3/z/BRrbSiW2I7AFTXoBFvdMEyglohPj//2mZS4hDOqeB0H1ACh3sBbg=="
,
"cpu"
:
[
"riscv64"
],
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
},
"funding"
:
{
"node_modules/@rollup/rollup-linux-riscv64-musl"
:
{
"type"
:
"opencollective"
,
"version"
:
"4.60.1"
,
"url"
:
"https://opencollective.com/parcel"
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.1.tgz"
,
"integrity"
:
"sha512-RAjXjP/8c6ZtzatZcA1RaQr6O1TRhzC+adn8YZDnChliZHviqIjmvFwHcxi4JKPSDAt6Uhf/7vqcBzQJy0PDJg=="
,
"cpu"
:
[
"riscv64"
],
"dev"
:
true
,
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
},
"optionalDependencies"
:
{
"node_modules/@rollup/rollup-linux-s390x-gnu"
:
{
"@parcel/watcher-android-arm64"
:
"2.5.6"
,
"version"
:
"4.60.1"
,
"@parcel/watcher-darwin-arm64"
:
"2.5.6"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.1.tgz"
,
"@parcel/watcher-darwin-x64"
:
"2.5.6"
,
"integrity"
:
"sha512-wcuocpaOlaL1COBYiA89O6yfjlp3RwKDeTIA0hM7OpmhR1Bjo9j31G1uQVpDlTvwxGn2nQs65fBFL5UFd76FcQ=="
,
"@parcel/watcher-freebsd-x64"
:
"2.5.6"
,
"cpu"
:
[
"@parcel/watcher-linux-arm-glibc"
:
"2.5.6"
,
"s390x"
"@parcel/watcher-linux-arm-musl"
:
"2.5.6"
,
],
"@parcel/watcher-linux-arm64-glibc"
:
"2.5.6"
,
"dev"
:
true
,
"@parcel/watcher-linux-arm64-musl"
:
"2.5.6"
,
"libc"
:
[
"@parcel/watcher-linux-x64-glibc"
:
"2.5.6"
,
"glibc"
"@parcel/watcher-linux-x64-musl"
:
"2.5.6"
,
],
"@parcel/watcher-win32-arm64"
:
"2.5.6"
,
"license"
:
"MIT"
,
"@parcel/watcher-win32-ia32"
:
"2.5.6"
,
"optional"
:
true
,
"@parcel/watcher-win32-x64"
:
"2.5.6"
"os"
:
[
}
"linux"
]
},
},
"node_modules/@
parcel/watcher-win32-x64
"
:
{
"node_modules/@
rollup/rollup-linux-x64-gnu
"
:
{
"version"
:
"
2.5.6
"
,
"version"
:
"
4.60.1
"
,
"resolved"
:
"https://registry.npmjs.org/@
parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.6
.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@
rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.1
.tgz"
,
"integrity"
:
"sha512-
hbQlYcCq5dlAX9Qx+kFb0FHue6vbjlf0FrNzSKdYK2APUf7tGfGxQCk2ihEREmbR6ZMc0MVAD5RIX/41gpUzTw
=="
,
"integrity"
:
"sha512-
77PpsFQUCOiZR9+LQEFg9GClyfkNXj1MP6wRnzYs0EeWbPcHs02AXu4xuUbM1zhwn3wqaizle3AEYg5aeoohhg
=="
,
"cpu"
:
[
"cpu"
:
[
"x64"
"x64"
],
],
"dev"
:
true
,
"dev"
:
true
,
"libc"
:
[
"glibc"
],
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"optional"
:
true
,
"os"
:
[
"os"
:
[
"win32"
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-musl"
:
{
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.1.tgz"
,
"integrity"
:
"sha512-5cIATbk5vynAjqqmyBjlciMJl1+R/CwX9oLk/EyiFXDWd95KpHdrOJT//rnUl4cUcskrd0jCCw3wpZnhIHdD9w=="
,
"cpu"
:
[
"x64"
],
],
"engines"
:
{
"dev"
:
true
,
"node"
:
">= 10.0.0"
"libc"
:
[
"musl"
],
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"linux"
]
},
},
"funding"
:
{
"node_modules/@rollup/rollup-openbsd-x64"
:
{
"type"
:
"opencollective"
,
"version"
:
"4.60.1"
,
"url"
:
"https://opencollective.com/parcel"
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.1.tgz"
,
}
"integrity"
:
"sha512-cl0w09WsCi17mcmWqqglez9Gk8isgeWvoUZ3WiJFYSR3zjBQc2J5/ihSjpl+VLjPqjQ/1hJRcqBfLjssREQILw=="
,
"cpu"
:
[
"x64"
],
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"openbsd"
]
},
},
"node_modules/@parcel/watcher/node_modules/node-addon-api"
:
{
"node_modules/@rollup/rollup-openharmony-arm64"
:
{
"version"
:
"7.1.1"
,
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.1.tgz"
,
"integrity"
:
"sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ=="
,
"integrity"
:
"sha512-4Cv23ZrONRbNtbZa37mLSueXUCtN7MXccChtKpUnQNgF010rjrjfHx3QxkS2PI7LqGT5xXyYs1a7LbzAwT0iCA=="
,
"cpu"
:
[
"arm64"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
"optional"
:
true
,
"os"
:
[
"openharmony"
]
},
},
"node_modules/@roll
down/binding-win32-x
64-msvc"
:
{
"node_modules/@roll
up/rollup-win32-arm
64-msvc"
:
{
"version"
:
"
1.0.0-rc.4
"
,
"version"
:
"
4.60.1
"
,
"resolved"
:
"https://registry.npmjs.org/@roll
down/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.4
.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@roll
up/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.1
.tgz"
,
"integrity"
:
"sha512-
p6UeR9y7ht82AH57qwGuFYn69S6CZ7LLKdCKy/8T3zS9VTrJei2/CGsTUV45Da4Z9Rbhc7G4gyWQ/Ioamqn09
g=="
,
"integrity"
:
"sha512-
i1okWYkA4FJICtr7KpYzFpRTHgy5jdDbZiWfvny21iIKky5YExiDXP+zbXzm3dUcFpkEeYNHgQ5fuG236JPq0
g=="
,
"cpu"
:
[
"cpu"
:
[
"
x
64"
"
arm
64"
],
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
,
"license"
:
"MIT"
,
"optional"
:
true
,
"optional"
:
true
,
"os"
:
[
"os"
:
[
"win32"
"win32"
],
]
"engines"
:
{
"node"
:
"^20.19.0 || >=22.12.0"
}
},
},
"node_modules/@rolldown/pluginutils"
:
{
"node_modules/@rollup/rollup-win32-ia32-msvc"
:
{
"version"
:
"1.0.0-rc.4"
,
"version"
:
"4.60.1"
,
"resolved"
:
"https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.4.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.1.tgz"
,
"integrity"
:
"sha512-1BrrmTu0TWfOP1riA8uakjFc9bpIUGzVKETsOtzY39pPga8zELGDl8eu1Dx7/gjM5CAz14UknsUMpBO8L+YntQ=="
,
"integrity"
:
"sha512-u09m3CuwLzShA0EYKMNiFgcjjzwqtUMLmuCJLeZWjjOYA3IT2Di09KaxGBTP9xVztWyIWjVdsB2E9goMjZvTQg=="
,
"cpu"
:
[
"ia32"
],
"dev"
:
true
,
"dev"
:
true
,
"license"
:
"MIT"
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"win32"
]
},
},
"node_modules/@rollup/rollup-win32-x64-gnu"
:
{
"node_modules/@rollup/rollup-win32-x64-gnu"
:
{
"version"
:
"4.60.1"
,
"version"
:
"4.60.1"
,
...
@@ -1996,6 +3734,17 @@
...
@@ -1996,6 +3734,17 @@
"node"
:
"^20.17.0 || >=22.9.0"
"node"
:
"^20.17.0 || >=22.9.0"
}
}
},
},
"node_modules/@tybys/wasm-util"
:
{
"version"
:
"0.10.2"
,
"resolved"
:
"https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz"
,
"integrity"
:
"sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg=="
,
"dev"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"dependencies"
:
{
"tslib"
:
"^2.4.0"
}
},
"node_modules/@types/estree"
:
{
"node_modules/@types/estree"
:
{
"version"
:
"1.0.8"
,
"version"
:
"1.0.8"
,
"resolved"
:
"https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
,
"resolved"
:
"https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz"
,
...
@@ -3164,6 +4913,21 @@
...
@@ -3164,6 +4913,21 @@
"node"
:
"^14.17.0 || ^16.13.0 || >=18.0.0"
"node"
:
"^14.17.0 || ^16.13.0 || >=18.0.0"
}
}
},
},
"node_modules/fsevents"
:
{
"version"
:
"2.3.3"
,
"resolved"
:
"https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz"
,
"integrity"
:
"sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="
,
"dev"
:
true
,
"hasInstallScript"
:
true
,
"license"
:
"MIT"
,
"optional"
:
true
,
"os"
:
[
"darwin"
],
"engines"
:
{
"node"
:
"^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind"
:
{
"node_modules/function-bind"
:
{
"version"
:
"1.1.2"
,
"version"
:
"1.1.2"
,
"resolved"
:
"https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
,
"resolved"
:
"https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz"
,
...
...
Frontend/src/app/pages/admin/group-interview/group-interview.css
View file @
954049ba
...
@@ -59,12 +59,15 @@
...
@@ -59,12 +59,15 @@
/* Candidate chips row */
/* Candidate chips row */
.candidate-chips
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
6px
;
margin-bottom
:
12px
;
}
.candidate-chips
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
6px
;
margin-bottom
:
12px
;
}
.candidate-chip
{
.candidate-chip
{
width
:
30px
;
height
:
30px
;
border-radius
:
50%
;
display
:
flex
;
align-items
:
center
;
padding
:
4px
12px
;
border-radius
:
16px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
12px
;
font-weight
:
700
;
color
:
#fff
;
justify-content
:
center
;
font-size
:
12px
;
font-weight
:
600
;
color
:
#fff
!important
;
border
:
2px
solid
var
(
--bg-card
);
background
:
#667eea
;
border
:
2px
solid
var
(
--bg-card
);
background
:
#667eea
;
transition
:
transform
0.15s
;
transition
:
transform
0.15s
;
white-space
:
nowrap
;
}
}
.candidate-chip
:hover
{
transform
:
scale
(
1.15
);
z-index
:
1
;
}
.candidate-chip
:hover
{
transform
:
scale
(
1.15
);
z-index
:
1
;
}
.candidate-chip.clickable-chip
{
cursor
:
pointer
;
}
.candidate-chip.clickable-chip
:hover
{
transform
:
scale
(
1.12
);
box-shadow
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0.18
);
}
.candidate-chip.badge-warning
{
background
:
#f59e0b
;
}
.candidate-chip.badge-warning
{
background
:
#f59e0b
;
}
.candidate-chip.badge-info
{
background
:
#3b82f6
;
}
.candidate-chip.badge-info
{
background
:
#3b82f6
;
}
.candidate-chip.badge-success
{
background
:
#22c55e
;
}
.candidate-chip.badge-success
{
background
:
#22c55e
;
}
...
@@ -73,6 +76,29 @@
...
@@ -73,6 +76,29 @@
.iv-card-bottom
{
display
:
flex
;
justify-content
:
flex-end
;
}
.iv-card-bottom
{
display
:
flex
;
justify-content
:
flex-end
;
}
.status-summary
{
font-size
:
12px
;
color
:
var
(
--text-muted
);
font-style
:
italic
;
}
.status-summary
{
font-size
:
12px
;
color
:
var
(
--text-muted
);
font-style
:
italic
;
}
/* ⚠ badge on chip */
.chip-alert
{
font-size
:
11px
;
margin-right
:
3px
;
}
/* Evaluation pending badge inside candidate row name */
.eval-pending-badge
{
display
:
inline-flex
;
align-items
:
center
;
font-size
:
11px
;
font-weight
:
600
;
padding
:
2px
7px
;
border-radius
:
10px
;
margin-left
:
8px
;
background
:
rgba
(
245
,
158
,
11
,
0.15
);
color
:
#f59e0b
;
}
.eval-pending-badge.warn
{
background
:
rgba
(
102
,
126
,
234
,
0.1
);
color
:
#667eea
;
}
/* Clickable candidate left section (button reset) */
.cr-clickable
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
background
:
none
;
border
:
none
;
cursor
:
pointer
;
text-align
:
left
;
padding
:
6px
10px
;
border-radius
:
10px
;
transition
:
background
0.15s
;
min-width
:
160px
;
flex-shrink
:
0
;
}
.cr-clickable
:hover
{
background
:
rgba
(
102
,
126
,
234
,
0.08
);
}
/* Hint text at end of bottom row */
.cr-open-hint
{
font-size
:
11px
;
color
:
var
(
--text-muted
);
margin-left
:
auto
;
font-style
:
italic
;
}
/* ═══════════════════════════════════════════════════════
/* ═══════════════════════════════════════════════════════
BADGES
BADGES
═══════════════════════════════════════════════════════ */
═══════════════════════════════════════════════════════ */
...
@@ -293,20 +319,26 @@
...
@@ -293,20 +319,26 @@
/* Candidate detail list */
/* Candidate detail list */
.candidate-detail-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.candidate-detail-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.candidate-row
{
.candidate-row
{
display
:
flex
;
align-items
:
flex-start
;
gap
:
16
px
;
padding
:
16px
;
display
:
flex
;
flex-direction
:
column
;
gap
:
12
px
;
padding
:
16px
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
12px
;
background
:
var
(
--bg-hover
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
12px
;
background
:
var
(
--bg-hover
);
flex-wrap
:
wrap
;
transition
:
border-color
0.2s
;
transition
:
border-color
0.2s
;
}
}
.candidate-row
:hover
{
border-color
:
rgba
(
102
,
126
,
234
,
0.35
);
}
.candidate-row
:hover
{
border-color
:
rgba
(
102
,
126
,
234
,
0.35
);
}
.candidate-row-left
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
min-width
:
180px
;
}
/* Top row: avatar+name on left, progress stepper fills the rest */
.candidate-row-top
{
display
:
flex
;
align-items
:
center
;
gap
:
16px
;
}
.candidate-row-left
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
min-width
:
160px
;
flex-shrink
:
0
;
}
.iv-avatar
{
width
:
40px
;
height
:
40px
;
border-radius
:
10px
;
background
:
linear-gradient
(
135deg
,
#667eea
,
#764ba2
);
color
:
#fff
;
font-weight
:
700
;
font-size
:
16px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-shrink
:
0
;
}
.iv-avatar
{
width
:
40px
;
height
:
40px
;
border-radius
:
10px
;
background
:
linear-gradient
(
135deg
,
#667eea
,
#764ba2
);
color
:
#fff
;
font-weight
:
700
;
font-size
:
16px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-shrink
:
0
;
}
.iv-avatar.small
{
width
:
3
2px
;
height
:
32px
;
font-size
:
14px
;
border-radius
:
8
px
;
}
.iv-avatar.small
{
width
:
3
6px
;
height
:
36px
;
font-size
:
15px
;
border-radius
:
9
px
;
}
.cr-name
{
font-size
:
14px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
}
.cr-name
{
font-size
:
14px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
}
.cr-email
{
font-size
:
12px
;
color
:
var
(
--text-muted
);
}
.cr-email
{
font-size
:
12px
;
color
:
var
(
--text-muted
);
}
.candidate-row-mid
{
flex
:
1
;
}
.candidate-row-mid
{
flex
:
1
;
min-width
:
280px
;
}
/* Bottom row: status badge + quiz chips + accept/reject — all left-aligned */
.candidate-row-right
{
display
:
flex
;
flex-direction
:
column
;
gap
:
8px
;
align-items
:
flex-end
;
}
.candidate-row-bottom
{
display
:
flex
;
flex-wrap
:
wrap
;
align-items
:
center
;
gap
:
8px
;
padding-top
:
10px
;
border-top
:
1px
solid
var
(
--border-color
);
}
/* Progress steps (mini) */
/* Progress steps (mini) */
.progress-steps
{
display
:
flex
;
align-items
:
center
;
}
.progress-steps
{
display
:
flex
;
align-items
:
center
;
}
...
@@ -352,6 +384,45 @@
...
@@ -352,6 +384,45 @@
@keyframes
fadeIn
{
from
{
opacity
:
0
;
}
to
{
opacity
:
1
;
}
}
@keyframes
fadeIn
{
from
{
opacity
:
0
;
}
to
{
opacity
:
1
;
}
}
@keyframes
slideUp
{
from
{
transform
:
translateY
(
24px
);
opacity
:
0
;
}
to
{
transform
:
translateY
(
0
);
opacity
:
1
;
}
}
@keyframes
slideUp
{
from
{
transform
:
translateY
(
24px
);
opacity
:
0
;
}
to
{
transform
:
translateY
(
0
);
opacity
:
1
;
}
}
/* ═══════════════════════════════════════════════════════
EVALUATION PANEL (member detail modal)
Mirrors individual-interview styles
═══════════════════════════════════════════════════════ */
.quiz-results-grid
{
display
:
grid
;
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
180px
,
1
fr
));
gap
:
12px
;
}
.quiz-result-card
{
padding
:
14px
16px
;
border-radius
:
12px
;
border
:
1px
solid
var
(
--border-color
);
background
:
var
(
--bg-input
);
}
.qr-title
{
font-size
:
13px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
margin-bottom
:
6px
;
}
.qr-score
{
font-size
:
22px
;
font-weight
:
700
;
color
:
#22c55e
;
}
.qr-pending
{
font-size
:
13px
;
color
:
var
(
--text-muted
);
font-style
:
italic
;
}
.eval-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
margin-bottom
:
16px
;
}
.eval-card
{
padding
:
14px
16px
;
border-radius
:
12px
;
border
:
1px
solid
var
(
--border-color
);
background
:
var
(
--bg-input
);
}
.eval-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
8px
;
}
.eval-evaluator
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
font-size
:
14px
;
}
.eval-role
{
font-size
:
10px
!important
;
padding
:
2px
6px
!important
;
}
.eval-comments
{
font-size
:
13px
;
color
:
var
(
--text-secondary
);
font-style
:
italic
;
margin
:
6px
0
8px
;
}
.eval-date
{
font-size
:
11px
;
color
:
var
(
--text-muted
);
}
.eval-form
{
margin-top
:
16px
;
padding
:
18px
20px
;
border-radius
:
14px
;
border
:
1px
dashed
rgba
(
102
,
126
,
234
,
0.35
);
background
:
rgba
(
102
,
126
,
234
,
0.04
);
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.eval-form
h4
{
font-size
:
14px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
margin
:
0
;
}
.form-textarea
{
resize
:
vertical
;
min-height
:
80px
;
}
.decision-section
{
border-bottom
:
none
;
}
.decision-buttons
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
10px
;
}
.btn-success
{
background
:
#22c55e
;
color
:
#fff
;
border
:
none
;
}
.btn-warning
{
background
:
#f59e0b
;
color
:
#fff
;
border
:
none
;
}
/* ═══════════════════════════════════════════════════════
/* ═══════════════════════════════════════════════════════
RESPONSIVE
RESPONSIVE
═══════════════════════════════════════════════════════ */
═══════════════════════════════════════════════════════ */
...
@@ -365,3 +436,41 @@
...
@@ -365,3 +436,41 @@
.candidate-row-right
{
align-items
:
flex-start
;
}
.candidate-row-right
{
align-items
:
flex-start
;
}
.da-header
,
.da-row
{
grid-template-columns
:
1
fr
;
}
.da-header
,
.da-row
{
grid-template-columns
:
1
fr
;
}
}
}
/* ═══════════════════════════════════════════════════════
PRINT STYLES FOR EVALUATION PDF
═══════════════════════════════════════════════════════ */
.print-container
{
display
:
none
;
}
@media
print
{
body
*
{
visibility
:
hidden
;
}
.print-container
,
.print-container
*
{
visibility
:
visible
;
}
.print-container
{
display
:
block
;
position
:
absolute
;
left
:
0
;
top
:
0
;
width
:
100%
;
padding
:
20px
;
background
:
white
;
color
:
black
;
font-family
:
Arial
,
sans-serif
;
}
.print-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
border-bottom
:
2px
solid
#0078d4
;
padding-bottom
:
10px
;
margin-bottom
:
20px
;
}
}
Frontend/src/app/pages/admin/group-interview/group-interview.html
View file @
954049ba
...
@@ -93,15 +93,22 @@
...
@@ -93,15 +93,22 @@
}
}
</div>
</div>
<!-- Candidate progress chips -->
<!-- Candidate progress chips
— click any chip to open the evaluation panel directly
-->
<div
class=
"candidate-chips"
>
<div
class=
"candidate-chips"
>
@for (m of g.members; track m._id) {
@for (m of g.members; track m._id) {
<span
class=
"candidate-chip"
[ngClass]=
"getStatusClass(m.status)"
[title]=
"m.candidateId?.name + ' — ' + formatStatus(m.status)"
>
<span
class=
"candidate-chip clickable-chip"
[ngClass]=
"getStatusClass(m.status)"
{{ m.candidateId?.name?.charAt(0)?.toUpperCase() }}
[title]=
"m.candidateId?.name + ' — ' + formatStatus(m.status) + (hasPendingEvaluations(m) ? ' (Evaluations pending)' : '')"
(click)=
"openMemberDetail(m._id, $event)"
>
@if (hasPendingEvaluations(m)) {
<span
class=
"chip-alert"
title=
"Evaluations still pending"
>
⚠️
</span>
}
{{ m.candidateId?.name }}
</span>
</span>
}
}
</div>
</div>
<div
class=
"iv-card-bottom"
>
<div
class=
"iv-card-bottom"
>
<span
class=
"status-summary"
>
{{ groupStatusSummary(g.members) }}
</span>
<span
class=
"status-summary"
>
{{ groupStatusSummary(g.members) }}
</span>
</div>
</div>
...
@@ -403,15 +410,25 @@
...
@@ -403,15 +410,25 @@
<div
class=
"candidate-detail-list"
>
<div
class=
"candidate-detail-list"
>
@for (m of selectedGroup().members; track m._id) {
@for (m of selectedGroup().members; track m._id) {
<div
class=
"candidate-row"
>
<div
class=
"candidate-row"
>
<div
class=
"candidate-row-left"
>
<!-- Top row: clickable avatar+name on the left, progress bar on the right -->
<div
class=
"candidate-row-top"
>
<button
class=
"cr-clickable"
(click)=
"openMemberDetail(m._id, $event)"
>
<div
class=
"iv-avatar small"
>
{{ m.candidateId?.name?.charAt(0)?.toUpperCase() }}
</div>
<div
class=
"iv-avatar small"
>
{{ m.candidateId?.name?.charAt(0)?.toUpperCase() }}
</div>
<div>
<div>
<div
class=
"cr-name"
>
{{ m.candidateId?.name }}
</div>
<div
class=
"cr-name"
>
<div
class=
"cr-email"
>
{{ m.candidateId?.email }}
</div>
{{ m.candidateId?.name }}
@if (needsEvaluation(m)) {
<span
class=
"eval-pending-badge"
title=
"Your evaluation is pending"
>
⚠️ Evaluate
</span>
}
@if (hasPendingEvaluations(m)) {
<span
class=
"eval-pending-badge warn"
title=
"Some evaluations are still pending"
>
⏳
</span>
}
</div>
</div>
<div
class=
"cr-email"
>
{{ m.candidateId?.email }}
</div>
</div>
</div>
</button>
<div
class=
"candidate-row-mid"
>
<div
class=
"candidate-row-mid"
>
<!-- Progress steps mini -->
<div
class=
"progress-steps mini"
>
<div
class=
"progress-steps mini"
>
<div
class=
"step"
[class.done]=
"['quiz_phase','coding_phase','evaluation','completed'].includes(m.status)"
>
<div
class=
"step"
[class.done]=
"['quiz_phase','coding_phase','evaluation','completed'].includes(m.status)"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Created
</span>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Created
</span>
...
@@ -434,29 +451,24 @@
...
@@ -434,29 +451,24 @@
</div>
</div>
</div>
</div>
</div>
</div>
<div
class=
"candidate-row-right"
>
</div>
<!-- Bottom row: status badge + quiz scores (no accept/reject here) -->
<div
class=
"candidate-row-bottom"
>
<span
class=
"badge"
[ngClass]=
"getStatusClass(m.status)"
>
{{ formatStatus(m.status) }}
</span>
<span
class=
"badge"
[ngClass]=
"getStatusClass(m.status)"
>
{{ formatStatus(m.status) }}
</span>
@if (m.finalDecision !== 'pending') {
@if (m.finalDecision !== 'pending') {
<span
class=
"badge"
[ngClass]=
"getDecisionClass(m.finalDecision)"
>
{{ formatDecision(m.finalDecision) }}
</span>
<span
class=
"badge"
[ngClass]=
"getDecisionClass(m.finalDecision)"
>
{{ formatDecision(m.finalDecision) }}
</span>
}
}
<!-- Quiz scores -->
@if (m.quizzes?.length > 0) {
@if (m.quizzes?.length > 0) {
<div
class=
"quiz-scores-inline"
>
@for (q of m.quizzes; track q.quizId) {
@for (q of m.quizzes; track q.quizId) {
<span
class=
"quiz-score-chip"
[class.completed]=
"q.completed"
>
<span
class=
"quiz-score-chip"
[class.completed]=
"q.completed"
>
{{ q.title }}: {{ q.completed ? q.score + '/' + q.totalMarks : 'Pending' }}
{{ q.title }}: {{ q.completed ? q.score + '/' + q.totalMarks : 'Pending' }}
</span>
</span>
}
}
</div>
}
<!-- Decision buttons (admin) -->
@if (authService.getUserRole() === 'admin'
&&
m.status !== 'completed') {
<div
class=
"mini-decision-btns"
>
<button
class=
"mini-btn success"
(click)=
"setDecision(m._id, 'accepted'); $event.stopPropagation()"
>
Accept
</button>
<button
class=
"mini-btn danger"
(click)=
"setDecision(m._id, 'rejected'); $event.stopPropagation()"
>
Reject
</button>
</div>
}
}
<span
class=
"cr-open-hint"
>
Click name to evaluate →
</span>
</div>
</div>
</div>
</div>
}
}
</div>
</div>
...
@@ -465,7 +477,7 @@
...
@@ -465,7 +477,7 @@
</div>
</div>
<div
class=
"modal-footer"
>
<div
class=
"modal-footer"
>
@if (authService.getUserRole() === 'admin') {
@if (authService.getUserRole() === 'admin'
|| authService.getUserRole() === 'hr'
) {
<button
class=
"btn btn-danger btn-sm"
(click)=
"deleteGroupInterview(selectedGroup().groupId)"
>
<button
class=
"btn btn-danger btn-sm"
(click)=
"deleteGroupInterview(selectedGroup().groupId)"
>
Delete All Interviews
Delete All Interviews
</button>
</button>
...
@@ -476,3 +488,261 @@
...
@@ -476,3 +488,261 @@
</div>
</div>
</div>
</div>
}
}
<!-- ═══════════════════════════════════════════════════════════
MEMBER DETAIL MODAL — full evaluation panel for one candidate
═══════════════════════════════════════════════════════════ -->
@if (showMemberDetailModal()
&&
selectedMember()) {
<div
class=
"modal-overlay"
(click)=
"closeMemberDetail()"
>
<div
class=
"modal-container modal-xl"
(click)=
"$event.stopPropagation()"
>
<!-- Header -->
<div
class=
"modal-header"
>
<div>
<h2>
{{ selectedMember().candidateId?.name }}
</h2>
<span
class=
"badge"
[ngClass]=
"getStatusClass(selectedMember().status)"
>
{{ formatStatus(selectedMember().status) }}
</span>
@if (selectedMember().finalDecision !== 'pending') {
<span
class=
"badge"
[ngClass]=
"getDecisionClass(selectedMember().finalDecision)"
style=
"margin-left:6px"
>
{{ formatDecision(selectedMember().finalDecision) }}
</span>
}
</div>
<button
class=
"icon-btn"
(click)=
"closeMemberDetail()"
><span
class=
"material-symbols-rounded"
>
close
</span></button>
</div>
<div
class=
"modal-body detail-body"
>
<!-- Candidate Info -->
<div
class=
"detail-section"
>
<h3
class=
"detail-section-title"
>
Candidate Information
</h3>
<div
class=
"detail-grid"
>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Name
</span>
<span
class=
"detail-value"
>
{{ selectedMember().candidateId?.name }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Email
</span>
<span
class=
"detail-value"
>
{{ selectedMember().candidateId?.email }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Position
</span>
<span
class=
"detail-value"
>
{{ selectedMember().position }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Tech Stack
</span>
<span
class=
"detail-value"
>
{{ selectedMember().techStack || '—' }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Date of Interview
</span>
<span
class=
"detail-value"
>
{{ selectedMember().dateOfInterview | date:'mediumDate' }}
</span>
</div>
</div>
</div>
<!-- Quiz Results -->
@if (selectedMember().quizzes?.length > 0) {
<div
class=
"detail-section"
>
<h3
class=
"detail-section-title"
>
Quiz Results
</h3>
<div
class=
"quiz-results-grid"
>
@for (q of selectedMember().quizzes; track q.quizId) {
<div
class=
"quiz-result-card"
>
<div
class=
"qr-title"
>
{{ q.title }}
</div>
@if (q.completed) {
<div
class=
"qr-score"
>
{{ q.score }}/{{ q.totalMarks }} ({{ q.percentage }}%)
</div>
} @else {
<div
class=
"qr-pending"
>
Not Taken
</div>
}
</div>
}
</div>
</div>
}
<!-- Evaluations -->
<div
class=
"detail-section"
>
<div
style=
"display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;"
>
<h3
class=
"detail-section-title"
style=
"margin:0;"
>
Evaluations
</h3>
<div
style=
"display: flex; gap: 8px; align-items: center;"
>
@if (allMemberEvaluationsDone()) {
<span
class=
"badge badge-success"
>
All evaluations done
</span>
<button
class=
"btn btn-primary btn-sm"
(click)=
"downloadEvaluationPdf()"
>
@if (isPdfGenerating()) {
<span
class=
"spinner spinner-sm"
></span>
Generating... } @else {
<span
class=
"material-symbols-rounded"
style=
"font-size: 16px; margin-right: 4px;"
>
download
</span>
Download PDF }
</button>
} @else {
<span
class=
"badge badge-warning"
>
Evaluations pending
</span>
}
</div>
</div>
<!-- Evaluation list -->
@if (selectedMember().evaluations?.length > 0) {
<div
class=
"eval-list"
>
@for (ev of selectedMember().evaluations; track ev._id) {
<div
class=
"eval-card"
>
<div
class=
"eval-header"
>
<div
class=
"eval-evaluator"
>
<strong>
{{ ev.evaluatorId?.name }}
</strong>
<span
class=
"eval-role badge badge-muted"
>
{{ ev.evaluatorRole | uppercase }}
</span>
</div>
<span
class=
"badge"
[ngClass]=
"getDecisionClass(ev.recommendation)"
>
{{ formatDecision(ev.recommendation) }}
</span>
</div>
@if (ev.comments) {
<p
class=
"eval-comments"
>
"{{ ev.comments }}"
</p>
}
<span
class=
"eval-date"
>
{{ ev.date | date:'medium' }}
</span>
</div>
}
</div>
} @else {
<p
class=
"text-muted"
>
No evaluations yet
</p>
}
<!-- Add evaluation form (HR / PM / Interviewer, not if already submitted or completed) -->
@if (!hasMemberEvaluated()
&&
selectedMember().status !== 'completed') {
<div
class=
"eval-form"
>
<h4>
Add Your Evaluation
</h4>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Comments
</label>
<textarea
class=
"form-input form-textarea"
[(ngModel)]=
"memberEvalComment"
placeholder=
"Enter your comments..."
rows=
"3"
></textarea>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Recommendation *
</label>
<select
class=
"form-input"
[(ngModel)]=
"memberEvalRecommendation"
>
<option
value=
""
>
Select recommendation
</option>
<option
value=
"offer"
>
Offer / Hire as Intern
</option>
<option
value=
"on_hold"
>
On Hold
</option>
<option
value=
"rejected"
>
Rejected
</option>
<option
value=
"2nd_round"
>
2nd Round
</option>
</select>
</div>
<button
class=
"btn btn-primary"
(click)=
"submitMemberEvaluation()"
[disabled]=
"!memberEvalRecommendation || isMemberSubmitting()"
>
@if (isMemberSubmitting()) {
<span
class=
"spinner spinner-sm"
></span>
Submitting... }
@else { Submit Evaluation }
</button>
</div>
}
</div>
<!-- Final Decision — Admin and HR, only when all evaluations done -->
@if ((authService.getUserRole() === 'admin' || authService.getUserRole() === 'hr')
&&
allMemberEvaluationsDone()
&&
selectedMember().status !== 'completed') {
<div
class=
"detail-section decision-section"
>
<h3
class=
"detail-section-title"
>
Final Decision
</h3>
<div
class=
"decision-buttons"
>
<button
class=
"btn btn-success"
(click)=
"setMemberDecision('accepted')"
>
<span
class=
"material-symbols-rounded"
>
check_circle
</span>
Accept
</button>
<button
class=
"btn btn-warning"
(click)=
"setMemberDecision('on_hold')"
>
<span
class=
"material-symbols-rounded"
>
pause_circle
</span>
On Hold
</button>
<button
class=
"btn btn-danger"
(click)=
"setMemberDecision('rejected')"
>
<span
class=
"material-symbols-rounded"
>
cancel
</span>
Reject
</button>
<button
class=
"btn btn-outline"
(click)=
"setMemberDecision('2nd_round')"
>
<span
class=
"material-symbols-rounded"
>
replay
</span>
2nd Round
</button>
</div>
</div>
}
</div>
<div
class=
"modal-footer"
>
<button
class=
"btn btn-outline"
(click)=
"closeMemberDetail()"
>
Close
</button>
</div>
</div>
</div>
}
<!-- Print Template for Evaluation PDF -->
@if (selectedMember()) {
<div
class=
"print-container"
>
<div
class=
"print-header"
>
<div
class=
"print-header-left"
>
<h2
style=
"margin: 0; font-size: 20px;"
>
Intern Interview Evaluation Form
</h2>
</div>
<div
class=
"print-header-right"
style=
"text-align: right;"
>
<span
style=
"color: #0078d4; font-weight: bold; font-size: 24px;"
>
IDEAL
</span><br>
<span
style=
"font-size: 10px; color: #555;"
>
TECH LABS
</span>
</div>
</div>
<table
class=
"print-table"
style=
"width: 100%; border-collapse: collapse; margin-bottom: 20px;"
>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold; width: 25%;"
>
Candidate Name:
</td>
<td
style=
"border: 1px solid #000; padding: 8px; width: 25%;"
>
{{ selectedMember().candidateId?.name }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold; width: 25%;"
>
Date of Interview:
</td>
<td
style=
"border: 1px solid #000; padding: 8px; width: 25%;"
>
{{ selectedMember().dateOfInterview | date:'mediumDate' }}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Position:
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().position }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Source
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().source || '—' }}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Tech Stack:
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().techStack || '—' }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Interviewer(s):
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
@if (selectedMember().assignedInterviewers?.length) {
@for (i of selectedMember().assignedInterviewers; track i._id; let last = $last) {
{{ i.name }}{{ !last ? ', ' : '' }}
}
} @else {
{{ selectedMember().interviewerId?.name || '—' }}
}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
General Aptitude Test QP Set
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().quizzes?.[0]?.title || '—' }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
General Aptitude Test Score
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().quizzes?.[0]?.completed ? selectedMember().quizzes[0].score + '/' + selectedMember().quizzes[0].totalMarks : 'Not Taken' }}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Technical MCQ Test QP Set
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().quizzes?.[1]?.title || '—' }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Technical MCQ Test Score
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().quizzes?.[1]?.completed ? selectedMember().quizzes[1].score + '/' + selectedMember().quizzes[1].totalMarks : 'Not Taken' }}
</td>
</tr>
</table>
<!-- Render evaluations -->
@for (ev of selectedMember().evaluations; track ev._id) {
<div
class=
"print-eval-block"
style=
"margin-bottom: 30px; page-break-inside: avoid;"
>
<div
class=
"print-eval-title"
style=
"font-weight: bold; margin-bottom: 10px;"
>
{{ ev.evaluatorRole === 'hr' ? 'HR' : ev.evaluatorRole === 'pm' ? 'Project Manager' : 'Interviewer' }}'s Comments ({{ ev.evaluatorId?.name }}):
</div>
<div
class=
"print-comments"
style=
"min-height: 80px; margin-bottom: 15px;"
>
{{ ev.comments || 'No comments provided.' }}
</div>
<div
class=
"print-recommendation"
style=
"margin-bottom: 20px;"
>
<strong
style=
"margin-right: 15px;"
>
Recommendation:
</strong>
<span
style=
"margin-right: 15px;"
><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === 'offer' ? '☑' : '☐' }}
</span>
Offer/Hire as Intern
</span>
<span
style=
"margin-right: 15px;"
><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === 'on_hold' ? '☑' : '☐' }}
</span>
On Hold
</span>
<span
style=
"margin-right: 15px;"
><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === 'rejected' ? '☑' : '☐' }}
</span>
Rejected
</span>
<span><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === '2nd_round' ? '☑' : '☐' }}
</span>
2nd Round
</span>
</div>
<div
class=
"print-signature-row"
style=
"display: flex; justify-content: space-between; align-items: flex-end;"
>
<div
class=
"print-signature"
style=
"display: flex; align-items: flex-end;"
>
<strong>
Evaluator's Signature:
</strong>
@if (ev.evaluatorId?.signature) {
<img
[src]=
"'http://localhost:5000' + ev.evaluatorId.signature"
style=
"max-height: 40px; margin-left: 10px;"
>
} @else {
<span
style=
"border-bottom: 1px solid #000; display: inline-block; width: 150px; margin-left: 10px;"
></span>
}
</div>
<div
class=
"print-date"
style=
"display: flex; align-items: flex-end;"
>
<strong>
Date:
</strong>
<span
style=
"border-bottom: 1px solid #000; display: inline-block; width: 120px; margin-left: 10px; text-align: center;"
>
{{ ev.date | date:'shortDate' }}
</span>
</div>
</div>
</div>
}
</div>
}
Frontend/src/app/pages/admin/group-interview/group-interview.ts
View file @
954049ba
...
@@ -74,11 +74,16 @@ export class GroupInterviewComponent implements OnInit {
...
@@ -74,11 +74,16 @@ export class GroupInterviewComponent implements OnInit {
pendingSetsCount
=
2
;
pendingSetsCount
=
2
;
quizSets
:
QuizSet
[]
=
[];
quizSets
:
QuizSet
[]
=
[];
// ──
Detail modal state ───────
─────────────────────────────
// ──
Group detail modal state
─────────────────────────────
showDetailModal
=
signal
(
false
);
showDetailModal
=
signal
(
false
);
selectedGroup
=
signal
<
any
>
(
null
);
selectedGroup
=
signal
<
any
>
(
null
);
evalComment
=
signal
(
''
);
evalRecommendation
=
signal
(
''
);
// ── Per-candidate (member) detail modal ──────────────────
showMemberDetailModal
=
signal
(
false
);
selectedMember
=
signal
<
any
>
(
null
);
// full interview doc
memberEvalComment
=
''
;
memberEvalRecommendation
=
''
;
isMemberSubmitting
=
signal
(
false
);
constructor
(
private
quizService
:
QuizService
,
public
authService
:
AuthService
)
{}
constructor
(
private
quizService
:
QuizService
,
public
authService
:
AuthService
)
{}
...
@@ -244,7 +249,7 @@ export class GroupInterviewComponent implements OnInit {
...
@@ -244,7 +249,7 @@ export class GroupInterviewComponent implements OnInit {
});
});
}
}
// ──
Detail modal ──────
────────────────────────────────────
// ──
Group detail modal
────────────────────────────────────
openDetail
(
group
:
any
):
void
{
openDetail
(
group
:
any
):
void
{
this
.
selectedGroup
.
set
(
group
);
this
.
selectedGroup
.
set
(
group
);
this
.
showDetailModal
.
set
(
true
);
this
.
showDetailModal
.
set
(
true
);
...
@@ -255,6 +260,108 @@ export class GroupInterviewComponent implements OnInit {
...
@@ -255,6 +260,108 @@ export class GroupInterviewComponent implements OnInit {
this
.
selectedGroup
.
set
(
null
);
this
.
selectedGroup
.
set
(
null
);
}
}
// ── Member (candidate) detail modal ──────────────────────
openMemberDetail
(
interviewId
:
string
,
event
:
Event
):
void
{
event
.
stopPropagation
();
this
.
quizService
.
getInterviewById
(
interviewId
).
subscribe
({
next
:
res
=>
{
this
.
selectedMember
.
set
(
res
.
interview
);
this
.
memberEvalComment
=
''
;
this
.
memberEvalRecommendation
=
''
;
this
.
showMemberDetailModal
.
set
(
true
);
}
});
}
closeMemberDetail
():
void
{
this
.
showMemberDetailModal
.
set
(
false
);
this
.
selectedMember
.
set
(
null
);
}
submitMemberEvaluation
():
void
{
const
m
=
this
.
selectedMember
();
if
(
!
m
||
!
this
.
memberEvalRecommendation
)
return
;
this
.
isMemberSubmitting
.
set
(
true
);
this
.
quizService
.
submitEvaluation
(
m
.
_id
,
{
comments
:
this
.
memberEvalComment
,
recommendation
:
this
.
memberEvalRecommendation
}).
subscribe
({
next
:
res
=>
{
this
.
selectedMember
.
set
(
res
.
interview
);
this
.
memberEvalComment
=
''
;
this
.
memberEvalRecommendation
=
''
;
this
.
isMemberSubmitting
.
set
(
false
);
// refresh the group list so chips update
this
.
loadInterviews
();
},
error
:
()
=>
this
.
isMemberSubmitting
.
set
(
false
)
});
}
setMemberDecision
(
decision
:
string
):
void
{
const
m
=
this
.
selectedMember
();
if
(
!
m
)
return
;
this
.
quizService
.
setInterviewDecision
(
m
.
_id
,
decision
).
subscribe
({
next
:
res
=>
{
this
.
selectedMember
.
set
(
res
.
interview
);
this
.
loadInterviews
();
this
.
loadStats
();
}
});
}
hasMemberEvaluated
():
boolean
{
const
m
=
this
.
selectedMember
();
if
(
!
m
)
return
false
;
const
userId
=
this
.
authService
.
currentUser
()?.
id
;
return
m
.
evaluations
?.
some
((
e
:
any
)
=>
e
.
evaluatorId
?.
_id
===
userId
||
e
.
evaluatorId
===
userId
);
}
allMemberEvaluationsDone
():
boolean
{
const
m
=
this
.
selectedMember
();
if
(
!
m
)
return
false
;
const
total
=
(
m
.
assignedInterviewers
?.
length
||
0
)
+
(
m
.
assignedHRs
?.
length
||
0
)
+
(
m
.
assignedPMs
?.
length
||
0
);
if
(
total
===
0
)
return
false
;
return
(
m
.
evaluations
?.
length
||
0
)
>=
total
;
}
/** True when THIS user still needs to evaluate this member */
needsEvaluation
(
m
:
any
):
boolean
{
if
(
m
.
status
===
'
completed
'
)
return
false
;
const
userId
=
this
.
authService
.
currentUser
()?.
id
;
const
role
=
this
.
authService
.
getUserRole
();
if
(
role
===
'
admin
'
)
return
false
;
// admin gives final decision, not evaluation
const
evaluated
=
m
.
evaluations
?.
some
(
(
e
:
any
)
=>
e
.
evaluatorId
?.
_id
===
userId
||
e
.
evaluatorId
===
userId
);
return
!
evaluated
;
}
/** Whether ANY assigned staff member hasn't evaluated a given candidate interview yet */
hasPendingEvaluations
(
m
:
any
):
boolean
{
if
(
m
.
status
===
'
completed
'
)
return
false
;
const
total
=
(
m
.
assignedInterviewers
?.
length
||
0
)
+
(
m
.
assignedHRs
?.
length
||
0
)
+
(
m
.
assignedPMs
?.
length
||
0
);
return
(
m
.
evaluations
?.
length
||
0
)
<
total
;
}
isPdfGenerating
=
signal
(
false
);
downloadEvaluationPdf
():
void
{
const
m
=
this
.
selectedMember
();
if
(
!
m
)
return
;
this
.
isPdfGenerating
.
set
(
true
);
setTimeout
(()
=>
{
window
.
print
();
this
.
isPdfGenerating
.
set
(
false
);
},
500
);
}
// ── Helpers ───────────────────────────────────────────────
// ── Helpers ───────────────────────────────────────────────
getStatusClass
(
status
:
string
):
string
{
getStatusClass
(
status
:
string
):
string
{
const
map
:
any
=
{
const
map
:
any
=
{
...
...
Frontend/src/app/pages/admin/individual-interview/individual-interview.html
View file @
954049ba
...
@@ -370,8 +370,8 @@
...
@@ -370,8 +370,8 @@
}
}
</div>
</div>
<!-- Final Decision (admin only) -->
<!-- Final Decision (admin
and hr
only) -->
@if (
authService.getUserRole() === 'admin'
&&
selectedInterview().status !== 'completed') {
@if (
(authService.getUserRole() === 'admin' || authService.getUserRole() === 'hr')
&&
selectedInterview().status !== 'completed') {
<div
class=
"detail-section decision-section"
>
<div
class=
"detail-section decision-section"
>
<h3
class=
"detail-section-title"
>
Final Decision
</h3>
<h3
class=
"detail-section-title"
>
Final Decision
</h3>
<div
class=
"decision-buttons"
>
<div
class=
"decision-buttons"
>
...
@@ -393,7 +393,7 @@
...
@@ -393,7 +393,7 @@
</div>
</div>
<div
class=
"modal-footer"
>
<div
class=
"modal-footer"
>
@if (authService.getUserRole() === 'admin') {
@if (authService.getUserRole() === 'admin'
|| authService.getUserRole() === 'hr'
) {
<button
class=
"btn btn-danger btn-sm"
(click)=
"deleteInterview(selectedInterview()._id)"
>
Delete Interview
</button>
<button
class=
"btn btn-danger btn-sm"
(click)=
"deleteInterview(selectedInterview()._id)"
>
Delete Interview
</button>
}
}
<button
class=
"btn btn-outline"
(click)=
"closeDetail()"
>
Close
</button>
<button
class=
"btn btn-outline"
(click)=
"closeDetail()"
>
Close
</button>
...
...
Frontend/src/app/pages/admin/users/users.html
View file @
954049ba
...
@@ -33,8 +33,8 @@
...
@@ -33,8 +33,8 @@
<div
class=
"user-card-info"
>
<div
class=
"user-card-info"
>
<div
class=
"name-row"
>
<div
class=
"name-row"
>
<h4>
{{ user.name }}
</h4>
<h4>
{{ user.name }}
</h4>
<span
class=
"level-badge"
[attr.data-level]=
"user.level || '
beginn
er'"
>
<span
class=
"level-badge"
[attr.data-level]=
"user.level || '
Fresh
er'"
>
{{ (user.level || '
beginn
er') | titlecase }}
{{ (user.level || '
Fresh
er') | titlecase }}
</span>
</span>
</div>
</div>
<p>
{{ user.email }}
</p>
<p>
{{ user.email }}
</p>
...
...
Frontend/src/app/pages/hr/group-interview/group-interview.css
View file @
954049ba
/* ═══════════════════════════════════════════════════════
PAGE LAYOUT
═══════════════════════════════════════════════════════ */
.page-container
{
padding
:
32px
40px
;
}
.content-wrapper
{
max-width
:
1200px
;
margin
:
0
;
}
.page-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
28px
;
}
.page-header
h1
{
font-size
:
24px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
margin
:
0
0
4px
;
}
.page-subtitle
{
font-size
:
14px
;
color
:
var
(
--text-muted
);
margin
:
0
;
}
/* Stats */
.stats-row
{
display
:
flex
;
gap
:
16px
;
margin-bottom
:
24px
;
}
.mini-stat
{
flex
:
1
;
background
:
var
(
--bg-card
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
12px
;
padding
:
16px
20px
;
text-align
:
center
;
}
.mini-stat-value
{
display
:
block
;
font-size
:
28px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
}
.mini-stat-value.orange
{
color
:
#f59e0b
;
}
.mini-stat-value.blue
{
color
:
#3b82f6
;
}
.mini-stat-value.green
{
color
:
#22c55e
;
}
.mini-stat-value.red
{
color
:
#ef4444
;
}
.mini-stat-label
{
font-size
:
12px
;
color
:
var
(
--text-muted
);
font-weight
:
500
;
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
}
/* Filter */
.filter-bar
{
margin-bottom
:
20px
;
}
.filter-select
{
padding
:
8px
14px
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
8px
;
background
:
var
(
--bg-input
);
color
:
var
(
--text-primary
);
font-size
:
14px
;
font-family
:
inherit
;
}
/* ═══════════════════════════════════════════════════════
INTERVIEW CARDS
═══════════════════════════════════════════════════════ */
.interview-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
16px
;
}
.interview-card
{
cursor
:
pointer
;
transition
:
all
0.2s
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
16px
;
background
:
var
(
--bg-card
);
}
.interview-card
:hover
{
border-color
:
var
(
--accent-primary
);
box-shadow
:
0
4px
20px
rgba
(
102
,
126
,
234
,
0.12
);
transform
:
translateY
(
-1px
);
}
.iv-card-top
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
12px
;
}
.iv-group-info
{
display
:
flex
;
align-items
:
center
;
gap
:
14px
;
}
.iv-group-avatar
{
width
:
48px
;
height
:
48px
;
border-radius
:
14px
;
background
:
linear-gradient
(
135deg
,
#667eea
0%
,
#764ba2
100%
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-shrink
:
0
;
}
.iv-group-avatar
.material-symbols-rounded
{
color
:
#fff
;
font-size
:
26px
;
}
.iv-name
{
font-size
:
16px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
margin
:
0
;
}
.iv-email
{
font-size
:
13px
;
color
:
var
(
--text-muted
);
margin
:
0
;
}
.iv-badges
{
display
:
flex
;
gap
:
8px
;
flex-wrap
:
wrap
;
}
.iv-card-meta
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
16px
;
margin-bottom
:
14px
;
padding-bottom
:
12px
;
border-bottom
:
1px
solid
var
(
--border-color
);
}
.meta-item
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
6px
;
font-size
:
13px
;
color
:
var
(
--text-secondary
);
}
.meta-item
.material-symbols-rounded
{
font-size
:
16px
;
color
:
var
(
--text-muted
);
}
/* Candidate chips row */
.candidate-chips
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
6px
;
margin-bottom
:
12px
;
}
.candidate-chip
{
padding
:
4px
12px
;
border-radius
:
16px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
font-size
:
12px
;
font-weight
:
600
;
color
:
#fff
!important
;
border
:
2px
solid
var
(
--bg-card
);
background
:
#667eea
;
transition
:
transform
0.15s
;
white-space
:
nowrap
;
}
.candidate-chip
:hover
{
transform
:
scale
(
1.15
);
z-index
:
1
;
}
.candidate-chip.clickable-chip
{
cursor
:
pointer
;
}
.candidate-chip.clickable-chip
:hover
{
transform
:
scale
(
1.12
);
box-shadow
:
0
4px
12px
rgba
(
0
,
0
,
0
,
0.18
);
}
.candidate-chip.badge-warning
{
background
:
#f59e0b
;
}
.candidate-chip.badge-info
{
background
:
#3b82f6
;
}
.candidate-chip.badge-success
{
background
:
#22c55e
;
}
.candidate-chip.badge-purple
{
background
:
#a855f7
;
}
.iv-card-bottom
{
display
:
flex
;
justify-content
:
flex-end
;
}
.status-summary
{
font-size
:
12px
;
color
:
var
(
--text-muted
);
font-style
:
italic
;
}
/* ⚠ badge on chip */
.chip-alert
{
font-size
:
11px
;
margin-right
:
3px
;
}
/* Evaluation pending badge inside candidate row name */
.eval-pending-badge
{
display
:
inline-flex
;
align-items
:
center
;
font-size
:
11px
;
font-weight
:
600
;
padding
:
2px
7px
;
border-radius
:
10px
;
margin-left
:
8px
;
background
:
rgba
(
245
,
158
,
11
,
0.15
);
color
:
#f59e0b
;
}
.eval-pending-badge.warn
{
background
:
rgba
(
102
,
126
,
234
,
0.1
);
color
:
#667eea
;
}
/* Clickable candidate left section (button reset) */
.cr-clickable
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
background
:
none
;
border
:
none
;
cursor
:
pointer
;
text-align
:
left
;
padding
:
6px
10px
;
border-radius
:
10px
;
transition
:
background
0.15s
;
min-width
:
160px
;
flex-shrink
:
0
;
}
.cr-clickable
:hover
{
background
:
rgba
(
102
,
126
,
234
,
0.08
);
}
/* Hint text at end of bottom row */
.cr-open-hint
{
font-size
:
11px
;
color
:
var
(
--text-muted
);
margin-left
:
auto
;
font-style
:
italic
;
}
/* ═══════════════════════════════════════════════════════
BADGES
═══════════════════════════════════════════════════════ */
.badge
{
padding
:
4px
10px
;
border-radius
:
6px
;
font-size
:
11px
;
font-weight
:
600
;
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
}
.badge-warning
{
background
:
rgba
(
245
,
158
,
11
,
0.1
);
color
:
#f59e0b
;
}
.badge-info
{
background
:
rgba
(
59
,
130
,
246
,
0.1
);
color
:
#3b82f6
;
}
.badge-purple
{
background
:
rgba
(
168
,
85
,
247
,
0.1
);
color
:
#a855f7
;
}
.badge-success
{
background
:
rgba
(
34
,
197
,
94
,
0.1
);
color
:
#22c55e
;
}
.badge-danger
{
background
:
rgba
(
239
,
68
,
68
,
0.1
);
color
:
#ef4444
;
}
.badge-muted
{
background
:
var
(
--bg-hover
);
color
:
var
(
--text-muted
);
}
.badge-group
{
background
:
linear-gradient
(
135deg
,
rgba
(
102
,
126
,
234
,
.15
),
rgba
(
118
,
75
,
162
,
.15
));
color
:
#667eea
;
}
/* ═══════════════════════════════════════════════════════
EMPTY / LOADING
═══════════════════════════════════════════════════════ */
.loading-center
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
padding
:
80px
;
gap
:
16px
;
color
:
var
(
--text-muted
);
}
.empty-state
{
text-align
:
center
;
padding
:
80px
;
color
:
var
(
--text-muted
);
}
.empty-state
.material-symbols-rounded
{
font-size
:
64px
;
display
:
block
;
margin-bottom
:
16px
;
opacity
:
0.35
;
}
.empty-state
h3
{
color
:
var
(
--text-primary
);
font-size
:
18px
;
margin
:
0
0
8px
;
}
.empty-state
p
{
font-size
:
14px
;
margin
:
0
;
}
/* ═══════════════════════════════════════════════════════
MODAL
═══════════════════════════════════════════════════════ */
.modal-overlay
{
position
:
fixed
;
inset
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.5
);
backdrop-filter
:
blur
(
5px
);
z-index
:
1000
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
animation
:
fadeIn
0.2s
ease-out
;
}
.modal-container
{
background
:
var
(
--bg-card
);
border-radius
:
18px
;
box-shadow
:
0
20px
60px
rgba
(
0
,
0
,
0
,
0.3
);
border
:
1px
solid
var
(
--border-color
);
width
:
92%
;
max-height
:
88vh
;
overflow-y
:
auto
;
animation
:
slideUp
0.3s
cubic-bezier
(
0.16
,
1
,
0.3
,
1
);
}
.modal-xl
{
max-width
:
860px
;
}
.modal-header
{
padding
:
22px
26px
;
border-bottom
:
1px
solid
var
(
--border-color
);
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
position
:
sticky
;
top
:
0
;
background
:
var
(
--bg-card
);
z-index
:
2
;
border-radius
:
18px
18px
0
0
;
}
.modal-header
h2
{
font-size
:
18px
;
font-weight
:
700
;
margin
:
0
;
}
.modal-body
{
padding
:
26px
;
display
:
flex
;
flex-direction
:
column
;
gap
:
8px
;
}
.modal-footer
{
padding
:
16px
26px
;
border-top
:
1px
solid
var
(
--border-color
);
display
:
flex
;
justify-content
:
flex-end
;
gap
:
12px
;
background
:
var
(
--bg-hover
);
position
:
sticky
;
bottom
:
0
;
border-radius
:
0
0
18px
18px
;
}
/* Success banner */
.success-banner
{
margin
:
24px
;
padding
:
20px
24px
;
border-radius
:
14px
;
background
:
linear-gradient
(
135deg
,
rgba
(
34
,
197
,
94
,
.12
),
rgba
(
16
,
185
,
129
,
.08
));
border
:
1px
solid
rgba
(
34
,
197
,
94
,
.3
);
color
:
#22c55e
;
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
font-size
:
15px
;
font-weight
:
500
;
}
.success-banner
.material-symbols-rounded
{
font-size
:
28px
;
}
.success-banner
.btn
{
margin-left
:
auto
;
}
/* ═══════════════════════════════════════════════════════
FORM ELEMENTS
═══════════════════════════════════════════════════════ */
.section-title
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
font-size
:
13px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
text-transform
:
uppercase
;
letter-spacing
:
0.8px
;
padding
:
10px
0
6px
;
border-bottom
:
1px
solid
var
(
--border-color
);
margin-bottom
:
14px
;
margin-top
:
10px
;
}
.section-title
.material-symbols-rounded
{
font-size
:
18px
;
color
:
var
(
--accent-primary
);
}
.form-row
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
;
gap
:
16px
;
margin-bottom
:
14px
;
}
.form-row.three-col
{
grid-template-columns
:
1
fr
1
fr
1
fr
;
}
.form-group
{
display
:
flex
;
flex-direction
:
column
;
gap
:
6px
;
}
.form-label
{
font-size
:
13px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
}
.form-input
{
padding
:
10px
14px
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
8px
;
background
:
var
(
--bg-input
);
color
:
var
(
--text-primary
);
font-size
:
14px
;
font-family
:
inherit
;
transition
:
all
0.2s
;
width
:
100%
;
box-sizing
:
border-box
;
}
.form-input
:focus
{
outline
:
none
;
border-color
:
var
(
--accent-primary
);
box-shadow
:
0
0
0
3px
rgba
(
102
,
126
,
234
,
0.12
);
}
.field-hint
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
4px
;
font-size
:
12px
;
color
:
#22c55e
;
margin-top
:
2px
;
}
.field-hint
.material-symbols-rounded
{
font-size
:
14px
;
}
/* Checklist box */
.checklist-box
{
border
:
1px
solid
var
(
--border-color
);
border-radius
:
10px
;
padding
:
10px
12px
;
max-height
:
130px
;
overflow-y
:
auto
;
background
:
var
(
--bg-input
);
display
:
flex
;
flex-direction
:
column
;
gap
:
6px
;
}
.check-item
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
font-size
:
13px
;
color
:
var
(
--text-secondary
);
cursor
:
pointer
;
padding
:
2px
0
;
}
.check-item
input
[
type
=
"checkbox"
]
{
accent-color
:
var
(
--accent-primary
);
}
.check-item
:hover
{
color
:
var
(
--text-primary
);
}
.text-muted
{
color
:
var
(
--text-muted
);
font-size
:
13px
;
}
/* ═══════════════════════════════════════════════════════
QUIZ CONFIGURATION
═══════════════════════════════════════════════════════ */
.quiz-empty-hint
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
gap
:
10px
;
padding
:
32px
;
border
:
2px
dashed
var
(
--border-color
);
border-radius
:
14px
;
background
:
var
(
--bg-hover
);
color
:
var
(
--text-muted
);
margin-bottom
:
8px
;
}
.quiz-empty-hint
.material-symbols-rounded
{
font-size
:
40px
;
opacity
:
0.4
;
}
.quiz-empty-hint
p
{
margin
:
0
;
font-size
:
14px
;
}
.quiz-setup-prompt
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
flex-wrap
:
wrap
;
padding
:
14px
18px
;
border-radius
:
10px
;
background
:
rgba
(
102
,
126
,
234
,
0.06
);
border
:
1px
solid
rgba
(
102
,
126
,
234
,
0.2
);
margin-bottom
:
16px
;
}
.quiz-setup-prompt
.material-symbols-rounded
{
font-size
:
20px
;
color
:
var
(
--accent-primary
);
}
.quiz-setup-prompt
span
{
font-size
:
14px
;
font-weight
:
500
;
color
:
var
(
--text-primary
);
}
.sets-count-input
{
width
:
72px
;
padding
:
8px
10px
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
8px
;
background
:
var
(
--bg-input
);
color
:
var
(
--text-primary
);
font-size
:
14px
;
text-align
:
center
;
font-family
:
inherit
;
}
.sets-count-input
:focus
{
outline
:
none
;
border-color
:
var
(
--accent-primary
);
}
/* Quiz Set Block */
.quiz-set-block
{
border
:
1px
solid
var
(
--border-color
);
border-radius
:
14px
;
padding
:
18px
20px
;
margin-bottom
:
14px
;
background
:
var
(
--bg-hover
);
display
:
flex
;
flex-direction
:
column
;
gap
:
10px
;
transition
:
border-color
0.2s
;
}
.quiz-set-block
:hover
{
border-color
:
rgba
(
102
,
126
,
234
,
0.4
);
}
.quiz-set-header
{
display
:
flex
;
align-items
:
center
;
justify-content
:
space-between
;
}
.quiz-set-title
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
}
.set-badge
{
background
:
linear-gradient
(
135deg
,
#667eea
,
#764ba2
);
color
:
#fff
;
font-size
:
12px
;
font-weight
:
700
;
padding
:
4px
12px
;
border-radius
:
20px
;
}
.set-note
{
font-size
:
12px
;
color
:
var
(
--text-muted
);
}
/* Quiz entry row */
.quiz-entry-row
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
}
.quiz-entry-row
.form-input
{
flex
:
1
;
}
/* Assignment mode */
.assignment-mode-row
{
display
:
flex
;
align-items
:
center
;
gap
:
14px
;
flex-wrap
:
wrap
;
padding-top
:
8px
;
border-top
:
1px
solid
var
(
--border-color
);
}
.mode-label
{
font-size
:
13px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
}
.mode-toggle
{
display
:
flex
;
gap
:
8px
;
}
.mode-btn
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
6px
;
padding
:
7px
16px
;
border-radius
:
8px
;
font-size
:
13px
;
font-weight
:
500
;
border
:
1px
solid
var
(
--border-color
);
background
:
var
(
--bg-input
);
color
:
var
(
--text-secondary
);
cursor
:
pointer
;
transition
:
all
0.2s
;
}
.mode-btn
.material-symbols-rounded
{
font-size
:
16px
;
}
.mode-btn
:hover
{
border-color
:
var
(
--accent-primary
);
color
:
var
(
--text-primary
);
}
.mode-btn.active
{
border-color
:
#667eea
;
background
:
rgba
(
102
,
126
,
234
,
0.12
);
color
:
#667eea
;
font-weight
:
700
;
}
/* Direct assignment table */
.direct-assignment-table
{
border
:
1px
solid
var
(
--border-color
);
border-radius
:
10px
;
overflow
:
hidden
;
margin-top
:
4px
;
}
.da-header
{
display
:
grid
;
grid-template-columns
:
1
fr
1.5
fr
;
padding
:
10px
14px
;
background
:
rgba
(
102
,
126
,
234
,
0.08
);
font-size
:
12px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
}
.da-row
{
display
:
grid
;
grid-template-columns
:
1
fr
1.5
fr
;
align-items
:
center
;
padding
:
10px
14px
;
border-top
:
1px
solid
var
(
--border-color
);
gap
:
12px
;
background
:
var
(
--bg-card
);
transition
:
background
0.15s
;
}
.da-row
:hover
{
background
:
var
(
--bg-hover
);
}
.da-candidate
{
display
:
flex
;
align-items
:
center
;
gap
:
10px
;
}
.da-avatar
{
width
:
30px
;
height
:
30px
;
border-radius
:
8px
;
font-size
:
13px
;
font-weight
:
700
;
background
:
linear-gradient
(
135deg
,
#667eea
,
#764ba2
);
color
:
#fff
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-shrink
:
0
;
}
.da-row
.form-input
{
padding
:
7px
10px
;
font-size
:
13px
;
}
/* Ghost btn */
.btn-ghost
{
background
:
none
;
border
:
1px
dashed
var
(
--border-color
);
color
:
var
(
--text-muted
);
padding
:
7px
14px
;
border-radius
:
8px
;
font-size
:
13px
;
cursor
:
pointer
;
display
:
inline-flex
;
align-items
:
center
;
gap
:
6px
;
transition
:
all
0.2s
;
font-family
:
inherit
;
}
.btn-ghost
:hover
{
border-color
:
var
(
--accent-primary
);
color
:
var
(
--accent-primary
);
}
.btn-ghost
.material-symbols-rounded
{
font-size
:
16px
;
}
.icon-btn.danger
{
color
:
#ef4444
;
}
.icon-btn.danger
:hover
{
background
:
rgba
(
239
,
68
,
68
,
0.08
);
}
/* ═══════════════════════════════════════════════════════
DETAIL MODAL
═══════════════════════════════════════════════════════ */
.detail-body
{
display
:
flex
;
flex-direction
:
column
;
gap
:
24px
;
}
.detail-section
{
padding-bottom
:
16px
;
border-bottom
:
1px
solid
var
(
--border-color
);
}
.detail-section
:last-child
{
border-bottom
:
none
;
}
.detail-section-title
{
font-size
:
12px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
margin
:
0
0
16px
;
text-transform
:
uppercase
;
letter-spacing
:
0.8px
;
}
.detail-grid
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
;
gap
:
12px
;
}
.detail-item
{
display
:
flex
;
flex-direction
:
column
;
gap
:
3px
;
}
.detail-label
{
font-size
:
11px
;
color
:
var
(
--text-muted
);
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
}
.detail-value
{
font-size
:
14px
;
color
:
var
(
--text-primary
);
font-weight
:
500
;
}
/* Candidate detail list */
.candidate-detail-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.candidate-row
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
padding
:
16px
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
12px
;
background
:
var
(
--bg-hover
);
transition
:
border-color
0.2s
;
}
.candidate-row
:hover
{
border-color
:
rgba
(
102
,
126
,
234
,
0.35
);
}
/* Top row: avatar+name on left, progress stepper fills the rest */
.candidate-row-top
{
display
:
flex
;
align-items
:
center
;
gap
:
16px
;
}
.candidate-row-left
{
display
:
flex
;
align-items
:
center
;
gap
:
12px
;
min-width
:
160px
;
flex-shrink
:
0
;
}
.iv-avatar
{
width
:
40px
;
height
:
40px
;
border-radius
:
10px
;
background
:
linear-gradient
(
135deg
,
#667eea
,
#764ba2
);
color
:
#fff
;
font-weight
:
700
;
font-size
:
16px
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
flex-shrink
:
0
;
}
.iv-avatar.small
{
width
:
36px
;
height
:
36px
;
font-size
:
15px
;
border-radius
:
9px
;
}
.cr-name
{
font-size
:
14px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
}
.cr-email
{
font-size
:
12px
;
color
:
var
(
--text-muted
);
}
.candidate-row-mid
{
flex
:
1
;
}
/* Bottom row: status badge + quiz chips + accept/reject — all left-aligned */
.candidate-row-bottom
{
display
:
flex
;
flex-wrap
:
wrap
;
align-items
:
center
;
gap
:
8px
;
padding-top
:
10px
;
border-top
:
1px
solid
var
(
--border-color
);
}
/* Progress steps (mini) */
.progress-steps
{
display
:
flex
;
align-items
:
center
;
}
.progress-steps.mini
.step-label
{
font-size
:
10px
;
}
.progress-steps.mini
.step-dot
{
width
:
8px
;
height
:
8px
;
}
.step
{
display
:
flex
;
align-items
:
center
;
gap
:
4px
;
}
.step-dot
{
width
:
10px
;
height
:
10px
;
border-radius
:
50%
;
background
:
var
(
--border-color
);
transition
:
all
0.3s
;
}
.step.done
.step-dot
{
background
:
#22c55e
;
}
.step.active
.step-dot
{
background
:
#667eea
;
box-shadow
:
0
0
0
3px
rgba
(
102
,
126
,
234
,
0.2
);
}
.step-label
{
font-size
:
11px
;
color
:
var
(
--text-muted
);
font-weight
:
500
;
}
.step.done
.step-label
{
color
:
#22c55e
;
}
.step.active
.step-label
{
color
:
#667eea
;
font-weight
:
600
;
}
.step-line
{
flex
:
1
;
height
:
2px
;
background
:
var
(
--border-color
);
margin
:
0
6px
;
transition
:
background
0.3s
;
min-width
:
16px
;
}
.step-line.done
{
background
:
#22c55e
;
}
/* Quiz score chips */
.quiz-scores-inline
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
6px
;
}
.quiz-score-chip
{
font-size
:
11px
;
padding
:
3px
10px
;
border-radius
:
20px
;
background
:
var
(
--bg-card
);
border
:
1px
solid
var
(
--border-color
);
color
:
var
(
--text-muted
);
white-space
:
nowrap
;
}
.quiz-score-chip.completed
{
color
:
#22c55e
;
border-color
:
rgba
(
34
,
197
,
94
,
0.3
);
background
:
rgba
(
34
,
197
,
94
,
0.07
);
}
/* Mini decision buttons */
.mini-decision-btns
{
display
:
flex
;
gap
:
6px
;
}
.mini-btn
{
padding
:
4px
12px
;
border-radius
:
6px
;
font-size
:
12px
;
font-weight
:
600
;
border
:
none
;
cursor
:
pointer
;
transition
:
opacity
0.2s
;
font-family
:
inherit
;
}
.mini-btn.success
{
background
:
rgba
(
34
,
197
,
94
,
0.15
);
color
:
#22c55e
;
}
.mini-btn.success
:hover
{
background
:
#22c55e
;
color
:
#fff
;
}
.mini-btn.danger
{
background
:
rgba
(
239
,
68
,
68
,
0.12
);
color
:
#ef4444
;
}
.mini-btn.danger
:hover
{
background
:
#ef4444
;
color
:
#fff
;
}
/* ═══════════════════════════════════════════════════════
BUTTONS (shared)
═══════════════════════════════════════════════════════ */
.btn-success
{
background
:
#22c55e
;
color
:
#fff
;
}
.btn-danger
{
background
:
#ef4444
;
color
:
#fff
;
}
.btn-sm
{
padding
:
6px
14px
;
font-size
:
13px
;
}
@keyframes
fadeIn
{
from
{
opacity
:
0
;
}
to
{
opacity
:
1
;
}
}
@keyframes
slideUp
{
from
{
transform
:
translateY
(
24px
);
opacity
:
0
;
}
to
{
transform
:
translateY
(
0
);
opacity
:
1
;
}
}
/* ═══════════════════════════════════════════════════════
EVALUATION PANEL (member detail modal)
Mirrors individual-interview styles
═══════════════════════════════════════════════════════ */
.quiz-results-grid
{
display
:
grid
;
grid-template-columns
:
repeat
(
auto-fill
,
minmax
(
180px
,
1
fr
));
gap
:
12px
;
}
.quiz-result-card
{
padding
:
14px
16px
;
border-radius
:
12px
;
border
:
1px
solid
var
(
--border-color
);
background
:
var
(
--bg-input
);
}
.qr-title
{
font-size
:
13px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
margin-bottom
:
6px
;
}
.qr-score
{
font-size
:
22px
;
font-weight
:
700
;
color
:
#22c55e
;
}
.qr-pending
{
font-size
:
13px
;
color
:
var
(
--text-muted
);
font-style
:
italic
;
}
.eval-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
margin-bottom
:
16px
;
}
.eval-card
{
padding
:
14px
16px
;
border-radius
:
12px
;
border
:
1px
solid
var
(
--border-color
);
background
:
var
(
--bg-input
);
}
.eval-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
8px
;
}
.eval-evaluator
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
font-size
:
14px
;
}
.eval-role
{
font-size
:
10px
!important
;
padding
:
2px
6px
!important
;
}
.eval-comments
{
font-size
:
13px
;
color
:
var
(
--text-secondary
);
font-style
:
italic
;
margin
:
6px
0
8px
;
}
.eval-date
{
font-size
:
11px
;
color
:
var
(
--text-muted
);
}
.eval-form
{
margin-top
:
16px
;
padding
:
18px
20px
;
border-radius
:
14px
;
border
:
1px
dashed
rgba
(
102
,
126
,
234
,
0.35
);
background
:
rgba
(
102
,
126
,
234
,
0.04
);
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
}
.eval-form
h4
{
font-size
:
14px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
margin
:
0
;
}
.form-textarea
{
resize
:
vertical
;
min-height
:
80px
;
}
.decision-section
{
border-bottom
:
none
;
}
.decision-buttons
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
10px
;
}
.btn-success
{
background
:
#22c55e
;
color
:
#fff
;
border
:
none
;
}
.btn-warning
{
background
:
#f59e0b
;
color
:
#fff
;
border
:
none
;
}
/* ═══════════════════════════════════════════════════════
RESPONSIVE
═══════════════════════════════════════════════════════ */
@media
(
max-width
:
768px
)
{
.page-container
{
padding
:
20px
;
}
.stats-row
{
flex-wrap
:
wrap
;
}
.mini-stat
{
min-width
:
100px
;
}
.form-row
,
.form-row.three-col
{
grid-template-columns
:
1
fr
;
}
.detail-grid
{
grid-template-columns
:
1
fr
;
}
.candidate-row
{
flex-direction
:
column
;
}
.candidate-row-right
{
align-items
:
flex-start
;
}
.da-header
,
.da-row
{
grid-template-columns
:
1
fr
;
}
}
/* ═══════════════════════════════════════════════════════
PRINT STYLES FOR EVALUATION PDF
═══════════════════════════════════════════════════════ */
.print-container
{
display
:
none
;
}
@media
print
{
body
*
{
visibility
:
hidden
;
}
.print-container
,
.print-container
*
{
visibility
:
visible
;
}
.print-container
{
display
:
block
;
position
:
absolute
;
left
:
0
;
top
:
0
;
width
:
100%
;
padding
:
20px
;
background
:
white
;
color
:
black
;
font-family
:
Arial
,
sans-serif
;
}
.print-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
border-bottom
:
2px
solid
#0078d4
;
padding-bottom
:
10px
;
margin-bottom
:
20px
;
}
}
Frontend/src/app/pages/hr/group-interview/group-interview.html
View file @
954049ba
<p>
group-interview works!
</p>
<div
class=
"page-container animate-fade-in"
>
<div
class=
"content-wrapper"
>
<!-- Page Header -->
<div
class=
"page-header"
>
<div>
<h1>
Group Interviews
</h1>
<p
class=
"page-subtitle"
>
Create and manage batch interview sessions for candidate groups
</p>
</div>
<button
class=
"btn btn-primary"
(click)=
"openCreateModal()"
>
<span
class=
"material-symbols-rounded"
>
group_add
</span>
Create Group Interview
</button>
</div>
<!-- Stats -->
<div
class=
"stats-row"
>
<div
class=
"mini-stat"
>
<span
class=
"mini-stat-value"
>
{{ stats().total }}
</span>
<span
class=
"mini-stat-label"
>
Total
</span>
</div>
<div
class=
"mini-stat"
>
<span
class=
"mini-stat-value orange"
>
{{ stats().pending }}
</span>
<span
class=
"mini-stat-label"
>
In Progress
</span>
</div>
<div
class=
"mini-stat"
>
<span
class=
"mini-stat-value blue"
>
{{ stats().completed }}
</span>
<span
class=
"mini-stat-label"
>
Completed
</span>
</div>
<div
class=
"mini-stat"
>
<span
class=
"mini-stat-value green"
>
{{ stats().accepted }}
</span>
<span
class=
"mini-stat-label"
>
Accepted
</span>
</div>
<div
class=
"mini-stat"
>
<span
class=
"mini-stat-value red"
>
{{ stats().rejected }}
</span>
<span
class=
"mini-stat-label"
>
Rejected
</span>
</div>
</div>
<!-- Filter -->
<div
class=
"filter-bar"
>
<select
class=
"filter-select"
[(ngModel)]=
"filterStatus"
(ngModelChange)=
"onFilterChange()"
>
<option
value=
""
>
All Statuses
</option>
<option
value=
"pending"
>
Pending
</option>
<option
value=
"quiz_phase"
>
Quiz Phase
</option>
<option
value=
"coding_phase"
>
Coding Phase
</option>
<option
value=
"evaluation"
>
Evaluation
</option>
<option
value=
"completed"
>
Completed
</option>
</select>
</div>
<!-- Interview Group List -->
@if (loading()) {
<div
class=
"loading-center"
><div
class=
"spinner spinner-lg"
></div><p>
Loading interviews...
</p></div>
} @else if (groupedList().length === 0) {
<div
class=
"empty-state"
>
<span
class=
"material-symbols-rounded"
>
groups
</span>
<h3>
No group interviews found
</h3>
<p>
Create your first group interview session to get started
</p>
</div>
} @else {
<div
class=
"interview-list"
>
@for (g of groupedList(); track g.groupId) {
<div
class=
"interview-card card card-padding"
(click)=
"openDetail(g)"
>
<div
class=
"iv-card-top"
>
<div
class=
"iv-group-info"
>
<div
class=
"iv-group-avatar"
>
<span
class=
"material-symbols-rounded"
>
groups
</span>
</div>
<div>
<h3
class=
"iv-name"
>
{{ g.groupId }}
</h3>
<p
class=
"iv-email"
>
{{ g.members.length }} candidate{{ g.members.length !== 1 ? 's' : '' }} · {{ g.position }}
</p>
</div>
</div>
<div
class=
"iv-badges"
>
<span
class=
"badge badge-group"
>
Group
</span>
@if (completedCount(g.members) === g.members.length
&&
g.members.length > 0) {
<span
class=
"badge badge-success"
>
All Done
</span>
} @else {
<span
class=
"badge badge-warning"
>
In Progress
</span>
}
</div>
</div>
<div
class=
"iv-card-meta"
>
<span
class=
"meta-item"
><span
class=
"material-symbols-rounded"
>
work
</span>
{{ g.position }}
</span>
@if (g.techStack) {
<span
class=
"meta-item"
><span
class=
"material-symbols-rounded"
>
code
</span>
{{ g.techStack }}
</span>
}
<span
class=
"meta-item"
><span
class=
"material-symbols-rounded"
>
calendar_today
</span>
{{ g.dateOfInterview | date:'mediumDate' }}
</span>
@if (g.assignedInterviewers?.length > 0) {
<span
class=
"meta-item"
><span
class=
"material-symbols-rounded"
>
person
</span>
{{ g.assignedInterviewers[0]?.name }}
</span>
}
</div>
<!-- Candidate progress chips — click any chip to open the evaluation panel directly -->
<div
class=
"candidate-chips"
>
@for (m of g.members; track m._id) {
<span
class=
"candidate-chip clickable-chip"
[ngClass]=
"getStatusClass(m.status)"
[title]=
"m.candidateId?.name + ' — ' + formatStatus(m.status) + (hasPendingEvaluations(m) ? ' (Evaluations pending)' : '')"
(click)=
"openMemberDetail(m._id, $event)"
>
@if (hasPendingEvaluations(m)) {
<span
class=
"chip-alert"
title=
"Evaluations still pending"
>
⚠️
</span>
}
{{ m.candidateId?.name }}
</span>
}
</div>
<div
class=
"iv-card-bottom"
>
<span
class=
"status-summary"
>
{{ groupStatusSummary(g.members) }}
</span>
</div>
</div>
}
</div>
}
</div>
</div>
<!-- ═══════════════════════════════════════════════════════════
CREATE GROUP INTERVIEW MODAL
═══════════════════════════════════════════════════════════ -->
@if (showCreateModal()) {
<div
class=
"modal-overlay"
(click)=
"closeCreateModal()"
>
<div
class=
"modal-container modal-xl"
(click)=
"$event.stopPropagation()"
>
<div
class=
"modal-header"
>
<h2>
Create Group Interview
</h2>
<button
class=
"icon-btn"
(click)=
"closeCreateModal()"
><span
class=
"material-symbols-rounded"
>
close
</span></button>
</div>
@if (successMsg()) {
<div
class=
"success-banner"
>
<span
class=
"material-symbols-rounded"
>
check_circle
</span>
{{ successMsg() }}
<button
class=
"btn btn-outline btn-sm"
(click)=
"closeCreateModal()"
>
Close
</button>
</div>
} @else {
<div
class=
"modal-body"
>
<!-- Staff assignment -->
<div
class=
"section-title"
>
<span
class=
"material-symbols-rounded"
>
badge
</span>
Staff Assignment
</div>
<div
class=
"form-row three-col"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Interviewers
</label>
<div
class=
"checklist-box"
>
@for (i of interviewers(); track i._id) {
<label
class=
"check-item"
>
<input
type=
"checkbox"
[value]=
"i._id"
(change)=
"toggleSelection($event, newGroupInterview.assignedInterviewers)"
>
<span>
{{ i.name }}
</span>
</label>
}
@if (interviewers().length === 0) {
<p
class=
"text-muted"
>
No interviewers found
</p>
}
</div>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
HR Personnel
</label>
<div
class=
"checklist-box"
>
@for (h of hrs(); track h._id) {
<label
class=
"check-item"
>
<input
type=
"checkbox"
[value]=
"h._id"
(change)=
"toggleSelection($event, newGroupInterview.assignedHRs)"
>
<span>
{{ h.name }}
</span>
</label>
}
@if (hrs().length === 0) {
<p
class=
"text-muted"
>
No HR found
</p>
}
</div>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Project Managers
</label>
<div
class=
"checklist-box"
>
@for (p of pms(); track p._id) {
<label
class=
"check-item"
>
<input
type=
"checkbox"
[value]=
"p._id"
(change)=
"toggleSelection($event, newGroupInterview.assignedPMs)"
>
<span>
{{ p.name }}
</span>
</label>
}
@if (pms().length === 0) {
<p
class=
"text-muted"
>
No PMs found
</p>
}
</div>
</div>
</div>
<!-- Group + Position -->
<div
class=
"section-title"
>
<span
class=
"material-symbols-rounded"
>
groups
</span>
Group
&
Position
</div>
<div
class=
"form-row"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Group *
</label>
<select
class=
"form-input"
[(ngModel)]=
"newGroupInterview.groupName"
(ngModelChange)=
"onGroupChange()"
>
<option
value=
""
>
Select group
</option>
@for (g of groups(); track g) {
<option
[value]=
"g"
>
{{ g }}
</option>
}
</select>
@if (groupMembers().length > 0) {
<span
class=
"field-hint"
>
<span
class=
"material-symbols-rounded"
>
person
</span>
{{ groupMembers().length }} candidate{{ groupMembers().length !== 1 ? 's' : '' }} in this group
</span>
}
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Position *
</label>
<input
class=
"form-input"
[(ngModel)]=
"newGroupInterview.position"
placeholder=
"e.g., MEAN Stack Developer"
>
</div>
</div>
<div
class=
"form-row"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Source
</label>
<input
class=
"form-input"
[(ngModel)]=
"newGroupInterview.source"
placeholder=
"e.g., LinkedIn, Campus"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Date of Interview
</label>
<input
class=
"form-input"
type=
"date"
[(ngModel)]=
"newGroupInterview.dateOfInterview"
>
</div>
</div>
<div
class=
"form-row"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Tech Stack
</label>
<input
class=
"form-input"
[(ngModel)]=
"newGroupInterview.techStack"
placeholder=
"e.g., MERN, Java, Python"
>
</div>
</div>
<!-- Quiz Configuration -->
<div
class=
"section-title"
>
<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
</button>
}
</div>
@if (quizSets.length === 0
&&
!showQuizSetup) {
<div
class=
"quiz-empty-hint"
>
<span
class=
"material-symbols-rounded"
>
quiz
</span>
<p>
No quiz sets configured yet.
</p>
<button
class=
"btn btn-primary"
(click)=
"showQuizSetup = true"
>
<span
class=
"material-symbols-rounded"
>
add
</span>
Add Quiz
</button>
</div>
}
@if (showQuizSetup) {
<div
class=
"quiz-setup-prompt"
>
<span
class=
"material-symbols-rounded"
>
help_outline
</span>
<span>
How many quiz sets?
</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>
<button
class=
"btn btn-outline btn-sm"
(click)=
"showQuizSetup = false"
>
Cancel
</button>
</div>
}
<!-- Quiz Sets -->
@for (set of quizSets; track $index; let si = $index) {
<div
class=
"quiz-set-block"
>
<div
class=
"quiz-set-header"
>
<div
class=
"quiz-set-title"
>
<span
class=
"set-badge"
>
Set {{ 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>
}
</div>
<button
class=
"icon-btn danger"
(click)=
"removeQuizSet(si)"
>
<span
class=
"material-symbols-rounded"
>
delete
</span>
</button>
</div>
<!-- Quiz entries in this set -->
@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>
@for (q of getAvailableQuizzes(si, ei); track q._id) {
<option
[value]=
"q._id"
>
{{ q.title }}
</option>
}
</select>
<button
class=
"icon-btn"
(click)=
"removeQuizEntry(si, ei)"
>
<span
class=
"material-symbols-rounded"
>
remove_circle_outline
</span>
</button>
</div>
}
<button
class=
"btn btn-ghost btn-sm"
(click)=
"addQuizToSet(si)"
>
<span
class=
"material-symbols-rounded"
>
add
</span>
Add quiz to Set {{ si + 1 }}
</button>
<!-- Assignment mode — only shown when multiple quizzes -->
@if (isMultiQuizSet(set)) {
<div
class=
"assignment-mode-row"
>
<span
class=
"mode-label"
>
Assignment Mode:
</span>
<div
class=
"mode-toggle"
>
<button
class=
"mode-btn"
[class.active]=
"set.mode === 'random'"
(click)=
"set.mode = 'random'"
>
<span
class=
"material-symbols-rounded"
>
shuffle
</span>
Random
</button>
<button
class=
"mode-btn"
[class.active]=
"set.mode === 'direct'"
(click)=
"set.mode = 'direct'"
>
<span
class=
"material-symbols-rounded"
>
assignment_ind
</span>
Direct
</button>
</div>
</div>
<!-- Direct assignment table -->
@if (set.mode === 'direct') {
@if (groupMembers().length === 0) {
<p
class=
"text-muted"
style=
"font-size:13px; margin-top:8px;"
>
⚠️ Select a group above to assign quizzes to individual candidates.
</p>
} @else {
<div
class=
"direct-assignment-table"
>
<div
class=
"da-header"
>
<span>
Candidate
</span>
<span>
Assigned Quiz (Set {{ si + 1 }})
</span>
</div>
@for (member of groupMembers(); track member._id) {
<div
class=
"da-row"
>
<div
class=
"da-candidate"
>
<div
class=
"da-avatar"
>
{{ member.name.charAt(0).toUpperCase() }}
</div>
<span>
{{ member.name }}
</span>
</div>
<select
class=
"form-input"
[(ngModel)]=
"set.directAssignments[member._id]"
>
<option
value=
""
>
— Select —
</option>
@for (entry of validEntries(set); track entry.quizId) {
<option
[value]=
"entry.quizId"
>
{{ getQuizTitle(entry.quizId) }}
</option>
}
</select>
</div>
}
</div>
}
}
}
</div>
}
</div>
<!-- /modal-body -->
<div
class=
"modal-footer"
>
<button
class=
"btn btn-outline"
(click)=
"closeCreateModal()"
>
Cancel
</button>
<button
class=
"btn btn-primary"
(click)=
"createGroupInterview()"
[disabled]=
"isSubmitting() || !newGroupInterview.groupName || !newGroupInterview.position"
>
@if (isSubmitting()) {
<span
class=
"spinner spinner-sm"
></span>
Creating...
} @else {
<span
class=
"material-symbols-rounded"
>
rocket_launch
</span>
Create Group Interview
}
</button>
</div>
}
</div>
</div>
}
<!-- ═══════════════════════════════════════════════════════════
DETAIL MODAL — All candidates in a group session
═══════════════════════════════════════════════════════════ -->
@if (showDetailModal()
&&
selectedGroup()) {
<div
class=
"modal-overlay"
(click)=
"closeDetail()"
>
<div
class=
"modal-container modal-xl"
(click)=
"$event.stopPropagation()"
>
<div
class=
"modal-header"
>
<div>
<h2>
{{ selectedGroup().groupId }}
</h2>
<span
style=
"font-size:13px; color:var(--text-muted)"
>
{{ selectedGroup().position }} · {{ selectedGroup().members.length }} candidates
</span>
</div>
<button
class=
"icon-btn"
(click)=
"closeDetail()"
><span
class=
"material-symbols-rounded"
>
close
</span></button>
</div>
<div
class=
"modal-body detail-body"
>
<!-- Session meta -->
<div
class=
"detail-section"
>
<h3
class=
"detail-section-title"
>
Session Info
</h3>
<div
class=
"detail-grid"
>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Position
</span>
<span
class=
"detail-value"
>
{{ selectedGroup().position }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Tech Stack
</span>
<span
class=
"detail-value"
>
{{ selectedGroup().techStack || '—' }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Source
</span>
<span
class=
"detail-value"
>
{{ selectedGroup().source || '—' }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Interview Date
</span>
<span
class=
"detail-value"
>
{{ selectedGroup().dateOfInterview | date:'mediumDate' }}
</span>
</div>
</div>
</div>
<!-- Candidate rows -->
<div
class=
"detail-section"
>
<h3
class=
"detail-section-title"
>
Candidates
</h3>
<div
class=
"candidate-detail-list"
>
@for (m of selectedGroup().members; track m._id) {
<div
class=
"candidate-row"
>
<!-- Top row: clickable avatar+name on the left, progress bar on the right -->
<div
class=
"candidate-row-top"
>
<button
class=
"cr-clickable"
(click)=
"openMemberDetail(m._id, $event)"
>
<div
class=
"iv-avatar small"
>
{{ m.candidateId?.name?.charAt(0)?.toUpperCase() }}
</div>
<div>
<div
class=
"cr-name"
>
{{ m.candidateId?.name }}
@if (needsEvaluation(m)) {
<span
class=
"eval-pending-badge"
title=
"Your evaluation is pending"
>
⚠️ Evaluate
</span>
}
@if (hasPendingEvaluations(m)) {
<span
class=
"eval-pending-badge warn"
title=
"Some evaluations are still pending"
>
⏳
</span>
}
</div>
<div
class=
"cr-email"
>
{{ m.candidateId?.email }}
</div>
</div>
</button>
<div
class=
"candidate-row-mid"
>
<div
class=
"progress-steps mini"
>
<div
class=
"step"
[class.done]=
"['quiz_phase','coding_phase','evaluation','completed'].includes(m.status)"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Created
</span>
</div>
<div
class=
"step-line"
[class.done]=
"['quiz_phase','coding_phase','evaluation','completed'].includes(m.status)"
></div>
<div
class=
"step"
[class.active]=
"m.status==='quiz_phase'"
[class.done]=
"['coding_phase','evaluation','completed'].includes(m.status)"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Quiz
</span>
</div>
<div
class=
"step-line"
[class.done]=
"['coding_phase','evaluation','completed'].includes(m.status)"
></div>
<div
class=
"step"
[class.active]=
"m.status==='coding_phase'"
[class.done]=
"['evaluation','completed'].includes(m.status)"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Coding
</span>
</div>
<div
class=
"step-line"
[class.done]=
"['evaluation','completed'].includes(m.status)"
></div>
<div
class=
"step"
[class.active]=
"m.status==='evaluation'"
[class.done]=
"m.status==='completed'"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Evaluate
</span>
</div>
<div
class=
"step-line"
[class.done]=
"m.status==='completed'"
></div>
<div
class=
"step"
[class.active]=
"m.status==='completed'"
[class.done]=
"m.status==='completed'"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Done
</span>
</div>
</div>
</div>
</div>
<!-- Bottom row: status badge + quiz scores (no accept/reject here) -->
<div
class=
"candidate-row-bottom"
>
<span
class=
"badge"
[ngClass]=
"getStatusClass(m.status)"
>
{{ formatStatus(m.status) }}
</span>
@if (m.finalDecision !== 'pending') {
<span
class=
"badge"
[ngClass]=
"getDecisionClass(m.finalDecision)"
>
{{ formatDecision(m.finalDecision) }}
</span>
}
@if (m.quizzes?.length > 0) {
@for (q of m.quizzes; track q.quizId) {
<span
class=
"quiz-score-chip"
[class.completed]=
"q.completed"
>
{{ q.title }}: {{ q.completed ? q.score + '/' + q.totalMarks : 'Pending' }}
</span>
}
}
<span
class=
"cr-open-hint"
>
Click name to evaluate →
</span>
</div>
</div>
}
</div>
</div>
</div>
<div
class=
"modal-footer"
>
@if (authService.getUserRole() === 'admin' || authService.getUserRole() === 'hr') {
<button
class=
"btn btn-danger btn-sm"
(click)=
"deleteGroupInterview(selectedGroup().groupId)"
>
Delete All Interviews
</button>
}
<button
class=
"btn btn-outline"
(click)=
"closeDetail()"
>
Close
</button>
</div>
</div>
</div>
}
<!-- ═══════════════════════════════════════════════════════════
MEMBER DETAIL MODAL — full evaluation panel for one candidate
═══════════════════════════════════════════════════════════ -->
@if (showMemberDetailModal()
&&
selectedMember()) {
<div
class=
"modal-overlay"
(click)=
"closeMemberDetail()"
>
<div
class=
"modal-container modal-xl"
(click)=
"$event.stopPropagation()"
>
<!-- Header -->
<div
class=
"modal-header"
>
<div>
<h2>
{{ selectedMember().candidateId?.name }}
</h2>
<span
class=
"badge"
[ngClass]=
"getStatusClass(selectedMember().status)"
>
{{ formatStatus(selectedMember().status) }}
</span>
@if (selectedMember().finalDecision !== 'pending') {
<span
class=
"badge"
[ngClass]=
"getDecisionClass(selectedMember().finalDecision)"
style=
"margin-left:6px"
>
{{ formatDecision(selectedMember().finalDecision) }}
</span>
}
</div>
<button
class=
"icon-btn"
(click)=
"closeMemberDetail()"
><span
class=
"material-symbols-rounded"
>
close
</span></button>
</div>
<div
class=
"modal-body detail-body"
>
<!-- Candidate Info -->
<div
class=
"detail-section"
>
<h3
class=
"detail-section-title"
>
Candidate Information
</h3>
<div
class=
"detail-grid"
>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Name
</span>
<span
class=
"detail-value"
>
{{ selectedMember().candidateId?.name }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Email
</span>
<span
class=
"detail-value"
>
{{ selectedMember().candidateId?.email }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Position
</span>
<span
class=
"detail-value"
>
{{ selectedMember().position }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Tech Stack
</span>
<span
class=
"detail-value"
>
{{ selectedMember().techStack || '—' }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Date of Interview
</span>
<span
class=
"detail-value"
>
{{ selectedMember().dateOfInterview | date:'mediumDate' }}
</span>
</div>
</div>
</div>
<!-- Quiz Results -->
@if (selectedMember().quizzes?.length > 0) {
<div
class=
"detail-section"
>
<h3
class=
"detail-section-title"
>
Quiz Results
</h3>
<div
class=
"quiz-results-grid"
>
@for (q of selectedMember().quizzes; track q.quizId) {
<div
class=
"quiz-result-card"
>
<div
class=
"qr-title"
>
{{ q.title }}
</div>
@if (q.completed) {
<div
class=
"qr-score"
>
{{ q.score }}/{{ q.totalMarks }} ({{ q.percentage }}%)
</div>
} @else {
<div
class=
"qr-pending"
>
Not Taken
</div>
}
</div>
}
</div>
</div>
}
<!-- Evaluations -->
<div
class=
"detail-section"
>
<div
style=
"display:flex; justify-content:space-between; align-items:center; margin-bottom:16px;"
>
<h3
class=
"detail-section-title"
style=
"margin:0;"
>
Evaluations
</h3>
<div
style=
"display: flex; gap: 8px; align-items: center;"
>
@if (allMemberEvaluationsDone()) {
<span
class=
"badge badge-success"
>
All evaluations done
</span>
<button
class=
"btn btn-primary btn-sm"
(click)=
"downloadEvaluationPdf()"
>
@if (isPdfGenerating()) {
<span
class=
"spinner spinner-sm"
></span>
Generating... } @else {
<span
class=
"material-symbols-rounded"
style=
"font-size: 16px; margin-right: 4px;"
>
download
</span>
Download PDF }
</button>
} @else {
<span
class=
"badge badge-warning"
>
Evaluations pending
</span>
}
</div>
</div>
<!-- Evaluation list -->
@if (selectedMember().evaluations?.length > 0) {
<div
class=
"eval-list"
>
@for (ev of selectedMember().evaluations; track ev._id) {
<div
class=
"eval-card"
>
<div
class=
"eval-header"
>
<div
class=
"eval-evaluator"
>
<strong>
{{ ev.evaluatorId?.name }}
</strong>
<span
class=
"eval-role badge badge-muted"
>
{{ ev.evaluatorRole | uppercase }}
</span>
</div>
<span
class=
"badge"
[ngClass]=
"getDecisionClass(ev.recommendation)"
>
{{ formatDecision(ev.recommendation) }}
</span>
</div>
@if (ev.comments) {
<p
class=
"eval-comments"
>
"{{ ev.comments }}"
</p>
}
<span
class=
"eval-date"
>
{{ ev.date | date:'medium' }}
</span>
</div>
}
</div>
} @else {
<p
class=
"text-muted"
>
No evaluations yet
</p>
}
<!-- Add evaluation form (HR / PM / Interviewer, not if already submitted or completed) -->
@if (!hasMemberEvaluated()
&&
selectedMember().status !== 'completed') {
<div
class=
"eval-form"
>
<h4>
Add Your Evaluation
</h4>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Comments
</label>
<textarea
class=
"form-input form-textarea"
[(ngModel)]=
"memberEvalComment"
placeholder=
"Enter your comments..."
rows=
"3"
></textarea>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Recommendation *
</label>
<select
class=
"form-input"
[(ngModel)]=
"memberEvalRecommendation"
>
<option
value=
""
>
Select recommendation
</option>
<option
value=
"offer"
>
Offer / Hire as Intern
</option>
<option
value=
"on_hold"
>
On Hold
</option>
<option
value=
"rejected"
>
Rejected
</option>
<option
value=
"2nd_round"
>
2nd Round
</option>
</select>
</div>
<button
class=
"btn btn-primary"
(click)=
"submitMemberEvaluation()"
[disabled]=
"!memberEvalRecommendation || isMemberSubmitting()"
>
@if (isMemberSubmitting()) {
<span
class=
"spinner spinner-sm"
></span>
Submitting... }
@else { Submit Evaluation }
</button>
</div>
}
</div>
<!-- Final Decision — Admin and HR, only when all evaluations done -->
@if ((authService.getUserRole() === 'admin' || authService.getUserRole() === 'hr')
&&
allMemberEvaluationsDone()
&&
selectedMember().status !== 'completed') {
<div
class=
"detail-section decision-section"
>
<h3
class=
"detail-section-title"
>
Final Decision
</h3>
<div
class=
"decision-buttons"
>
<button
class=
"btn btn-success"
(click)=
"setMemberDecision('accepted')"
>
<span
class=
"material-symbols-rounded"
>
check_circle
</span>
Accept
</button>
<button
class=
"btn btn-warning"
(click)=
"setMemberDecision('on_hold')"
>
<span
class=
"material-symbols-rounded"
>
pause_circle
</span>
On Hold
</button>
<button
class=
"btn btn-danger"
(click)=
"setMemberDecision('rejected')"
>
<span
class=
"material-symbols-rounded"
>
cancel
</span>
Reject
</button>
<button
class=
"btn btn-outline"
(click)=
"setMemberDecision('2nd_round')"
>
<span
class=
"material-symbols-rounded"
>
replay
</span>
2nd Round
</button>
</div>
</div>
}
</div>
<div
class=
"modal-footer"
>
<button
class=
"btn btn-outline"
(click)=
"closeMemberDetail()"
>
Close
</button>
</div>
</div>
</div>
}
<!-- Print Template for Evaluation PDF -->
@if (selectedMember()) {
<div
class=
"print-container"
>
<div
class=
"print-header"
>
<div
class=
"print-header-left"
>
<h2
style=
"margin: 0; font-size: 20px;"
>
Intern Interview Evaluation Form
</h2>
</div>
<div
class=
"print-header-right"
style=
"text-align: right;"
>
<span
style=
"color: #0078d4; font-weight: bold; font-size: 24px;"
>
IDEAL
</span><br>
<span
style=
"font-size: 10px; color: #555;"
>
TECH LABS
</span>
</div>
</div>
<table
class=
"print-table"
style=
"width: 100%; border-collapse: collapse; margin-bottom: 20px;"
>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold; width: 25%;"
>
Candidate Name:
</td>
<td
style=
"border: 1px solid #000; padding: 8px; width: 25%;"
>
{{ selectedMember().candidateId?.name }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold; width: 25%;"
>
Date of Interview:
</td>
<td
style=
"border: 1px solid #000; padding: 8px; width: 25%;"
>
{{ selectedMember().dateOfInterview | date:'mediumDate' }}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Position:
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().position }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Source
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().source || '—' }}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Tech Stack:
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().techStack || '—' }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Interviewer(s):
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
@if (selectedMember().assignedInterviewers?.length) {
@for (i of selectedMember().assignedInterviewers; track i._id; let last = $last) {
{{ i.name }}{{ !last ? ', ' : '' }}
}
} @else {
{{ selectedMember().interviewerId?.name || '—' }}
}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
General Aptitude Test QP Set
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().quizzes?.[0]?.title || '—' }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
General Aptitude Test Score
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().quizzes?.[0]?.completed ? selectedMember().quizzes[0].score + '/' + selectedMember().quizzes[0].totalMarks : 'Not Taken' }}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Technical MCQ Test QP Set
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().quizzes?.[1]?.title || '—' }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Technical MCQ Test Score
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedMember().quizzes?.[1]?.completed ? selectedMember().quizzes[1].score + '/' + selectedMember().quizzes[1].totalMarks : 'Not Taken' }}
</td>
</tr>
</table>
<!-- Render evaluations -->
@for (ev of selectedMember().evaluations; track ev._id) {
<div
class=
"print-eval-block"
style=
"margin-bottom: 30px; page-break-inside: avoid;"
>
<div
class=
"print-eval-title"
style=
"font-weight: bold; margin-bottom: 10px;"
>
{{ ev.evaluatorRole === 'hr' ? 'HR' : ev.evaluatorRole === 'pm' ? 'Project Manager' : 'Interviewer' }}'s Comments ({{ ev.evaluatorId?.name }}):
</div>
<div
class=
"print-comments"
style=
"min-height: 80px; margin-bottom: 15px;"
>
{{ ev.comments || 'No comments provided.' }}
</div>
<div
class=
"print-recommendation"
style=
"margin-bottom: 20px;"
>
<strong
style=
"margin-right: 15px;"
>
Recommendation:
</strong>
<span
style=
"margin-right: 15px;"
><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === 'offer' ? '☑' : '☐' }}
</span>
Offer/Hire as Intern
</span>
<span
style=
"margin-right: 15px;"
><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === 'on_hold' ? '☑' : '☐' }}
</span>
On Hold
</span>
<span
style=
"margin-right: 15px;"
><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === 'rejected' ? '☑' : '☐' }}
</span>
Rejected
</span>
<span><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === '2nd_round' ? '☑' : '☐' }}
</span>
2nd Round
</span>
</div>
<div
class=
"print-signature-row"
style=
"display: flex; justify-content: space-between; align-items: flex-end;"
>
<div
class=
"print-signature"
style=
"display: flex; align-items: flex-end;"
>
<strong>
Evaluator's Signature:
</strong>
@if (ev.evaluatorId?.signature) {
<img
[src]=
"'http://localhost:5000' + ev.evaluatorId.signature"
style=
"max-height: 40px; margin-left: 10px;"
>
} @else {
<span
style=
"border-bottom: 1px solid #000; display: inline-block; width: 150px; margin-left: 10px;"
></span>
}
</div>
<div
class=
"print-date"
style=
"display: flex; align-items: flex-end;"
>
<strong>
Date:
</strong>
<span
style=
"border-bottom: 1px solid #000; display: inline-block; width: 120px; margin-left: 10px; text-align: center;"
>
{{ ev.date | date:'shortDate' }}
</span>
</div>
</div>
</div>
}
</div>
}
Frontend/src/app/pages/hr/group-interview/group-interview.ts
View file @
954049ba
import
{
Component
}
from
'
@angular/core
'
;
import
{
Component
,
OnInit
,
signal
,
computed
}
from
'
@angular/core
'
;
import
{
CommonModule
}
from
'
@angular/common
'
;
import
{
FormsModule
}
from
'
@angular/forms
'
;
import
{
QuizService
}
from
'
../../../services/quiz.service
'
;
import
{
AuthService
}
from
'
../../../services/auth.service
'
;
export
interface
QuizEntry
{
quizId
:
string
;
}
export
interface
QuizSet
{
quizEntries
:
QuizEntry
[];
mode
:
'
random
'
|
'
direct
'
;
directAssignments
:
{
[
candidateId
:
string
]:
string
};
}
@
Component
({
@
Component
({
selector
:
'
app-group-interview
'
,
selector
:
'
app-group-interview
'
,
imports
:
[],
standalone
:
true
,
imports
:
[
CommonModule
,
FormsModule
],
templateUrl
:
'
./group-interview.html
'
,
templateUrl
:
'
./group-interview.html
'
,
styleUrl
:
'
./group-interview.css
'
,
styleUrl
:
'
./group-interview.css
'
})
})
export
class
GroupInterviewComponent
{}
export
class
GroupInterviewComponent
implements
OnInit
{
// ── List state ────────────────────────────────────────────
interviews
=
signal
<
any
[]
>
([]);
loading
=
signal
(
true
);
filterStatus
=
''
;
// ── Grouped view (one card per groupId) ──────────────────
groupedList
=
computed
<
any
[]
>
(()
=>
{
const
map
=
new
Map
<
string
,
any
>
();
for
(
const
iv
of
this
.
interviews
())
{
const
key
=
iv
.
groupId
||
'
ungrouped
'
;
if
(
!
map
.
has
(
key
))
{
map
.
set
(
key
,
{
groupId
:
key
,
position
:
iv
.
position
,
techStack
:
iv
.
techStack
,
source
:
iv
.
source
,
dateOfInterview
:
iv
.
dateOfInterview
,
assignedInterviewers
:
iv
.
assignedInterviewers
,
assignedHRs
:
iv
.
assignedHRs
,
assignedPMs
:
iv
.
assignedPMs
,
members
:
[]
});
}
map
.
get
(
key
)
!
.
members
.
push
(
iv
);
}
return
Array
.
from
(
map
.
values
());
});
stats
=
signal
<
any
>
({
total
:
0
,
pending
:
0
,
completed
:
0
,
accepted
:
0
,
rejected
:
0
});
// ── Create modal state ────────────────────────────────────
showCreateModal
=
signal
(
false
);
isSubmitting
=
signal
(
false
);
successMsg
=
signal
(
''
);
// dropdown data
groups
=
signal
<
any
[]
>
([]);
interviewers
=
signal
<
any
[]
>
([]);
hrs
=
signal
<
any
[]
>
([]);
pms
=
signal
<
any
[]
>
([]);
quizzes
=
signal
<
any
[]
>
([]);
groupMembers
=
signal
<
any
[]
>
([]);
// form
newGroupInterview
=
this
.
blankForm
();
// quiz-set builder
showQuizSetup
=
false
;
pendingSetsCount
=
2
;
quizSets
:
QuizSet
[]
=
[];
// ── Group detail modal state ─────────────────────────────
showDetailModal
=
signal
(
false
);
selectedGroup
=
signal
<
any
>
(
null
);
// ── Per-candidate (member) detail modal ──────────────────
showMemberDetailModal
=
signal
(
false
);
selectedMember
=
signal
<
any
>
(
null
);
// full interview doc
memberEvalComment
=
''
;
memberEvalRecommendation
=
''
;
isMemberSubmitting
=
signal
(
false
);
constructor
(
private
quizService
:
QuizService
,
public
authService
:
AuthService
)
{}
ngOnInit
():
void
{
this
.
loadInterviews
();
this
.
loadStats
();
}
// ── Data loaders ──────────────────────────────────────────
loadInterviews
():
void
{
this
.
loading
.
set
(
true
);
const
params
:
any
=
{
type
:
'
group
'
};
if
(
this
.
filterStatus
)
params
.
status
=
this
.
filterStatus
;
this
.
quizService
.
getInterviews
(
params
).
subscribe
({
next
:
res
=>
{
this
.
interviews
.
set
(
res
.
interviews
||
[]);
this
.
loading
.
set
(
false
);
},
error
:
()
=>
this
.
loading
.
set
(
false
)
});
}
loadStats
():
void
{
this
.
quizService
.
getInterviewStats
().
subscribe
({
next
:
res
=>
this
.
stats
.
set
(
res
)
});
}
onFilterChange
():
void
{
this
.
loadInterviews
();
}
// ── Create modal ──────────────────────────────────────────
openCreateModal
():
void
{
this
.
newGroupInterview
=
this
.
blankForm
();
this
.
quizSets
=
[];
this
.
showQuizSetup
=
false
;
this
.
pendingSetsCount
=
2
;
this
.
groupMembers
.
set
([]);
this
.
successMsg
.
set
(
''
);
this
.
quizService
.
getGroups
().
subscribe
({
next
:
r
=>
this
.
groups
.
set
(
r
.
groups
||
[])
});
this
.
quizService
.
getInterviewers
().
subscribe
({
next
:
r
=>
{
const
staff
=
r
.
interviewers
||
[];
this
.
interviewers
.
set
(
staff
.
filter
((
s
:
any
)
=>
s
.
role
===
'
interviewer
'
));
this
.
pms
.
set
(
staff
.
filter
((
s
:
any
)
=>
s
.
role
===
'
pm
'
));
this
.
hrs
.
set
(
staff
.
filter
((
s
:
any
)
=>
s
.
role
===
'
hr
'
));
}
});
this
.
quizService
.
getAdminQuizzes
().
subscribe
({
next
:
r
=>
this
.
quizzes
.
set
(
r
.
quizzes
||
[])
});
this
.
showCreateModal
.
set
(
true
);
}
closeCreateModal
():
void
{
this
.
showCreateModal
.
set
(
false
);
this
.
successMsg
.
set
(
''
);
}
blankForm
()
{
return
{
groupName
:
''
,
assignedInterviewers
:
[]
as
string
[],
assignedHRs
:
[]
as
string
[],
assignedPMs
:
[]
as
string
[],
position
:
''
,
techStack
:
''
,
source
:
''
,
dateOfInterview
:
new
Date
().
toISOString
().
split
(
'
T
'
)[
0
]
};
}
toggleSelection
(
event
:
any
,
array
:
string
[]):
void
{
const
val
=
event
.
target
.
value
;
if
(
event
.
target
.
checked
)
{
array
.
push
(
val
);
}
else
{
const
i
=
array
.
indexOf
(
val
);
if
(
i
>=
0
)
array
.
splice
(
i
,
1
);
}
}
onGroupChange
():
void
{
if
(
!
this
.
newGroupInterview
.
groupName
)
{
this
.
groupMembers
.
set
([]);
return
;
}
this
.
quizService
.
getGroupMembers
(
this
.
newGroupInterview
.
groupName
).
subscribe
({
next
:
r
=>
{
this
.
groupMembers
.
set
(
r
.
candidates
||
[]);
// reset direct assignments when group changes
for
(
const
set
of
this
.
quizSets
)
set
.
directAssignments
=
{};
}
});
}
// ── Quiz Sets builder ─────────────────────────────────────
confirmSetsCount
():
void
{
const
n
=
Math
.
max
(
1
,
Math
.
min
(
10
,
this
.
pendingSetsCount
||
1
));
this
.
quizSets
=
Array
.
from
({
length
:
n
},
(
_
,
i
)
=>
({
quizEntries
:
[],
mode
:
'
random
'
,
directAssignments
:
{}
}));
this
.
showQuizSetup
=
false
;
}
addQuizToSet
(
setIdx
:
number
):
void
{
this
.
quizSets
[
setIdx
].
quizEntries
.
push
({
quizId
:
''
});
}
removeQuizEntry
(
setIdx
:
number
,
entryIdx
:
number
):
void
{
this
.
quizSets
[
setIdx
].
quizEntries
.
splice
(
entryIdx
,
1
);
}
removeQuizSet
(
setIdx
:
number
):
void
{
this
.
quizSets
.
splice
(
setIdx
,
1
);
}
addMoreSet
():
void
{
this
.
quizSets
.
push
({
quizEntries
:
[],
mode
:
'
random
'
,
directAssignments
:
{}
});
}
/** Quizzes available for a specific dropdown (setIdx, entryIdx).
* Excludes any quizId already chosen in ANY other (set, entry) pair. */
getAvailableQuizzes
(
setIdx
:
number
,
entryIdx
:
number
):
any
[]
{
const
usedIds
=
new
Set
<
string
>
();
this
.
quizSets
.
forEach
((
set
,
si
)
=>
{
set
.
quizEntries
.
forEach
((
entry
,
ei
)
=>
{
if
(
entry
.
quizId
&&
!
(
si
===
setIdx
&&
ei
===
entryIdx
))
usedIds
.
add
(
entry
.
quizId
);
});
});
return
this
.
quizzes
().
filter
(
q
=>
!
usedIds
.
has
(
q
.
_id
));
}
getQuizTitle
(
quizId
:
string
):
string
{
return
this
.
quizzes
().
find
(
q
=>
q
.
_id
===
quizId
)?.
title
||
quizId
;
}
validEntries
(
set
:
QuizSet
):
QuizEntry
[]
{
return
set
.
quizEntries
.
filter
(
e
=>
e
.
quizId
);
}
isSingleQuizSet
(
set
:
QuizSet
):
boolean
{
return
this
.
validEntries
(
set
).
length
===
1
;
}
isMultiQuizSet
(
set
:
QuizSet
):
boolean
{
return
this
.
validEntries
(
set
).
length
>
1
;
}
// ── Create interview ──────────────────────────────────────
createGroupInterview
():
void
{
if
(
!
this
.
newGroupInterview
.
groupName
||
!
this
.
newGroupInterview
.
position
)
return
;
this
.
isSubmitting
.
set
(
true
);
const
payload
=
{
...
this
.
newGroupInterview
,
quizSets
:
this
.
quizSets
.
map
(
set
=>
({
quizEntries
:
set
.
quizEntries
.
filter
(
e
=>
e
.
quizId
),
mode
:
set
.
mode
,
directAssignments
:
set
.
directAssignments
}))
};
this
.
quizService
.
createGroupInterview
(
payload
).
subscribe
({
next
:
res
=>
{
this
.
isSubmitting
.
set
(
false
);
this
.
successMsg
.
set
(
res
.
message
||
`Interview created for
${
res
.
count
}
candidates!`
);
this
.
loadInterviews
();
this
.
loadStats
();
},
error
:
err
=>
{
this
.
isSubmitting
.
set
(
false
);
alert
(
err
.
error
?.
message
||
'
Failed to create group interview
'
);
}
});
}
// ── Group detail modal ────────────────────────────────────
openDetail
(
group
:
any
):
void
{
this
.
selectedGroup
.
set
(
group
);
this
.
showDetailModal
.
set
(
true
);
}
closeDetail
():
void
{
this
.
showDetailModal
.
set
(
false
);
this
.
selectedGroup
.
set
(
null
);
}
// ── Member (candidate) detail modal ──────────────────────
openMemberDetail
(
interviewId
:
string
,
event
:
Event
):
void
{
event
.
stopPropagation
();
this
.
quizService
.
getInterviewById
(
interviewId
).
subscribe
({
next
:
res
=>
{
this
.
selectedMember
.
set
(
res
.
interview
);
this
.
memberEvalComment
=
''
;
this
.
memberEvalRecommendation
=
''
;
this
.
showMemberDetailModal
.
set
(
true
);
}
});
}
closeMemberDetail
():
void
{
this
.
showMemberDetailModal
.
set
(
false
);
this
.
selectedMember
.
set
(
null
);
}
submitMemberEvaluation
():
void
{
const
m
=
this
.
selectedMember
();
if
(
!
m
||
!
this
.
memberEvalRecommendation
)
return
;
this
.
isMemberSubmitting
.
set
(
true
);
this
.
quizService
.
submitEvaluation
(
m
.
_id
,
{
comments
:
this
.
memberEvalComment
,
recommendation
:
this
.
memberEvalRecommendation
}).
subscribe
({
next
:
res
=>
{
this
.
selectedMember
.
set
(
res
.
interview
);
this
.
memberEvalComment
=
''
;
this
.
memberEvalRecommendation
=
''
;
this
.
isMemberSubmitting
.
set
(
false
);
// refresh the group list so chips update
this
.
loadInterviews
();
},
error
:
()
=>
this
.
isMemberSubmitting
.
set
(
false
)
});
}
setMemberDecision
(
decision
:
string
):
void
{
const
m
=
this
.
selectedMember
();
if
(
!
m
)
return
;
this
.
quizService
.
setInterviewDecision
(
m
.
_id
,
decision
).
subscribe
({
next
:
res
=>
{
this
.
selectedMember
.
set
(
res
.
interview
);
this
.
loadInterviews
();
this
.
loadStats
();
}
});
}
hasMemberEvaluated
():
boolean
{
const
m
=
this
.
selectedMember
();
if
(
!
m
)
return
false
;
const
userId
=
this
.
authService
.
currentUser
()?.
id
;
return
m
.
evaluations
?.
some
((
e
:
any
)
=>
e
.
evaluatorId
?.
_id
===
userId
||
e
.
evaluatorId
===
userId
);
}
allMemberEvaluationsDone
():
boolean
{
const
m
=
this
.
selectedMember
();
if
(
!
m
)
return
false
;
const
total
=
(
m
.
assignedInterviewers
?.
length
||
0
)
+
(
m
.
assignedHRs
?.
length
||
0
)
+
(
m
.
assignedPMs
?.
length
||
0
);
if
(
total
===
0
)
return
false
;
return
(
m
.
evaluations
?.
length
||
0
)
>=
total
;
}
/** True when THIS user still needs to evaluate this member */
needsEvaluation
(
m
:
any
):
boolean
{
if
(
m
.
status
===
'
completed
'
)
return
false
;
const
userId
=
this
.
authService
.
currentUser
()?.
id
;
const
role
=
this
.
authService
.
getUserRole
();
if
(
role
===
'
admin
'
)
return
false
;
// admin gives final decision, not evaluation
const
evaluated
=
m
.
evaluations
?.
some
(
(
e
:
any
)
=>
e
.
evaluatorId
?.
_id
===
userId
||
e
.
evaluatorId
===
userId
);
return
!
evaluated
;
}
/** Whether ANY assigned staff member hasn't evaluated a given candidate interview yet */
hasPendingEvaluations
(
m
:
any
):
boolean
{
if
(
m
.
status
===
'
completed
'
)
return
false
;
const
total
=
(
m
.
assignedInterviewers
?.
length
||
0
)
+
(
m
.
assignedHRs
?.
length
||
0
)
+
(
m
.
assignedPMs
?.
length
||
0
);
return
(
m
.
evaluations
?.
length
||
0
)
<
total
;
}
isPdfGenerating
=
signal
(
false
);
downloadEvaluationPdf
():
void
{
const
m
=
this
.
selectedMember
();
if
(
!
m
)
return
;
this
.
isPdfGenerating
.
set
(
true
);
setTimeout
(()
=>
{
window
.
print
();
this
.
isPdfGenerating
.
set
(
false
);
},
500
);
}
// ── Helpers ───────────────────────────────────────────────
getStatusClass
(
status
:
string
):
string
{
const
map
:
any
=
{
pending
:
'
badge-warning
'
,
quiz_phase
:
'
badge-info
'
,
coding_phase
:
'
badge-info
'
,
evaluation
:
'
badge-purple
'
,
completed
:
'
badge-success
'
};
return
map
[
status
]
||
''
;
}
getDecisionClass
(
decision
:
string
):
string
{
const
map
:
any
=
{
accepted
:
'
badge-success
'
,
rejected
:
'
badge-danger
'
,
on_hold
:
'
badge-warning
'
,
'
2nd_round
'
:
'
badge-info
'
};
return
map
[
decision
]
||
'
badge-muted
'
;
}
formatStatus
(
status
:
string
):
string
{
return
(
status
||
''
).
replace
(
/_/g
,
'
'
).
replace
(
/
\b\w
/g
,
l
=>
l
.
toUpperCase
());
}
formatDecision
(
d
:
string
):
string
{
const
m
:
any
=
{
pending
:
'
Pending
'
,
accepted
:
'
Accepted
'
,
rejected
:
'
Rejected
'
,
on_hold
:
'
On Hold
'
,
'
2nd_round
'
:
'
2nd Round
'
};
return
m
[
d
]
||
d
;
}
groupStatusSummary
(
members
:
any
[]):
string
{
const
counts
:
any
=
{};
for
(
const
m
of
members
)
counts
[
m
.
status
]
=
(
counts
[
m
.
status
]
||
0
)
+
1
;
return
Object
.
entries
(
counts
).
map
(([
s
,
c
])
=>
`
${
this
.
formatStatus
(
s
)}
:
${
c
}
`
).
join
(
'
·
'
);
}
pendingCount
(
members
:
any
[]):
number
{
return
members
.
filter
(
m
=>
m
.
status
!==
'
completed
'
).
length
;
}
completedCount
(
members
:
any
[]):
number
{
return
members
.
filter
(
m
=>
m
.
status
===
'
completed
'
).
length
;
}
deleteGroupInterview
(
groupId
:
string
):
void
{
if
(
!
confirm
(
`Delete all interviews in group "
${
groupId
}
"?`
))
return
;
const
ids
=
(
this
.
groupedList
().
find
(
g
=>
g
.
groupId
===
groupId
)?.
members
||
[]).
map
((
m
:
any
)
=>
m
.
_id
);
let
done
=
0
;
for
(
const
id
of
ids
)
{
this
.
quizService
.
deleteInterview
(
id
).
subscribe
({
next
:
()
=>
{
done
++
;
if
(
done
===
ids
.
length
)
{
this
.
loadInterviews
();
this
.
closeDetail
();
}
}
});
}
}
setDecision
(
interviewId
:
string
,
decision
:
string
):
void
{
this
.
quizService
.
setInterviewDecision
(
interviewId
,
decision
).
subscribe
({
next
:
()
=>
this
.
loadInterviews
()
});
}
}
Frontend/src/app/pages/hr/individual-interview/individual-interview.css
View file @
954049ba
.page-container
{
padding
:
32px
40px
;
}
.content-wrapper
{
max-width
:
1200px
;
margin
:
0
;
}
.page-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
28px
;
}
.page-header
h1
{
font-size
:
24px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
margin
:
0
0
4px
;
}
.page-subtitle
{
font-size
:
14px
;
color
:
var
(
--text-muted
);
margin
:
0
;
}
/* Stats Row */
.stats-row
{
display
:
flex
;
gap
:
16px
;
margin-bottom
:
24px
;
}
.mini-stat
{
flex
:
1
;
background
:
var
(
--bg-card
);
border
:
1px
solid
var
(
--border-color
);
border-radius
:
12px
;
padding
:
16px
20px
;
text-align
:
center
;
}
.mini-stat-value
{
display
:
block
;
font-size
:
28px
;
font-weight
:
700
;
color
:
var
(
--text-primary
);
}
.mini-stat-value.orange
{
color
:
#f59e0b
;
}
.mini-stat-value.blue
{
color
:
#3b82f6
;
}
.mini-stat-value.green
{
color
:
#22c55e
;
}
.mini-stat-value.red
{
color
:
#ef4444
;
}
.mini-stat-label
{
font-size
:
12px
;
color
:
var
(
--text-muted
);
font-weight
:
500
;
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
}
/* Filter */
.filter-bar
{
margin-bottom
:
20px
;
}
.filter-select
{
padding
:
8px
14px
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
8px
;
background
:
var
(
--bg-input
);
color
:
var
(
--text-primary
);
font-size
:
14px
;
font-family
:
inherit
;
}
/* Interview Cards */
.interview-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
16px
;
}
.interview-card
{
cursor
:
pointer
;
transition
:
all
0.2s
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
16px
;
background
:
var
(
--bg-card
);
}
.interview-card
:hover
{
border-color
:
var
(
--accent-primary
);
box-shadow
:
0
4px
16px
rgba
(
102
,
126
,
234
,
0.1
);
transform
:
translateY
(
-1px
);
}
.iv-card-top
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
14px
;
}
.iv-candidate
{
display
:
flex
;
align-items
:
center
;
gap
:
14px
;
}
.iv-avatar
{
width
:
44px
;
height
:
44px
;
border-radius
:
12px
;
background
:
linear-gradient
(
135deg
,
#667eea
,
#764ba2
);
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
color
:
#fff
;
font-weight
:
700
;
font-size
:
18px
;
flex-shrink
:
0
;
}
.iv-name
{
font-size
:
16px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
margin
:
0
;
}
.iv-email
{
font-size
:
13px
;
color
:
var
(
--text-muted
);
margin
:
0
;
}
.iv-badges
{
display
:
flex
;
gap
:
8px
;
}
.iv-card-meta
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
16px
;
margin-bottom
:
14px
;
padding-bottom
:
14px
;
border-bottom
:
1px
solid
var
(
--border-color
);
}
.meta-item
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
6px
;
font-size
:
13px
;
color
:
var
(
--text-secondary
);
}
.meta-item
.material-symbols-rounded
{
font-size
:
16px
;
color
:
var
(
--text-muted
);
}
/* Progress Steps */
.progress-steps
{
display
:
flex
;
align-items
:
center
;
}
.step
{
display
:
flex
;
align-items
:
center
;
gap
:
6px
;
}
.step-dot
{
width
:
10px
;
height
:
10px
;
border-radius
:
50%
;
background
:
var
(
--border-color
);
transition
:
all
0.3s
;
}
.step.done
.step-dot
{
background
:
#22c55e
;
}
.step.active
.step-dot
{
background
:
#667eea
;
box-shadow
:
0
0
0
3px
rgba
(
102
,
126
,
234
,
0.2
);
}
.step-label
{
font-size
:
11px
;
color
:
var
(
--text-muted
);
font-weight
:
500
;
}
.step.done
.step-label
{
color
:
#22c55e
;
}
.step.active
.step-label
{
color
:
#667eea
;
font-weight
:
600
;
}
.step-line
{
flex
:
1
;
height
:
2px
;
background
:
var
(
--border-color
);
margin
:
0
8px
;
transition
:
background
0.3s
;
}
.step-line.done
{
background
:
#22c55e
;
}
/* Badges */
.badge
{
padding
:
4px
10px
;
border-radius
:
6px
;
font-size
:
11px
;
font-weight
:
600
;
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
}
.badge-warning
{
background
:
rgba
(
245
,
158
,
11
,
0.1
);
color
:
#f59e0b
;
}
.badge-info
{
background
:
rgba
(
59
,
130
,
246
,
0.1
);
color
:
#3b82f6
;
}
.badge-purple
{
background
:
rgba
(
168
,
85
,
247
,
0.1
);
color
:
#a855f7
;
}
.badge-success
{
background
:
rgba
(
34
,
197
,
94
,
0.1
);
color
:
#22c55e
;
}
.badge-danger
{
background
:
rgba
(
239
,
68
,
68
,
0.1
);
color
:
#ef4444
;
}
.badge-muted
{
background
:
var
(
--bg-hover
);
color
:
var
(
--text-muted
);
}
/* Empty / Loading */
.loading-center
{
display
:
flex
;
flex-direction
:
column
;
align-items
:
center
;
padding
:
80px
;
gap
:
16px
;
color
:
var
(
--text-muted
);
}
.empty-state
{
text-align
:
center
;
padding
:
80px
;
color
:
var
(
--text-muted
);
}
.empty-state
.material-symbols-rounded
{
font-size
:
56px
;
display
:
block
;
margin-bottom
:
16px
;
opacity
:
0.4
;
}
.empty-state
h3
{
color
:
var
(
--text-primary
);
font-size
:
18px
;
margin
:
0
0
8px
;
}
.empty-state
p
{
font-size
:
14px
;
margin
:
0
;
}
/* Modal */
.modal-overlay
{
position
:
fixed
;
top
:
0
;
left
:
0
;
right
:
0
;
bottom
:
0
;
background
:
rgba
(
0
,
0
,
0
,
0.45
);
backdrop-filter
:
blur
(
4px
);
z-index
:
1000
;
display
:
flex
;
align-items
:
center
;
justify-content
:
center
;
animation
:
fadeIn
0.2s
ease-out
;
}
.modal-container
{
background
:
var
(
--bg-card
);
border-radius
:
16px
;
box-shadow
:
0
10px
50px
rgba
(
0
,
0
,
0
,
0.25
);
border
:
1px
solid
var
(
--border-color
);
width
:
90%
;
max-height
:
85vh
;
overflow-y
:
auto
;
animation
:
slideUp
0.3s
cubic-bezier
(
0.16
,
1
,
0.3
,
1
);
}
.modal-lg
{
max-width
:
640px
;
}
.modal-xl
{
max-width
:
800px
;
}
.modal-header
{
padding
:
20px
24px
;
border-bottom
:
1px
solid
var
(
--border-color
);
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
position
:
sticky
;
top
:
0
;
background
:
var
(
--bg-card
);
z-index
:
2
;
border-radius
:
16px
16px
0
0
;
}
.modal-header
h2
{
font-size
:
18px
;
font-weight
:
600
;
margin
:
0
12px
0
0
;
display
:
inline
;
}
.modal-body
{
padding
:
24px
;
}
.modal-footer
{
padding
:
16px
24px
;
border-top
:
1px
solid
var
(
--border-color
);
display
:
flex
;
justify-content
:
flex-end
;
gap
:
12px
;
background
:
var
(
--bg-hover
);
position
:
sticky
;
bottom
:
0
;
border-radius
:
0
0
16px
16px
;
}
/* Form */
.form-row
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
;
gap
:
16px
;
margin-bottom
:
16px
;
}
.form-group
{
display
:
flex
;
flex-direction
:
column
;
gap
:
6px
;
}
.form-label
{
font-size
:
13px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
}
.form-input
{
padding
:
10px
14px
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
8px
;
background
:
var
(
--bg-input
);
color
:
var
(
--text-primary
);
font-size
:
14px
;
font-family
:
inherit
;
transition
:
all
0.2s
;
width
:
100%
;
box-sizing
:
border-box
;
}
.form-input
:focus
{
outline
:
none
;
border-color
:
var
(
--accent-primary
);
box-shadow
:
0
0
0
3px
rgba
(
102
,
126
,
234
,
0.1
);
}
.form-textarea
{
resize
:
vertical
;
min-height
:
80px
;
}
/* Quiz Selector */
.quiz-select-grid
{
display
:
flex
;
flex-wrap
:
wrap
;
gap
:
8px
;
margin-top
:
4px
;
}
.quiz-select-item
{
display
:
inline-flex
;
align-items
:
center
;
gap
:
6px
;
padding
:
8px
14px
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
8px
;
cursor
:
pointer
;
font-size
:
13px
;
color
:
var
(
--text-secondary
);
transition
:
all
0.2s
;
}
.quiz-select-item
.material-symbols-rounded
{
font-size
:
18px
;
}
.quiz-select-item.selected
{
border-color
:
#667eea
;
background
:
rgba
(
102
,
126
,
234
,
0.08
);
color
:
#667eea
;
}
.quiz-select-item
:hover
{
border-color
:
var
(
--accent-primary
);
}
/* Detail */
.detail-body
{
display
:
flex
;
flex-direction
:
column
;
gap
:
24px
;
}
.detail-section
{
padding-bottom
:
16px
;
border-bottom
:
1px
solid
var
(
--border-color
);
}
.detail-section
:last-child
{
border-bottom
:
none
;
}
.detail-section-title
{
font-size
:
14px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
margin
:
0
0
16px
;
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
}
.detail-grid
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
;
gap
:
12px
;
}
.detail-item
{
display
:
flex
;
flex-direction
:
column
;
gap
:
2px
;
}
.detail-label
{
font-size
:
11px
;
color
:
var
(
--text-muted
);
text-transform
:
uppercase
;
letter-spacing
:
0.5px
;
}
.detail-value
{
font-size
:
14px
;
color
:
var
(
--text-primary
);
font-weight
:
500
;
}
/* Quiz Results */
.quiz-results-grid
{
display
:
grid
;
grid-template-columns
:
1
fr
1
fr
;
gap
:
12px
;
}
.quiz-result-card
{
padding
:
14px
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
10px
;
background
:
var
(
--bg-hover
);
}
.qr-title
{
font-size
:
13px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
margin-bottom
:
6px
;
}
.qr-score
{
font-size
:
14px
;
color
:
#22c55e
;
font-weight
:
700
;
}
.qr-pending
{
font-size
:
13px
;
color
:
var
(
--text-muted
);
font-style
:
italic
;
}
/* Evaluations */
.eval-list
{
display
:
flex
;
flex-direction
:
column
;
gap
:
12px
;
margin-bottom
:
20px
;
}
.eval-card
{
padding
:
16px
;
border
:
1px
solid
var
(
--border-color
);
border-radius
:
10px
;
background
:
var
(
--bg-hover
);
}
.eval-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
margin-bottom
:
8px
;
}
.eval-evaluator
{
display
:
flex
;
align-items
:
center
;
gap
:
8px
;
}
.eval-evaluator
strong
{
font-size
:
14px
;
color
:
var
(
--text-primary
);
}
.eval-role
{
font-size
:
10px
;
}
.eval-comments
{
font-size
:
14px
;
color
:
var
(
--text-secondary
);
margin
:
8px
0
;
font-style
:
italic
;
line-height
:
1.5
;
}
.eval-date
{
font-size
:
11px
;
color
:
var
(
--text-muted
);
}
.eval-form
{
margin-top
:
16px
;
padding
:
20px
;
border
:
1px
dashed
rgba
(
102
,
126
,
234
,
0.3
);
border-radius
:
12px
;
background
:
rgba
(
102
,
126
,
234
,
0.03
);
}
.eval-form
h4
{
font-size
:
14px
;
font-weight
:
600
;
color
:
var
(
--text-primary
);
margin
:
0
0
16px
;
}
/* Decision */
.decision-section
{
padding
:
16px
;
background
:
rgba
(
102
,
126
,
234
,
0.03
);
border-radius
:
12px
;
border
:
1px
dashed
rgba
(
102
,
126
,
234
,
0.2
);
}
.decision-buttons
{
display
:
flex
;
gap
:
12px
;
flex-wrap
:
wrap
;
}
.btn-success
{
background
:
#22c55e
;
color
:
#fff
;
}
.btn-success
:hover
{
background
:
#16a34a
;
}
.btn-warning
{
background
:
#f59e0b
;
color
:
#fff
;
}
.btn-warning
:hover
{
background
:
#d97706
;
}
.btn-danger
{
background
:
#ef4444
;
color
:
#fff
;
}
.btn-danger
:hover
{
background
:
#dc2626
;
}
.text-muted
{
color
:
var
(
--text-muted
);
font-size
:
13px
;
}
@keyframes
fadeIn
{
from
{
opacity
:
0
;
}
to
{
opacity
:
1
;
}
}
@keyframes
slideUp
{
from
{
transform
:
translateY
(
20px
);
opacity
:
0
;
}
to
{
transform
:
translateY
(
0
);
opacity
:
1
;
}
}
@media
(
max-width
:
768px
)
{
.page-container
{
padding
:
20px
;
}
.stats-row
{
flex-wrap
:
wrap
;
}
.mini-stat
{
min-width
:
120px
;
}
.form-row
{
grid-template-columns
:
1
fr
;
}
.detail-grid
{
grid-template-columns
:
1
fr
;
}
}
/* Print Styles */
.print-container
{
display
:
none
;
}
@media
print
{
body
*
{
visibility
:
hidden
;
}
.print-container
,
.print-container
*
{
visibility
:
visible
;
}
.print-container
{
display
:
block
;
position
:
absolute
;
left
:
0
;
top
:
0
;
width
:
100%
;
padding
:
20px
;
background
:
white
;
color
:
black
;
font-family
:
Arial
,
sans-serif
;
}
.print-header
{
display
:
flex
;
justify-content
:
space-between
;
align-items
:
center
;
border-bottom
:
2px
solid
#0078d4
;
padding-bottom
:
10px
;
margin-bottom
:
20px
;
}
}
Frontend/src/app/pages/hr/individual-interview/individual-interview.html
View file @
954049ba
<p>
individual-interview works!
</p>
<div
class=
"page-container animate-fade-in"
>
<div
class=
"content-wrapper"
>
<div
class=
"page-header"
>
<div>
<h1>
Individual Interviews
</h1>
<p
class=
"page-subtitle"
>
Manage one-on-one candidate interview evaluations
</p>
</div>
<button
class=
"btn btn-primary"
(click)=
"openCreateModal()"
>
<span
class=
"material-symbols-rounded"
>
add
</span>
New Interview
</button>
</div>
<!-- Stats -->
<div
class=
"stats-row"
>
<div
class=
"mini-stat"
>
<span
class=
"mini-stat-value"
>
{{ stats().total }}
</span>
<span
class=
"mini-stat-label"
>
Total
</span>
</div>
<div
class=
"mini-stat"
>
<span
class=
"mini-stat-value orange"
>
{{ stats().pending }}
</span>
<span
class=
"mini-stat-label"
>
In Progress
</span>
</div>
<div
class=
"mini-stat"
>
<span
class=
"mini-stat-value blue"
>
{{ stats().completed }}
</span>
<span
class=
"mini-stat-label"
>
Completed
</span>
</div>
<div
class=
"mini-stat"
>
<span
class=
"mini-stat-value green"
>
{{ stats().accepted }}
</span>
<span
class=
"mini-stat-label"
>
Accepted
</span>
</div>
<div
class=
"mini-stat"
>
<span
class=
"mini-stat-value red"
>
{{ stats().rejected }}
</span>
<span
class=
"mini-stat-label"
>
Rejected
</span>
</div>
</div>
<!-- Filter -->
<div
class=
"filter-bar"
>
<select
class=
"filter-select"
[(ngModel)]=
"filterStatus"
(ngModelChange)=
"onFilterChange()"
>
<option
value=
""
>
All Statuses
</option>
<option
value=
"pending"
>
Pending
</option>
<option
value=
"quiz_phase"
>
Quiz Phase
</option>
<option
value=
"coding_phase"
>
Coding Phase
</option>
<option
value=
"evaluation"
>
Evaluation
</option>
<option
value=
"completed"
>
Completed
</option>
</select>
</div>
<!-- Interview List -->
@if (loading()) {
<div
class=
"loading-center"
><div
class=
"spinner spinner-lg"
></div><p>
Loading interviews...
</p></div>
} @else if (interviews().length === 0) {
<div
class=
"empty-state"
>
<span
class=
"material-symbols-rounded"
>
work_off
</span>
<h3>
No interviews found
</h3>
<p>
Create your first interview to get started
</p>
</div>
} @else {
<div
class=
"interview-list"
>
@for (iv of interviews(); track iv._id) {
<div
class=
"interview-card card card-padding"
(click)=
"openDetail(iv)"
>
<div
class=
"iv-card-top"
>
<div
class=
"iv-candidate"
>
<div
class=
"iv-avatar"
>
{{ iv.candidateId?.name?.charAt(0)?.toUpperCase() }}
</div>
<div>
<h3
class=
"iv-name"
>
{{ iv.candidateId?.name }}
</h3>
<p
class=
"iv-email"
>
{{ iv.candidateId?.email }}
</p>
</div>
</div>
<div
class=
"iv-badges"
>
<span
class=
"badge"
[ngClass]=
"getStatusClass(iv.status)"
>
{{ formatStatus(iv.status) }}
</span>
@if (iv.finalDecision !== 'pending') {
<span
class=
"badge"
[ngClass]=
"getDecisionClass(iv.finalDecision)"
>
{{ formatDecision(iv.finalDecision) }}
</span>
}
</div>
</div>
<div
class=
"iv-card-meta"
>
<span
class=
"meta-item"
><span
class=
"material-symbols-rounded"
>
work
</span>
{{ iv.position }}
</span>
@if (iv.techStack) {
<span
class=
"meta-item"
><span
class=
"material-symbols-rounded"
>
code
</span>
{{ iv.techStack }}
</span>
}
<span
class=
"meta-item"
><span
class=
"material-symbols-rounded"
>
person
</span>
{{ iv.interviewerId?.name }}
</span>
<span
class=
"meta-item"
><span
class=
"material-symbols-rounded"
>
calendar_today
</span>
{{ iv.dateOfInterview | date:'mediumDate' }}
</span>
</div>
<div
class=
"iv-card-progress"
>
<div
class=
"progress-steps"
>
<div
class=
"step"
[class.active]=
"iv.status === 'pending'"
[class.done]=
"['quiz_phase','coding_phase','evaluation','completed'].includes(iv.status)"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Created
</span>
</div>
<div
class=
"step-line"
[class.done]=
"['quiz_phase','coding_phase','evaluation','completed'].includes(iv.status)"
></div>
<div
class=
"step"
[class.active]=
"iv.status === 'quiz_phase'"
[class.done]=
"['coding_phase','evaluation','completed'].includes(iv.status)"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Quiz
</span>
</div>
<div
class=
"step-line"
[class.done]=
"['coding_phase','evaluation','completed'].includes(iv.status)"
></div>
<div
class=
"step"
[class.active]=
"iv.status === 'coding_phase'"
[class.done]=
"['evaluation','completed'].includes(iv.status)"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Coding
</span>
</div>
<div
class=
"step-line"
[class.done]=
"['evaluation','completed'].includes(iv.status)"
></div>
<div
class=
"step"
[class.active]=
"iv.status === 'evaluation'"
[class.done]=
"iv.status === 'completed'"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Evaluate
</span>
</div>
<div
class=
"step-line"
[class.done]=
"iv.status === 'completed'"
></div>
<div
class=
"step"
[class.active]=
"iv.status === 'completed'"
[class.done]=
"iv.status === 'completed'"
>
<span
class=
"step-dot"
></span><span
class=
"step-label"
>
Done
</span>
</div>
</div>
</div>
</div>
}
</div>
}
</div>
</div>
<!-- Create Interview Modal -->
@if (showCreateModal()) {
<div
class=
"modal-overlay"
(click)=
"closeCreateModal()"
>
<div
class=
"modal-container modal-lg"
(click)=
"$event.stopPropagation()"
>
<div
class=
"modal-header"
>
<h2>
Create Individual Interview
</h2>
<button
class=
"icon-btn"
(click)=
"closeCreateModal()"
><span
class=
"material-symbols-rounded"
>
close
</span></button>
</div>
<div
class=
"modal-body"
>
<div
class=
"form-row"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Candidate *
</label>
<select
class=
"form-input"
[(ngModel)]=
"newInterview.candidateId"
>
<option
value=
""
>
Select candidate
</option>
@for (c of candidates(); track c._id) {
<option
[value]=
"c._id"
>
{{ c.name }} ({{ c.email }})
</option>
}
</select>
</div>
</div>
<div
class=
"form-row"
>
<div
class=
"form-group"
style=
"flex: 1;"
>
<label
class=
"form-label"
>
Interviewers
</label>
<div
class=
"quiz-select-grid"
style=
"max-height: 120px; overflow-y: auto;"
>
@for (i of interviewers(); track i._id) {
<label
class=
"quiz-select-item"
style=
"cursor: pointer;"
>
<input
type=
"checkbox"
[value]=
"i._id"
(change)=
"toggleSelection($event, newInterview.assignedInterviewers)"
style=
"margin-right: 8px;"
>
{{ i.name }}
</label>
}
@if (interviewers().length === 0) {
<p
class=
"text-muted"
style=
"font-size: 12px;"
>
No interviewers found
</p>
}
</div>
</div>
<div
class=
"form-group"
style=
"flex: 1;"
>
<label
class=
"form-label"
>
Project Managers
</label>
<div
class=
"quiz-select-grid"
style=
"max-height: 120px; overflow-y: auto;"
>
@for (p of pms(); track p._id) {
<label
class=
"quiz-select-item"
style=
"cursor: pointer;"
>
<input
type=
"checkbox"
[value]=
"p._id"
(change)=
"toggleSelection($event, newInterview.assignedPMs)"
style=
"margin-right: 8px;"
>
{{ p.name }}
</label>
}
@if (pms().length === 0) {
<p
class=
"text-muted"
style=
"font-size: 12px;"
>
No PMs found
</p>
}
</div>
</div>
<div
class=
"form-group"
style=
"flex: 1;"
>
<label
class=
"form-label"
>
HRs
</label>
<div
class=
"quiz-select-grid"
style=
"max-height: 120px; overflow-y: auto;"
>
@for (h of hrs(); track h._id) {
<label
class=
"quiz-select-item"
style=
"cursor: pointer;"
>
<input
type=
"checkbox"
[value]=
"h._id"
(change)=
"toggleSelection($event, newInterview.assignedHRs)"
style=
"margin-right: 8px;"
>
{{ h.name }}
</label>
}
@if (hrs().length === 0) {
<p
class=
"text-muted"
style=
"font-size: 12px;"
>
No HRs found
</p>
}
</div>
</div>
</div>
<div
class=
"form-row"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Position *
</label>
<input
class=
"form-input"
[(ngModel)]=
"newInterview.position"
placeholder=
"e.g., Software Intern"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Tech Stack
</label>
<input
class=
"form-input"
[(ngModel)]=
"newInterview.techStack"
placeholder=
"e.g., React, Node.js"
>
</div>
</div>
<div
class=
"form-row"
>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Source
</label>
<input
class=
"form-input"
[(ngModel)]=
"newInterview.source"
placeholder=
"e.g., LinkedIn, Campus"
>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Date of Interview
</label>
<input
class=
"form-input"
type=
"date"
[(ngModel)]=
"newInterview.dateOfInterview"
>
</div>
</div>
<!-- Quiz Selection -->
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Assign Quizzes (optional)
</label>
<div
class=
"quiz-select-grid"
>
@for (q of quizzes(); track q._id) {
<div
class=
"quiz-select-item"
[class.selected]=
"isQuizSelected(q._id)"
(click)=
"toggleQuizSelection(q._id)"
>
<span
class=
"material-symbols-rounded"
>
{{ isQuizSelected(q._id) ? 'check_box' : 'check_box_outline_blank' }}
</span>
<span>
{{ q.title }}
</span>
</div>
}
@if (quizzes().length === 0) {
<p
class=
"text-muted"
>
No quizzes available
</p>
}
</div>
</div>
</div>
<div
class=
"modal-footer"
>
<button
class=
"btn btn-outline"
(click)=
"closeCreateModal()"
>
Cancel
</button>
<button
class=
"btn btn-primary"
(click)=
"createInterview()"
[disabled]=
"isSubmitting() || !newInterview.candidateId || !newInterview.position"
>
@if (isSubmitting()) {
<span
class=
"spinner spinner-sm"
></span>
Creating... } @else { Create Interview }
</button>
</div>
</div>
</div>
}
<!-- Interview Detail Modal -->
@if (showDetailModal()
&&
selectedInterview()) {
<div
class=
"modal-overlay"
(click)=
"closeDetail()"
>
<div
class=
"modal-container modal-xl"
(click)=
"$event.stopPropagation()"
>
<div
class=
"modal-header"
>
<div>
<h2>
Interview Detail
</h2>
<span
class=
"badge"
[ngClass]=
"getStatusClass(selectedInterview().status)"
>
{{ formatStatus(selectedInterview().status) }}
</span>
@if (selectedInterview().finalDecision !== 'pending') {
<span
class=
"badge"
[ngClass]=
"getDecisionClass(selectedInterview().finalDecision)"
style=
"margin-left:8px"
>
{{ formatDecision(selectedInterview().finalDecision) }}
</span>
}
</div>
<button
class=
"icon-btn"
(click)=
"closeDetail()"
><span
class=
"material-symbols-rounded"
>
close
</span></button>
</div>
<div
class=
"modal-body detail-body"
>
<!-- Candidate Info -->
<div
class=
"detail-section"
>
<h3
class=
"detail-section-title"
>
Candidate Information
</h3>
<div
class=
"detail-grid"
>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Name
</span>
<span
class=
"detail-value"
>
{{ selectedInterview().candidateId?.name }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Email
</span>
<span
class=
"detail-value"
>
{{ selectedInterview().candidateId?.email }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Position
</span>
<span
class=
"detail-value"
>
{{ selectedInterview().position }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Tech Stack
</span>
<span
class=
"detail-value"
>
{{ selectedInterview().techStack || '—' }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Source
</span>
<span
class=
"detail-value"
>
{{ selectedInterview().source || '—' }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Interviewer
</span>
<span
class=
"detail-value"
>
{{ selectedInterview().interviewerId?.name }}
</span>
</div>
<div
class=
"detail-item"
>
<span
class=
"detail-label"
>
Date of Interview
</span>
<span
class=
"detail-value"
>
{{ selectedInterview().dateOfInterview | date:'mediumDate' }}
</span>
</div>
</div>
</div>
<!-- Quiz Results -->
@if (selectedInterview().quizzes?.length > 0) {
<div
class=
"detail-section"
>
<h3
class=
"detail-section-title"
>
Quiz Results
</h3>
<div
class=
"quiz-results-grid"
>
@for (q of selectedInterview().quizzes; track q.quizId) {
<div
class=
"quiz-result-card"
>
<div
class=
"qr-title"
>
{{ q.title }}
</div>
@if (q.completed) {
<div
class=
"qr-score"
>
{{ q.score }}/{{ q.totalMarks }} ({{ q.percentage }}%)
</div>
} @else {
<div
class=
"qr-pending"
>
Not Taken
</div>
}
</div>
}
</div>
</div>
}
<!-- Coding Round Submission -->
<div
class=
"detail-section"
>
<h3
class=
"detail-section-title"
>
Coding Challenge Submission
</h3>
@if (selectedInterview().codingRound?.zipFile) {
<div
class=
"eval-card"
style=
"display: flex; justify-content: space-between; align-items: center;"
>
<div>
<span
class=
"material-symbols-rounded"
style=
"color: var(--accent-primary); vertical-align: middle; margin-right: 8px; font-size: 24px;"
>
folder_zip
</span>
<strong>
Submitted on:
</strong>
{{ selectedInterview().codingRound.submittedAt | date:'medium' }}
</div>
<div
style=
"display: flex; gap: 12px; align-items: center;"
>
@if (selectedInterview().codingRound.validated) {
<span
class=
"badge badge-success"
>
Validated by {{ selectedInterview().codingRound.validatedBy?.name || 'Reviewer' }}
</span>
}
<a
[href]=
"'http://localhost:5000' + selectedInterview().codingRound.zipFile"
target=
"_blank"
class=
"btn btn-primary"
style=
"padding: 6px 12px; font-size: 14px; border-radius: 6px;"
>
<span
class=
"material-symbols-rounded"
style=
"font-size: 18px; margin-right: 4px;"
>
download
</span>
Download
</a>
</div>
</div>
} @else {
<div
class=
"quiz-results-grid"
>
<div
class=
"quiz-result-card"
style=
"border: 1px dashed var(--border-color); background: var(--bg-body);"
>
<div
class=
"qr-title"
>
Coding Challenge
</div>
<div
class=
"qr-pending"
>
Not Submitted
</div>
</div>
</div>
}
</div>
<!-- Evaluations -->
<div
class=
"detail-section"
>
<div
style=
"display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px;"
>
<h3
class=
"detail-section-title"
style=
"margin: 0;"
>
Evaluations
</h3>
@if (allEvaluationsDone()) {
<button
class=
"btn btn-primary"
(click)=
"downloadEvaluationPdf()"
>
@if (isPdfGenerating()) {
<span
class=
"spinner spinner-sm"
></span>
Generating... } @else {
<span
class=
"material-symbols-rounded"
>
download
</span>
Download PDF }
</button>
}
</div>
@if (selectedInterview().evaluations?.length > 0) {
<div
class=
"eval-list"
>
@for (ev of selectedInterview().evaluations; track ev._id) {
<div
class=
"eval-card"
>
<div
class=
"eval-header"
>
<div
class=
"eval-evaluator"
>
<strong>
{{ ev.evaluatorId?.name }}
</strong>
<span
class=
"eval-role badge badge-muted"
>
{{ ev.evaluatorRole | uppercase }}
</span>
</div>
<span
class=
"badge"
[ngClass]=
"getDecisionClass(ev.recommendation)"
>
{{ formatDecision(ev.recommendation) }}
</span>
</div>
@if (ev.comments) {
<p
class=
"eval-comments"
>
"{{ ev.comments }}"
</p>
}
<span
class=
"eval-date"
>
{{ ev.date | date:'medium' }}
</span>
</div>
}
</div>
} @else {
<p
class=
"text-muted"
>
No evaluations yet
</p>
}
<!-- Add evaluation form -->
@if (!hasUserEvaluated()
&&
selectedInterview().status !== 'completed') {
<div
class=
"eval-form"
>
<h4>
Add Your Evaluation
</h4>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Comments
</label>
<textarea
class=
"form-input form-textarea"
[(ngModel)]=
"evalComment"
placeholder=
"Enter your comments..."
rows=
"3"
></textarea>
</div>
<div
class=
"form-group"
>
<label
class=
"form-label"
>
Recommendation *
</label>
<select
class=
"form-input"
[(ngModel)]=
"evalRecommendation"
>
<option
value=
""
>
Select recommendation
</option>
<option
value=
"offer"
>
Offer / Hire as Intern
</option>
<option
value=
"on_hold"
>
On Hold
</option>
<option
value=
"rejected"
>
Rejected
</option>
<option
value=
"2nd_round"
>
2nd Round
</option>
</select>
</div>
<button
class=
"btn btn-primary"
(click)=
"submitEvaluation()"
[disabled]=
"!evalRecommendation() || isSubmitting()"
>
Submit Evaluation
</button>
</div>
}
</div>
<!-- Final Decision (admin and hr only) -->
@if ((authService.getUserRole() === 'admin' || authService.getUserRole() === 'hr')
&&
selectedInterview().status !== 'completed') {
<div
class=
"detail-section decision-section"
>
<h3
class=
"detail-section-title"
>
Final Decision
</h3>
<div
class=
"decision-buttons"
>
<button
class=
"btn btn-success"
(click)=
"setDecision('accepted')"
>
<span
class=
"material-symbols-rounded"
>
check_circle
</span>
Accept
</button>
<button
class=
"btn btn-warning"
(click)=
"setDecision('on_hold')"
>
<span
class=
"material-symbols-rounded"
>
pause_circle
</span>
On Hold
</button>
<button
class=
"btn btn-danger"
(click)=
"setDecision('rejected')"
>
<span
class=
"material-symbols-rounded"
>
cancel
</span>
Reject
</button>
<button
class=
"btn btn-outline"
(click)=
"setDecision('2nd_round')"
>
<span
class=
"material-symbols-rounded"
>
replay
</span>
2nd Round
</button>
</div>
</div>
}
</div>
<div
class=
"modal-footer"
>
@if (authService.getUserRole() === 'admin' || authService.getUserRole() === 'hr') {
<button
class=
"btn btn-danger btn-sm"
(click)=
"deleteInterview(selectedInterview()._id)"
>
Delete Interview
</button>
}
<button
class=
"btn btn-outline"
(click)=
"closeDetail()"
>
Close
</button>
</div>
</div>
</div>
}
<!-- Print Template for Evaluation PDF -->
@if (selectedInterview()) {
<div
class=
"print-container"
>
<div
class=
"print-header"
>
<div
class=
"print-header-left"
>
<h2
style=
"margin: 0; font-size: 20px;"
>
Intern Interview Evaluation Form
</h2>
</div>
<div
class=
"print-header-right"
style=
"text-align: right;"
>
<span
style=
"color: #0078d4; font-weight: bold; font-size: 24px;"
>
IDEAL
</span><br>
<span
style=
"font-size: 10px; color: #555;"
>
TECH LABS
</span>
</div>
</div>
<table
class=
"print-table"
style=
"width: 100%; border-collapse: collapse; margin-bottom: 20px;"
>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold; width: 25%;"
>
Candidate Name:
</td>
<td
style=
"border: 1px solid #000; padding: 8px; width: 25%;"
>
{{ selectedInterview().candidateId?.name }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold; width: 25%;"
>
Date of Interview:
</td>
<td
style=
"border: 1px solid #000; padding: 8px; width: 25%;"
>
{{ selectedInterview().dateOfInterview | date:'mediumDate' }}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Position:
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedInterview().position }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Source
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedInterview().source || '—' }}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Tech Stack:
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedInterview().techStack || '—' }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Interviewer(s):
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
@if (selectedInterview().assignedInterviewers?.length) {
@for (i of selectedInterview().assignedInterviewers; track i._id; let last = $last) {
{{ i.name }}{{ !last ? ', ' : '' }}
}
} @else {
{{ selectedInterview().interviewerId?.name || '—' }}
}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
General Aptitude Test QP Set
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedInterview().quizzes?.[0]?.title || '—' }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
General Aptitude Test Score
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedInterview().quizzes?.[0]?.completed ? selectedInterview().quizzes[0].score + '/' + selectedInterview().quizzes[0].totalMarks : 'Not Taken' }}
</td>
</tr>
<tr>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Technical MCQ Test QP Set
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedInterview().quizzes?.[1]?.title || '—' }}
</td>
<td
style=
"border: 1px solid #000; padding: 8px; font-weight: bold;"
>
Technical MCQ Test Score
</td>
<td
style=
"border: 1px solid #000; padding: 8px;"
>
{{ selectedInterview().quizzes?.[1]?.completed ? selectedInterview().quizzes[1].score + '/' + selectedInterview().quizzes[1].totalMarks : 'Not Taken' }}
</td>
</tr>
</table>
<!-- Render evaluations -->
@for (ev of selectedInterview().evaluations; track ev._id) {
<div
class=
"print-eval-block"
style=
"margin-bottom: 30px; page-break-inside: avoid;"
>
<div
class=
"print-eval-title"
style=
"font-weight: bold; margin-bottom: 10px;"
>
{{ ev.evaluatorRole === 'hr' ? 'HR' : ev.evaluatorRole === 'pm' ? 'Project Manager' : 'Interviewer' }}'s Comments ({{ ev.evaluatorId?.name }}):
</div>
<div
class=
"print-comments"
style=
"min-height: 80px; margin-bottom: 15px;"
>
{{ ev.comments || 'No comments provided.' }}
</div>
<div
class=
"print-recommendation"
style=
"margin-bottom: 20px;"
>
<strong
style=
"margin-right: 15px;"
>
Recommendation:
</strong>
<span
style=
"margin-right: 15px;"
><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === 'offer' ? '☑' : '☐' }}
</span>
Offer/Hire as Intern
</span>
<span
style=
"margin-right: 15px;"
><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === 'on_hold' ? '☑' : '☐' }}
</span>
On Hold
</span>
<span
style=
"margin-right: 15px;"
><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === 'rejected' ? '☑' : '☐' }}
</span>
Rejected
</span>
<span><span
style=
"font-size: 16px;"
>
{{ ev.recommendation === '2nd_round' ? '☑' : '☐' }}
</span>
2nd Round
</span>
</div>
<div
class=
"print-signature-row"
style=
"display: flex; justify-content: space-between; align-items: flex-end;"
>
<div
class=
"print-signature"
style=
"display: flex; align-items: flex-end;"
>
<strong>
Evaluator's Signature:
</strong>
@if (ev.evaluatorId?.signature) {
<img
[src]=
"'http://localhost:5000' + ev.evaluatorId.signature"
style=
"max-height: 40px; margin-left: 10px;"
>
} @else {
<span
style=
"border-bottom: 1px solid #000; display: inline-block; width: 150px; margin-left: 10px;"
></span>
}
</div>
<div
class=
"print-date"
style=
"display: flex; align-items: flex-end;"
>
<strong>
Date:
</strong>
<span
style=
"border-bottom: 1px solid #000; display: inline-block; width: 120px; margin-left: 10px; text-align: center;"
>
{{ ev.date | date:'shortDate' }}
</span>
</div>
</div>
</div>
}
</div>
}
Frontend/src/app/pages/hr/individual-interview/individual-interview.ts
View file @
954049ba
import
{
Component
}
from
'
@angular/core
'
;
import
{
Component
,
OnInit
,
signal
}
from
'
@angular/core
'
;
import
{
CommonModule
}
from
'
@angular/common
'
;
import
{
FormsModule
}
from
'
@angular/forms
'
;
import
{
QuizService
}
from
'
../../../services/quiz.service
'
;
import
{
AuthService
}
from
'
../../../services/auth.service
'
;
@
Component
({
@
Component
({
selector
:
'
app-individual-interview
'
,
selector
:
'
app-individual-interview
'
,
imports
:
[],
standalone
:
true
,
imports
:
[
CommonModule
,
FormsModule
],
templateUrl
:
'
./individual-interview.html
'
,
templateUrl
:
'
./individual-interview.html
'
,
styleUrl
:
'
./individual-interview.css
'
,
styleUrl
:
'
./individual-interview.css
'
})
})
export
class
IndividualInterview
{}
export
class
IndividualInterviewComponent
implements
OnInit
{
interviews
=
signal
<
any
[]
>
([]);
loading
=
signal
(
true
);
showCreateModal
=
signal
(
false
);
showDetailModal
=
signal
(
false
);
selectedInterview
=
signal
<
any
>
(
null
);
// Create form data
candidates
=
signal
<
any
[]
>
([]);
interviewers
=
signal
<
any
[]
>
([]);
hrs
=
signal
<
any
[]
>
([]);
pms
=
signal
<
any
[]
>
([]);
quizzes
=
signal
<
any
[]
>
([]);
newInterview
=
{
candidateId
:
''
,
assignedInterviewers
:
[]
as
string
[],
assignedHRs
:
[]
as
string
[],
assignedPMs
:
[]
as
string
[],
position
:
''
,
techStack
:
''
,
source
:
''
,
dateOfInterview
:
new
Date
().
toISOString
().
split
(
'
T
'
)[
0
],
quizIds
:
[]
as
string
[]
};
// Evaluation form
evalComment
=
signal
(
''
);
evalRecommendation
=
signal
(
''
);
// Stats
stats
=
signal
<
any
>
({
total
:
0
,
pending
:
0
,
completed
:
0
,
accepted
:
0
,
rejected
:
0
});
// Filter
filterStatus
=
signal
(
''
);
isSubmitting
=
signal
(
false
);
constructor
(
private
quizService
:
QuizService
,
public
authService
:
AuthService
)
{}
ngOnInit
():
void
{
this
.
loadInterviews
();
this
.
loadStats
();
}
loadInterviews
():
void
{
this
.
loading
.
set
(
true
);
const
params
:
any
=
{};
if
(
this
.
filterStatus
())
params
.
status
=
this
.
filterStatus
();
params
.
type
=
'
individual
'
;
this
.
quizService
.
getInterviews
(
params
).
subscribe
({
next
:
(
res
)
=>
{
this
.
interviews
.
set
(
res
.
interviews
||
[]);
this
.
loading
.
set
(
false
);
},
error
:
()
=>
this
.
loading
.
set
(
false
)
});
}
loadStats
():
void
{
this
.
quizService
.
getInterviewStats
().
subscribe
({
next
:
(
res
)
=>
this
.
stats
.
set
(
res
)
});
}
openCreateModal
():
void
{
// Load dropdown data
this
.
quizService
.
getInterviewCandidates
().
subscribe
({
next
:
(
res
)
=>
this
.
candidates
.
set
(
res
.
candidates
||
[])
});
this
.
quizService
.
getInterviewers
().
subscribe
({
next
:
(
res
)
=>
{
const
staff
=
res
.
interviewers
||
[];
this
.
interviewers
.
set
(
staff
.
filter
((
s
:
any
)
=>
s
.
role
===
'
interviewer
'
));
this
.
pms
.
set
(
staff
.
filter
((
s
:
any
)
=>
s
.
role
===
'
pm
'
));
this
.
hrs
.
set
(
staff
.
filter
((
s
:
any
)
=>
s
.
role
===
'
hr
'
));
}
});
this
.
quizService
.
getAdminQuizzes
().
subscribe
({
next
:
(
res
)
=>
this
.
quizzes
.
set
(
res
.
quizzes
||
[])
});
this
.
newInterview
=
{
candidateId
:
''
,
assignedInterviewers
:
[],
assignedHRs
:
[],
assignedPMs
:
[],
position
:
''
,
techStack
:
''
,
source
:
''
,
dateOfInterview
:
new
Date
().
toISOString
().
split
(
'
T
'
)[
0
],
quizIds
:
[]
};
this
.
showCreateModal
.
set
(
true
);
}
closeCreateModal
():
void
{
this
.
showCreateModal
.
set
(
false
);
}
toggleQuizSelection
(
quizId
:
string
):
void
{
const
idx
=
this
.
newInterview
.
quizIds
.
indexOf
(
quizId
);
if
(
idx
>=
0
)
{
this
.
newInterview
.
quizIds
.
splice
(
idx
,
1
);
}
else
{
this
.
newInterview
.
quizIds
.
push
(
quizId
);
}
}
toggleSelection
(
event
:
any
,
array
:
string
[]):
void
{
const
val
=
event
.
target
.
value
;
if
(
event
.
target
.
checked
)
{
array
.
push
(
val
);
}
else
{
const
idx
=
array
.
indexOf
(
val
);
if
(
idx
>=
0
)
array
.
splice
(
idx
,
1
);
}
}
isQuizSelected
(
quizId
:
string
):
boolean
{
return
this
.
newInterview
.
quizIds
.
includes
(
quizId
);
}
createInterview
():
void
{
if
(
!
this
.
newInterview
.
candidateId
||
!
this
.
newInterview
.
position
)
return
;
this
.
isSubmitting
.
set
(
true
);
this
.
quizService
.
createInterview
(
this
.
newInterview
).
subscribe
({
next
:
()
=>
{
this
.
isSubmitting
.
set
(
false
);
this
.
closeCreateModal
();
this
.
loadInterviews
();
this
.
loadStats
();
},
error
:
(
err
)
=>
{
this
.
isSubmitting
.
set
(
false
);
alert
(
err
.
error
?.
message
||
'
Failed to create interview
'
);
}
});
}
openDetail
(
interview
:
any
):
void
{
this
.
quizService
.
getInterviewById
(
interview
.
_id
).
subscribe
({
next
:
(
res
)
=>
{
this
.
selectedInterview
.
set
(
res
.
interview
);
this
.
evalComment
.
set
(
''
);
this
.
evalRecommendation
.
set
(
''
);
this
.
showDetailModal
.
set
(
true
);
}
});
}
closeDetail
():
void
{
this
.
showDetailModal
.
set
(
false
);
this
.
selectedInterview
.
set
(
null
);
}
submitEvaluation
():
void
{
const
interview
=
this
.
selectedInterview
();
if
(
!
interview
||
!
this
.
evalRecommendation
())
return
;
this
.
isSubmitting
.
set
(
true
);
this
.
quizService
.
submitEvaluation
(
interview
.
_id
,
{
comments
:
this
.
evalComment
(),
recommendation
:
this
.
evalRecommendation
()
}).
subscribe
({
next
:
(
res
)
=>
{
this
.
selectedInterview
.
set
(
res
.
interview
);
this
.
evalComment
.
set
(
''
);
this
.
evalRecommendation
.
set
(
''
);
this
.
isSubmitting
.
set
(
false
);
},
error
:
()
=>
this
.
isSubmitting
.
set
(
false
)
});
}
setDecision
(
decision
:
string
):
void
{
const
interview
=
this
.
selectedInterview
();
if
(
!
interview
)
return
;
this
.
quizService
.
setInterviewDecision
(
interview
.
_id
,
decision
).
subscribe
({
next
:
(
res
)
=>
{
this
.
selectedInterview
.
set
(
res
.
interview
);
this
.
loadInterviews
();
this
.
loadStats
();
}
});
}
deleteInterview
(
id
:
string
):
void
{
if
(
confirm
(
'
Are you sure you want to delete this interview?
'
))
{
this
.
quizService
.
deleteInterview
(
id
).
subscribe
({
next
:
()
=>
{
this
.
loadInterviews
();
this
.
loadStats
();
this
.
closeDetail
();
}
});
}
}
onFilterChange
():
void
{
this
.
loadInterviews
();
}
getStatusClass
(
status
:
string
):
string
{
switch
(
status
)
{
case
'
pending
'
:
return
'
badge-warning
'
;
case
'
quiz_phase
'
:
return
'
badge-info
'
;
case
'
coding_phase
'
:
return
'
badge-info
'
;
case
'
evaluation
'
:
return
'
badge-purple
'
;
case
'
completed
'
:
return
'
badge-success
'
;
default
:
return
''
;
}
}
getDecisionClass
(
decision
:
string
):
string
{
switch
(
decision
)
{
case
'
accepted
'
:
return
'
badge-success
'
;
case
'
rejected
'
:
return
'
badge-danger
'
;
case
'
on_hold
'
:
return
'
badge-warning
'
;
case
'
2nd_round
'
:
return
'
badge-info
'
;
default
:
return
'
badge-muted
'
;
}
}
formatStatus
(
status
:
string
):
string
{
return
status
.
replace
(
/_/g
,
'
'
).
replace
(
/
\b\w
/g
,
l
=>
l
.
toUpperCase
());
}
formatDecision
(
decision
:
string
):
string
{
const
map
:
any
=
{
pending
:
'
Pending
'
,
accepted
:
'
Accepted
'
,
rejected
:
'
Rejected
'
,
on_hold
:
'
On Hold
'
,
'
2nd_round
'
:
'
2nd Round
'
};
return
map
[
decision
]
||
decision
;
}
hasUserEvaluated
():
boolean
{
const
interview
=
this
.
selectedInterview
();
if
(
!
interview
)
return
false
;
const
userId
=
this
.
authService
.
currentUser
()?.
id
;
return
interview
.
evaluations
?.
some
((
e
:
any
)
=>
e
.
evaluatorId
?.
_id
===
userId
);
}
isPdfGenerating
=
signal
(
false
);
allEvaluationsDone
():
boolean
{
const
iv
=
this
.
selectedInterview
();
if
(
!
iv
)
return
false
;
const
numInterviewers
=
iv
.
assignedInterviewers
?.
length
||
0
;
const
numHrs
=
iv
.
assignedHRs
?.
length
||
0
;
const
numPms
=
iv
.
assignedPMs
?.
length
||
0
;
const
totalExpected
=
numInterviewers
+
numHrs
+
numPms
;
if
(
totalExpected
===
0
)
return
false
;
const
numEvaluations
=
iv
.
evaluations
?.
length
||
0
;
return
numEvaluations
>=
totalExpected
;
}
downloadEvaluationPdf
():
void
{
const
iv
=
this
.
selectedInterview
();
if
(
!
iv
)
return
;
this
.
isPdfGenerating
.
set
(
true
);
setTimeout
(()
=>
{
window
.
print
();
this
.
isPdfGenerating
.
set
(
false
);
},
500
);
}
}
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