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:
sepehr 2025-10-16 17:34:13 +03:00
parent 746711fa24
commit 069e53cec6
3 changed files with 696 additions and 112 deletions

View file

@ -56,124 +56,200 @@
<c-modal #EditTaskModal backdrop="static" size="lg" [(visible)]="EditTaskModalVisible" id="EditTaskModal"> <c-modal #EditTaskModal backdrop="static" size="lg" [(visible)]="EditTaskModalVisible" id="EditTaskModal">
<c-modal-header> <c-modal-header>
<h5 *ngIf="SelectedUser['action']=='edit'" cModalTitle>Editing User {{SelectedUser['name']}}</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>Adding new User</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> <button [cModalToggle]="EditTaskModal.id" cButtonClose></button>
</c-modal-header> </c-modal-header>
<c-modal-body> <c-modal-body class="p-3">
<div [cFormFloating]="true" class="mb-3"> <!-- User Details -->
<input cFormControl id="floatingInput" placeholder="User Name" [(ngModel)]="SelectedUser['username']" /> <div class="user-form-section mb-4">
<label cLabel for="floatingInput">User Name</label> <div class="section-header mb-3">
</div> <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>
<c-input-group class="mb-3"> </div>
<span cInputGroupText>First Name</span> <c-row class="g-3">
<input cFormControl id="floatingInput" placeholder="First Name" [(ngModel)]="SelectedUser['first_name']" /> <c-col xs="12" md="6">
<input cFormControl placeholder="Username (required for login)" [(ngModel)]="SelectedUser['username']"
<span cInputGroupText>Last Name</span> class="form-input" title="Unique username for system login" />
<input cFormControl id="floatingInput" placeholder="Last Name" [(ngModel)]="SelectedUser['last_name']" /> </c-col>
</c-input-group> <c-col xs="12" md="6">
<input cFormControl placeholder="Email Address (for notifications)" [(ngModel)]="SelectedUser['email']"
<div [cFormFloating]="true" class="mb-3"> class="form-input" type="email" title="Valid email address for system notifications" />
<input cFormControl id="floatingInput" placeholder="Email Address" [(ngModel)]="SelectedUser['email']" /> </c-col>
<label cLabel for="floatingInput">Email Address</label> <c-col xs="12" md="4">
</div> <input cFormControl placeholder="First Name (optional)" [(ngModel)]="SelectedUser['first_name']"
class="form-input" title="User's first name" />
<div [cFormFloating]="true" class="mb-3"> </c-col>
<input type="password" cFormControl id="floatingInput" placeholder="Password" [(ngModel)]="SelectedUser['password']" /> <c-col xs="12" md="4">
<label cLabel for="floatingInput">Password</label> <input cFormControl placeholder="Last Name (optional)" [(ngModel)]="SelectedUser['last_name']"
</div> class="form-input" title="User's last name" />
<c-input-group> </c-col>
<h5>MikroWizard permisssions :</h5> <c-col xs="12" md="4">
<c-container> <input type="password" cFormControl placeholder="Password (min 6 characters)" [(ngModel)]="SelectedUser['password']"
<c-row> class="form-input" title="Secure password for user login" />
<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-col> </c-col>
</c-row> </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">
&nbsp; {{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> </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> <div>
<button *ngIf="SelectedUser['action']=='add'" (click)="submit('add')" cButton color="primary">Add</button> <button *ngIf="SelectedUser['role']!='disabled'" (click)="SelectedUser['role']='disabled'" cButton color="danger" size="sm">
<button *ngIf="SelectedUser['action']=='edit'" (click)="submit('edit')" cButton color="primary">save</button> <i class="fa-solid fa-user-slash me-1"></i><span class="d-none d-sm-inline">Deactivate</span>
<button [cModalToggle]="EditTaskModal.id" cButton color="secondary"> </button>
Close <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> </button>
</div> </div>
</c-modal-footer> </c-modal-footer>

View file

@ -78,6 +78,12 @@ export class UserManagerComponent implements OnInit {
public permission: any = {}; public permission: any = {};
public allDevGroups: any = []; public allDevGroups: any = [];
public allPerms: 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 DeletePermConfirmModalVisible: boolean = false;
public userperms: any = {}; public userperms: any = {};
public userresttrictions: any = false; public userresttrictions: any = false;
@ -129,6 +135,40 @@ export class UserManagerComponent implements OnInit {
this.adminperms[key] = value; 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 = { public rowSelection: boolean | GuiRowSelection = {
enabled: true, enabled: true,
type: GuiRowSelectionType.CHECKBOX, type: GuiRowSelectionType.CHECKBOX,
@ -226,6 +266,7 @@ export class UserManagerComponent implements OnInit {
_self.allPerms = res.map((x: any) => { _self.allPerms = res.map((x: any) => {
return { id: x["id"], name: x.name }; return { id: x["id"], name: x.name };
}); });
_self.filteredPermissions = [..._self.allPerms];
_self.data_provider.get_devgroup_list().then((res) => { _self.data_provider.get_devgroup_list().then((res) => {
if ("error" in res && res.error.indexOf("Unauthorized")) { if ("error" in res && res.error.indexOf("Unauthorized")) {
_self.show_toast( _self.show_toast(
@ -238,6 +279,7 @@ export class UserManagerComponent implements OnInit {
_self.allDevGroups = res.map((x: any) => { _self.allDevGroups = res.map((x: any) => {
return { id: x["id"], name: x.name }; return { id: x["id"], name: x.name };
}); });
_self.filteredDevGroups = [..._self.allDevGroups];
} }
}); });
} }
@ -254,6 +296,10 @@ export class UserManagerComponent implements OnInit {
action: "add", action: "add",
}; };
this.adminperms = { ...this.defadminperms }; this.adminperms = { ...this.defadminperms };
this.devgroup = {};
this.permission = {};
this.devgroupSearch = '';
this.permissionSearch = '';
this.EditTaskModalVisible = true; this.EditTaskModalVisible = true;
return; return;
} }
@ -263,6 +309,10 @@ export class UserManagerComponent implements OnInit {
} else this.adminperms = { ...this.defadminperms }; } else this.adminperms = { ...this.defadminperms };
_self.SelectedUser["action"] = "edit"; _self.SelectedUser["action"] = "edit";
_self.get_user_perms(_self.SelectedUser["id"]); _self.get_user_perms(_self.SelectedUser["id"]);
_self.devgroup = {};
_self.permission = {};
_self.devgroupSearch = '';
_self.permissionSearch = '';
_self.EditTaskModalVisible = true; _self.EditTaskModalVisible = true;
} }

View file

@ -1,4 +1,462 @@
table tr td{ table tr td{
padding-bottom:20px; padding-bottom:20px;
vertical-align:top 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;
}
} }