Skip to content

Commit c2c6b10

Browse files
committed
UI Added for Role Permissions
1 parent e2bb943 commit c2c6b10

File tree

7 files changed

+566
-0
lines changed

7 files changed

+566
-0
lines changed

frontend/src/app/@core/models/permission.interface.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,42 @@ export interface Permission {
1212
}
1313

1414
export interface RolePermission {
15+
roleId: string;
1516
roleName: string;
17+
pageId: string;
1618
pageName: string;
1719
pageUrl: string;
20+
operationId: string;
1821
operationName: string;
22+
permissionId: string;
23+
}
24+
25+
export interface RolePermissionMappingResponse {
26+
roleId: string;
27+
roleName: string;
28+
pages: PageOperationResponse[];
29+
}
30+
31+
export interface PageOperationResponse {
32+
pageId: string;
33+
pageName: string;
34+
operations: OperationResponse[];
35+
}
36+
37+
export interface OperationResponse {
38+
operationId: string;
39+
operationName: string;
40+
isSelected: boolean;
41+
}
42+
43+
export interface RolePermissionMappingRequest {
44+
roleId: string;
45+
permissions: PageOperationRequest[];
46+
}
47+
48+
export interface PageOperationRequest {
49+
pageId: string;
50+
operationIds: string[];
1951
}
2052

