mirror of
https://github.com/MikroWizard/mikrofront.git
synced 2025-12-06 01:59:29 +00:00
feat: redesign user management interface
- Complete UI/UX overhaul of user manager - Improved user permissions handling - Enhanced user role management - Better user filtering and search capabilities
This commit is contained in:
parent
746711fa24
commit
069e53cec6
3 changed files with 696 additions and 112 deletions
|
|
@ -56,124 +56,200 @@
|
|||
|
||||
<c-modal #EditTaskModal backdrop="static" size="lg" [(visible)]="EditTaskModalVisible" id="EditTaskModal">
|
||||
<c-modal-header>
|
||||
<h5 *ngIf="SelectedUser['action']=='edit'" cModalTitle>Editing User {{SelectedUser['name']}}</h5>
|
||||
<h5 *ngIf="SelectedUser['action']=='add'" cModalTitle>Adding new User</h5>
|
||||
<h5 *ngIf="SelectedUser['action']=='edit'" cModalTitle><i class="fa-solid fa-user-pen me-1"></i>Edit: {{SelectedUser['username']}}</h5>
|
||||
<h5 *ngIf="SelectedUser['action']=='add'" cModalTitle><i class="fa-solid fa-user-plus me-1"></i>Add User</h5>
|
||||
<button [cModalToggle]="EditTaskModal.id" cButtonClose></button>
|
||||
</c-modal-header>
|
||||
<c-modal-body>
|
||||
<div [cFormFloating]="true" class="mb-3">
|
||||
<input cFormControl id="floatingInput" placeholder="User Name" [(ngModel)]="SelectedUser['username']" />
|
||||
<label cLabel for="floatingInput">User Name</label>
|
||||
</div>
|
||||
|
||||
<c-input-group class="mb-3">
|
||||
<span cInputGroupText>First Name</span>
|
||||
<input cFormControl id="floatingInput" placeholder="First Name" [(ngModel)]="SelectedUser['first_name']" />
|
||||
|
||||
<span cInputGroupText>Last Name</span>
|
||||
<input cFormControl id="floatingInput" placeholder="Last Name" [(ngModel)]="SelectedUser['last_name']" />
|
||||
</c-input-group>
|
||||
|
||||
<div [cFormFloating]="true" class="mb-3">
|
||||
<input cFormControl id="floatingInput" placeholder="Email Address" [(ngModel)]="SelectedUser['email']" />
|
||||
<label cLabel for="floatingInput">Email Address</label>
|
||||
</div>
|
||||
|
||||
<div [cFormFloating]="true" class="mb-3">
|
||||
<input type="password" cFormControl id="floatingInput" placeholder="Password" [(ngModel)]="SelectedUser['password']" />
|
||||
<label cLabel for="floatingInput">Password</label>
|
||||
</div>
|
||||
<c-input-group>
|
||||
<h5>MikroWizard permisssions :</h5>
|
||||
<c-container>
|
||||
<c-row>
|
||||
<c-col *ngFor='let perm of adminperms | keyvalue' [md]="6" class="mb-1">
|
||||
<label cFormCheckLabel style="text-transform: capitalize">{{ perm.key}} :</label>
|
||||
<c-form-check class="md-6" [switch]="true" style="float: right;">
|
||||
<c-button-group>
|
||||
<c-button-group aria-label="Basic example" role="group">
|
||||
<button cButton color="info" variant="outline" size="sm" [active]="adminperms[perm.key]=='read'"
|
||||
(click)="setRadioValue(perm.key,'read')">Read</button>
|
||||
<button cButton color="danger" variant="outline" size="sm" [active]="adminperms[perm.key]=='write'"
|
||||
(click)="setRadioValue(perm.key,'write')">Write</button>
|
||||
<button cButton color="success" variant="outline" size="sm" [active]="adminperms[perm.key]=='full'"
|
||||
(click)="setRadioValue(perm.key,'full')">Full</button>
|
||||
<button cButton color="dark" variant="outline" size="sm" [active]="adminperms[perm.key]=='none'"
|
||||
(click)="setRadioValue(perm.key,'none')">None</button>
|
||||
</c-button-group>
|
||||
</c-button-group>
|
||||
</c-form-check>
|
||||
<c-modal-body class="p-3">
|
||||
<!-- User Details -->
|
||||
<div class="user-form-section mb-4">
|
||||
<div class="section-header mb-3">
|
||||
<h6 class="section-title mb-1"><i class="fa-solid fa-user me-2"></i>Basic Information</h6>
|
||||
<small class="text-muted">Enter the user's personal details and login credentials</small>
|
||||
</div>
|
||||
<c-row class="g-3">
|
||||
<c-col xs="12" md="6">
|
||||
<input cFormControl placeholder="Username (required for login)" [(ngModel)]="SelectedUser['username']"
|
||||
class="form-input" title="Unique username for system login" />
|
||||
</c-col>
|
||||
<c-col xs="12" md="6">
|
||||
<input cFormControl placeholder="Email Address (for notifications)" [(ngModel)]="SelectedUser['email']"
|
||||
class="form-input" type="email" title="Valid email address for system notifications" />
|
||||
</c-col>
|
||||
<c-col xs="12" md="4">
|
||||
<input cFormControl placeholder="First Name (optional)" [(ngModel)]="SelectedUser['first_name']"
|
||||
class="form-input" title="User's first name" />
|
||||
</c-col>
|
||||
<c-col xs="12" md="4">
|
||||
<input cFormControl placeholder="Last Name (optional)" [(ngModel)]="SelectedUser['last_name']"
|
||||
class="form-input" title="User's last name" />
|
||||
</c-col>
|
||||
<c-col xs="12" md="4">
|
||||
<input type="password" cFormControl placeholder="Password (min 6 characters)" [(ngModel)]="SelectedUser['password']"
|
||||
class="form-input" title="Secure password for user login" />
|
||||
</c-col>
|
||||
</c-row>
|
||||
</c-container>
|
||||
</c-input-group>
|
||||
<c-input-group *ngIf="userperms.length>0" class="mb-3">
|
||||
<h5>Mikrotik permisssions :</h5>
|
||||
<gui-grid [autoResizeWidth]="true" [source]="userperms" [columnMenu]="columnMenu" [sorting]="sorting"
|
||||
[autoResizeWidth]=true [paging]="paging" >
|
||||
<gui-grid-column header="Group Name" field="group_name">
|
||||
<ng-template let-value="item.group_name" let-item="item" let-index="index">
|
||||
{{value}} </ng-template>
|
||||
</gui-grid-column>
|
||||
<gui-grid-column header="perm Name" field="perm_name">
|
||||
<ng-template let-value="item.perm_name" let-item="item" let-index="index">
|
||||
{{value}}
|
||||
</ng-template>
|
||||
</gui-grid-column>
|
||||
<gui-grid-column header="Actions" width="120" field="action">
|
||||
<ng-template let-value="item.id" let-item="item" let-index="index">
|
||||
<button cButton color="danger" size="sm" (click)="confirm_delete_perm(item);"><i
|
||||
class="fa-regular fa-trash-can"></i></button>
|
||||
</ng-template>
|
||||
</gui-grid-column>
|
||||
</gui-grid>
|
||||
</c-input-group>
|
||||
<hr />
|
||||
<table >
|
||||
<td style="width: 30%;">
|
||||
<span>Add new Permission</span>
|
||||
</td>
|
||||
<td>
|
||||
<mat-form-field>
|
||||
<mat-select cFormControl [(ngModel)]="devgroup" placeholder="Device Group" #singleSelect>
|
||||
<mat-option>
|
||||
<ngx-mat-select-search></ngx-mat-select-search>
|
||||
</mat-option>
|
||||
<mat-option *ngFor="let group of allDevGroups" [value]="group">
|
||||
{{group.name}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
<td>
|
||||
<mat-form-field>
|
||||
<mat-select cFormControl placeholder="Permission" [(ngModel)]="permission" #singleSelect>
|
||||
<mat-option>
|
||||
<ngx-mat-select-search></ngx-mat-select-search>
|
||||
</mat-option>
|
||||
<mat-option *ngFor="let perm of allPerms" [value]="perm">
|
||||
{{perm.name}}
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</td>
|
||||
<td>
|
||||
<button *ngIf="SelectedUser['action']=='edit'" cButton color="primary" (click)="add_user_perm()">Add+</button>
|
||||
<button *ngIf="SelectedUser['action']=='add'" cButton color="primary" (click)="add_new_user_perm()">Add+</button>
|
||||
<!-- <button *ngIf="SelectedUser['action']=='add'" cButton color="primary" (click)="loading=!loading">++</button> -->
|
||||
</td>
|
||||
</table>
|
||||
</c-modal-body>
|
||||
<c-modal-footer style="justify-content: space-between;">
|
||||
<div>
|
||||
<button *ngIf="SelectedUser['role']!='disabled'" (click)="SelectedUser['role']='disabled'" cButton color="danger">Deactive</button>
|
||||
<button *ngIf="SelectedUser['role']=='disabled'" (click)="SelectedUser['role']='admin'" cButton color="success">Activate</button>
|
||||
</div>
|
||||
|
||||
<!-- System Permissions -->
|
||||
<div class="permissions-section mb-4">
|
||||
<div class="section-header mb-3">
|
||||
<h6 class="section-title mb-1"><i class="fa-solid fa-shield-halved me-2"></i>System Access Levels</h6>
|
||||
<small class="text-muted">Control what system features this user can access and modify</small>
|
||||
</div>
|
||||
<div class="permission-legend mb-2">
|
||||
<span class="legend-item"><span class="legend-color bg-info"></span>R = Read Only</span>
|
||||
<span class="legend-item"><span class="legend-color bg-warning"></span>W = Read & Write</span>
|
||||
<span class="legend-item"><span class="legend-color bg-success"></span>F = Full Control</span>
|
||||
<span class="legend-item"><span class="legend-color bg-secondary"></span>× = No Access</span>
|
||||
</div>
|
||||
<div class="permissions-compact">
|
||||
<div *ngFor='let perm of adminperms | keyvalue' class="perm-row">
|
||||
<span class="perm-label">{{ perm.key.replace('_', ' ') }}</span>
|
||||
<div class="perm-controls">
|
||||
<button cButton size="sm" variant="outline" color="info" [active]="adminperms[perm.key]=='read'"
|
||||
(click)="setRadioValue(perm.key,'read')" title="Read Only Access">R</button>
|
||||
<button cButton size="sm" variant="outline" color="warning" [active]="adminperms[perm.key]=='write'"
|
||||
(click)="setRadioValue(perm.key,'write')" title="Read & Write Access">W</button>
|
||||
<button cButton size="sm" variant="outline" color="success" [active]="adminperms[perm.key]=='full'"
|
||||
(click)="setRadioValue(perm.key,'full')" title="Full Control Access">F</button>
|
||||
<button cButton size="sm" variant="outline" color="secondary" [active]="adminperms[perm.key]=='none'"
|
||||
(click)="setRadioValue(perm.key,'none')" title="No Access">×</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Device Permissions -->
|
||||
<div class="device-permissions-section">
|
||||
<div class="section-header mb-3">
|
||||
<h6 class="section-title mb-1"><i class="fa-solid fa-network-wired me-2"></i>Device Access Control</h6>
|
||||
<small class="text-muted">Grant access to specific device groups with defined permission levels</small>
|
||||
</div>
|
||||
|
||||
<!-- Add New Permission -->
|
||||
<div class="add-permission-card mb-3">
|
||||
<div class="add-header">
|
||||
<i class="fa-solid fa-plus-circle text-primary me-2"></i>
|
||||
<span class="fw-semibold">Add Device Permission</span>
|
||||
</div>
|
||||
<c-row class="g-2 mt-2">
|
||||
<c-col xs="12" md="5">
|
||||
<div class="search-select-wrapper">
|
||||
<label class="search-label">Device Group</label>
|
||||
<input cFormControl
|
||||
[(ngModel)]="devgroupSearch"
|
||||
(input)="filterDevGroups($event)"
|
||||
(focus)="showDevGroupDropdown = true"
|
||||
(blur)="hideDevGroupDropdown()"
|
||||
placeholder="Search and select device group..."
|
||||
class="search-input compact-select"
|
||||
autocomplete="off" />
|
||||
<div *ngIf="showDevGroupDropdown && filteredDevGroups.length > 0" class="search-dropdown">
|
||||
<div *ngFor="let group of filteredDevGroups"
|
||||
class="search-option"
|
||||
(mousedown)="selectDevGroup(group)">
|
||||
{{group.name}}
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="showDevGroupDropdown && filteredDevGroups.length === 0 && devgroupSearch" class="search-no-results">
|
||||
No groups found
|
||||
</div>
|
||||
</div>
|
||||
</c-col>
|
||||
<c-col xs="12" md="5">
|
||||
<div class="search-select-wrapper">
|
||||
<label class="search-label">Permission Level</label>
|
||||
<input cFormControl
|
||||
[(ngModel)]="permissionSearch"
|
||||
(input)="filterPermissions($event)"
|
||||
(focus)="showPermissionDropdown = true"
|
||||
(blur)="hidePermissionDropdown()"
|
||||
placeholder="Search and select permission..."
|
||||
class="search-input compact-select"
|
||||
autocomplete="off" />
|
||||
<div *ngIf="showPermissionDropdown && filteredPermissions.length > 0" class="search-dropdown">
|
||||
<div *ngFor="let perm of filteredPermissions"
|
||||
class="search-option"
|
||||
(mousedown)="selectPermission(perm)">
|
||||
{{perm.name}}
|
||||
</div>
|
||||
</div>
|
||||
<div *ngIf="showPermissionDropdown && filteredPermissions.length === 0 && permissionSearch" class="search-no-results">
|
||||
No permissions found
|
||||
</div>
|
||||
</div>
|
||||
</c-col>
|
||||
<c-col xs="12" md="2" class="d-flex align-items-end">
|
||||
<button *ngIf="SelectedUser['action']=='edit'" cButton color="success" size="sm" class="w-100 add-btn"
|
||||
(click)="add_user_perm()" [disabled]="!devgroup || !permission">
|
||||
<i class="fa-solid fa-plus me-1"></i>Add Access
|
||||
</button>
|
||||
<button *ngIf="SelectedUser['action']=='add'" cButton color="success" size="sm" class="w-100 add-btn"
|
||||
(click)="add_new_user_perm()" [disabled]="!devgroup || !permission">
|
||||
<i class="fa-solid fa-plus me-1"></i>Add Access
|
||||
</button>
|
||||
</c-col>
|
||||
</c-row>
|
||||
</div>
|
||||
|
||||
<!-- Current Permissions -->
|
||||
<div class="current-permissions">
|
||||
<div class="permissions-header">
|
||||
<h6 class="mb-1"><i class="fa-solid fa-list-check me-2 text-success"></i>Current Device Access</h6>
|
||||
<span class="badge bg-info">{{userperms.length}} permission(s)</span>
|
||||
</div>
|
||||
|
||||
<div *ngIf="userperms.length>0" class="permissions-list mt-2">
|
||||
<div *ngFor="let perm of userperms; let i = index" class="permission-item">
|
||||
<div class="perm-number">{{i + 1}}</div>
|
||||
<div class="perm-info">
|
||||
<div class="perm-main">
|
||||
<i class="fa-solid fa-server me-1 text-primary"></i>
|
||||
<span class="group-name">{{perm.group_name}}</span>
|
||||
</div>
|
||||
<div class="perm-detail">
|
||||
<i class="fa-solid fa-key me-1 text-warning"></i>
|
||||
<span class="perm-name">{{perm.perm_name}}</span>
|
||||
</div>
|
||||
</div>
|
||||
<button cButton color="danger" size="sm" variant="outline" (click)="confirm_delete_perm(perm)"
|
||||
title="Remove this permission" class="remove-btn">
|
||||
<i class="fa-solid fa-trash-can"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="userperms.length==0" class="empty-permissions">
|
||||
<div class="empty-icon">
|
||||
<i class="fa-solid fa-shield-xmark text-muted"></i>
|
||||
</div>
|
||||
<div class="empty-text">
|
||||
<strong>No device access granted</strong>
|
||||
<p class="mb-0 text-muted">Add permissions above to grant access to device groups</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</c-modal-body>
|
||||
<c-modal-footer class="d-flex justify-content-between flex-wrap gap-2 p-2">
|
||||
<div>
|
||||
<button *ngIf="SelectedUser['action']=='add'" (click)="submit('add')" cButton color="primary">Add</button>
|
||||
<button *ngIf="SelectedUser['action']=='edit'" (click)="submit('edit')" cButton color="primary">save</button>
|
||||
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary">
|
||||
Close
|
||||
<button *ngIf="SelectedUser['role']!='disabled'" (click)="SelectedUser['role']='disabled'" cButton color="danger" size="sm">
|
||||
<i class="fa-solid fa-user-slash me-1"></i><span class="d-none d-sm-inline">Deactivate</span>
|
||||
</button>
|
||||
<button *ngIf="SelectedUser['role']=='disabled'" (click)="SelectedUser['role']='admin'" cButton color="success" size="sm">
|
||||
<i class="fa-solid fa-user-check me-1"></i><span class="d-none d-sm-inline">Activate</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="d-flex gap-2">
|
||||
<button *ngIf="SelectedUser['action']=='add'" (click)="submit('add')" cButton color="primary" size="sm">
|
||||
<i class="fa-solid fa-plus me-1"></i>Add
|
||||
</button>
|
||||
<button *ngIf="SelectedUser['action']=='edit'" (click)="submit('edit')" cButton color="primary" size="sm">
|
||||
<i class="fa-solid fa-save me-1"></i>Save
|
||||
</button>
|
||||
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary" size="sm">
|
||||
<i class="fa-solid fa-times me-1"></i>Close
|
||||
</button>
|
||||
</div>
|
||||
</c-modal-footer>
|
||||
|
|
|
|||
|
|
@ -78,6 +78,12 @@ export class UserManagerComponent implements OnInit {
|
|||
public permission: any = {};
|
||||
public allDevGroups: any = [];
|
||||
public allPerms: any = [];
|
||||
public devgroupSearch: string = '';
|
||||
public permissionSearch: string = '';
|
||||
public filteredDevGroups: any = [];
|
||||
public filteredPermissions: any = [];
|
||||
public showDevGroupDropdown: boolean = false;
|
||||
public showPermissionDropdown: boolean = false;
|
||||
public DeletePermConfirmModalVisible: boolean = false;
|
||||
public userperms: any = {};
|
||||
public userresttrictions: any = false;
|
||||
|
|
@ -129,6 +135,40 @@ export class UserManagerComponent implements OnInit {
|
|||
this.adminperms[key] = value;
|
||||
}
|
||||
|
||||
filterDevGroups(event: any): void {
|
||||
const query = event.target.value.toLowerCase();
|
||||
this.filteredDevGroups = this.allDevGroups.filter((group: any) =>
|
||||
group.name.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
filterPermissions(event: any): void {
|
||||
const query = event.target.value.toLowerCase();
|
||||
this.filteredPermissions = this.allPerms.filter((perm: any) =>
|
||||
perm.name.toLowerCase().includes(query)
|
||||
);
|
||||
}
|
||||
|
||||
selectDevGroup(group: any): void {
|
||||
this.devgroup = group;
|
||||
this.devgroupSearch = group.name;
|
||||
this.showDevGroupDropdown = false;
|
||||
}
|
||||
|
||||
selectPermission(perm: any): void {
|
||||
this.permission = perm;
|
||||
this.permissionSearch = perm.name;
|
||||
this.showPermissionDropdown = false;
|
||||
}
|
||||
|
||||
hideDevGroupDropdown(): void {
|
||||
setTimeout(() => this.showDevGroupDropdown = false, 200);
|
||||
}
|
||||
|
||||
hidePermissionDropdown(): void {
|
||||
setTimeout(() => this.showPermissionDropdown = false, 200);
|
||||
}
|
||||
|
||||
public rowSelection: boolean | GuiRowSelection = {
|
||||
enabled: true,
|
||||
type: GuiRowSelectionType.CHECKBOX,
|
||||
|
|
@ -226,6 +266,7 @@ export class UserManagerComponent implements OnInit {
|
|||
_self.allPerms = res.map((x: any) => {
|
||||
return { id: x["id"], name: x.name };
|
||||
});
|
||||
_self.filteredPermissions = [..._self.allPerms];
|
||||
_self.data_provider.get_devgroup_list().then((res) => {
|
||||
if ("error" in res && res.error.indexOf("Unauthorized")) {
|
||||
_self.show_toast(
|
||||
|
|
@ -238,6 +279,7 @@ export class UserManagerComponent implements OnInit {
|
|||
_self.allDevGroups = res.map((x: any) => {
|
||||
return { id: x["id"], name: x.name };
|
||||
});
|
||||
_self.filteredDevGroups = [..._self.allDevGroups];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -254,6 +296,10 @@ export class UserManagerComponent implements OnInit {
|
|||
action: "add",
|
||||
};
|
||||
this.adminperms = { ...this.defadminperms };
|
||||
this.devgroup = {};
|
||||
this.permission = {};
|
||||
this.devgroupSearch = '';
|
||||
this.permissionSearch = '';
|
||||
this.EditTaskModalVisible = true;
|
||||
return;
|
||||
}
|
||||
|
|
@ -263,6 +309,10 @@ export class UserManagerComponent implements OnInit {
|
|||
} else this.adminperms = { ...this.defadminperms };
|
||||
_self.SelectedUser["action"] = "edit";
|
||||
_self.get_user_perms(_self.SelectedUser["id"]);
|
||||
_self.devgroup = {};
|
||||
_self.permission = {};
|
||||
_self.devgroupSearch = '';
|
||||
_self.permissionSearch = '';
|
||||
_self.EditTaskModalVisible = true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,462 @@
|
|||
table tr td{
|
||||
padding-bottom:20px;
|
||||
vertical-align:top
|
||||
}
|
||||
|
||||
/* Enhanced Modal Styles */
|
||||
.permission-item {
|
||||
transition: all 0.2s ease;
|
||||
background: #fafafa;
|
||||
min-height: 60px;
|
||||
}
|
||||
|
||||
.permission-item:hover {
|
||||
background: #f0f0f0;
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
/* Modern Form Sections */
|
||||
.user-form-section, .permissions-section, .device-permissions-section {
|
||||
border-radius: 8px;
|
||||
background: #f8f9fa;
|
||||
padding: 1rem;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
color: #495057;
|
||||
font-weight: 600;
|
||||
margin-bottom: 0.75rem;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.form-input {
|
||||
border-radius: 6px;
|
||||
border: 1px solid #ced4da;
|
||||
padding: 0.5rem 0.75rem;
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.form-input:focus {
|
||||
border-color: #0d6efd;
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
/* Compact Permissions */
|
||||
.permissions-compact {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.perm-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0.4rem 0.6rem;
|
||||
background: white;
|
||||
border: 1px solid #dee2e6;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.perm-row:hover {
|
||||
background: #f1f3f4;
|
||||
border-color: #adb5bd;
|
||||
}
|
||||
|
||||
.perm-label {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
color: #495057;
|
||||
text-transform: capitalize;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.perm-controls {
|
||||
display: flex;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.perm-controls .btn {
|
||||
padding: 0.2rem 0.4rem;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
min-width: 24px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Section Headers */
|
||||
.section-header {
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Permission Legend */
|
||||
.permission-legend {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
padding: 0.5rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.legend-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.legend-color {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
border-radius: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
/* Add Permission Card */
|
||||
.add-permission-card {
|
||||
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
|
||||
border: 2px dashed #0d6efd;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.add-permission-card:hover {
|
||||
border-color: #0a58ca;
|
||||
background: linear-gradient(135deg, #e7f3ff 0%, #cfe2ff 100%);
|
||||
}
|
||||
|
||||
.add-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 0.9rem;
|
||||
color: #495057;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.add-btn {
|
||||
font-weight: 600;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.add-btn:disabled {
|
||||
opacity: 0.6;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Current Permissions */
|
||||
.current-permissions {
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #dee2e6;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.permissions-header {
|
||||
background: #f8f9fa;
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid #dee2e6;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.permissions-list {
|
||||
max-height: 250px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.permission-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.permission-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.permission-item:hover {
|
||||
background: #f8f9fa;
|
||||
}
|
||||
|
||||
.perm-number {
|
||||
background: #0d6efd;
|
||||
color: white;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
margin-right: 0.75rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.perm-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.perm-main {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.perm-detail {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.group-name {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.perm-name {
|
||||
font-size: 0.75rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.remove-btn {
|
||||
margin-left: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.empty-permissions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 2rem;
|
||||
margin-right: 1rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-text strong {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
color: #495057;
|
||||
}
|
||||
|
||||
.compact-select {
|
||||
font-size: 0.85rem;
|
||||
height: calc(1.8em + 0.75rem + 2px);
|
||||
padding: 0.375rem 0.75rem;
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.compact-select:focus {
|
||||
border-color: #0d6efd;
|
||||
box-shadow: 0 0 0 0.2rem rgba(13, 110, 253, 0.25);
|
||||
}
|
||||
|
||||
/* Search Select Components */
|
||||
.search-select-wrapper {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-label {
|
||||
display: block;
|
||||
font-size: 0.8rem;
|
||||
color: #6c757d;
|
||||
margin-bottom: 0.25rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.search-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
background: white;
|
||||
border: 1px solid #ced4da;
|
||||
border-top: none;
|
||||
border-radius: 0 0 0.375rem 0.375rem;
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
z-index: 1000;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.search-option {
|
||||
padding: 0.5rem 0.75rem;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid #f1f3f4;
|
||||
font-size: 0.85rem;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.search-option:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.search-option:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.search-no-results {
|
||||
padding: 0.75rem;
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
font-size: 0.8rem;
|
||||
font-style: italic;
|
||||
background: white;
|
||||
border: 1px solid #ced4da;
|
||||
border-top: none;
|
||||
border-radius: 0 0 0.375rem 0.375rem;
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.add-permission-form {
|
||||
border: 1px dashed #dee2e6;
|
||||
}
|
||||
|
||||
.c-modal-body {
|
||||
max-height: 85vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.c-card-header {
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.permission-item small {
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.c-button-group .btn {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
min-width: 28px;
|
||||
}
|
||||
|
||||
.compact-field {
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
|
||||
.compact-field .mat-form-field-wrapper {
|
||||
padding-bottom: 0.5em;
|
||||
}
|
||||
|
||||
/* Mobile Responsive */
|
||||
@media (max-width: 576px) {
|
||||
.c-modal-dialog {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
|
||||
.user-form-section, .permissions-section, .device-permissions-section {
|
||||
padding: 0.75rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.permissions-compact {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.perm-row {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.perm-label {
|
||||
text-align: center;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.perm-controls {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.add-permission-card {
|
||||
padding: 0.75rem;
|
||||
}
|
||||
|
||||
.permission-legend {
|
||||
gap: 0.5rem;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.permissions-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.empty-permissions {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
margin-right: 0;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.c-button-group .btn {
|
||||
font-size: 0.7rem;
|
||||
padding: 0.25rem 0.3rem;
|
||||
min-width: 24px;
|
||||
}
|
||||
|
||||
.c-modal-body {
|
||||
max-height: 90vh;
|
||||
padding: 0.5rem !important;
|
||||
}
|
||||
|
||||
.c-card-body {
|
||||
padding: 0.75rem !important;
|
||||
}
|
||||
|
||||
.add-permission-form {
|
||||
padding: 0.75rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.c-modal-footer {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
|
||||
.c-modal-footer > div {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue