Further updates on 03-11-2025
This commit is contained in:
@ -0,0 +1,811 @@
|
||||
/* User Layout */
|
||||
.user-layout {
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100vh;
|
||||
width: 260px;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.user-content {
|
||||
margin-left: 260px;
|
||||
flex: 1;
|
||||
padding: 32px;
|
||||
width: calc(100% - 260px);
|
||||
max-width: 1600px;
|
||||
}
|
||||
|
||||
/* Header Section */
|
||||
.user-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 32px;
|
||||
gap: 20px;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 4px 0;
|
||||
letter-spacing: -0.5px;
|
||||
}
|
||||
|
||||
.page-subtitle {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* Search Box */
|
||||
.search-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.search-box i {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
padding: 10px 16px 10px 42px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #1a1a1a;
|
||||
width: 300px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.search-input:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.search-input::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn-primary,
|
||||
.btn-secondary,
|
||||
.btn-refresh {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 10px 20px;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #3b82f6;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn-primary:hover:not(:disabled) {
|
||||
background: #2563eb;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-primary:disabled {
|
||||
background: #9ca3af;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: white;
|
||||
color: #374151;
|
||||
border: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.btn-secondary:hover {
|
||||
background: #f9fafb;
|
||||
border-color: #d1d5db;
|
||||
}
|
||||
|
||||
.btn-refresh {
|
||||
background: white;
|
||||
color: #6b7280;
|
||||
border: 1px solid #e5e7eb;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
|
||||
.btn-refresh:hover {
|
||||
background: #f9fafb;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
/* Table Container */
|
||||
.table-container {
|
||||
animation: fadeIn 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.users-table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
|
||||
.users-table thead {
|
||||
background: #f9fafb;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.users-table thead th {
|
||||
padding: 16px 20px;
|
||||
text-align: left;
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.users-table tbody tr {
|
||||
border-bottom: 1px solid #f3f4f6;
|
||||
transition: background 0.2s ease;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.users-table tbody tr:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.users-table tbody tr:hover {
|
||||
background: #f9fafb;
|
||||
}
|
||||
|
||||
.users-table tbody td {
|
||||
padding: 16px 20px;
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* User Avatar */
|
||||
.user-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
border: 2px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.user-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
/* User Info Cells */
|
||||
.user-id {
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
background: #f3f4f6;
|
||||
padding: 4px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.user-name-cell .full-name {
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.username-text {
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.email-text {
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
/* Role Badge */
|
||||
.role-badge {
|
||||
display: inline-block;
|
||||
padding: 4px 12px;
|
||||
background: #dbeafe;
|
||||
color: #1e40af;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Status Badge */
|
||||
.status-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.status-active {
|
||||
background: #d1fae5;
|
||||
color: #065f46;
|
||||
}
|
||||
|
||||
.status-inactive {
|
||||
background: #fee2e2;
|
||||
color: #991b1b;
|
||||
}
|
||||
|
||||
/* Action Buttons */
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn-action {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e5e7eb;
|
||||
background: white;
|
||||
color: #6b7280;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn-action:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.btn-edit:hover {
|
||||
background: #eff6ff;
|
||||
border-color: #3b82f6;
|
||||
color: #3b82f6;
|
||||
}
|
||||
|
||||
.btn-delete:hover {
|
||||
background: #fef2f2;
|
||||
border-color: #dc2626;
|
||||
color: #dc2626;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-state {
|
||||
background: white;
|
||||
border: 1px solid #e5e7eb;
|
||||
border-radius: 12px;
|
||||
padding: 60px 20px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
margin: 0 auto 20px;
|
||||
background: #f3f4f6;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-icon i {
|
||||
font-size: 36px;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.empty-state h3 {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.empty-state p {
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* Modal Styles */
|
||||
.modal-overlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 2000;
|
||||
animation: fadeIn 0.2s ease;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
width: 90%;
|
||||
max-width: 600px;
|
||||
max-height: 90vh;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
animation: slideUp 0.3s ease;
|
||||
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24px 32px;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 6px;
|
||||
border: none;
|
||||
background: #f3f4f6;
|
||||
color: #6b7280;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
background: #e5e7eb;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 24px 32px;
|
||||
overflow-y: auto;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 20px 32px;
|
||||
border-top: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
/* User Detail Card */
|
||||
.user-detail-card {
|
||||
background: #f9fafb;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.user-detail-header {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
padding: 24px;
|
||||
background: white;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.user-detail-avatar {
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
border: 2px solid #e5e7eb;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.user-detail-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.user-detail-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.user-detail-info h4 {
|
||||
margin: 0 0 4px 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.user-detail-info .username {
|
||||
margin: 0 0 8px 0;
|
||||
font-size: 14px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.user-detail-info .last-login {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: #6b7280;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.user-detail-list {
|
||||
padding: 20px 24px;
|
||||
}
|
||||
|
||||
.detail-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
.detail-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.detail-item i {
|
||||
width: 20px;
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.detail-label {
|
||||
font-weight: 500;
|
||||
color: #6b7280;
|
||||
font-size: 13px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.detail-value {
|
||||
color: #1a1a1a;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* Form Styles */
|
||||
.form-row {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
||||
gap: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.form-group label i {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group label small {
|
||||
font-weight: 400;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.form-input,
|
||||
.form-select {
|
||||
width: 100%;
|
||||
padding: 12px 16px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
color: #1a1a1a;
|
||||
transition: all 0.2s ease;
|
||||
background: white;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
.form-input:focus,
|
||||
.form-select:focus {
|
||||
outline: none;
|
||||
border-color: #3b82f6;
|
||||
box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1);
|
||||
}
|
||||
|
||||
.form-input:disabled,
|
||||
.form-select:disabled {
|
||||
background: #f3f4f6;
|
||||
color: #9ca3af;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.form-input::placeholder {
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
/* File Upload */
|
||||
.file-upload-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.file-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.file-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 16px;
|
||||
border: 2px dashed #d1d5db;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
.file-label:hover {
|
||||
border-color: #3b82f6;
|
||||
background: #f0f9ff;
|
||||
}
|
||||
|
||||
.file-label i {
|
||||
font-size: 20px;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.file-label span {
|
||||
font-size: 14px;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
/* Checkbox */
|
||||
.form-section {
|
||||
margin: 24px 0;
|
||||
padding: 20px;
|
||||
background: #f9fafb;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.checkbox-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.checkbox-wrapper:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.checkbox-input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.checkbox-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.checkbox-custom {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: 2px solid #d1d5db;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
background: white;
|
||||
}
|
||||
|
||||
.checkbox-input:checked + .checkbox-label .checkbox-custom {
|
||||
background: #3b82f6;
|
||||
border-color: #3b82f6;
|
||||
}
|
||||
|
||||
.checkbox-input:checked + .checkbox-label .checkbox-custom::after {
|
||||
content: '\f00c';
|
||||
font-family: 'Font Awesome 5 Free';
|
||||
font-weight: 900;
|
||||
color: white;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.checkbox-input:disabled + .checkbox-label {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.checkbox-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.checkbox-text i {
|
||||
color: #6b7280;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.checkbox-text small {
|
||||
font-weight: 400;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
/* Responsive Design */
|
||||
@media (max-width: 992px) {
|
||||
.user-content {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
padding: 24px;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
transform: translateX(-100%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.user-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
width: 100%;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.user-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.table-wrapper {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.users-table {
|
||||
min-width: 800px;
|
||||
}
|
||||
|
||||
.modal-container {
|
||||
width: 95%;
|
||||
}
|
||||
|
||||
.modal-header,
|
||||
.modal-body,
|
||||
.modal-footer {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 576px) {
|
||||
.user-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.header-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.header-actions > * {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* Print Styles */
|
||||
@media print {
|
||||
.sidebar,
|
||||
.header-actions,
|
||||
.action-buttons {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.user-content {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@ -1,572 +1,447 @@
|
||||
<div class="container">
|
||||
<div class="user-layout">
|
||||
<!-- Sidebar -->
|
||||
<app-menu class="sidebar"></app-menu>
|
||||
|
||||
<app-menu></app-menu>
|
||||
|
||||
|
||||
<div *ngIf="false" class="row mb-2 mt-2 text-center">
|
||||
<div class="col-md-4">
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<h5>User Management Portal</h5>
|
||||
<small *ngIf="titleAction$ | async as title">{{title}}</small>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- nav bar -->
|
||||
<nav *ngIf="false" class="navbar navbar-expand-md breadcrumb">
|
||||
<div class="collapse navbar-collapse" id="navbarCollapse">
|
||||
<div class="nav nav-pills">
|
||||
<a class="nav-item nav-link active ml-1" (click)="changeTitle('Users')" data-bs-toggle="tab" href="#users">
|
||||
<i class="fa fa-users"></i>
|
||||
Users
|
||||
</a>
|
||||
<a class="nav-item nav-link active ml-1" (click)="changeTitle('Professors')" [routerLink]="['/dashboard/professorManagement']" >
|
||||
<i class="fa fa-users"></i>
|
||||
Professors
|
||||
</a>
|
||||
|
||||
<!-- Possible attacks-->
|
||||
<!-- document.getElementsByClassName('nav-item nav-link ml-3')[0].click()-->
|
||||
<!-- document.getElementsByName('reset-password-email')[0].value='d.art.shishkin@gmail.com'-->
|
||||
<!-- document.getElementsByName('reset-password-email')[0].closest('form').querySelector('button[type="submit"]').disabled=false -->
|
||||
<!-- document.getElementsByClassName('nav-item nav-link ml-3')[0].hidden=false-->
|
||||
<!-- document.getElementById('reset-password').hidden=false-->
|
||||
|
||||
<a class="nav-item nav-link ml-3" (click)="changeTitle('Settings')" data-bs-toggle="tab"
|
||||
href="#reset-password">
|
||||
<i class="fa fa-cogs"></i>
|
||||
Settings
|
||||
</a>
|
||||
<a class="nav-item nav-link move-right mr-3" (click)="changeTitle('Profile')" data-bs-toggle="tab"
|
||||
href="#profile">
|
||||
Welcome, {{loggedInUser.firstName}} {{loggedInUser.lastName}}
|
||||
<i class="fa fa-user"></i>
|
||||
</a>
|
||||
<!-- Main Content -->
|
||||
<div class="user-content">
|
||||
<!-- Header Section -->
|
||||
<div class="user-header">
|
||||
<div class="header-left">
|
||||
<h1 class="page-title">User Management</h1>
|
||||
<p class="page-subtitle">Manage system users, roles, and permissions</p>
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<div class="search-box">
|
||||
<i class="fa fa-search"></i>
|
||||
<input
|
||||
name="searchTerm"
|
||||
#searchTerm="ngModel"
|
||||
class="search-input"
|
||||
type="search"
|
||||
placeholder="Search users..."
|
||||
ngModel
|
||||
(ngModelChange)="searchUsers(searchTerm.value)">
|
||||
</div>
|
||||
<button *ngIf="isManager" class="btn-primary" (click)="openModal('add')">
|
||||
<i class="fa fa-plus"></i>
|
||||
<span>New User</span>
|
||||
</button>
|
||||
<button class="btn-refresh" (click)="getUsers(true)">
|
||||
<i class="fas fa-sync" [ngClass]="{'fa-spin': refreshing }"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<!-- main content -->
|
||||
<div class="tab-content mt-3" id="myTabContent">
|
||||
<!-- user table -->
|
||||
<div class="tab-pane fade show active" id="users">
|
||||
<div class="mb-3 float-end">
|
||||
<div class="btn-group mr-2">
|
||||
<!-- Users Table -->
|
||||
<div class="table-container">
|
||||
<div class="table-wrapper">
|
||||
<table class="users-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Photo</th>
|
||||
<th>User ID</th>
|
||||
<th>Name</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Role</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr *ngFor="let appUser of users">
|
||||
<td (click)="onSelectUser(appUser)">
|
||||
<div class="user-avatar">
|
||||
<img [src]="appUser?.profileImageUrl" [alt]="appUser?.username">
|
||||
</div>
|
||||
</td>
|
||||
<td (click)="onSelectUser(appUser)">
|
||||
<span class="user-id">{{ appUser?.userId }}</span>
|
||||
</td>
|
||||
<td (click)="onSelectUser(appUser)">
|
||||
<div class="user-name-cell">
|
||||
<span class="full-name">{{ appUser?.firstName }} {{ appUser?.lastName }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td (click)="onSelectUser(appUser)">
|
||||
<span class="username-text">{{ appUser?.username }}</span>
|
||||
</td>
|
||||
<td (click)="onSelectUser(appUser)">
|
||||
<span class="email-text">{{ appUser?.email }}</span>
|
||||
</td>
|
||||
<td (click)="onSelectUser(appUser)">
|
||||
<span class="role-badge">{{ appUser?.role?.substring(5) || 'USER' }}</span>
|
||||
</td>
|
||||
<td (click)="onSelectUser(appUser)">
|
||||
<span class="status-badge" [class.status-active]="appUser?.active" [class.status-inactive]="!appUser?.active">
|
||||
<i class="fa" [class.fa-check-circle]="appUser?.active" [class.fa-times-circle]="!appUser?.active"></i>
|
||||
{{ appUser?.active ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<button class="btn-action btn-edit" (click)="onEditUser(appUser)" title="Edit">
|
||||
<i class="fas fa-edit"></i>
|
||||
</button>
|
||||
<button *ngIf="isAdmin" class="btn-action btn-delete" (click)="onDeleteUser(appUser)" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<form class="form-inline my-2 my-lg-0 justify-content-center">
|
||||
<input name="searchTerm" #searchTerm="ngModel" class="form-control mr-sm-2" type="search"
|
||||
placeholder="Search users..."
|
||||
ngModel (ngModelChange)="searchUsers(searchTerm.value)">
|
||||
<!-- Empty State -->
|
||||
<div *ngIf="users.length === 0 && !refreshing" class="empty-state">
|
||||
<div class="empty-icon">
|
||||
<i class="fa fa-users"></i>
|
||||
</div>
|
||||
<h3>No users found</h3>
|
||||
<p>Try adjusting your search or add a new user</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- View User Modal -->
|
||||
<div *ngIf="selectedUser && showViewUserModal" class="modal-overlay" (click)="closeModal('view')">
|
||||
<div class="modal-container" (click)="$event.stopPropagation()">
|
||||
<div class="modal-header">
|
||||
<h3>User Details</h3>
|
||||
<button class="modal-close" (click)="closeModal('view')">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="user-detail-card">
|
||||
<div class="user-detail-header">
|
||||
<div class="user-detail-avatar">
|
||||
<img [src]="selectedUser.profileImageUrl" [alt]="selectedUser.username">
|
||||
</div>
|
||||
<div class="user-detail-info">
|
||||
<h4>{{ selectedUser.firstName }} {{ selectedUser.lastName }}</h4>
|
||||
<p class="username">@{{ selectedUser.username }}</p>
|
||||
<span class="status-badge" [class.status-active]="selectedUser.active" [class.status-inactive]="!selectedUser.active">
|
||||
<i class="fa" [class.fa-check-circle]="selectedUser.active" [class.fa-times-circle]="!selectedUser.active"></i>
|
||||
{{ selectedUser.active ? 'Active' : 'Inactive' }}
|
||||
</span>
|
||||
<div *ngIf="selectedUser.lastLoginDateDisplay" class="last-login">
|
||||
<i class="fa fa-clock"></i>
|
||||
Last login: {{ selectedUser.lastLoginDateDisplay | date:'medium' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="user-detail-list">
|
||||
<div class="detail-item">
|
||||
<i class="fa fa-id-badge"></i>
|
||||
<span class="detail-label">User ID:</span>
|
||||
<span class="detail-value">{{ selectedUser.userId }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<i class="fa fa-envelope"></i>
|
||||
<span class="detail-label">Email:</span>
|
||||
<span class="detail-value">{{ selectedUser.email }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<span class="detail-label">Role:</span>
|
||||
<span class="detail-value">{{ selectedUser?.role?.substring(5) }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<i class="fa fa-calendar"></i>
|
||||
<span class="detail-label">Joined:</span>
|
||||
<span class="detail-value">{{ selectedUser.joinDate | date:'medium' }}</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<i class="fa" [class.fa-unlock]="selectedUser?.notLocked" [class.fa-lock]="!selectedUser?.notLocked"></i>
|
||||
<span class="detail-label">Account:</span>
|
||||
<span class="detail-value">{{ selectedUser?.notLocked ? 'Unlocked' : 'Locked' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" (click)="closeModal('view')">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add User Modal -->
|
||||
<div *ngIf="showAddUserModal && isManager" class="modal-overlay" (click)="closeModal('add')">
|
||||
<div class="modal-container" (click)="$event.stopPropagation()">
|
||||
<div class="modal-header">
|
||||
<h3>Add New User</h3>
|
||||
<button class="modal-close" (click)="closeModal('add')">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form #newUserForm="ngForm" (ngSubmit)="onAddNewUser(newUserForm)">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="firstName">
|
||||
<i class="fa fa-user"></i>
|
||||
First Name
|
||||
</label>
|
||||
<input type="text" name="firstName" required ngModel class="form-input" placeholder="Enter first name">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="lastName">
|
||||
<i class="fa fa-user"></i>
|
||||
Last Name
|
||||
</label>
|
||||
<input type="text" name="lastName" required ngModel class="form-input" placeholder="Enter last name">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="username">
|
||||
<i class="fa fa-at"></i>
|
||||
Username
|
||||
</label>
|
||||
<input type="text" name="username" required ngModel class="form-input" placeholder="Enter username">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">
|
||||
<i class="fa fa-envelope"></i>
|
||||
Email
|
||||
</label>
|
||||
<input type="email" name="email" required ngModel class="form-input" placeholder="user@example.com">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="role">
|
||||
<i class="fa fa-shield-alt"></i>
|
||||
Role
|
||||
</label>
|
||||
<select *ngIf="isAdmin" name="role" required ngModel="ROLE_USER" class="form-select">
|
||||
<option value="ROLE_USER">USER</option>
|
||||
<option value="ROLE_HR">HR</option>
|
||||
<option value="ROLE_MANAGER">MANAGER</option>
|
||||
<option value="ROLE_ADMIN">ADMIN</option>
|
||||
<option value="ROLE_SUPER_ADMIN">SUPER ADMIN</option>
|
||||
</select>
|
||||
<input *ngIf="!isAdmin" type="text" name="role" required ngModel="ROLE_USER" readonly class="form-input">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<i class="fa fa-image"></i>
|
||||
Profile Picture
|
||||
</label>
|
||||
<div class="file-upload-wrapper">
|
||||
<input type="file"
|
||||
id="newUserProfileImage"
|
||||
accept="image/*"
|
||||
name="profileImage"
|
||||
(change)="onProfileImageChange($any($event).target.files)"
|
||||
class="file-input">
|
||||
<label for="newUserProfileImage" class="file-label">
|
||||
<i class="fa fa-cloud-upload-alt"></i>
|
||||
<span>{{ profileImageFileName || 'Choose profile picture' }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-section">
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox" id="newUserActive" name="active" ngModel class="checkbox-input">
|
||||
<label for="newUserActive" class="checkbox-label">
|
||||
<span class="checkbox-custom"></span>
|
||||
<span class="checkbox-text">
|
||||
<i class="fa fa-check-circle"></i>
|
||||
Active
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox" id="newUserUnlocked" name="notLocked" ngModel class="checkbox-input">
|
||||
<label for="newUserUnlocked" class="checkbox-label">
|
||||
<span class="checkbox-custom"></span>
|
||||
<span class="checkbox-text">
|
||||
<i class="fa fa-unlock"></i>
|
||||
Unlocked
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<button type="submit" style="display: none;" id="new-user-save"></button>
|
||||
</form>
|
||||
|
||||
<button *ngIf="isManager" type="button" class="btn btn-info" data-bs-toggle="modal"
|
||||
data-bs-target="#addUserModal">
|
||||
<i class="fa fa-plus"></i>New User
|
||||
</button>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-info" (click)="getUsers(true)">
|
||||
<i class="fas fa-sync" [ngClass]="{'fa-spin': refreshing }"></i>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" (click)="closeModal('add')">Cancel</button>
|
||||
<button class="btn-primary" (click)="saveNewUser()" [disabled]="newUserForm.invalid">
|
||||
<i class="fa fa-save"></i>
|
||||
Create User
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<table class="table table-hover">
|
||||
<thead class="table-borderless">
|
||||
<tr class="text-center">
|
||||
<th>Photo</th>
|
||||
<th>User ID</th>
|
||||
<th>First Name</th>
|
||||
<th>Last Name</th>
|
||||
<th>Username</th>
|
||||
<th>Email</th>
|
||||
<th>Status</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="text-center" *ngFor="let appUser of users">
|
||||
<td (click)="onSelectUser(appUser)">
|
||||
<img height="40" width="40" src="{{appUser?.profileImageUrl}}"
|
||||
class="rounded-circle img-fluid img-thumbnail" alt=""/>
|
||||
</td>
|
||||
<td (click)="onSelectUser(appUser)">{{appUser?.userId}}</td>
|
||||
<td (click)="onSelectUser(appUser)">{{appUser?.firstName}}</td>
|
||||
<td (click)="onSelectUser(appUser)">{{appUser?.lastName}}</td>
|
||||
<td (click)="onSelectUser(appUser)">{{appUser?.username}}</td>
|
||||
<td (click)="onSelectUser(appUser)">{{appUser?.email}}</td>
|
||||
<td (click)="onSelectUser(appUser)">
|
||||
<span class="badge" [ngClass]="{ 'bg-success': appUser?.active, 'bg-danger': !appUser?.active }">
|
||||
{{appUser?.active ? 'Active' : 'Inactive'}}
|
||||
</span>
|
||||
</td>
|
||||
<td class="">
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-outline-info" (click)="onEditUser(appUser)"><i class="fas fa-edit"></i></button>
|
||||
<button *ngIf="isAdmin" class="btn btn-outline-danger" (click)="onDeleteUser(appUser)"><i
|
||||
class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<button [hidden]="true" type="button" id="openUserInfo" data-bs-toggle="modal" data-bs-target="#viewUserModal">
|
||||
</button>
|
||||
<button [hidden]="true" type="button" id="openUserEdit" data-bs-toggle="modal" data-bs-target="#editUserModal">
|
||||
</button>
|
||||
|
||||
<!-- change password -->
|
||||
<div *ngIf="isAdmin" class="tab-pane fade" id="reset-password">
|
||||
<form #resetPasswordForm="ngForm" (ngSubmit)="onResetPassword(resetPasswordForm)">
|
||||
<fieldset>
|
||||
<legend>User Password Management</legend>
|
||||
<div class="form-group">
|
||||
<label for="exampleInputEmail1">Email address</label>
|
||||
<input type="email" name="reset-password-email" required ngModel class="form-control"
|
||||
placeholder="Enter email (example@email.com)">
|
||||
<small class="form-text text-muted">We'll never share your email with anyone else.</small>
|
||||
</div>
|
||||
<button type="submit" [disabled]="resetPasswordForm.invalid" class="btn btn-primary">
|
||||
<i *ngIf="refreshing" class="fas fa-spinner fa-spin"></i>
|
||||
<span>{{refreshing ? 'Loading...' : 'Reset Password'}}</span>
|
||||
<!-- Edit User Modal -->
|
||||
<div *ngIf="showEditUserModal" class="modal-overlay" (click)="closeModal('edit')">
|
||||
<div class="modal-container" (click)="$event.stopPropagation()">
|
||||
<div class="modal-header">
|
||||
<h3>Edit User {{ editUser.firstName }} {{ editUser.lastName }}</h3>
|
||||
<button class="modal-close" (click)="closeModal('edit')">
|
||||
<i class="fa fa-times"></i>
|
||||
</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- user profile -->
|
||||
<div class="tab-pane fade" id="profile">
|
||||
<div class="container">
|
||||
<div class="row flex-lg-nowrap">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="e-profile">
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-auto">
|
||||
<div class="mx-auto" style="width: 120px;">
|
||||
<div class="d-flex justify-content-center align-items-center rounded">
|
||||
<img class="rounded" height="135" width="135" src="{{loggedInUser?.profileImageUrl}}"
|
||||
alt="">
|
||||
</div>
|
||||
<div *ngIf="fileUploadStatus?.status==='progress'" class="progress mt-1">
|
||||
<div class="progress-bar bg-info" role="progressbar"
|
||||
[style.width.%]="fileUploadStatus?.percentage" aria-valuenow="0" aria-valuemin="0"
|
||||
aria-valuemax="100">{{fileUploadStatus?.percentage}}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col d-flex flex-column flex-sm-row justify-content-between mb-3">
|
||||
<div class="text-center text-sm-left mb-2 mb-sm-0">
|
||||
<h4
|
||||
class="pt-sm-2 pb-1 mb-0 text-nowrap">{{loggedInUser?.firstName}} {{loggedInUser?.lastName}}</h4>
|
||||
<p class="mb-0">{{loggedInUser?.username}}</p>
|
||||
<div *ngIf="loggedInUser?.lastLoginDateDisplay !== null" class="text-muted"><small>Last
|
||||
login:
|
||||
{{loggedInUser?.lastLoginDateDisplay | date:'medium'}}</small></div>
|
||||
<div class="mt-2">
|
||||
<button (click)="updateProfileImage()" class="btn btn-primary" type="button">
|
||||
<i class="fa fa-fw fa-camera"></i>
|
||||
<span>Change Photo</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center text-sm-right">
|
||||
<div class="text-muted"><small>Joined {{loggedInUser?.joinDate | date:'mediumDate'}}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab-content pt-3">
|
||||
<div class="tab-pane active">
|
||||
<form #profileUserForm="ngForm" (ngSubmit)="onUpdateCurrentUser(profileUserForm.value)"
|
||||
class="form" novalidate>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label>First Name</label>
|
||||
<input type="text" name="firstName" required [(ngModel)]="loggedInUser.firstName"
|
||||
class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label>Last Name</label>
|
||||
<input type="text" name="lastName" required [(ngModel)]="loggedInUser.lastName"
|
||||
class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label>Username</label>
|
||||
<input type="text" name="username" required [(ngModel)]="loggedInUser.username"
|
||||
class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="form-group">
|
||||
<label>Email</label>
|
||||
<input type="text" name="email" required [(ngModel)]="loggedInUser.email"
|
||||
class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col mb-3">
|
||||
<div class="form-group">
|
||||
<label>Role</label><small [hidden]="isAdmin">(read only)</small>
|
||||
<select [disabled]="!isAdmin" name="role" required [(ngModel)]="loggedInUser.role"
|
||||
class="form-control">
|
||||
<option value="ROLE_USER">USER</option>
|
||||
<option value="ROLE_HR">HR</option>
|
||||
<option value="ROLE_MANAGER">MANAGER</option>
|
||||
<option value="ROLE_ADMIN">ADMIN</option>
|
||||
<option value="ROLE_SUPER_ADMIN">SUPER ADMIN</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-5 offset-sm-1 mb-3">
|
||||
<div class="mb-2"><b>Account Settings</b></div>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<div class="custom-controls-stacked px-2">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input [disabled]="!isAdmin" name="active" type="checkbox"
|
||||
[(ngModel)]="loggedInUser.active"
|
||||
class="custom-control-input">
|
||||
<label class="custom-control-label">Active</label>
|
||||
</div>
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input [disabled]="!isAdmin" name="notLocked" type="checkbox"
|
||||
[(ngModel)]="loggedInUser.notLocked" class="custom-control-input">
|
||||
<label class="custom-control-label">Unlocked</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col d-flex justify-content-end">
|
||||
<button class="btn btn-primary" type="submit">
|
||||
<i *ngIf="refreshing" class="fas fa-spinner fa-spin"></i>
|
||||
<span>{{refreshing ? 'Loading...' : 'Save Changes'}}</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form #editUserForm="ngForm">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="editFirstName">
|
||||
<i class="fa fa-user"></i>
|
||||
First Name
|
||||
</label>
|
||||
<input type="text"
|
||||
name="firstName"
|
||||
required
|
||||
[(ngModel)]="editUser.firstName"
|
||||
class="form-input"
|
||||
[disabled]="!isManager">
|
||||
</div>
|
||||
<div class="col-12 col-md-3 mb-3">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body">
|
||||
<div class="px-xl-3">
|
||||
<button (click)="onLogOut()" class="btn btn-block btn-secondary">
|
||||
<span>Logout</span>
|
||||
<i class="fas fa-sign-in-alt ml-1"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title font-weight-bold">Permissions From Role</h6>
|
||||
<h6 *ngFor="let authority of loggedInUser?.authorities" class="card-text">{{authority}}</h6>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editLastName">
|
||||
<i class="fa fa-user"></i>
|
||||
Last Name
|
||||
</label>
|
||||
<input type="text"
|
||||
name="lastName"
|
||||
required
|
||||
[(ngModel)]="editUser.lastName"
|
||||
class="form-input"
|
||||
[disabled]="!isManager">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal user info -->
|
||||
<div *ngIf="selectedUser" class="modal fade bd-example-modal-lg" id="viewUserModal" tabindex="-1" role="dialog"
|
||||
aria-labelledby=""
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title text-center"
|
||||
id="exampleModalLongTitle">{{selectedUser.firstName}} {{selectedUser.lastName}}</h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<div class="row">
|
||||
<div class="col-12 col-sm-auto">
|
||||
<div class="mx-auto" style="width: 120px;">
|
||||
<div class="d-flex justify-content-center align-items-center rounded">
|
||||
<img class="rounded" height="120" width="120" src="{{selectedUser.profileImageUrl}}"
|
||||
alt="{{selectedUser.username}}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col d-flex flex-column flex-sm-row justify-content-between">
|
||||
<div class="text-center text-sm-left mb-sm-0">
|
||||
<h6
|
||||
class="pt-sm-2 pb-1 mb-0 text-nowrap">{{selectedUser.firstName}} {{selectedUser.lastName}}</h6>
|
||||
<p class="mb-1">{{selectedUser.username}}</p>
|
||||
<div class="">Status:
|
||||
<span class="badge"
|
||||
[ngClass]="{'bg-success':selectedUser.active,'bg-danger':!selectedUser.active}">
|
||||
{{selectedUser.active ? 'Active' : 'Inactive'}}
|
||||
</span>
|
||||
</div>
|
||||
<div *ngIf="selectedUser.lastLoginDateDisplay" class="text-muted">
|
||||
<small>Last Login: {{selectedUser.lastLoginDateDisplay | date: 'medium' }}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center text-sm-right">
|
||||
<div class="text-muted"><small>Joined {{selectedUser.joinDate | date: 'medium' }}</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="list-group list-group-flush">
|
||||
<li class="list-group-item"></li>
|
||||
<li class="list-group-item"><i class="fa fa-id-badge float-end"></i>{{selectedUser.userId}}
|
||||
</li>
|
||||
<li class="list-group-item"><i class="fa fa-envelope float-end"></i>{{selectedUser.email}}
|
||||
</li>
|
||||
<li class="list-group-item"><i class="fas fa-shield-alt float-end"></i>
|
||||
{{selectedUser?.role?.substring(5)}}
|
||||
<li *ngIf="selectedUser.lastLoginDateDisplay" class="list-group-item">
|
||||
<i class="fas fa-sign-in-alt float-end"></i>
|
||||
{{ selectedUser.lastLoginDateDisplay | date: 'medium' }}
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<span>
|
||||
<i class="fa float-end"
|
||||
[ngClass]="{'fa-unlock':selectedUser?.notLocked, 'fa-lock':!selectedUser?.notLocked}"
|
||||
[ngStyle]="{color: selectedUser?.notLocked ? 'green' : 'red'}"></i>
|
||||
Account {{selectedUser?.notLocked ? 'Unlocked' : 'Locked'}}
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<div class="form-group">
|
||||
<label for="editUsername">
|
||||
<i class="fa fa-at"></i>
|
||||
Username
|
||||
</label>
|
||||
<input type="text"
|
||||
name="username"
|
||||
required
|
||||
[(ngModel)]="editUser.username"
|
||||
class="form-input"
|
||||
[disabled]="!isManager">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editEmail">
|
||||
<i class="fa fa-envelope"></i>
|
||||
Email
|
||||
</label>
|
||||
<input type="email"
|
||||
name="email"
|
||||
required
|
||||
[(ngModel)]="editUser.email"
|
||||
class="form-input"
|
||||
[disabled]="!isManager">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="editRole">
|
||||
<i class="fa fa-shield-alt"></i>
|
||||
Role <small *ngIf="!isAdmin">(read only)</small>
|
||||
</label>
|
||||
<select name="role"
|
||||
required
|
||||
[(ngModel)]="editUser.role"
|
||||
class="form-select"
|
||||
[disabled]="!isAdmin">
|
||||
<option value="ROLE_USER">USER</option>
|
||||
<option value="ROLE_HR">HR</option>
|
||||
<option value="ROLE_MANAGER">MANAGER</option>
|
||||
<option value="ROLE_ADMIN">ADMIN</option>
|
||||
<option value="ROLE_SUPER_ADMIN">SUPER ADMIN</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>
|
||||
<i class="fa fa-image"></i>
|
||||
Profile Picture
|
||||
</label>
|
||||
<div class="file-upload-wrapper">
|
||||
<input type="file"
|
||||
id="editUserProfileImage"
|
||||
accept="image/*"
|
||||
name="profileImage"
|
||||
(change)="onProfileImageChange($any($event).target.files)"
|
||||
class="file-input"
|
||||
[disabled]="!isManager">
|
||||
<label for="editUserProfileImage" class="file-label">
|
||||
<i class="fa fa-cloud-upload-alt"></i>
|
||||
<span>{{ profileImageFileName || 'Choose profile picture' }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal add user -->
|
||||
<div *ngIf="isManager" class="modal draggable fade bd-example-modal-lg" id="addUserModal" tabindex="-1"
|
||||
role="dialog"
|
||||
aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title text-center">New User</h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
<form #newUserForm="ngForm" (ngSubmit)="onAddNewUser(newUserForm)">
|
||||
<div class="form-group">
|
||||
<label for="firstName">First Name</label>
|
||||
<input type="text" name="firstName" required ngModel class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lastName">Last Name</label>
|
||||
<input type="text" name="lastName" required ngModel class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" name="username" required ngModel class="form-control">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" name="email" required ngModel class="form-control">
|
||||
</div>
|
||||
|
||||
<div *ngIf="isAdmin" class="form-group">
|
||||
<label for="authority">Role</label>
|
||||
<select name="role" required ngModel="ROLE_USER" class="form-control">
|
||||
<option value="ROLE_USER">USER</option>
|
||||
<option value="ROLE_HR">HR</option>
|
||||
<option value="ROLE_MANAGER">MANAGER</option>
|
||||
<option value="ROLE_ADMIN">ADMIN</option>
|
||||
<option value="ROLE_SUPER_ADMIN">SUPER ADMIN</option>
|
||||
</select>
|
||||
</div>
|
||||
<div *ngIf="!isAdmin" class="form-group">
|
||||
<label for="authority">Role</label>
|
||||
<input type="text" name="role" required ngModel="USER" readonly class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="input-group mb-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Profile Picture </span>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<input type="file" accept="image/*" name="profileImage"
|
||||
(change)="onProfileImageChange($any($event).target.files)"
|
||||
class="custom-file-input">
|
||||
<label class="custom-file-label">
|
||||
<span>{{profileImageFileName ? profileImageFileName : 'Choose File'}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<fieldset class="form-group">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox" name="active" ngModel class="form-check-input">
|
||||
Active
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check disabled">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox" name="notLocked" ngModel class="form-check-input">
|
||||
Unlocked
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<button type="submit" style="display: none;" id="new-user-save"></button>
|
||||
</form>
|
||||
<div class="form-section">
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox"
|
||||
id="editUserActive"
|
||||
name="active"
|
||||
[(ngModel)]="editUser.active"
|
||||
class="checkbox-input"
|
||||
[disabled]="!isManager">
|
||||
<label for="editUserActive" class="checkbox-label">
|
||||
<span class="checkbox-custom"></span>
|
||||
<span class="checkbox-text">
|
||||
<i class="fa fa-check-circle"></i>
|
||||
Active <small *ngIf="!isManager">(read only)</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="checkbox-wrapper">
|
||||
<input type="checkbox"
|
||||
id="editUserUnlocked"
|
||||
name="notLocked"
|
||||
[(ngModel)]="editUser.notLocked"
|
||||
class="checkbox-input"
|
||||
[disabled]="!isManager">
|
||||
<label for="editUserUnlocked" class="checkbox-label">
|
||||
<span class="checkbox-custom"></span>
|
||||
<span class="checkbox-text">
|
||||
<i class="fa fa-unlock"></i>
|
||||
Unlocked <small *ngIf="!isManager">(read only)</small>
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="new-user-close">Close</button>
|
||||
<button type="button" class="btn btn-primary" (click)="saveNewUser()" [disabled]="newUserForm.invalid">Save
|
||||
changes
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn-secondary" (click)="closeModal('edit')">Cancel</button>
|
||||
<button *ngIf="isManager"
|
||||
class="btn-primary"
|
||||
(click)="onUpdateUser()"
|
||||
[disabled]="editUserForm.invalid">
|
||||
<i class="fa fa-save"></i>
|
||||
Save Changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- modal edit user -->
|
||||
<div class="modal draggable fade bd-example-modal-lg" id="editUserModal" tabindex="-1" role="dialog"
|
||||
aria-labelledby="exampleModalCenterTitle" aria-hidden="true">
|
||||
<div class="modal-dialog" role="document">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title text-center">Edit {{editUser.firstName}} {{editUser.lastName}}
|
||||
<small [hidden]="isManager"> (read only)</small>
|
||||
</h5>
|
||||
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div>
|
||||
<form #editUserForm="ngForm">
|
||||
<div class="form-group">
|
||||
<label for="firstName">First Name</label>
|
||||
<input type="text" name="firstName" required [(ngModel)]="editUser.firstName" class="form-control"
|
||||
[disabled]="!isManager">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="lastName">Last Name</label>
|
||||
<input type="text" name="lastName" required [(ngModel)]="editUser.lastName" class="form-control"
|
||||
[disabled]="!isManager">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="username">Username</label>
|
||||
<input type="text" name="username" required [(ngModel)]="editUser.username" class="form-control"
|
||||
[disabled]="!isManager">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<input type="email" name="email" required [(ngModel)]="editUser.email" class="form-control"
|
||||
[disabled]="!isManager">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="authority">Role<small [hidden]="isAdmin"> (read only)</small></label>
|
||||
<select name="role" required [(ngModel)]="editUser.role" class="form-control"
|
||||
[disabled]="!isAdmin">
|
||||
<option value="ROLE_USER">USER</option>
|
||||
<option value="ROLE_HR">HR</option>
|
||||
<option value="ROLE_MANAGER">MANAGER</option>
|
||||
<option value="ROLE_ADMIN">ADMIN</option>
|
||||
<option value="ROLE_SUPER_ADMIN">SUPER ADMIN</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="input-group mb-2">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Profile Picture </span>
|
||||
</div>
|
||||
<div class="custom-file">
|
||||
<input type="file" accept="image/*" name="profileImage"
|
||||
(change)="onProfileImageChange($any($event).target.files)"
|
||||
class="custom-file-input" [disabled]="!isManager">
|
||||
<label class="custom-file-label">
|
||||
<span>{{profileImageFileName ? profileImageFileName : 'Choose File'}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<fieldset class="form-group">
|
||||
<div class="form-check">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox" name="active" [(ngModel)]="editUser.active" class="form-check-input"
|
||||
[disabled]="!isManager">
|
||||
Active<small [hidden]="isManager"> (read only)</small>
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-check disabled">
|
||||
<label class="form-check-label">
|
||||
<input type="checkbox" name="notLocked" [(ngModel)]="editUser.notLocked" class="form-check-input"
|
||||
[disabled]="!isManager">
|
||||
Unlocked<small [hidden]="isManager"> (read only)</small>
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal" id="closeEditUserButton">Close
|
||||
</button>
|
||||
<button *ngIf="isManager" type="button" class="btn btn-primary" (click)="onUpdateUser()"
|
||||
[disabled]="editUserForm.invalid">
|
||||
Save changes
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- profile image change form -->
|
||||
<!-- Profile Image Change Form (Hidden) -->
|
||||
<form enctype="multipart/form-data" style="display:none;">
|
||||
<input type="file"
|
||||
(change)="onProfileImageChange($any($event).target.files); onUpdateProfileImage()"
|
||||
name="profile-image-input" id="profile-image-input" placeholder="file" accept="image/*"/>
|
||||
name="profile-image-input"
|
||||
id="profile-image-input"
|
||||
accept="image/*"/>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1,21 +1,17 @@
|
||||
import {Component, ElementRef, OnDestroy, OnInit, ViewChild} from '@angular/core';
|
||||
import {BehaviorSubject} from "rxjs";
|
||||
import {User} from "../../model/user";
|
||||
import {UserService} from "../../service/user.service";
|
||||
import {NotificationService} from "../../service/notification.service";
|
||||
import {NotificationType} from "../../notification/notification-type";
|
||||
import {HttpErrorResponse, HttpEvent, HttpEventType} from "@angular/common/http";
|
||||
import {NgForm} from "@angular/forms";
|
||||
import {CustomHttpResponse} from "../../dto/custom-http-response";
|
||||
import { Component, ElementRef, OnDestroy, OnInit, ViewChild, Renderer2 } from '@angular/core';
|
||||
import { BehaviorSubject } from "rxjs";
|
||||
import { User } from "../../model/user";
|
||||
import { UserService } from "../../service/user.service";
|
||||
import { NotificationService } from "../../service/notification.service";
|
||||
import { NotificationType } from "../../notification/notification-type";
|
||||
import { HttpErrorResponse, HttpEvent, HttpEventType } from "@angular/common/http";
|
||||
import { NgForm } from "@angular/forms";
|
||||
import { CustomHttpResponse } from "../../dto/custom-http-response";
|
||||
import { AuthenticationService } from 'src/app/service/authentication.service';
|
||||
;
|
||||
|
||||
|
||||
|
||||
import {Router} from "@angular/router";
|
||||
import {FileUploadStatus} from "../../model/file-upload.status";
|
||||
import {Role} from "../../enum/role.enum";
|
||||
import {SubSink} from "subsink";
|
||||
import { Router } from "@angular/router";
|
||||
import { FileUploadStatus } from "../../model/file-upload.status";
|
||||
import { Role } from "../../enum/role.enum";
|
||||
import { SubSink } from "subsink";
|
||||
|
||||
@Component({
|
||||
selector: 'app-user',
|
||||
@ -27,24 +23,28 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
private titleSubject = new BehaviorSubject<string>('Users');
|
||||
public titleAction$ = this.titleSubject.asObservable();
|
||||
|
||||
|
||||
public users: User[] = [];
|
||||
|
||||
public loggedInUser: User;
|
||||
|
||||
public refreshing: boolean;
|
||||
private subs = new SubSink();
|
||||
public selectedUser: User;
|
||||
public profileImageFileName: string | null;
|
||||
public profileImage: File | null;
|
||||
public editUser: User = new User();
|
||||
|
||||
public fileUploadStatus: FileUploadStatus = new FileUploadStatus();
|
||||
|
||||
constructor(private userService: UserService,
|
||||
private notificationService: NotificationService,
|
||||
private authenticationService: AuthenticationService,
|
||||
private router: Router) {
|
||||
// Modal visibility flags
|
||||
public showViewUserModal = false;
|
||||
public showAddUserModal = false;
|
||||
public showEditUserModal = false;
|
||||
|
||||
constructor(
|
||||
private userService: UserService,
|
||||
private notificationService: NotificationService,
|
||||
private authenticationService: AuthenticationService,
|
||||
private router: Router,
|
||||
private renderer: Renderer2
|
||||
) {
|
||||
}
|
||||
|
||||
ngOnInit(): void {
|
||||
@ -64,7 +64,6 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
this.titleSubject.next(title);
|
||||
}
|
||||
|
||||
|
||||
public getUsers(showNotification: boolean) {
|
||||
this.refreshing = true;
|
||||
|
||||
@ -84,12 +83,11 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
this.refreshing = false;
|
||||
}
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
public onSelectUser(selectedUser: User): void {
|
||||
this.selectedUser = selectedUser;
|
||||
this.clickButton('openUserInfo');
|
||||
this.openModal('view');
|
||||
}
|
||||
|
||||
public onProfileImageChange(fileList: FileList): void {
|
||||
@ -110,7 +108,7 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
this.subs.sink = this.userService.addUser(formData)
|
||||
.subscribe(
|
||||
(user: User) => {
|
||||
this.clickButton('new-user-close');
|
||||
this.closeModal('add');
|
||||
this.getUsers(false);
|
||||
this.invalidateVariables();
|
||||
userForm.reset();
|
||||
@ -128,11 +126,14 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public saveNewUser(): void {
|
||||
this.clickButton('new-user-save');
|
||||
// This will trigger form submission
|
||||
const saveButton = document.getElementById('new-user-save');
|
||||
if (saveButton) {
|
||||
saveButton.click();
|
||||
}
|
||||
}
|
||||
|
||||
public searchUsers(searchTerm: string): void {
|
||||
|
||||
if (!searchTerm) {
|
||||
this.users = this.userService.getUsersFromLocalStorage();
|
||||
return;
|
||||
@ -150,16 +151,11 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
}
|
||||
this.users = matchUsers;
|
||||
|
||||
}
|
||||
|
||||
private clickButton(buttonId: string): void {
|
||||
document.getElementById(buttonId)?.click();
|
||||
}
|
||||
|
||||
public onEditUser(user: User): void {
|
||||
this.editUser = user;
|
||||
this.clickButton('openUserEdit');
|
||||
this.editUser = { ...user }; // Create a copy to avoid mutating original
|
||||
this.openModal('edit');
|
||||
}
|
||||
|
||||
public onUpdateUser(): void {
|
||||
@ -167,7 +163,7 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
this.subs.sink = this.userService.updateUser(this.editUser.userId, formData)
|
||||
.subscribe(
|
||||
(user: User) => {
|
||||
this.clickButton('closeEditUserButton');
|
||||
this.closeModal('edit');
|
||||
this.getUsers(false);
|
||||
this.invalidateVariables();
|
||||
this.notificationService.notify(NotificationType.SUCCESS, `User ${user.username} updated successfully`);
|
||||
@ -243,7 +239,10 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
public updateProfileImage(): void {
|
||||
this.clickButton('profile-image-input');
|
||||
const input = document.getElementById('profile-image-input');
|
||||
if (input) {
|
||||
input.click();
|
||||
}
|
||||
}
|
||||
|
||||
public onUpdateProfileImage(): void {
|
||||
@ -270,7 +269,6 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
}
|
||||
|
||||
private reportUploadProgress(event: HttpEvent<any>): void {
|
||||
|
||||
switch (event.type) {
|
||||
case HttpEventType.UploadProgress:
|
||||
this.fileUploadStatus.percentage = Math.round(100 * event.loaded / event.total!);
|
||||
@ -288,23 +286,45 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
break;
|
||||
default:
|
||||
this.fileUploadStatus.status = 'default';
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ViewChild('addUserModal') addUserModal!: ElementRef;
|
||||
|
||||
openAddUserModal() {
|
||||
console.log("clicked")
|
||||
if (this.addUserModal) {
|
||||
|
||||
// const modalInstance = new Modal(this.addUserModal.nativeElement);
|
||||
// modalInstance.show();
|
||||
// Modal control methods
|
||||
public openModal(type: 'view' | 'add' | 'edit'): void {
|
||||
switch (type) {
|
||||
case 'view':
|
||||
this.showViewUserModal = true;
|
||||
break;
|
||||
case 'add':
|
||||
this.showAddUserModal = true;
|
||||
break;
|
||||
case 'edit':
|
||||
this.showEditUserModal = true;
|
||||
break;
|
||||
}
|
||||
// Prevent body scroll when modal is open
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
public closeModal(type: 'view' | 'add' | 'edit'): void {
|
||||
switch (type) {
|
||||
case 'view':
|
||||
this.showViewUserModal = false;
|
||||
break;
|
||||
case 'add':
|
||||
this.showAddUserModal = false;
|
||||
this.profileImageFileName = null;
|
||||
this.profileImage = null;
|
||||
break;
|
||||
case 'edit':
|
||||
this.showEditUserModal = false;
|
||||
this.profileImageFileName = null;
|
||||
this.profileImage = null;
|
||||
break;
|
||||
}
|
||||
// Restore body scroll
|
||||
document.body.style.overflow = 'auto';
|
||||
}
|
||||
|
||||
public get isAdmin(): boolean {
|
||||
return this.loggedInUser.role === Role.ADMIN || this.loggedInUser.role === Role.SUPER_ADMIN;
|
||||
@ -313,4 +333,4 @@ export class UserComponent implements OnInit, OnDestroy {
|
||||
public get isManager(): boolean {
|
||||
return this.isAdmin || this.loggedInUser.role === Role.MANAGER;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user