2153
export interface UserWithPermissions {
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Models for role-permission mapping
2+
export interface RolePermissionMappingResponse {
3+
roleId: string;
4+
roleName: string;
5+
pages: PageOperationResponse[];
6+
}
7+
8+
export interface PageOperationResponse {
9+
pageId: string;
10+
pageName: string;
11+
operations: OperationResponse[];
12+
}
13+
14+
export interface OperationResponse {
15+
operationId: string;
16+
operationName: string;
17+
isSelected: boolean;
18+
}
19+
20+
export interface RolePermissionMappingRequest {
21+
roleId: string;
22+
permissions: PageOperationRequest[];
23+
}
24+
25+
export interface PageOperationRequest {
26+
pageId: string;
27+
operationIds: string[];
28+
}
29+
30+
// Simple role model for dropdown selection
31+
export interface RoleModel {
32+
id: string;
33+
name: string;
34+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { HttpClient } from '@angular/common/http';
2+
import { Injectable, inject } from '@angular/core';
3+
import { Observable } from 'rxjs';
4+
import { environment } from '@environments/environment';
5+
import {
6+
RolePermissionMappingResponse,
7+
RolePermissionMappingRequest,
8+
RoleModel
9+
} from '@core/models/role-permission-mapping.model';
10+
11+
@Injectable({
12+
providedIn: 'root'
13+
})
14+
export class RolePermissionMappingService {
15+
private http = inject(HttpClient);
16+
private baseUrl = `${environment.apiEndpoint}/RolePermissionMapping`;
17+
18+
/**
19+
* Get all available roles for selection
20+
*/
21+
getRoles(): Observable<RoleModel[]> {
22+
return this.http.get<RoleModel[]>(`${environment.apiEndpoint}/roles`);
23+
}
24+
25+
/**
26+
* Get role-permission mapping for a specific role
27+
* @param roleId The ID of the role to get mapping for
28+
*/
29+
getRolePermissionMapping(roleId: string): Observable<RolePermissionMappingResponse> {
30+
return this.http.get<RolePermissionMappingResponse>(`${this.baseUrl}/${roleId}`);
31+
}
32+
33+
/**
34+
* Save updated role-permission mapping
35+
* @param mapping The updated role-permission mapping to save
36+
*/
37+
saveRolePermissionMapping(mapping: RolePermissionMappingRequest): Observable<any> {
38+
return this.http.post<any>(this.baseUrl, mapping);
39+
}
40+
}

frontend/src/app/feature/admin/admin.routes.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { RolePermissionsComponent } from "./role-permissions/role-permissions.co
1010
import { UserRolesComponent } from "./user-roles/user-roles.component";
1111
import { RolesComponent } from "./roles/roles.component";
1212
import { UsersComponent } from "./users/users.component";
13+
import { RolePermissionMappingComponent } from "./role-permission-mapping/role-permission-mapping.component";
1314

1415
export default [
1516
{
@@ -57,6 +58,11 @@ export default [
5758
component: UserRolesComponent,
5859
canActivate: [PermissionGuard('UserRoles', 'Read')]
5960
},
61+
{
62+
path: 'role-permission-mapping',
63+
component: RolePermissionMappingComponent,
64+
canActivate: [PermissionGuard('RolePermissions', 'Read')]
65+
},
6066
{
6167
path: '',
6268
redirectTo: 'users',
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
<div class="container mx-auto p-4">
2+
<!-- Header -->
3+
<div class="max-w-6xl mx-auto mb-6 bg-primary text-white rounded-lg shadow-sm overflow-hidden transition-all duration-300">
4+
<div class="p-6 flex justify-between items-center">
5+
<h1 class="text-2xl font-bold">Role Permission Mapping</h1>
6+
</div>
7+
</div>
8+
9+
<!-- Role Selection Card -->
10+
<div class="max-w-6xl mx-auto mb-6 bg-surface-container-highest rounded-lg shadow-sm overflow-hidden transition-all duration-300">
11+
<div class="p-6">
12+
<h2 class="text-xl font-medium mb-4">Select Role</h2>
13+
14+
<div class="flex flex-col md:flex-row items-center gap-4">
15+
<mat-form-field appearance="outline" class="w-full md:w-1/2">
16+
<mat-label>Role</mat-label>
17+
<select matNativeControl [value]="selectedRoleId()" (change)="onRoleChange($event)">
18+
<option>Please select a Role</option>
19+
@for (role of roles(); track role.id) {
20+
<option [value]="role.id">{{ role.name }}</option>
21+
}
22+
</select>
23+
</mat-form-field>
24+
25+
<div class="flex-grow"></div>
26+
27+
<button mat-raised-button color="primary" (click)="saveMapping()" [disabled]="loading() || saving() || !mapping()">
28+
<div class="flex items-center gap-2">
29+
@if (saving()) {
30+
<mat-spinner [diameter]="20"></mat-spinner>
31+
}
32+
<mat-icon>save</mat-icon>
33+
<span>Save Permissions</span>
34+
</div>
35+
</button>
36+
</div>
37+
</div>
38+
</div>
39+
40+
<!-- Loading State -->
41+
@if (loading()) {
42+
<div class="max-w-6xl mx-auto mt-8 text-center">
43+
<mat-spinner diameter="40" class="mx-auto"></mat-spinner>
44+
<p class="mt-4 text-on-surface-variant">Loading role permissions...</p>
45+
</div>
46+
}
47+
48+
<!-- Page-Operation Matrix -->
49+
@if (mapping() && !loading()) {
50+
<div class="max-w-6xl mx-auto">
51+
<h2 class="text-xl font-medium mb-4">Configure Permissions for {{ mapping()?.roleName }}</h2>
52+
53+
<div class="bg-surface-container-highest rounded-lg shadow-sm overflow-hidden transition-all duration-300 mb-6">
54+
<div class="p-4">
55+
<mat-card appearance="outlined" class="mb-4">
56+
<mat-card-header>
57+
<mat-card-title>Instructions</mat-card-title>
58+
</mat-card-header>
59+
<mat-card-content>
60+
<p>Select the operations each page should have access to. Click on a page row to expand and view all available operations.</p>
61+
<ul class="mt-2 list-disc pl-6">
62+
<li>Use the checkboxes to grant or revoke specific permissions</li>
63+
<li>Click the page name to toggle all operations for that page</li>
64+
<li>Click "Save Permissions" when you're done making changes</li>
65+
</ul>
66+
</mat-card-content>
67+
</mat-card>
68+
69+
<!-- Table of Permission Mappings -->
70+
<table class="w-full border-collapse">
71+
<thead>
72+
<tr class="bg-surface-container">
73+
<th class="px-4 py-3 text-left">Page</th>
74+
<th class="px-4 py-3 text-left">Permissions</th>
75+
</tr>
76+
</thead>
77+
<tbody>
78+
@for (page of mapping()?.pages; track page.pageId) {
79+
<tr class="border-b border-outline-variant hover:bg-surface-container transition-colors cursor-pointer">
80+
<td class="px-4 py-4 w-1/4">
81+
<!-- Page Name with Expand/Collapse Icon -->
82+
<div class="flex items-center" (click)="togglePanel(page.pageId)">
83+
<mat-icon>{{ isPanelExpanded(page.pageId) ? 'expand_less' : 'expand_more' }}</mat-icon>
84+
<span class="ml-2 font-medium">{{ page.pageName }}</span>
85+
</div>
86+
</td>
87+
<td class="px-4 py-4">
88+
<!-- All Operations Toggle -->
89+
<div class="flex items-center">
90+
<mat-checkbox
91+
[checked]="areAllOperationsSelected(page)"
92+
[indeterminate]="areSomeOperationsSelected(page)"
93+
(change)="toggleAllOperations(page, $event.checked)">
94+
{{ areAllOperationsSelected(page) ? 'All operations' : areSomeOperationsSelected(page) ? 'Some operations' : 'No operations' }}
95+
</mat-checkbox>
96+
</div>
97+
98+
<!-- Expanded Operations List -->
99+
@if (isPanelExpanded(page.pageId)) {
100+
<div class="mt-3 pl-8 grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-3">
101+
@for (operation of page.operations; track operation.operationId) {
102+
<mat-checkbox
103+
[checked]="operation.isSelected"
104+
(change)="toggleOperation(page, operation.operationId)">
105+
{{ operation.operationName }}
106+
</mat-checkbox>
107+
}
108+
</div>
109+
}
110+
</td>
111+
</tr>
112+
}
113+
</tbody>
114+
</table>
115+
</div>
116+
</div>
117+
118+
<!-- Save Button (Bottom) -->
119+
<div class="flex justify-end mb-8">
120+
<button mat-raised-button color="primary" (click)="saveMapping()" [disabled]="saving()">
121+
<div class="flex items-center gap-2">
122+
@if (saving()) {
123+
<mat-spinner [diameter]="20"></mat-spinner>
124+
}
125+
<mat-icon>save</mat-icon>
126+
<span>Save Permissions</span>
127+
</div>
128+
</button>
129+
</div>
130+
</div>
131+
}
132+
133+
<!-- No Data State -->
134+
@if (!mapping() && !loading()) {
135+
<div class="max-w-6xl mx-auto my-8 p-6 bg-surface-container-highest rounded-lg text-center">
136+
<mat-icon class="text-6xl text-on-surface-variant mb-4">find_in_page</mat-icon>
137+
<p class="text-xl text-on-surface-variant">No roles available or permission data could not be loaded.</p>
138+
<button mat-raised-button color="primary" class="mt-4" (click)="loadRoles()">
139+
<mat-icon>refresh</mat-icon>
140+
Retry
141+
</button>
142+
</div>
143+
}
144+
</div>
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
:host {
2+
display: block;
3+
}
4+
5+
mat-card {
6+
@reference bg-surface-container-low dark:bg-surface-container;
7+
}
8+
9+
mat-expansion-panel {
10+
@reference mb-2;
11+
}
12+
13+
mat-checkbox {
14+
@reference my-1;
15+
16+
&:hover {
17+
.mdc-checkbox__background {
18+
border-color: var(--md-sys-color-primary);
19+
}
20+
}
21+
}
22+
23+
.mat-mdc-card-header {
24+
@reference pb-4;
25+
}
26+
27+
.mat-expansion-panel-header {
28+
@reference h-14;
29+
}
30+
31+
// Styling for the role permission mapping component
32+
.mat-mdc-card {
33+
border-radius: 8px;
34+
}
35+
36+
.mat-spinner {
37+
display: inline-block;
38+
}
39+
40+
// Custom styling for expansion panel animation
41+
.mat-icon {
42+
transition: transform 0.3s ease;
43+
}
44+
45+
// Add hover effects to table rows
46+
tr.border-b:hover {
47+
background-color: var(--md-sys-color-surface-container-high);
48+
}
49+
50+
// Custom styles for the role selector
51+
mat-form-field {
52+
.mat-mdc-form-field-subscript-wrapper {
53+
height: 0;
54+
}
55+
}
56+
57+
// Responsive adjustments for smaller screens
58+
@media (max-width: 768px) {
59+
.grid-cols-3 {
60+
grid-template-columns: repeat(1, minmax(0, 1fr));
61+
}
62+
}

0 commit comments

Comments
 (0)