447 lines
18 KiB
HTML
447 lines
18 KiB
HTML
<div class="user-layout">
|
|
<!-- Sidebar -->
|
|
<app-menu class="sidebar"></app-menu>
|
|
|
|
<!-- 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>
|
|
|
|
<!-- 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>
|
|
|
|
<!-- 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>
|
|
</div>
|
|
<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>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
</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="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 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 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>
|
|
</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>
|
|
|
|
<!-- 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"
|
|
accept="image/*"/>
|
|
</form>
|
|
|
|
</div>
|
|
</div> |