@memberjunction/ng-explorer-settings 2.70.0 → 2.71.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/lib/application-management/application-dialog/application-dialog.component.d.ts +60 -0
- package/dist/lib/application-management/application-dialog/application-dialog.component.d.ts.map +1 -0
- package/dist/lib/application-management/application-dialog/application-dialog.component.js +584 -0
- package/dist/lib/application-management/application-dialog/application-dialog.component.js.map +1 -0
- package/dist/lib/application-management/application-management.component.d.ts +4 -2
- package/dist/lib/application-management/application-management.component.d.ts.map +1 -1
- package/dist/lib/application-management/application-management.component.js +79 -35
- package/dist/lib/application-management/application-management.component.js.map +1 -1
- package/dist/lib/entity-permissions/entity-permissions.component.d.ts +3 -1
- package/dist/lib/entity-permissions/entity-permissions.component.d.ts.map +1 -1
- package/dist/lib/entity-permissions/entity-permissions.component.js +43 -66
- package/dist/lib/entity-permissions/entity-permissions.component.js.map +1 -1
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.d.ts +50 -0
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.d.ts.map +1 -0
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.js +464 -0
- package/dist/lib/entity-permissions/permission-dialog/permission-dialog.component.js.map +1 -0
- package/dist/lib/role-management/role-dialog/role-dialog.component.d.ts +38 -0
- package/dist/lib/role-management/role-dialog/role-dialog.component.d.ts.map +1 -0
- package/dist/lib/role-management/role-dialog/role-dialog.component.js +329 -0
- package/dist/lib/role-management/role-dialog/role-dialog.component.js.map +1 -0
- package/dist/lib/role-management/role-management.component.d.ts +4 -0
- package/dist/lib/role-management/role-management.component.d.ts.map +1 -1
- package/dist/lib/role-management/role-management.component.js +114 -72
- package/dist/lib/role-management/role-management.component.js.map +1 -1
- package/dist/lib/settings/settings.component.js +8 -8
- package/dist/lib/settings/settings.component.js.map +1 -1
- package/dist/lib/shared/components/settings-card/settings-card.component.d.ts.map +1 -1
- package/dist/lib/shared/components/settings-card/settings-card.component.js +11 -8
- package/dist/lib/shared/components/settings-card/settings-card.component.js.map +1 -1
- package/dist/lib/user-management/user-dialog/user-dialog.component.d.ts +44 -0
- package/dist/lib/user-management/user-dialog/user-dialog.component.d.ts.map +1 -0
- package/dist/lib/user-management/user-dialog/user-dialog.component.js +470 -0
- package/dist/lib/user-management/user-dialog/user-dialog.component.js.map +1 -0
- package/dist/lib/user-management/user-management.component.d.ts +9 -0
- package/dist/lib/user-management/user-management.component.d.ts.map +1 -1
- package/dist/lib/user-management/user-management.component.js +213 -98
- package/dist/lib/user-management/user-management.component.js.map +1 -1
- package/package.json +13 -13
|
@@ -3,8 +3,10 @@ import { CommonModule } from '@angular/common';
|
|
|
3
3
|
import { FormsModule } from '@angular/forms';
|
|
4
4
|
import { Subject, BehaviorSubject } from 'rxjs';
|
|
5
5
|
import { takeUntil, debounceTime, distinctUntilChanged } from 'rxjs/operators';
|
|
6
|
-
import { RunView } from '@memberjunction/core';
|
|
6
|
+
import { RunView, Metadata } from '@memberjunction/core';
|
|
7
7
|
import { SharedSettingsModule } from '../shared/shared-settings.module';
|
|
8
|
+
import { UserDialogComponent } from './user-dialog/user-dialog.component';
|
|
9
|
+
import { WindowModule } from '@progress/kendo-angular-dialog';
|
|
8
10
|
import * as i0 from "@angular/core";
|
|
9
11
|
import * as i1 from "@angular/common";
|
|
10
12
|
import * as i2 from "@angular/forms";
|
|
@@ -20,21 +22,21 @@ function UserManagementComponent_For_66_Template(rf, ctx) { if (rf & 1) {
|
|
|
20
22
|
i0.ɵɵtextInterpolate(role_r1.Name);
|
|
21
23
|
} }
|
|
22
24
|
function UserManagementComponent_Conditional_72_Template(rf, ctx) { if (rf & 1) {
|
|
23
|
-
i0.ɵɵelementStart(0, "div", 38)(1, "div",
|
|
24
|
-
i0.ɵɵelement(2, "div",
|
|
25
|
+
i0.ɵɵelementStart(0, "div", 38)(1, "div", 43);
|
|
26
|
+
i0.ɵɵelement(2, "div", 44)(3, "div", 44)(4, "div", 44);
|
|
25
27
|
i0.ɵɵelementEnd();
|
|
26
|
-
i0.ɵɵelementStart(5, "div",
|
|
28
|
+
i0.ɵɵelementStart(5, "div", 45);
|
|
27
29
|
i0.ɵɵtext(6, "Loading users...");
|
|
28
30
|
i0.ɵɵelementEnd()();
|
|
29
31
|
} }
|
|
30
32
|
function UserManagementComponent_Conditional_73_Template(rf, ctx) { if (rf & 1) {
|
|
31
33
|
const _r2 = i0.ɵɵgetCurrentView();
|
|
32
|
-
i0.ɵɵelementStart(0, "div", 39)(1, "div",
|
|
33
|
-
i0.ɵɵelement(2, "i",
|
|
34
|
-
i0.ɵɵelementStart(3, "p",
|
|
34
|
+
i0.ɵɵelementStart(0, "div", 39)(1, "div", 46);
|
|
35
|
+
i0.ɵɵelement(2, "i", 47);
|
|
36
|
+
i0.ɵɵelementStart(3, "p", 48);
|
|
35
37
|
i0.ɵɵtext(4);
|
|
36
38
|
i0.ɵɵelementEnd();
|
|
37
|
-
i0.ɵɵelementStart(5, "button",
|
|
39
|
+
i0.ɵɵelementStart(5, "button", 49);
|
|
38
40
|
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_73_Template_button_click_5_listener() { i0.ɵɵrestoreView(_r2); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.loadInitialData()); });
|
|
39
41
|
i0.ɵɵelement(6, "i", 3);
|
|
40
42
|
i0.ɵɵtext(7, " Try Again ");
|
|
@@ -46,49 +48,49 @@ function UserManagementComponent_Conditional_73_Template(rf, ctx) { if (rf & 1)
|
|
|
46
48
|
} }
|
|
47
49
|
function UserManagementComponent_Conditional_74_Conditional_1_For_20_Template(rf, ctx) { if (rf & 1) {
|
|
48
50
|
const _r4 = i0.ɵɵgetCurrentView();
|
|
49
|
-
i0.ɵɵelementStart(0, "tr",
|
|
51
|
+
i0.ɵɵelementStart(0, "tr", 58);
|
|
50
52
|
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_74_Conditional_1_For_20_Template_tr_click_0_listener() { const user_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.selectUser(user_r5)); });
|
|
51
|
-
i0.ɵɵelementStart(1, "td",
|
|
53
|
+
i0.ɵɵelementStart(1, "td", 59);
|
|
52
54
|
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_74_Conditional_1_For_20_Template_td_click_1_listener($event) { i0.ɵɵrestoreView(_r4); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
53
|
-
i0.ɵɵelement(2, "input",
|
|
55
|
+
i0.ɵɵelement(2, "input", 54);
|
|
54
56
|
i0.ɵɵelementEnd();
|
|
55
|
-
i0.ɵɵelementStart(3, "td")(4, "div",
|
|
57
|
+
i0.ɵɵelementStart(3, "td")(4, "div", 60)(5, "div", 61);
|
|
56
58
|
i0.ɵɵtext(6);
|
|
57
59
|
i0.ɵɵelementEnd();
|
|
58
|
-
i0.ɵɵelementStart(7, "div",
|
|
60
|
+
i0.ɵɵelementStart(7, "div", 62)(8, "div", 63);
|
|
59
61
|
i0.ɵɵtext(9);
|
|
60
62
|
i0.ɵɵelementEnd();
|
|
61
|
-
i0.ɵɵelementStart(10, "div",
|
|
63
|
+
i0.ɵɵelementStart(10, "div", 64);
|
|
62
64
|
i0.ɵɵtext(11);
|
|
63
65
|
i0.ɵɵelementEnd()()()();
|
|
64
66
|
i0.ɵɵelementStart(12, "td");
|
|
65
67
|
i0.ɵɵtext(13);
|
|
66
68
|
i0.ɵɵelementEnd();
|
|
67
|
-
i0.ɵɵelementStart(14, "td")(15, "div",
|
|
69
|
+
i0.ɵɵelementStart(14, "td")(15, "div", 65);
|
|
68
70
|
i0.ɵɵelement(16, "i");
|
|
69
71
|
i0.ɵɵtext(17);
|
|
70
72
|
i0.ɵɵelementEnd()();
|
|
71
|
-
i0.ɵɵelementStart(18, "td")(19, "span",
|
|
73
|
+
i0.ɵɵelementStart(18, "td")(19, "span", 66);
|
|
72
74
|
i0.ɵɵelement(20, "i");
|
|
73
75
|
i0.ɵɵtext(21);
|
|
74
76
|
i0.ɵɵelementEnd()();
|
|
75
|
-
i0.ɵɵelementStart(22, "td")(23, "span",
|
|
77
|
+
i0.ɵɵelementStart(22, "td")(23, "span", 67);
|
|
76
78
|
i0.ɵɵtext(24);
|
|
77
79
|
i0.ɵɵpipe(25, "date");
|
|
78
80
|
i0.ɵɵelementEnd()();
|
|
79
|
-
i0.ɵɵelementStart(26, "td",
|
|
81
|
+
i0.ɵɵelementStart(26, "td", 68);
|
|
80
82
|
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_74_Conditional_1_For_20_Template_td_click_26_listener($event) { i0.ɵɵrestoreView(_r4); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
81
|
-
i0.ɵɵelementStart(27, "button",
|
|
83
|
+
i0.ɵɵelementStart(27, "button", 69);
|
|
82
84
|
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_74_Conditional_1_For_20_Template_button_click_27_listener() { const user_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.editUser(user_r5)); });
|
|
83
|
-
i0.ɵɵelement(28, "i",
|
|
85
|
+
i0.ɵɵelement(28, "i", 70);
|
|
84
86
|
i0.ɵɵelementEnd();
|
|
85
|
-
i0.ɵɵelementStart(29, "button",
|
|
87
|
+
i0.ɵɵelementStart(29, "button", 71);
|
|
86
88
|
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_74_Conditional_1_For_20_Template_button_click_29_listener() { const user_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.toggleUserStatus(user_r5)); });
|
|
87
89
|
i0.ɵɵelement(30, "i");
|
|
88
90
|
i0.ɵɵelementEnd();
|
|
89
|
-
i0.ɵɵelementStart(31, "button",
|
|
91
|
+
i0.ɵɵelementStart(31, "button", 72);
|
|
90
92
|
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_74_Conditional_1_For_20_Template_button_click_31_listener() { const user_r5 = i0.ɵɵrestoreView(_r4).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.confirmDeleteUser(user_r5)); });
|
|
91
|
-
i0.ɵɵelement(32, "i",
|
|
93
|
+
i0.ɵɵelement(32, "i", 73);
|
|
92
94
|
i0.ɵɵelementEnd()()();
|
|
93
95
|
} if (rf & 2) {
|
|
94
96
|
const user_r5 = ctx.$implicit;
|
|
@@ -119,18 +121,18 @@ function UserManagementComponent_Conditional_74_Conditional_1_For_20_Template(rf
|
|
|
119
121
|
i0.ɵɵclassMap(user_r5.IsActive ? "fa-solid fa-toggle-on" : "fa-solid fa-toggle-off");
|
|
120
122
|
} }
|
|
121
123
|
function UserManagementComponent_Conditional_74_Conditional_1_Conditional_21_Template(rf, ctx) { if (rf & 1) {
|
|
122
|
-
i0.ɵɵelementStart(0, "div",
|
|
123
|
-
i0.ɵɵelement(1, "i",
|
|
124
|
-
i0.ɵɵelementStart(2, "p",
|
|
124
|
+
i0.ɵɵelementStart(0, "div", 57);
|
|
125
|
+
i0.ɵɵelement(1, "i", 74);
|
|
126
|
+
i0.ɵɵelementStart(2, "p", 75);
|
|
125
127
|
i0.ɵɵtext(3, "No users found");
|
|
126
128
|
i0.ɵɵelementEnd();
|
|
127
|
-
i0.ɵɵelementStart(4, "p",
|
|
129
|
+
i0.ɵɵelementStart(4, "p", 76);
|
|
128
130
|
i0.ɵɵtext(5, "Try adjusting your filters or add a new user");
|
|
129
131
|
i0.ɵɵelementEnd()();
|
|
130
132
|
} }
|
|
131
133
|
function UserManagementComponent_Conditional_74_Conditional_1_Template(rf, ctx) { if (rf & 1) {
|
|
132
|
-
i0.ɵɵelementStart(0, "div",
|
|
133
|
-
i0.ɵɵelement(5, "input",
|
|
134
|
+
i0.ɵɵelementStart(0, "div", 50)(1, "table", 52)(2, "thead")(3, "tr")(4, "th", 53);
|
|
135
|
+
i0.ɵɵelement(5, "input", 54);
|
|
134
136
|
i0.ɵɵelementEnd();
|
|
135
137
|
i0.ɵɵelementStart(6, "th");
|
|
136
138
|
i0.ɵɵtext(7, "User");
|
|
@@ -147,13 +149,13 @@ function UserManagementComponent_Conditional_74_Conditional_1_Template(rf, ctx)
|
|
|
147
149
|
i0.ɵɵelementStart(14, "th");
|
|
148
150
|
i0.ɵɵtext(15, "Last Updated");
|
|
149
151
|
i0.ɵɵelementEnd();
|
|
150
|
-
i0.ɵɵelementStart(16, "th",
|
|
152
|
+
i0.ɵɵelementStart(16, "th", 55);
|
|
151
153
|
i0.ɵɵtext(17, "Actions");
|
|
152
154
|
i0.ɵɵelementEnd()()();
|
|
153
155
|
i0.ɵɵelementStart(18, "tbody");
|
|
154
|
-
i0.ɵɵrepeaterCreate(19, UserManagementComponent_Conditional_74_Conditional_1_For_20_Template, 33, 20, "tr",
|
|
156
|
+
i0.ɵɵrepeaterCreate(19, UserManagementComponent_Conditional_74_Conditional_1_For_20_Template, 33, 20, "tr", 56, _forTrack0);
|
|
155
157
|
i0.ɵɵelementEnd()();
|
|
156
|
-
i0.ɵɵtemplate(21, UserManagementComponent_Conditional_74_Conditional_1_Conditional_21_Template, 6, 0, "div",
|
|
158
|
+
i0.ɵɵtemplate(21, UserManagementComponent_Conditional_74_Conditional_1_Conditional_21_Template, 6, 0, "div", 57);
|
|
157
159
|
i0.ɵɵelementEnd();
|
|
158
160
|
} if (rf & 2) {
|
|
159
161
|
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
@@ -164,39 +166,39 @@ function UserManagementComponent_Conditional_74_Conditional_1_Template(rf, ctx)
|
|
|
164
166
|
} }
|
|
165
167
|
function UserManagementComponent_Conditional_74_Conditional_2_For_2_Template(rf, ctx) { if (rf & 1) {
|
|
166
168
|
const _r6 = i0.ɵɵgetCurrentView();
|
|
167
|
-
i0.ɵɵelementStart(0, "div",
|
|
169
|
+
i0.ɵɵelementStart(0, "div", 78);
|
|
168
170
|
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_74_Conditional_2_For_2_Template_div_click_0_listener() { const user_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); return i0.ɵɵresetView(ctx_r2.selectUser(user_r7)); });
|
|
169
|
-
i0.ɵɵelementStart(1, "div",
|
|
171
|
+
i0.ɵɵelementStart(1, "div", 79)(2, "div", 80);
|
|
170
172
|
i0.ɵɵtext(3);
|
|
171
173
|
i0.ɵɵelementEnd();
|
|
172
|
-
i0.ɵɵelementStart(4, "div",
|
|
174
|
+
i0.ɵɵelementStart(4, "div", 81)(5, "button", 69);
|
|
173
175
|
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_74_Conditional_2_For_2_Template_button_click_5_listener($event) { const user_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); ctx_r2.editUser(user_r7); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
174
|
-
i0.ɵɵelement(6, "i",
|
|
176
|
+
i0.ɵɵelement(6, "i", 70);
|
|
175
177
|
i0.ɵɵelementEnd();
|
|
176
|
-
i0.ɵɵelementStart(7, "button",
|
|
178
|
+
i0.ɵɵelementStart(7, "button", 72);
|
|
177
179
|
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_74_Conditional_2_For_2_Template_button_click_7_listener($event) { const user_r7 = i0.ɵɵrestoreView(_r6).$implicit; const ctx_r2 = i0.ɵɵnextContext(3); ctx_r2.confirmDeleteUser(user_r7); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
178
|
-
i0.ɵɵelement(8, "i",
|
|
180
|
+
i0.ɵɵelement(8, "i", 73);
|
|
179
181
|
i0.ɵɵelementEnd()()();
|
|
180
|
-
i0.ɵɵelementStart(9, "div",
|
|
182
|
+
i0.ɵɵelementStart(9, "div", 82)(10, "h3", 63);
|
|
181
183
|
i0.ɵɵtext(11);
|
|
182
184
|
i0.ɵɵelementEnd();
|
|
183
|
-
i0.ɵɵelementStart(12, "p",
|
|
185
|
+
i0.ɵɵelementStart(12, "p", 64);
|
|
184
186
|
i0.ɵɵtext(13);
|
|
185
187
|
i0.ɵɵelementEnd();
|
|
186
|
-
i0.ɵɵelementStart(14, "p",
|
|
187
|
-
i0.ɵɵelement(15, "i",
|
|
188
|
+
i0.ɵɵelementStart(14, "p", 83);
|
|
189
|
+
i0.ɵɵelement(15, "i", 84);
|
|
188
190
|
i0.ɵɵtext(16);
|
|
189
191
|
i0.ɵɵelementEnd();
|
|
190
|
-
i0.ɵɵelementStart(17, "div",
|
|
192
|
+
i0.ɵɵelementStart(17, "div", 85)(18, "div", 86);
|
|
191
193
|
i0.ɵɵelement(19, "i");
|
|
192
194
|
i0.ɵɵtext(20);
|
|
193
195
|
i0.ɵɵelementEnd();
|
|
194
|
-
i0.ɵɵelementStart(21, "span",
|
|
196
|
+
i0.ɵɵelementStart(21, "span", 66);
|
|
195
197
|
i0.ɵɵelement(22, "i");
|
|
196
198
|
i0.ɵɵtext(23);
|
|
197
199
|
i0.ɵɵelementEnd()();
|
|
198
|
-
i0.ɵɵelementStart(24, "div",
|
|
199
|
-
i0.ɵɵelement(26, "i",
|
|
200
|
+
i0.ɵɵelementStart(24, "div", 87)(25, "div", 67);
|
|
201
|
+
i0.ɵɵelement(26, "i", 88);
|
|
200
202
|
i0.ɵɵtext(27);
|
|
201
203
|
i0.ɵɵpipe(28, "date");
|
|
202
204
|
i0.ɵɵelementEnd()()()();
|
|
@@ -225,19 +227,19 @@ function UserManagementComponent_Conditional_74_Conditional_2_For_2_Template(rf,
|
|
|
225
227
|
i0.ɵɵtextInterpolate1(" Last updated: ", user_r7.__mj_UpdatedAt ? i0.ɵɵpipeBind2(28, 14, user_r7.__mj_UpdatedAt, "short") : "Never", " ");
|
|
226
228
|
} }
|
|
227
229
|
function UserManagementComponent_Conditional_74_Conditional_2_Conditional_3_Template(rf, ctx) { if (rf & 1) {
|
|
228
|
-
i0.ɵɵelementStart(0, "div",
|
|
229
|
-
i0.ɵɵelement(1, "i",
|
|
230
|
-
i0.ɵɵelementStart(2, "p",
|
|
230
|
+
i0.ɵɵelementStart(0, "div", 57);
|
|
231
|
+
i0.ɵɵelement(1, "i", 74);
|
|
232
|
+
i0.ɵɵelementStart(2, "p", 75);
|
|
231
233
|
i0.ɵɵtext(3, "No users found");
|
|
232
234
|
i0.ɵɵelementEnd();
|
|
233
|
-
i0.ɵɵelementStart(4, "p",
|
|
235
|
+
i0.ɵɵelementStart(4, "p", 76);
|
|
234
236
|
i0.ɵɵtext(5, "Try adjusting your filters or add a new user");
|
|
235
237
|
i0.ɵɵelementEnd()();
|
|
236
238
|
} }
|
|
237
239
|
function UserManagementComponent_Conditional_74_Conditional_2_Template(rf, ctx) { if (rf & 1) {
|
|
238
|
-
i0.ɵɵelementStart(0, "div",
|
|
239
|
-
i0.ɵɵrepeaterCreate(1, UserManagementComponent_Conditional_74_Conditional_2_For_2_Template, 29, 17, "div",
|
|
240
|
-
i0.ɵɵtemplate(3, UserManagementComponent_Conditional_74_Conditional_2_Conditional_3_Template, 6, 0, "div",
|
|
240
|
+
i0.ɵɵelementStart(0, "div", 51);
|
|
241
|
+
i0.ɵɵrepeaterCreate(1, UserManagementComponent_Conditional_74_Conditional_2_For_2_Template, 29, 17, "div", 77, _forTrack0);
|
|
242
|
+
i0.ɵɵtemplate(3, UserManagementComponent_Conditional_74_Conditional_2_Conditional_3_Template, 6, 0, "div", 57);
|
|
241
243
|
i0.ɵɵelementEnd();
|
|
242
244
|
} if (rf & 2) {
|
|
243
245
|
const ctx_r2 = i0.ɵɵnextContext(2);
|
|
@@ -248,7 +250,7 @@ function UserManagementComponent_Conditional_74_Conditional_2_Template(rf, ctx)
|
|
|
248
250
|
} }
|
|
249
251
|
function UserManagementComponent_Conditional_74_Template(rf, ctx) { if (rf & 1) {
|
|
250
252
|
i0.ɵɵelementStart(0, "div", 40);
|
|
251
|
-
i0.ɵɵtemplate(1, UserManagementComponent_Conditional_74_Conditional_1_Template, 22, 1, "div",
|
|
253
|
+
i0.ɵɵtemplate(1, UserManagementComponent_Conditional_74_Conditional_1_Template, 22, 1, "div", 50)(2, UserManagementComponent_Conditional_74_Conditional_2_Template, 4, 1, "div", 51);
|
|
252
254
|
i0.ɵɵelementEnd();
|
|
253
255
|
} if (rf & 2) {
|
|
254
256
|
const ctx_r2 = i0.ɵɵnextContext();
|
|
@@ -257,37 +259,37 @@ function UserManagementComponent_Conditional_74_Template(rf, ctx) { if (rf & 1)
|
|
|
257
259
|
i0.ɵɵadvance();
|
|
258
260
|
i0.ɵɵconditional(ctx_r2.viewMode === "cards" ? 2 : -1);
|
|
259
261
|
} }
|
|
260
|
-
function
|
|
262
|
+
function UserManagementComponent_Conditional_76_Template(rf, ctx) { if (rf & 1) {
|
|
261
263
|
const _r8 = i0.ɵɵgetCurrentView();
|
|
262
|
-
i0.ɵɵelementStart(0, "div",
|
|
263
|
-
i0.ɵɵlistener("click", function
|
|
264
|
-
i0.ɵɵelementStart(1, "div",
|
|
265
|
-
i0.ɵɵlistener("click", function
|
|
266
|
-
i0.ɵɵelementStart(2, "div",
|
|
267
|
-
i0.ɵɵelement(4, "i",
|
|
264
|
+
i0.ɵɵelementStart(0, "div", 89);
|
|
265
|
+
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_76_Template_div_click_0_listener() { i0.ɵɵrestoreView(_r8); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.showDeleteConfirm = false); });
|
|
266
|
+
i0.ɵɵelementStart(1, "div", 90);
|
|
267
|
+
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_76_Template_div_click_1_listener($event) { i0.ɵɵrestoreView(_r8); return i0.ɵɵresetView($event.stopPropagation()); });
|
|
268
|
+
i0.ɵɵelementStart(2, "div", 91)(3, "h3", 92);
|
|
269
|
+
i0.ɵɵelement(4, "i", 93);
|
|
268
270
|
i0.ɵɵtext(5, " Confirm Delete ");
|
|
269
271
|
i0.ɵɵelementEnd();
|
|
270
|
-
i0.ɵɵelementStart(6, "button",
|
|
271
|
-
i0.ɵɵlistener("click", function
|
|
272
|
-
i0.ɵɵelement(7, "i",
|
|
272
|
+
i0.ɵɵelementStart(6, "button", 94);
|
|
273
|
+
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_76_Template_button_click_6_listener() { i0.ɵɵrestoreView(_r8); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.showDeleteConfirm = false); });
|
|
274
|
+
i0.ɵɵelement(7, "i", 95);
|
|
273
275
|
i0.ɵɵelementEnd()();
|
|
274
|
-
i0.ɵɵelementStart(8, "div",
|
|
276
|
+
i0.ɵɵelementStart(8, "div", 96)(9, "p");
|
|
275
277
|
i0.ɵɵtext(10, "Are you sure you want to delete user ");
|
|
276
278
|
i0.ɵɵelementStart(11, "strong");
|
|
277
279
|
i0.ɵɵtext(12);
|
|
278
280
|
i0.ɵɵelementEnd();
|
|
279
281
|
i0.ɵɵtext(13, "?");
|
|
280
282
|
i0.ɵɵelementEnd();
|
|
281
|
-
i0.ɵɵelementStart(14, "p",
|
|
283
|
+
i0.ɵɵelementStart(14, "p", 97);
|
|
282
284
|
i0.ɵɵtext(15, "This action cannot be undone.");
|
|
283
285
|
i0.ɵɵelementEnd()();
|
|
284
|
-
i0.ɵɵelementStart(16, "div",
|
|
285
|
-
i0.ɵɵlistener("click", function
|
|
286
|
+
i0.ɵɵelementStart(16, "div", 98)(17, "button", 4);
|
|
287
|
+
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_76_Template_button_click_17_listener() { i0.ɵɵrestoreView(_r8); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.showDeleteConfirm = false); });
|
|
286
288
|
i0.ɵɵtext(18, "Cancel");
|
|
287
289
|
i0.ɵɵelementEnd();
|
|
288
|
-
i0.ɵɵelementStart(19, "button",
|
|
289
|
-
i0.ɵɵlistener("click", function
|
|
290
|
-
i0.ɵɵelement(20, "i",
|
|
290
|
+
i0.ɵɵelementStart(19, "button", 99);
|
|
291
|
+
i0.ɵɵlistener("click", function UserManagementComponent_Conditional_76_Template_button_click_19_listener() { i0.ɵɵrestoreView(_r8); const ctx_r2 = i0.ɵɵnextContext(); return i0.ɵɵresetView(ctx_r2.deleteUser()); });
|
|
292
|
+
i0.ɵɵelement(20, "i", 73);
|
|
291
293
|
i0.ɵɵtext(21, " Delete User ");
|
|
292
294
|
i0.ɵɵelementEnd()()()();
|
|
293
295
|
} if (rf & 2) {
|
|
@@ -303,6 +305,9 @@ export class UserManagementComponent {
|
|
|
303
305
|
selectedUser = null;
|
|
304
306
|
isLoading = false;
|
|
305
307
|
error = null;
|
|
308
|
+
// Dialog state
|
|
309
|
+
showUserDialog = false;
|
|
310
|
+
userDialogData = null;
|
|
306
311
|
// Stats
|
|
307
312
|
stats = {
|
|
308
313
|
totalUsers: 0,
|
|
@@ -321,6 +326,8 @@ export class UserManagementComponent {
|
|
|
321
326
|
showEditDialog = false;
|
|
322
327
|
showDeleteConfirm = false;
|
|
323
328
|
viewMode = 'grid';
|
|
329
|
+
// User-Role mapping
|
|
330
|
+
userRoleMap = new Map(); // userId -> roleIds[]
|
|
324
331
|
// Grid configuration
|
|
325
332
|
gridConfig = {
|
|
326
333
|
pageSize: 20,
|
|
@@ -328,6 +335,7 @@ export class UserManagementComponent {
|
|
|
328
335
|
sortDirection: 'asc'
|
|
329
336
|
};
|
|
330
337
|
destroy$ = new Subject();
|
|
338
|
+
metadata = new Metadata();
|
|
331
339
|
constructor() { }
|
|
332
340
|
ngOnInit() {
|
|
333
341
|
this.loadInitialData();
|
|
@@ -341,13 +349,16 @@ export class UserManagementComponent {
|
|
|
341
349
|
try {
|
|
342
350
|
this.isLoading = true;
|
|
343
351
|
this.error = null;
|
|
344
|
-
// Load users and
|
|
345
|
-
const [users, roles] = await Promise.all([
|
|
352
|
+
// Load users, roles, and user-role relationships in parallel
|
|
353
|
+
const [users, roles, userRoles] = await Promise.all([
|
|
346
354
|
this.loadUsers(),
|
|
347
|
-
this.loadRoles()
|
|
355
|
+
this.loadRoles(),
|
|
356
|
+
this.loadUserRoles()
|
|
348
357
|
]);
|
|
349
358
|
this.users = users;
|
|
350
359
|
this.roles = roles;
|
|
360
|
+
// Build user-role mapping
|
|
361
|
+
this.buildUserRoleMapping(userRoles);
|
|
351
362
|
this.calculateStats();
|
|
352
363
|
this.applyFilters();
|
|
353
364
|
}
|
|
@@ -376,6 +387,25 @@ export class UserManagementComponent {
|
|
|
376
387
|
});
|
|
377
388
|
return result.Success ? result.Results : [];
|
|
378
389
|
}
|
|
390
|
+
async loadUserRoles() {
|
|
391
|
+
const rv = new RunView();
|
|
392
|
+
const result = await rv.RunView({
|
|
393
|
+
EntityName: 'User Roles',
|
|
394
|
+
ResultType: 'entity_object'
|
|
395
|
+
});
|
|
396
|
+
return result.Success ? result.Results : [];
|
|
397
|
+
}
|
|
398
|
+
buildUserRoleMapping(userRoles) {
|
|
399
|
+
this.userRoleMap.clear();
|
|
400
|
+
userRoles.forEach(userRole => {
|
|
401
|
+
const userId = userRole.UserID;
|
|
402
|
+
const roleId = userRole.RoleID;
|
|
403
|
+
if (!this.userRoleMap.has(userId)) {
|
|
404
|
+
this.userRoleMap.set(userId, []);
|
|
405
|
+
}
|
|
406
|
+
this.userRoleMap.get(userId).push(roleId);
|
|
407
|
+
});
|
|
408
|
+
}
|
|
379
409
|
setupFilterSubscription() {
|
|
380
410
|
this.filters$
|
|
381
411
|
.pipe(debounceTime(300), distinctUntilChanged((a, b) => JSON.stringify(a) === JSON.stringify(b)), takeUntil(this.destroy$))
|
|
@@ -392,8 +422,10 @@ export class UserManagementComponent {
|
|
|
392
422
|
}
|
|
393
423
|
// Apply role filter
|
|
394
424
|
if (filters.role) {
|
|
395
|
-
|
|
396
|
-
|
|
425
|
+
filtered = filtered.filter(user => {
|
|
426
|
+
const userRoles = this.userRoleMap.get(user.ID) || [];
|
|
427
|
+
return userRoles.includes(filters.role);
|
|
428
|
+
});
|
|
397
429
|
}
|
|
398
430
|
// Apply search filter
|
|
399
431
|
if (filters.search) {
|
|
@@ -435,12 +467,19 @@ export class UserManagementComponent {
|
|
|
435
467
|
this.showEditDialog = true;
|
|
436
468
|
}
|
|
437
469
|
createNewUser() {
|
|
438
|
-
this.
|
|
439
|
-
|
|
470
|
+
this.userDialogData = {
|
|
471
|
+
mode: 'create',
|
|
472
|
+
availableRoles: this.roles
|
|
473
|
+
};
|
|
474
|
+
this.showUserDialog = true;
|
|
440
475
|
}
|
|
441
476
|
editUser(user) {
|
|
442
|
-
this.
|
|
443
|
-
|
|
477
|
+
this.userDialogData = {
|
|
478
|
+
user: user,
|
|
479
|
+
mode: 'edit',
|
|
480
|
+
availableRoles: this.roles
|
|
481
|
+
};
|
|
482
|
+
this.showUserDialog = true;
|
|
444
483
|
}
|
|
445
484
|
confirmDeleteUser(user) {
|
|
446
485
|
this.selectedUser = user;
|
|
@@ -450,13 +489,27 @@ export class UserManagementComponent {
|
|
|
450
489
|
if (!this.selectedUser)
|
|
451
490
|
return;
|
|
452
491
|
try {
|
|
453
|
-
//
|
|
454
|
-
|
|
455
|
-
await this.
|
|
492
|
+
// Load user entity to delete
|
|
493
|
+
const user = await this.metadata.GetEntityObject('Users');
|
|
494
|
+
const loadResult = await user.Load(this.selectedUser.ID);
|
|
495
|
+
if (loadResult) {
|
|
496
|
+
const deleteResult = await user.Delete();
|
|
497
|
+
if (deleteResult) {
|
|
498
|
+
this.showDeleteConfirm = false;
|
|
499
|
+
this.selectedUser = null;
|
|
500
|
+
await this.loadInitialData();
|
|
501
|
+
}
|
|
502
|
+
else {
|
|
503
|
+
throw new Error(user.LatestResult?.Message || 'Failed to delete user');
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
else {
|
|
507
|
+
throw new Error('User not found or permission denied');
|
|
508
|
+
}
|
|
456
509
|
}
|
|
457
510
|
catch (error) {
|
|
458
511
|
console.error('Error deleting user:', error);
|
|
459
|
-
this.error = 'Failed to delete user';
|
|
512
|
+
this.error = error.message || 'Failed to delete user';
|
|
460
513
|
}
|
|
461
514
|
}
|
|
462
515
|
async toggleUserStatus(user) {
|
|
@@ -474,8 +527,52 @@ export class UserManagementComponent {
|
|
|
474
527
|
this.viewMode = this.viewMode === 'grid' ? 'cards' : 'grid';
|
|
475
528
|
}
|
|
476
529
|
exportUsers() {
|
|
477
|
-
|
|
478
|
-
|
|
530
|
+
if (this.filteredUsers.length === 0) {
|
|
531
|
+
this.error = 'No users to export';
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
try {
|
|
535
|
+
// Create CSV content
|
|
536
|
+
const headers = ['Name', 'First Name', 'Last Name', 'Email', 'Type', 'Status', 'Created', 'Updated'];
|
|
537
|
+
const csvRows = [headers.join(',')];
|
|
538
|
+
// Add user data
|
|
539
|
+
this.filteredUsers.forEach(user => {
|
|
540
|
+
const row = [
|
|
541
|
+
this.escapeCSV(user.Name || ''),
|
|
542
|
+
this.escapeCSV(user.FirstName || ''),
|
|
543
|
+
this.escapeCSV(user.LastName || ''),
|
|
544
|
+
this.escapeCSV(user.Email || ''),
|
|
545
|
+
this.escapeCSV(user.Type || ''),
|
|
546
|
+
user.IsActive ? 'Active' : 'Inactive',
|
|
547
|
+
user.__mj_CreatedAt ? new Date(user.__mj_CreatedAt).toLocaleDateString() : '',
|
|
548
|
+
user.__mj_UpdatedAt ? new Date(user.__mj_UpdatedAt).toLocaleDateString() : ''
|
|
549
|
+
];
|
|
550
|
+
csvRows.push(row.join(','));
|
|
551
|
+
});
|
|
552
|
+
// Create and download file
|
|
553
|
+
const csvContent = csvRows.join('\n');
|
|
554
|
+
const blob = new Blob([csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
555
|
+
const link = document.createElement('a');
|
|
556
|
+
if (link.download !== undefined) {
|
|
557
|
+
const url = URL.createObjectURL(blob);
|
|
558
|
+
link.setAttribute('href', url);
|
|
559
|
+
link.setAttribute('download', `users_export_${new Date().toISOString().split('T')[0]}.csv`);
|
|
560
|
+
link.style.visibility = 'hidden';
|
|
561
|
+
document.body.appendChild(link);
|
|
562
|
+
link.click();
|
|
563
|
+
document.body.removeChild(link);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
catch (error) {
|
|
567
|
+
console.error('Error exporting users:', error);
|
|
568
|
+
this.error = 'Failed to export users';
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
escapeCSV(value) {
|
|
572
|
+
if (value.includes(',') || value.includes('"') || value.includes('\n')) {
|
|
573
|
+
return `"${value.replace(/"/g, '""')}"`;
|
|
574
|
+
}
|
|
575
|
+
return value;
|
|
479
576
|
}
|
|
480
577
|
refreshData() {
|
|
481
578
|
this.loadInitialData();
|
|
@@ -501,8 +598,16 @@ export class UserManagementComponent {
|
|
|
501
598
|
const last = user.LastName?.charAt(0) || '';
|
|
502
599
|
return (first + last).toUpperCase() || user.Name?.charAt(0).toUpperCase() || 'U';
|
|
503
600
|
}
|
|
601
|
+
onUserDialogResult(result) {
|
|
602
|
+
this.showUserDialog = false;
|
|
603
|
+
this.userDialogData = null;
|
|
604
|
+
if (result.action === 'save') {
|
|
605
|
+
// Refresh the user list to show changes
|
|
606
|
+
this.loadInitialData();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
504
609
|
static ɵfac = function UserManagementComponent_Factory(t) { return new (t || UserManagementComponent)(); };
|
|
505
|
-
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: UserManagementComponent, selectors: [["mj-user-management"]], standalone: true, features: [i0.ɵɵStandaloneFeature], decls:
|
|
610
|
+
static ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: UserManagementComponent, selectors: [["mj-user-management"]], standalone: true, features: [i0.ɵɵStandaloneFeature], decls: 77, vars: 28, consts: [[1, "user-management-container"], [1, "action-buttons"], [1, "mj-btn", "mj-btn-secondary", 3, "click", "disabled"], [1, "fa-solid", "fa-refresh"], [1, "mj-btn", "mj-btn-secondary", 3, "click"], [1, "fa-solid", "fa-download"], [1, "mj-btn", "mj-btn-primary", 3, "click"], [1, "fa-solid", "fa-plus"], [1, "mj-grid", "mj-grid-4"], [1, "mj-card"], [1, "stat-icon", "stat-icon-total"], [1, "fa-solid", "fa-users"], [1, "stat-content"], [1, "stat-value"], [1, "stat-label"], [1, "stat-icon", "stat-icon-active"], [1, "fa-solid", "fa-user-check"], [1, "stat-icon", "stat-icon-inactive"], [1, "fa-solid", "fa-user-xmark"], [1, "stat-icon", "stat-icon-admin"], [1, "fa-solid", "fa-shield-halved"], [1, "filters-section"], [1, "filters-row"], [1, "search-container"], [1, "fa-solid", "fa-search", "search-icon"], ["type", "text", "placeholder", "Search users by name or email...", 1, "search-input", 3, "input", "value"], [1, "filter-group"], [1, "filter-label"], [1, "filter-buttons"], [1, "mj-btn", "mj-btn-ghost", 3, "click"], [1, "filter-select", 3, "change"], ["value", ""], [3, "value"], [1, "view-toggle"], ["title", "Grid View", 1, "mj-btn", "mj-btn-icon-only", 3, "click"], [1, "fa-solid", "fa-table"], ["title", "Card View", 1, "mj-btn", "mj-btn-icon-only", 3, "click"], [1, "fa-solid", "fa-th-large"], [1, "loading-container"], [1, "error-container"], [1, "content-area"], [3, "result", "data", "visible"], [1, "modal-backdrop"], [1, "loading-spinner"], [1, "spinner-ring"], [1, "loading-text"], [1, "error-content"], [1, "fa-solid", "fa-exclamation-triangle", "error-icon"], [1, "error-message"], [1, "retry-button", 3, "click"], [1, "users-table"], [1, "users-grid"], [1, "modern-table"], [1, "th-checkbox"], ["type", "checkbox", 1, "checkbox"], [1, "th-actions"], [1, "table-row"], [1, "empty-state"], [1, "table-row", 3, "click"], [1, "td-checkbox", 3, "click"], [1, "user-cell"], [1, "user-avatar"], [1, "user-info"], [1, "user-name"], [1, "user-fullname"], [1, "user-type"], [1, "status-badge"], [1, "last-login"], [1, "td-actions", 3, "click"], ["title", "Edit", 1, "mj-btn", "mj-btn-ghost", "mj-btn-sm", 3, "click"], [1, "fa-solid", "fa-edit"], [1, "mj-btn", "mj-btn-ghost", "mj-btn-sm", 3, "click", "title"], ["title", "Delete", 1, "mj-btn", "mj-btn-ghost", "mj-btn-sm", "text-danger", 3, "click"], [1, "fa-solid", "fa-trash"], [1, "fa-solid", "fa-users-slash", "empty-icon"], [1, "empty-text"], [1, "empty-subtext"], [1, "user-card"], [1, "user-card", 3, "click"], [1, "card-header"], [1, "user-avatar-large"], [1, "card-actions"], [1, "card-body"], [1, "user-email"], [1, "fa-solid", "fa-envelope"], [1, "card-meta"], [1, "meta-item"], [1, "card-footer"], [1, "fa-solid", "fa-clock"], [1, "modal-backdrop", 3, "click"], [1, "modal-dialog", 3, "click"], [1, "modal-header"], [1, "modal-title"], [1, "fa-solid", "fa-exclamation-triangle", "text-danger"], [1, "modal-close", 3, "click"], [1, "fa-solid", "fa-times"], [1, "modal-body"], [1, "text-muted"], [1, "modal-footer"], [1, "mj-btn", "mj-btn-primary", "text-danger", 3, "click"]], template: function UserManagementComponent_Template(rf, ctx) { if (rf & 1) {
|
|
506
611
|
i0.ɵɵelementStart(0, "div", 0)(1, "div", 1)(2, "button", 2);
|
|
507
612
|
i0.ɵɵlistener("click", function UserManagementComponent_Template_button_click_2_listener() { return ctx.refreshData(); });
|
|
508
613
|
i0.ɵɵelement(3, "i", 3);
|
|
@@ -592,7 +697,11 @@ export class UserManagementComponent {
|
|
|
592
697
|
i0.ɵɵlistener("click", function UserManagementComponent_Template_button_click_70_listener() { return ctx.viewMode = "cards"; });
|
|
593
698
|
i0.ɵɵelement(71, "i", 37);
|
|
594
699
|
i0.ɵɵelementEnd()()()();
|
|
595
|
-
i0.ɵɵtemplate(72, UserManagementComponent_Conditional_72_Template, 7, 0, "div", 38)(73, UserManagementComponent_Conditional_73_Template, 8, 1, "div", 39)(74, UserManagementComponent_Conditional_74_Template, 3, 2, "div", 40)
|
|
700
|
+
i0.ɵɵtemplate(72, UserManagementComponent_Conditional_72_Template, 7, 0, "div", 38)(73, UserManagementComponent_Conditional_73_Template, 8, 1, "div", 39)(74, UserManagementComponent_Conditional_74_Template, 3, 2, "div", 40);
|
|
701
|
+
i0.ɵɵelementStart(75, "mj-user-dialog", 41);
|
|
702
|
+
i0.ɵɵlistener("result", function UserManagementComponent_Template_mj_user_dialog_result_75_listener($event) { return ctx.onUserDialogResult($event); });
|
|
703
|
+
i0.ɵɵelementEnd();
|
|
704
|
+
i0.ɵɵtemplate(76, UserManagementComponent_Conditional_76_Template, 22, 1, "div", 42);
|
|
596
705
|
i0.ɵɵelementEnd();
|
|
597
706
|
} if (rf & 2) {
|
|
598
707
|
i0.ɵɵadvance(2);
|
|
@@ -610,17 +719,17 @@ export class UserManagementComponent {
|
|
|
610
719
|
i0.ɵɵadvance(7);
|
|
611
720
|
i0.ɵɵproperty("value", ctx.filters$.value.search);
|
|
612
721
|
i0.ɵɵadvance(5);
|
|
613
|
-
i0.ɵɵclassProp("
|
|
722
|
+
i0.ɵɵclassProp("mj-btn-primary", ctx.filters$.value.status === "all");
|
|
614
723
|
i0.ɵɵadvance(2);
|
|
615
|
-
i0.ɵɵclassProp("
|
|
724
|
+
i0.ɵɵclassProp("mj-btn-primary", ctx.filters$.value.status === "active");
|
|
616
725
|
i0.ɵɵadvance(2);
|
|
617
|
-
i0.ɵɵclassProp("
|
|
726
|
+
i0.ɵɵclassProp("mj-btn-primary", ctx.filters$.value.status === "inactive");
|
|
618
727
|
i0.ɵɵadvance(8);
|
|
619
728
|
i0.ɵɵrepeater(ctx.roles);
|
|
620
729
|
i0.ɵɵadvance(3);
|
|
621
|
-
i0.ɵɵclassProp("
|
|
730
|
+
i0.ɵɵclassProp("mj-btn-primary", ctx.viewMode === "grid")("mj-btn-ghost", ctx.viewMode !== "grid");
|
|
622
731
|
i0.ɵɵadvance(2);
|
|
623
|
-
i0.ɵɵclassProp("
|
|
732
|
+
i0.ɵɵclassProp("mj-btn-primary", ctx.viewMode === "cards")("mj-btn-ghost", ctx.viewMode !== "cards");
|
|
624
733
|
i0.ɵɵadvance(2);
|
|
625
734
|
i0.ɵɵconditional(ctx.isLoading ? 72 : -1);
|
|
626
735
|
i0.ɵɵadvance();
|
|
@@ -628,16 +737,22 @@ export class UserManagementComponent {
|
|
|
628
737
|
i0.ɵɵadvance();
|
|
629
738
|
i0.ɵɵconditional(!ctx.isLoading && !ctx.error ? 74 : -1);
|
|
630
739
|
i0.ɵɵadvance();
|
|
631
|
-
i0.ɵɵ
|
|
632
|
-
} }, dependencies: [CommonModule, i1.DatePipe, FormsModule, i2.NgSelectOption, i2.ɵNgSelectMultipleOption, SharedSettingsModule], styles: ["@import '../shared/styles/variables';\n@import '../shared/styles/mixins';\n\n.user-management-container[_ngcontent-%COMP%] {\n @include scrollable-container;\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n}\n\n//[_ngcontent-%COMP%] Action[_ngcontent-%COMP%] Buttons\n.action-buttons[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n margin-bottom: 1.5rem;\n\n @media (max-width: 768px) {\n justify-content: center;\n flex-wrap: wrap;\n }\n}\n\n//[_ngcontent-%COMP%] Buttons\n.btn-primary[_ngcontent-%COMP%] {\n @include button-base;\n background-color: #2196f3;\n color: white;\n \n &:hover {\n background-color: #1976d2;\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3);\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n }\n}\n\n.btn-secondary[_ngcontent-%COMP%] {\n @include button-base;\n background-color: #ffffff;\n color: #374151;\n border: 1px solid #e5e7eb;\n \n &:hover {\n background-color: #f9fafb;\n border-color: #2196f3;\n color: #2196f3;\n }\n}\n\n.btn-danger[_ngcontent-%COMP%] {\n @include button-base;\n background-color: #f44336;\n color: white;\n \n &:hover {\n background-color: #d32f2f;\n }\n}\n\n//[_ngcontent-%COMP%] Stats[_ngcontent-%COMP%] Grid\n.stats-grid[_ngcontent-%COMP%] {\n display: grid !important;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 1.5rem;\n margin-bottom: 2rem;\n width: 100%;\n\n @media (max-width: 768px) {\n grid-template-columns: repeat(2, 1fr);\n gap: 1rem;\n }\n}\n\n.stat-card[_ngcontent-%COMP%] {\n background: white;\n border-radius: 12px;\n padding: 1.5rem;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n display: flex;\n margin-right: 10px;\n align-items: center;\n gap: 1rem;\n transition: all 0.3s ease;\n min-width: 0; // Prevent grid blowout\n\n &:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n }\n}\n\n.stat-icon[_ngcontent-%COMP%] {\n width: 60px;\n height: 60px;\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.5rem;\n\n &-total {\n background: rgba(33, 150, 243, 0.1);\n color: #2196f3;\n }\n\n &-active {\n background: rgba(76, 175, 80, 0.1);\n color: #4caf50;\n }\n\n &-inactive {\n background: rgba(244, 67, 54, 0.1);\n color: #f44336;\n }\n\n &-admin {\n background: rgba(156, 39, 176, 0.1);\n color: #9c27b0;\n }\n}\n\n.stat-content[_ngcontent-%COMP%] {\n flex: 1;\n\n .stat-value {\n font-size: 2rem;\n font-weight: 700;\n color: #1f2937;\n line-height: 1;\n }\n\n .stat-label {\n color: #6b7280;\n font-size: 0.875rem;\n margin-top: 0.25rem;\n }\n}\n\n//[_ngcontent-%COMP%] Filters[_ngcontent-%COMP%] Section\n.filters-section[_ngcontent-%COMP%] {\n background: white;\n border-radius: 12px;\n padding: 1.5rem;\n margin-bottom: 1.5rem;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n\n.filters-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 1.5rem;\n align-items: flex-end;\n flex-wrap: wrap;\n\n @media (max-width: 768px) {\n gap: 1rem;\n }\n}\n\n.search-container[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 250px;\n position: relative;\n\n .search-icon {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: #6b7280;\n font-size: 1rem;\n }\n\n .search-input {\n width: 100%;\n padding: 0.75rem 1rem 0.75rem 2.75rem;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n font-size: 0.95rem;\n transition: all 0.2s;\n\n &:focus {\n outline: none;\n border-color: #2196f3;\n box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n }\n }\n}\n\n.filter-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n\n .filter-label {\n font-size: 0.875rem;\n font-weight: 500;\n color: #374151;\n }\n\n .filter-buttons {\n display: flex;\n background: #f3f4f6;\n border-radius: 8px;\n padding: 4px;\n }\n\n .filter-btn {\n padding: 0.5rem 1rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 0.875rem;\n font-weight: 500;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n color: #374151;\n }\n\n &.active {\n background: white;\n color: #2196f3;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n }\n\n .filter-select {\n padding: 0.625rem 1rem;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n background: white;\n font-size: 0.875rem;\n color: #374151;\n cursor: pointer;\n transition: all 0.2s;\n\n &:focus {\n outline: none;\n border-color: #2196f3;\n box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n }\n }\n}\n\n.view-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: #f3f4f6;\n border-radius: 8px;\n padding: 4px;\n\n .view-btn {\n padding: 0.5rem 0.75rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1rem;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n color: #374151;\n }\n\n &.active {\n background: white;\n color: #2196f3;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n }\n}\n\n//[_ngcontent-%COMP%] Content[_ngcontent-%COMP%] Area\n.content-area[_ngcontent-%COMP%] {\n @include scrollable-content;\n background: white;\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n}\n\n//[_ngcontent-%COMP%] Table[_ngcontent-%COMP%] View\n.users-table[_ngcontent-%COMP%] {\n overflow-x: auto;\n max-height: calc(100vh - 450px); // Dynamic height\n overflow-y: auto;\n}\n\n.modern-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n\n thead {\n background: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n\n th {\n padding: 1rem;\n text-align: left;\n font-size: 0.875rem;\n font-weight: 600;\n color: #374151;\n white-space: nowrap;\n\n &.th-checkbox {\n width: 50px;\n }\n\n &.th-actions {\n text-align: center;\n width: 150px;\n }\n }\n }\n\n tbody {\n tr {\n border-bottom: 1px solid #f3f4f6;\n transition: background-color 0.2s;\n cursor: pointer;\n\n &:hover {\n background-color: #f9fafb;\n }\n }\n\n td {\n padding: 1rem;\n font-size: 0.875rem;\n color: #374151;\n\n &.td-checkbox {\n width: 50px;\n }\n\n &.td-actions {\n text-align: center;\n }\n }\n }\n}\n\n.checkbox[_ngcontent-%COMP%] {\n width: 18px;\n height: 18px;\n cursor: pointer;\n}\n\n.user-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n}\n\n.user-avatar[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 0.875rem;\n}\n\n.user-avatar-large[_ngcontent-%COMP%] {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 1.5rem;\n margin: 0 auto 1rem;\n}\n\n.user-info[_ngcontent-%COMP%] {\n .user-name {\n font-weight: 600;\n color: #1f2937;\n }\n\n .user-fullname {\n font-size: 0.75rem;\n color: #6b7280;\n }\n}\n\n.user-type[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n color: #4b5563;\n\n i {\n font-size: 0.875rem;\n }\n}\n\n.status-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.75rem;\n border-radius: 20px;\n font-size: 0.75rem;\n font-weight: 500;\n\n &.status-active {\n background: rgba(76, 175, 80, 0.1);\n color: #388e3c;\n }\n\n &.status-inactive {\n background: rgba(244, 67, 54, 0.1);\n color: #d32f2f;\n }\n\n i {\n font-size: 0.625rem;\n }\n}\n\n.last-login[_ngcontent-%COMP%] {\n color: #6b7280;\n font-size: 0.875rem;\n}\n\n.action-btn[_ngcontent-%COMP%] {\n padding: 0.5rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1rem;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n background: #f3f4f6;\n color: #2196f3;\n }\n\n &-danger:hover {\n color: #f44336;\n }\n}\n\n//[_ngcontent-%COMP%] Card[_ngcontent-%COMP%] View\n.users-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 1.5rem;\n padding: 1.5rem;\n\n @media (max-width: 768px) {\n grid-template-columns: 1fr;\n gap: 1rem;\n padding: 1rem;\n }\n}\n\n.user-card[_ngcontent-%COMP%] {\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 12px;\n padding: 1.5rem;\n cursor: pointer;\n transition: all 0.3s ease;\n\n &:hover {\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);\n transform: translateY(-2px);\n border-color: #2196f3;\n }\n}\n\n.card-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 1rem;\n}\n\n.card-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.5rem;\n}\n\n.card-body[_ngcontent-%COMP%] {\n text-align: center;\n\n .user-name {\n font-size: 1.25rem;\n font-weight: 600;\n color: #1f2937;\n margin: 0 0 0.25rem 0;\n }\n\n .user-fullname {\n color: #6b7280;\n margin: 0 0 0.75rem 0;\n }\n\n .user-email {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n color: #4b5563;\n font-size: 0.875rem;\n margin-bottom: 1rem;\n\n i {\n color: #6b7280;\n }\n }\n}\n\n.card-meta[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 1rem;\n padding: 1rem 0;\n border-top: 1px solid #f3f4f6;\n border-bottom: 1px solid #f3f4f6;\n margin-bottom: 1rem;\n\n .meta-item {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n color: #4b5563;\n font-size: 0.875rem;\n }\n}\n\n.card-footer[_ngcontent-%COMP%] {\n .last-login {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.375rem;\n color: #6b7280;\n font-size: 0.75rem;\n\n i {\n font-size: 0.875rem;\n }\n }\n}\n\n//[_ngcontent-%COMP%] Empty[_ngcontent-%COMP%] State\n.empty-state[_ngcontent-%COMP%] {\n text-align: center;\n padding: 4rem 2rem;\n\n .empty-icon {\n font-size: 4rem;\n color: #e5e7eb;\n margin-bottom: 1rem;\n }\n\n .empty-text {\n font-size: 1.25rem;\n font-weight: 600;\n color: #374151;\n margin: 0 0 0.5rem 0;\n }\n\n .empty-subtext {\n color: #6b7280;\n margin: 0;\n }\n}\n\n//[_ngcontent-%COMP%] Loading[_ngcontent-%COMP%] State\n.loading-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 4rem 2rem;\n}\n\n.loading-spinner[_ngcontent-%COMP%] {\n position: relative;\n width: 60px;\n height: 60px;\n margin-bottom: 1rem;\n\n .spinner-ring {\n position: absolute;\n width: 100%;\n height: 100%;\n border: 3px solid transparent;\n border-radius: 50%;\n animation: _ngcontent-%COMP%_spin 1.5s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n\n &:nth-child(1) {\n border-color: #2196f3 transparent transparent transparent;\n animation-delay: -0.45s;\n }\n\n &:nth-child(2) {\n border-color: transparent #4caf50 transparent transparent;\n animation-delay: -0.3s;\n }\n\n &:nth-child(3) {\n border-color: transparent transparent #ff9800 transparent;\n animation-delay: -0.15s;\n }\n }\n}\n\n@keyframes _ngcontent-%COMP%_spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.loading-text[_ngcontent-%COMP%] {\n color: #6b7280;\n font-size: 0.95rem;\n}\n\n//[_ngcontent-%COMP%] Error[_ngcontent-%COMP%] State\n.error-container[_ngcontent-%COMP%] {\n text-align: center;\n padding: 4rem 2rem;\n\n .error-icon {\n font-size: 3rem;\n color: #f44336;\n margin-bottom: 1rem;\n }\n\n .error-message {\n color: #374151;\n margin-bottom: 1.5rem;\n }\n\n .retry-button {\n @include button-base;\n background-color: #2196f3;\n color: white;\n }\n}\n\n//[_ngcontent-%COMP%] Modal[_ngcontent-%COMP%] Styles\n.modal-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s ease;\n}\n\n.modal-dialog[_ngcontent-%COMP%] {\n background: white;\n border-radius: 12px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n max-width: 500px;\n width: 90%;\n max-height: 90vh;\n overflow: hidden;\n animation: _ngcontent-%COMP%_slideUp 0.3s ease;\n}\n\n.modal-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem;\n border-bottom: 1px solid #e5e7eb;\n\n .modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n color: #1f2937;\n margin: 0;\n }\n\n .modal-close {\n padding: 0.5rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: 6px;\n transition: all 0.2s;\n\n &:hover {\n background: #f3f4f6;\n color: #374151;\n }\n }\n}\n\n.modal-body[_ngcontent-%COMP%] {\n padding: 1.5rem;\n\n p {\n margin: 0 0 1rem 0;\n color: #374151;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n .text-muted {\n color: #6b7280;\n font-size: 0.875rem;\n }\n}\n\n.modal-footer[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-end;\n gap: 0.75rem;\n padding: 1.5rem;\n border-top: 1px solid #e5e7eb;\n background: #f9fafb;\n}\n\n//[_ngcontent-%COMP%] Animations\n@keyframes[_ngcontent-%COMP%] fadeIn[_ngcontent-%COMP%] {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes _ngcontent-%COMP%_slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n//[_ngcontent-%COMP%] Utility[_ngcontent-%COMP%] Classes\n.text-danger[_ngcontent-%COMP%] {\n color: #f44336;\n}"] });
|
|
740
|
+
i0.ɵɵproperty("data", ctx.userDialogData)("visible", ctx.showUserDialog);
|
|
741
|
+
i0.ɵɵadvance();
|
|
742
|
+
i0.ɵɵconditional(ctx.showDeleteConfirm && ctx.selectedUser ? 76 : -1);
|
|
743
|
+
} }, dependencies: [CommonModule, i1.DatePipe, FormsModule, i2.NgSelectOption, i2.ɵNgSelectMultipleOption, SharedSettingsModule,
|
|
744
|
+
UserDialogComponent,
|
|
745
|
+
WindowModule], styles: ["@import '../shared/styles/variables';\n@import '../shared/styles/mixins';\n\n.user-management-container[_ngcontent-%COMP%] {\n @include scrollable-container;\n width: 100%;\n height: 100%;\n}\n\n//[_ngcontent-%COMP%] Action[_ngcontent-%COMP%] Buttons\n.action-buttons[_ngcontent-%COMP%] {\n @include fixed-header;\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n padding: 1rem 2rem;\n background: white;\n border-bottom: 1px solid $border-light;\n\n @media (max-width: 768px) {\n justify-content: center;\n flex-wrap: wrap;\n padding: 1rem;\n }\n}\n\n//[_ngcontent-%COMP%] Buttons\n.btn-primary[_ngcontent-%COMP%] {\n @include button-base;\n background-color: #2196f3;\n color: white;\n \n &:hover {\n background-color: #1976d2;\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3);\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n }\n}\n\n.btn-secondary[_ngcontent-%COMP%] {\n @include button-base;\n background-color: #ffffff;\n color: #374151;\n border: 1px solid #e5e7eb;\n \n &:hover {\n background-color: #f9fafb;\n border-color: #2196f3;\n color: #2196f3;\n }\n}\n\n.btn-danger[_ngcontent-%COMP%] {\n @include button-base;\n background-color: #f44336;\n color: white;\n \n &:hover {\n background-color: #d32f2f;\n }\n}\n\n//[_ngcontent-%COMP%] Stats[_ngcontent-%COMP%] Grid\n.stats-grid[_ngcontent-%COMP%] {\n @include fixed-header;\n display: grid !important;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 1.5rem;\n padding: 1rem 2rem;\n background: white;\n border-bottom: 1px solid $border-light;\n width: 100%;\n\n @media (max-width: 768px) {\n grid-template-columns: repeat(2, 1fr);\n gap: 1rem;\n padding: 1rem;\n }\n}\n\n.stat-card[_ngcontent-%COMP%] {\n background: white;\n border-radius: 12px;\n padding: 1.5rem;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n display: flex;\n margin-right: 10px;\n align-items: center;\n gap: 1rem;\n transition: all 0.3s ease;\n min-width: 0; // Prevent grid blowout\n\n &:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n }\n}\n\n.stat-icon[_ngcontent-%COMP%] {\n width: 60px;\n height: 60px;\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.5rem;\n\n &-total {\n background: rgba(33, 150, 243, 0.1);\n color: #2196f3;\n }\n\n &-active {\n background: rgba(76, 175, 80, 0.1);\n color: #4caf50;\n }\n\n &-inactive {\n background: rgba(244, 67, 54, 0.1);\n color: #f44336;\n }\n\n &-admin {\n background: rgba(156, 39, 176, 0.1);\n color: #9c27b0;\n }\n}\n\n.stat-content[_ngcontent-%COMP%] {\n flex: 1;\n\n .stat-value {\n font-size: 2rem;\n font-weight: 700;\n color: #1f2937;\n line-height: 1;\n }\n\n .stat-label {\n color: #6b7280;\n font-size: 0.875rem;\n margin-top: 0.25rem;\n }\n}\n\n//[_ngcontent-%COMP%] Filters[_ngcontent-%COMP%] Section\n.filters-section[_ngcontent-%COMP%] {\n @include fixed-header;\n background: white;\n padding: 1.5rem 2rem;\n border-bottom: 1px solid $border-light;\n\n @media (max-width: 768px) {\n padding: 1rem;\n }\n}\n\n.filters-row[_ngcontent-%COMP%] {\n display: flex;\n gap: 1.5rem;\n align-items: flex-end;\n flex-wrap: wrap;\n\n @media (max-width: 768px) {\n gap: 1rem;\n }\n}\n\n.search-container[_ngcontent-%COMP%] {\n flex: 1;\n min-width: 250px;\n position: relative;\n\n .search-icon {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: #6b7280;\n font-size: 1rem;\n }\n\n .search-input {\n width: 100%;\n padding: 0.75rem 1rem 0.75rem 2.75rem;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n font-size: 0.95rem;\n transition: all 0.2s;\n\n &:focus {\n outline: none;\n border-color: #2196f3;\n box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n }\n }\n}\n\n.filter-group[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n\n .filter-label {\n font-size: 0.875rem;\n font-weight: 500;\n color: #374151;\n }\n\n .filter-buttons {\n display: flex;\n background: #f3f4f6;\n border-radius: 8px;\n padding: 4px;\n }\n\n .filter-btn {\n padding: 0.5rem 1rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 0.875rem;\n font-weight: 500;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n color: #374151;\n }\n\n &.active {\n background: white;\n color: #2196f3;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n }\n\n .filter-select {\n padding: 0.625rem 1rem;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n background: white;\n font-size: 0.875rem;\n color: #374151;\n cursor: pointer;\n transition: all 0.2s;\n\n &:focus {\n outline: none;\n border-color: #2196f3;\n box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n }\n }\n}\n\n.view-toggle[_ngcontent-%COMP%] {\n display: flex;\n background: #f3f4f6;\n border-radius: 8px;\n padding: 4px;\n\n .view-btn {\n padding: 0.5rem 0.75rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1rem;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n color: #374151;\n }\n\n &.active {\n background: white;\n color: #2196f3;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n }\n}\n\n//[_ngcontent-%COMP%] Content[_ngcontent-%COMP%] Area\n.content-area[_ngcontent-%COMP%] {\n @include scrollable-content;\n padding: 2rem;\n background: white;\n\n @media (max-width: 768px) {\n padding: 1rem;\n }\n}\n\n//[_ngcontent-%COMP%] Table[_ngcontent-%COMP%] View\n.users-table[_ngcontent-%COMP%] {\n width: 100%;\n overflow-x: auto;\n}\n\n.modern-table[_ngcontent-%COMP%] {\n width: 100%;\n border-collapse: collapse;\n\n thead {\n background: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n\n th {\n padding: 1rem;\n text-align: left;\n font-size: 0.875rem;\n font-weight: 600;\n color: #374151;\n white-space: nowrap;\n\n &.th-checkbox {\n width: 50px;\n }\n\n &.th-actions {\n text-align: center;\n width: 150px;\n }\n }\n }\n\n tbody {\n tr {\n border-bottom: 1px solid #f3f4f6;\n transition: background-color 0.2s;\n cursor: pointer;\n\n &:hover {\n background-color: #f9fafb;\n }\n }\n\n td {\n padding: 1rem;\n font-size: 0.875rem;\n color: #374151;\n\n &.td-checkbox {\n width: 50px;\n }\n\n &.td-actions {\n text-align: center;\n }\n }\n }\n}\n\n.checkbox[_ngcontent-%COMP%] {\n width: 18px;\n height: 18px;\n cursor: pointer;\n}\n\n.user-cell[_ngcontent-%COMP%] {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n}\n\n.user-avatar[_ngcontent-%COMP%] {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 0.875rem;\n}\n\n.user-avatar-large[_ngcontent-%COMP%] {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 1.5rem;\n margin: 0 auto 1rem;\n}\n\n.user-info[_ngcontent-%COMP%] {\n .user-name {\n font-weight: 600;\n color: #1f2937;\n }\n\n .user-fullname {\n font-size: 0.75rem;\n color: #6b7280;\n }\n}\n\n.user-type[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n color: #4b5563;\n\n i {\n font-size: 0.875rem;\n }\n}\n\n.status-badge[_ngcontent-%COMP%] {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.75rem;\n border-radius: 20px;\n font-size: 0.75rem;\n font-weight: 500;\n\n &.status-active {\n background: rgba(76, 175, 80, 0.1);\n color: #388e3c;\n }\n\n &.status-inactive {\n background: rgba(244, 67, 54, 0.1);\n color: #d32f2f;\n }\n\n i {\n font-size: 0.625rem;\n }\n}\n\n.last-login[_ngcontent-%COMP%] {\n color: #6b7280;\n font-size: 0.875rem;\n}\n\n.action-btn[_ngcontent-%COMP%] {\n padding: 0.5rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1rem;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n background: #f3f4f6;\n color: #2196f3;\n }\n\n &-danger:hover {\n color: #f44336;\n }\n}\n\n//[_ngcontent-%COMP%] Card[_ngcontent-%COMP%] View\n.users-grid[_ngcontent-%COMP%] {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 1.5rem;\n padding: 1.5rem;\n\n @media (max-width: 768px) {\n grid-template-columns: 1fr;\n gap: 1rem;\n padding: 1rem;\n }\n}\n\n.user-card[_ngcontent-%COMP%] {\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 12px;\n padding: 1.5rem;\n cursor: pointer;\n transition: all 0.3s ease;\n\n &:hover {\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);\n transform: translateY(-2px);\n border-color: #2196f3;\n }\n}\n\n.card-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 1rem;\n}\n\n.card-actions[_ngcontent-%COMP%] {\n display: flex;\n gap: 0.5rem;\n}\n\n.card-body[_ngcontent-%COMP%] {\n text-align: center;\n\n .user-name {\n font-size: 1.25rem;\n font-weight: 600;\n color: #1f2937;\n margin: 0 0 0.25rem 0;\n }\n\n .user-fullname {\n color: #6b7280;\n margin: 0 0 0.75rem 0;\n }\n\n .user-email {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n color: #4b5563;\n font-size: 0.875rem;\n margin-bottom: 1rem;\n\n i {\n color: #6b7280;\n }\n }\n}\n\n.card-meta[_ngcontent-%COMP%] {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 1rem;\n padding: 1rem 0;\n border-top: 1px solid #f3f4f6;\n border-bottom: 1px solid #f3f4f6;\n margin-bottom: 1rem;\n\n .meta-item {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n color: #4b5563;\n font-size: 0.875rem;\n }\n}\n\n.card-footer[_ngcontent-%COMP%] {\n .last-login {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.375rem;\n color: #6b7280;\n font-size: 0.75rem;\n\n i {\n font-size: 0.875rem;\n }\n }\n}\n\n//[_ngcontent-%COMP%] Empty[_ngcontent-%COMP%] State\n.empty-state[_ngcontent-%COMP%] {\n text-align: center;\n padding: 4rem 2rem;\n\n .empty-icon {\n font-size: 4rem;\n color: #e5e7eb;\n margin-bottom: 1rem;\n }\n\n .empty-text {\n font-size: 1.25rem;\n font-weight: 600;\n color: #374151;\n margin: 0 0 0.5rem 0;\n }\n\n .empty-subtext {\n color: #6b7280;\n margin: 0;\n }\n}\n\n//[_ngcontent-%COMP%] Loading[_ngcontent-%COMP%] State\n.loading-container[_ngcontent-%COMP%] {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 4rem 2rem;\n}\n\n.loading-spinner[_ngcontent-%COMP%] {\n position: relative;\n width: 60px;\n height: 60px;\n margin-bottom: 1rem;\n\n .spinner-ring {\n position: absolute;\n width: 100%;\n height: 100%;\n border: 3px solid transparent;\n border-radius: 50%;\n animation: _ngcontent-%COMP%_spin 1.5s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n\n &:nth-child(1) {\n border-color: #2196f3 transparent transparent transparent;\n animation-delay: -0.45s;\n }\n\n &:nth-child(2) {\n border-color: transparent #4caf50 transparent transparent;\n animation-delay: -0.3s;\n }\n\n &:nth-child(3) {\n border-color: transparent transparent #ff9800 transparent;\n animation-delay: -0.15s;\n }\n }\n}\n\n@keyframes _ngcontent-%COMP%_spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.loading-text[_ngcontent-%COMP%] {\n color: #6b7280;\n font-size: 0.95rem;\n}\n\n//[_ngcontent-%COMP%] Error[_ngcontent-%COMP%] State\n.error-container[_ngcontent-%COMP%] {\n text-align: center;\n padding: 4rem 2rem;\n\n .error-icon {\n font-size: 3rem;\n color: #f44336;\n margin-bottom: 1rem;\n }\n\n .error-message {\n color: #374151;\n margin-bottom: 1.5rem;\n }\n\n .retry-button {\n @include button-base;\n background-color: #2196f3;\n color: white;\n }\n}\n\n//[_ngcontent-%COMP%] Modal[_ngcontent-%COMP%] Styles\n.modal-backdrop[_ngcontent-%COMP%] {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s ease;\n}\n\n.modal-dialog[_ngcontent-%COMP%] {\n background: white;\n border-radius: 12px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n max-width: 500px;\n width: 90%;\n max-height: 90vh;\n overflow: hidden;\n animation: _ngcontent-%COMP%_slideUp 0.3s ease;\n}\n\n.modal-header[_ngcontent-%COMP%] {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem;\n border-bottom: 1px solid #e5e7eb;\n\n .modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n color: #1f2937;\n margin: 0;\n }\n\n .modal-close {\n padding: 0.5rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: 6px;\n transition: all 0.2s;\n\n &:hover {\n background: #f3f4f6;\n color: #374151;\n }\n }\n}\n\n.modal-body[_ngcontent-%COMP%] {\n padding: 1.5rem;\n\n p {\n margin: 0 0 1rem 0;\n color: #374151;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n .text-muted {\n color: #6b7280;\n font-size: 0.875rem;\n }\n}\n\n.modal-footer[_ngcontent-%COMP%] {\n display: flex;\n justify-content: flex-end;\n gap: 0.75rem;\n padding: 1.5rem;\n border-top: 1px solid #e5e7eb;\n background: #f9fafb;\n}\n\n//[_ngcontent-%COMP%] Animations\n@keyframes[_ngcontent-%COMP%] fadeIn[_ngcontent-%COMP%] {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes _ngcontent-%COMP%_slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n//[_ngcontent-%COMP%] Utility[_ngcontent-%COMP%] Classes\n.text-danger[_ngcontent-%COMP%] {\n color: #f44336;\n}"] });
|
|
633
746
|
}
|
|
634
747
|
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(UserManagementComponent, [{
|
|
635
748
|
type: Component,
|
|
636
749
|
args: [{ selector: 'mj-user-management', standalone: true, imports: [
|
|
637
750
|
CommonModule,
|
|
638
751
|
FormsModule,
|
|
639
|
-
SharedSettingsModule
|
|
640
|
-
], template: "<div class=\"user-management-container\">\n <!-- Action Buttons -->\n <div class=\"action-buttons\">\n <button class=\"btn-secondary\" (click)=\"refreshData()\" [disabled]=\"isLoading\">\n <i class=\"fa-solid fa-refresh\" [class.fa-spin]=\"isLoading\"></i>\n Refresh\n </button>\n <button class=\"btn-secondary\" (click)=\"exportUsers()\">\n <i class=\"fa-solid fa-download\"></i>\n Export\n </button>\n <button class=\"btn-primary\" (click)=\"createNewUser()\">\n <i class=\"fa-solid fa-plus\"></i>\n Add User\n </button>\n </div>\n\n <!-- Stats Cards -->\n <div class=\"stats-grid\" style=\"display: flex\">\n <div class=\"stat-card\">\n <div class=\"stat-icon stat-icon-total\">\n <i class=\"fa-solid fa-users\"></i>\n </div>\n <div class=\"stat-content\">\n <div class=\"stat-value\">{{ stats.totalUsers }}</div>\n <div class=\"stat-label\">Total Users</div>\n </div>\n </div>\n \n <div class=\"stat-card\">\n <div class=\"stat-icon stat-icon-active\">\n <i class=\"fa-solid fa-user-check\"></i>\n </div>\n <div class=\"stat-content\">\n <div class=\"stat-value\">{{ stats.activeUsers }}</div>\n <div class=\"stat-label\">Active Users</div>\n </div>\n </div>\n \n <div class=\"stat-card\">\n <div class=\"stat-icon stat-icon-inactive\">\n <i class=\"fa-solid fa-user-xmark\"></i>\n </div>\n <div class=\"stat-content\">\n <div class=\"stat-value\">{{ stats.inactiveUsers }}</div>\n <div class=\"stat-label\">Inactive Users</div>\n </div>\n </div>\n \n <div class=\"stat-card\">\n <div class=\"stat-icon stat-icon-admin\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n </div>\n <div class=\"stat-content\">\n <div class=\"stat-value\">{{ stats.adminUsers }}</div>\n <div class=\"stat-label\">Owners</div>\n </div>\n </div>\n </div>\n\n <!-- Filters Section -->\n <div class=\"filters-section\">\n <div class=\"filters-row\">\n <!-- Search -->\n <div class=\"search-container\">\n <i class=\"fa-solid fa-search search-icon\"></i>\n <input \n type=\"text\" \n class=\"search-input\" \n placeholder=\"Search users by name or email...\"\n (input)=\"onSearchChange($event)\"\n [value]=\"filters$.value.search\"\n />\n </div>\n \n <!-- Status Filter -->\n <div class=\"filter-group\">\n <label class=\"filter-label\">Status</label>\n <div class=\"filter-buttons\">\n <button \n class=\"filter-btn\"\n [class.active]=\"filters$.value.status === 'all'\"\n (click)=\"onStatusFilterChange('all')\"\n >\n All\n </button>\n <button \n class=\"filter-btn\"\n [class.active]=\"filters$.value.status === 'active'\"\n (click)=\"onStatusFilterChange('active')\"\n >\n Active\n </button>\n <button \n class=\"filter-btn\"\n [class.active]=\"filters$.value.status === 'inactive'\"\n (click)=\"onStatusFilterChange('inactive')\"\n >\n Inactive\n </button>\n </div>\n </div>\n \n <!-- Role Filter -->\n <div class=\"filter-group\">\n <label class=\"filter-label\">Role</label>\n <select class=\"filter-select\" (change)=\"onRoleFilterChange($any($event.target).value)\">\n <option value=\"\">All Roles</option>\n @for (role of roles; track role.ID) {\n <option [value]=\"role.ID\">{{ role.Name }}</option>\n }\n </select>\n </div>\n \n <!-- View Toggle -->\n <div class=\"view-toggle\">\n <button \n class=\"view-btn\"\n [class.active]=\"viewMode === 'grid'\"\n (click)=\"viewMode = 'grid'\"\n title=\"Grid View\"\n >\n <i class=\"fa-solid fa-table\"></i>\n </button>\n <button \n class=\"view-btn\"\n [class.active]=\"viewMode === 'cards'\"\n (click)=\"viewMode = 'cards'\"\n title=\"Card View\"\n >\n <i class=\"fa-solid fa-th-large\"></i>\n </button>\n </div>\n </div>\n </div>\n\n <!-- Loading State -->\n @if (isLoading) {\n <div class=\"loading-container\">\n <div class=\"loading-spinner\">\n <div class=\"spinner-ring\"></div>\n <div class=\"spinner-ring\"></div>\n <div class=\"spinner-ring\"></div>\n </div>\n <div class=\"loading-text\">Loading users...</div>\n </div>\n }\n\n <!-- Error State -->\n @if (error && !isLoading) {\n <div class=\"error-container\">\n <div class=\"error-content\">\n <i class=\"fa-solid fa-exclamation-triangle error-icon\"></i>\n <p class=\"error-message\">{{ error }}</p>\n <button class=\"retry-button\" (click)=\"loadInitialData()\">\n <i class=\"fa-solid fa-refresh\"></i>\n Try Again\n </button>\n </div>\n </div>\n }\n\n <!-- Content Area -->\n @if (!isLoading && !error) {\n <div class=\"content-area\">\n <!-- Grid View -->\n @if (viewMode === 'grid') {\n <div class=\"users-table\">\n <table class=\"modern-table\">\n <thead>\n <tr>\n <th class=\"th-checkbox\">\n <input type=\"checkbox\" class=\"checkbox\" />\n </th>\n <th>User</th>\n <th>Email</th>\n <th>Type</th>\n <th>Status</th>\n <th>Last Updated</th>\n <th class=\"th-actions\">Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (user of filteredUsers; track user.ID) {\n <tr class=\"table-row\" (click)=\"selectUser(user)\">\n <td class=\"td-checkbox\" (click)=\"$event.stopPropagation()\">\n <input type=\"checkbox\" class=\"checkbox\" />\n </td>\n <td>\n <div class=\"user-cell\">\n <div class=\"user-avatar\">\n {{ getUserInitials(user) }}\n </div>\n <div class=\"user-info\">\n <div class=\"user-name\">{{ user.Name }}</div>\n <div class=\"user-fullname\">\n {{ user.FirstName }} {{ user.LastName }}\n </div>\n </div>\n </div>\n </td>\n <td>{{ user.Email }}</td>\n <td>\n <div class=\"user-type\">\n <i [class]=\"'fa-solid ' + getUserTypeIcon(user)\"></i>\n {{ user.Type }}\n </div>\n </td>\n <td>\n <span class=\"status-badge\" [class]=\"getStatusClass(user)\">\n <i [class]=\"'fa-solid ' + getStatusIcon(user)\"></i>\n {{ user.IsActive ? 'Active' : 'Inactive' }}\n </span>\n </td>\n <td>\n <span class=\"last-login\">\n {{ user.__mj_UpdatedAt ? (user.__mj_UpdatedAt | date:'short') : 'Never' }}\n </span>\n </td>\n <td class=\"td-actions\" (click)=\"$event.stopPropagation()\">\n <button class=\"action-btn\" (click)=\"editUser(user)\" title=\"Edit\">\n <i class=\"fa-solid fa-edit\"></i>\n </button>\n <button \n class=\"action-btn\" \n (click)=\"toggleUserStatus(user)\"\n [title]=\"user.IsActive ? 'Deactivate' : 'Activate'\"\n >\n <i [class]=\"user.IsActive ? 'fa-solid fa-toggle-on' : 'fa-solid fa-toggle-off'\"></i>\n </button>\n <button class=\"action-btn action-btn-danger\" (click)=\"confirmDeleteUser(user)\" title=\"Delete\">\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n \n @if (filteredUsers.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-users-slash empty-icon\"></i>\n <p class=\"empty-text\">No users found</p>\n <p class=\"empty-subtext\">Try adjusting your filters or add a new user</p>\n </div>\n }\n </div>\n }\n\n <!-- Card View -->\n @if (viewMode === 'cards') {\n <div class=\"users-grid\">\n @for (user of filteredUsers; track user.ID) {\n <div class=\"user-card\" (click)=\"selectUser(user)\">\n <div class=\"card-header\">\n <div class=\"user-avatar-large\">\n {{ getUserInitials(user) }}\n </div>\n <div class=\"card-actions\">\n <button class=\"action-btn\" (click)=\"editUser(user); $event.stopPropagation()\" title=\"Edit\">\n <i class=\"fa-solid fa-edit\"></i>\n </button>\n <button class=\"action-btn action-btn-danger\" (click)=\"confirmDeleteUser(user); $event.stopPropagation()\" title=\"Delete\">\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n </div>\n </div>\n \n <div class=\"card-body\">\n <h3 class=\"user-name\">{{ user.Name }}</h3>\n <p class=\"user-fullname\">{{ user.FirstName }} {{ user.LastName }}</p>\n <p class=\"user-email\">\n <i class=\"fa-solid fa-envelope\"></i>\n {{ user.Email }}\n </p>\n \n <div class=\"card-meta\">\n <div class=\"meta-item\">\n <i [class]=\"'fa-solid ' + getUserTypeIcon(user)\"></i>\n {{ user.Type }}\n </div>\n <span class=\"status-badge\" [class]=\"getStatusClass(user)\">\n <i [class]=\"'fa-solid ' + getStatusIcon(user)\"></i>\n {{ user.IsActive ? 'Active' : 'Inactive' }}\n </span>\n </div>\n \n <div class=\"card-footer\">\n <div class=\"last-login\">\n <i class=\"fa-solid fa-clock\"></i>\n Last updated: {{ user.__mj_UpdatedAt ? (user.__mj_UpdatedAt | date:'short') : 'Never' }}\n </div>\n </div>\n </div>\n </div>\n }\n \n @if (filteredUsers.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-users-slash empty-icon\"></i>\n <p class=\"empty-text\">No users found</p>\n <p class=\"empty-subtext\">Try adjusting your filters or add a new user</p>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- Delete Confirmation Dialog -->\n @if (showDeleteConfirm && selectedUser) {\n <div class=\"modal-backdrop\" (click)=\"showDeleteConfirm = false\">\n <div class=\"modal-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3 class=\"modal-title\">\n <i class=\"fa-solid fa-exclamation-triangle text-danger\"></i>\n Confirm Delete\n </h3>\n <button class=\"modal-close\" (click)=\"showDeleteConfirm = false\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"modal-body\">\n <p>Are you sure you want to delete user <strong>{{ selectedUser.Name }}</strong>?</p>\n <p class=\"text-muted\">This action cannot be undone.</p>\n </div>\n <div class=\"modal-footer\">\n <button class=\"btn-secondary\" (click)=\"showDeleteConfirm = false\">Cancel</button>\n <button class=\"btn-danger\" (click)=\"deleteUser()\">\n <i class=\"fa-solid fa-trash\"></i>\n Delete User\n </button>\n </div>\n </div>\n </div>\n }\n</div>", styles: ["@import '../shared/styles/variables';\n@import '../shared/styles/mixins';\n\n.user-management-container {\n @include scrollable-container;\n max-width: 1400px;\n margin: 0 auto;\n padding: 2rem;\n}\n\n// Action Buttons\n.action-buttons {\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n margin-bottom: 1.5rem;\n\n @media (max-width: 768px) {\n justify-content: center;\n flex-wrap: wrap;\n }\n}\n\n// Buttons\n.btn-primary {\n @include button-base;\n background-color: #2196f3;\n color: white;\n \n &:hover {\n background-color: #1976d2;\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3);\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n }\n}\n\n.btn-secondary {\n @include button-base;\n background-color: #ffffff;\n color: #374151;\n border: 1px solid #e5e7eb;\n \n &:hover {\n background-color: #f9fafb;\n border-color: #2196f3;\n color: #2196f3;\n }\n}\n\n.btn-danger {\n @include button-base;\n background-color: #f44336;\n color: white;\n \n &:hover {\n background-color: #d32f2f;\n }\n}\n\n// Stats Grid\n.stats-grid {\n display: grid !important;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 1.5rem;\n margin-bottom: 2rem;\n width: 100%;\n\n @media (max-width: 768px) {\n grid-template-columns: repeat(2, 1fr);\n gap: 1rem;\n }\n}\n\n.stat-card {\n background: white;\n border-radius: 12px;\n padding: 1.5rem;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n display: flex;\n margin-right: 10px;\n align-items: center;\n gap: 1rem;\n transition: all 0.3s ease;\n min-width: 0; // Prevent grid blowout\n\n &:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n }\n}\n\n.stat-icon {\n width: 60px;\n height: 60px;\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.5rem;\n\n &-total {\n background: rgba(33, 150, 243, 0.1);\n color: #2196f3;\n }\n\n &-active {\n background: rgba(76, 175, 80, 0.1);\n color: #4caf50;\n }\n\n &-inactive {\n background: rgba(244, 67, 54, 0.1);\n color: #f44336;\n }\n\n &-admin {\n background: rgba(156, 39, 176, 0.1);\n color: #9c27b0;\n }\n}\n\n.stat-content {\n flex: 1;\n\n .stat-value {\n font-size: 2rem;\n font-weight: 700;\n color: #1f2937;\n line-height: 1;\n }\n\n .stat-label {\n color: #6b7280;\n font-size: 0.875rem;\n margin-top: 0.25rem;\n }\n}\n\n// Filters Section\n.filters-section {\n background: white;\n border-radius: 12px;\n padding: 1.5rem;\n margin-bottom: 1.5rem;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n}\n\n.filters-row {\n display: flex;\n gap: 1.5rem;\n align-items: flex-end;\n flex-wrap: wrap;\n\n @media (max-width: 768px) {\n gap: 1rem;\n }\n}\n\n.search-container {\n flex: 1;\n min-width: 250px;\n position: relative;\n\n .search-icon {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: #6b7280;\n font-size: 1rem;\n }\n\n .search-input {\n width: 100%;\n padding: 0.75rem 1rem 0.75rem 2.75rem;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n font-size: 0.95rem;\n transition: all 0.2s;\n\n &:focus {\n outline: none;\n border-color: #2196f3;\n box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n }\n }\n}\n\n.filter-group {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n\n .filter-label {\n font-size: 0.875rem;\n font-weight: 500;\n color: #374151;\n }\n\n .filter-buttons {\n display: flex;\n background: #f3f4f6;\n border-radius: 8px;\n padding: 4px;\n }\n\n .filter-btn {\n padding: 0.5rem 1rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 0.875rem;\n font-weight: 500;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n color: #374151;\n }\n\n &.active {\n background: white;\n color: #2196f3;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n }\n\n .filter-select {\n padding: 0.625rem 1rem;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n background: white;\n font-size: 0.875rem;\n color: #374151;\n cursor: pointer;\n transition: all 0.2s;\n\n &:focus {\n outline: none;\n border-color: #2196f3;\n box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n }\n }\n}\n\n.view-toggle {\n display: flex;\n background: #f3f4f6;\n border-radius: 8px;\n padding: 4px;\n\n .view-btn {\n padding: 0.5rem 0.75rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1rem;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n color: #374151;\n }\n\n &.active {\n background: white;\n color: #2196f3;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n }\n}\n\n// Content Area\n.content-area {\n @include scrollable-content;\n background: white;\n border-radius: 12px;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n overflow: hidden;\n}\n\n// Table View\n.users-table {\n overflow-x: auto;\n max-height: calc(100vh - 450px); // Dynamic height\n overflow-y: auto;\n}\n\n.modern-table {\n width: 100%;\n border-collapse: collapse;\n\n thead {\n background: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n\n th {\n padding: 1rem;\n text-align: left;\n font-size: 0.875rem;\n font-weight: 600;\n color: #374151;\n white-space: nowrap;\n\n &.th-checkbox {\n width: 50px;\n }\n\n &.th-actions {\n text-align: center;\n width: 150px;\n }\n }\n }\n\n tbody {\n tr {\n border-bottom: 1px solid #f3f4f6;\n transition: background-color 0.2s;\n cursor: pointer;\n\n &:hover {\n background-color: #f9fafb;\n }\n }\n\n td {\n padding: 1rem;\n font-size: 0.875rem;\n color: #374151;\n\n &.td-checkbox {\n width: 50px;\n }\n\n &.td-actions {\n text-align: center;\n }\n }\n }\n}\n\n.checkbox {\n width: 18px;\n height: 18px;\n cursor: pointer;\n}\n\n.user-cell {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n}\n\n.user-avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 0.875rem;\n}\n\n.user-avatar-large {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 1.5rem;\n margin: 0 auto 1rem;\n}\n\n.user-info {\n .user-name {\n font-weight: 600;\n color: #1f2937;\n }\n\n .user-fullname {\n font-size: 0.75rem;\n color: #6b7280;\n }\n}\n\n.user-type {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n color: #4b5563;\n\n i {\n font-size: 0.875rem;\n }\n}\n\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.75rem;\n border-radius: 20px;\n font-size: 0.75rem;\n font-weight: 500;\n\n &.status-active {\n background: rgba(76, 175, 80, 0.1);\n color: #388e3c;\n }\n\n &.status-inactive {\n background: rgba(244, 67, 54, 0.1);\n color: #d32f2f;\n }\n\n i {\n font-size: 0.625rem;\n }\n}\n\n.last-login {\n color: #6b7280;\n font-size: 0.875rem;\n}\n\n.action-btn {\n padding: 0.5rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1rem;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n background: #f3f4f6;\n color: #2196f3;\n }\n\n &-danger:hover {\n color: #f44336;\n }\n}\n\n// Card View\n.users-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 1.5rem;\n padding: 1.5rem;\n\n @media (max-width: 768px) {\n grid-template-columns: 1fr;\n gap: 1rem;\n padding: 1rem;\n }\n}\n\n.user-card {\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 12px;\n padding: 1.5rem;\n cursor: pointer;\n transition: all 0.3s ease;\n\n &:hover {\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);\n transform: translateY(-2px);\n border-color: #2196f3;\n }\n}\n\n.card-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 1rem;\n}\n\n.card-actions {\n display: flex;\n gap: 0.5rem;\n}\n\n.card-body {\n text-align: center;\n\n .user-name {\n font-size: 1.25rem;\n font-weight: 600;\n color: #1f2937;\n margin: 0 0 0.25rem 0;\n }\n\n .user-fullname {\n color: #6b7280;\n margin: 0 0 0.75rem 0;\n }\n\n .user-email {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n color: #4b5563;\n font-size: 0.875rem;\n margin-bottom: 1rem;\n\n i {\n color: #6b7280;\n }\n }\n}\n\n.card-meta {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 1rem;\n padding: 1rem 0;\n border-top: 1px solid #f3f4f6;\n border-bottom: 1px solid #f3f4f6;\n margin-bottom: 1rem;\n\n .meta-item {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n color: #4b5563;\n font-size: 0.875rem;\n }\n}\n\n.card-footer {\n .last-login {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.375rem;\n color: #6b7280;\n font-size: 0.75rem;\n\n i {\n font-size: 0.875rem;\n }\n }\n}\n\n// Empty State\n.empty-state {\n text-align: center;\n padding: 4rem 2rem;\n\n .empty-icon {\n font-size: 4rem;\n color: #e5e7eb;\n margin-bottom: 1rem;\n }\n\n .empty-text {\n font-size: 1.25rem;\n font-weight: 600;\n color: #374151;\n margin: 0 0 0.5rem 0;\n }\n\n .empty-subtext {\n color: #6b7280;\n margin: 0;\n }\n}\n\n// Loading State\n.loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 4rem 2rem;\n}\n\n.loading-spinner {\n position: relative;\n width: 60px;\n height: 60px;\n margin-bottom: 1rem;\n\n .spinner-ring {\n position: absolute;\n width: 100%;\n height: 100%;\n border: 3px solid transparent;\n border-radius: 50%;\n animation: spin 1.5s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n\n &:nth-child(1) {\n border-color: #2196f3 transparent transparent transparent;\n animation-delay: -0.45s;\n }\n\n &:nth-child(2) {\n border-color: transparent #4caf50 transparent transparent;\n animation-delay: -0.3s;\n }\n\n &:nth-child(3) {\n border-color: transparent transparent #ff9800 transparent;\n animation-delay: -0.15s;\n }\n }\n}\n\n@keyframes spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.loading-text {\n color: #6b7280;\n font-size: 0.95rem;\n}\n\n// Error State\n.error-container {\n text-align: center;\n padding: 4rem 2rem;\n\n .error-icon {\n font-size: 3rem;\n color: #f44336;\n margin-bottom: 1rem;\n }\n\n .error-message {\n color: #374151;\n margin-bottom: 1.5rem;\n }\n\n .retry-button {\n @include button-base;\n background-color: #2196f3;\n color: white;\n }\n}\n\n// Modal Styles\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s ease;\n}\n\n.modal-dialog {\n background: white;\n border-radius: 12px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n max-width: 500px;\n width: 90%;\n max-height: 90vh;\n overflow: hidden;\n animation: slideUp 0.3s ease;\n}\n\n.modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem;\n border-bottom: 1px solid #e5e7eb;\n\n .modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n color: #1f2937;\n margin: 0;\n }\n\n .modal-close {\n padding: 0.5rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: 6px;\n transition: all 0.2s;\n\n &:hover {\n background: #f3f4f6;\n color: #374151;\n }\n }\n}\n\n.modal-body {\n padding: 1.5rem;\n\n p {\n margin: 0 0 1rem 0;\n color: #374151;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n .text-muted {\n color: #6b7280;\n font-size: 0.875rem;\n }\n}\n\n.modal-footer {\n display: flex;\n justify-content: flex-end;\n gap: 0.75rem;\n padding: 1.5rem;\n border-top: 1px solid #e5e7eb;\n background: #f9fafb;\n}\n\n// Animations\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n// Utility Classes\n.text-danger {\n color: #f44336;\n}"] }]
|
|
752
|
+
SharedSettingsModule,
|
|
753
|
+
UserDialogComponent,
|
|
754
|
+
WindowModule
|
|
755
|
+
], template: "<div class=\"user-management-container\">\n <!-- Action Buttons -->\n <div class=\"action-buttons\">\n <button class=\"mj-btn mj-btn-secondary\" (click)=\"refreshData()\" [disabled]=\"isLoading\">\n <i class=\"fa-solid fa-refresh\" [class.fa-spin]=\"isLoading\"></i>\n Refresh\n </button>\n <button class=\"mj-btn mj-btn-secondary\" (click)=\"exportUsers()\">\n <i class=\"fa-solid fa-download\"></i>\n Export\n </button>\n <button class=\"mj-btn mj-btn-primary\" (click)=\"createNewUser()\">\n <i class=\"fa-solid fa-plus\"></i>\n Add User\n </button>\n </div>\n\n <!-- Stats Cards -->\n <div class=\"mj-grid mj-grid-4\">\n <div class=\"mj-card\">\n <div class=\"stat-icon stat-icon-total\">\n <i class=\"fa-solid fa-users\"></i>\n </div>\n <div class=\"stat-content\">\n <div class=\"stat-value\">{{ stats.totalUsers }}</div>\n <div class=\"stat-label\">Total Users</div>\n </div>\n </div>\n \n <div class=\"mj-card\">\n <div class=\"stat-icon stat-icon-active\">\n <i class=\"fa-solid fa-user-check\"></i>\n </div>\n <div class=\"stat-content\">\n <div class=\"stat-value\">{{ stats.activeUsers }}</div>\n <div class=\"stat-label\">Active Users</div>\n </div>\n </div>\n \n <div class=\"mj-card\">\n <div class=\"stat-icon stat-icon-inactive\">\n <i class=\"fa-solid fa-user-xmark\"></i>\n </div>\n <div class=\"stat-content\">\n <div class=\"stat-value\">{{ stats.inactiveUsers }}</div>\n <div class=\"stat-label\">Inactive Users</div>\n </div>\n </div>\n \n <div class=\"mj-card\">\n <div class=\"stat-icon stat-icon-admin\">\n <i class=\"fa-solid fa-shield-halved\"></i>\n </div>\n <div class=\"stat-content\">\n <div class=\"stat-value\">{{ stats.adminUsers }}</div>\n <div class=\"stat-label\">Owners</div>\n </div>\n </div>\n </div>\n\n <!-- Filters Section -->\n <div class=\"filters-section\">\n <div class=\"filters-row\">\n <!-- Search -->\n <div class=\"search-container\">\n <i class=\"fa-solid fa-search search-icon\"></i>\n <input \n type=\"text\" \n class=\"search-input\" \n placeholder=\"Search users by name or email...\"\n (input)=\"onSearchChange($event)\"\n [value]=\"filters$.value.search\"\n />\n </div>\n \n <!-- Status Filter -->\n <div class=\"filter-group\">\n <label class=\"filter-label\">Status</label>\n <div class=\"filter-buttons\">\n <button \n class=\"mj-btn mj-btn-ghost\"\n [class.mj-btn-primary]=\"filters$.value.status === 'all'\"\n (click)=\"onStatusFilterChange('all')\"\n >\n All\n </button>\n <button \n class=\"mj-btn mj-btn-ghost\"\n [class.mj-btn-primary]=\"filters$.value.status === 'active'\"\n (click)=\"onStatusFilterChange('active')\"\n >\n Active\n </button>\n <button \n class=\"mj-btn mj-btn-ghost\"\n [class.mj-btn-primary]=\"filters$.value.status === 'inactive'\"\n (click)=\"onStatusFilterChange('inactive')\"\n >\n Inactive\n </button>\n </div>\n </div>\n \n <!-- Role Filter -->\n <div class=\"filter-group\">\n <label class=\"filter-label\">Role</label>\n <select class=\"filter-select\" (change)=\"onRoleFilterChange($any($event.target).value)\">\n <option value=\"\">All Roles</option>\n @for (role of roles; track role.ID) {\n <option [value]=\"role.ID\">{{ role.Name }}</option>\n }\n </select>\n </div>\n \n <!-- View Toggle -->\n <div class=\"view-toggle\">\n <button \n class=\"mj-btn mj-btn-icon-only\"\n [class.mj-btn-primary]=\"viewMode === 'grid'\"\n [class.mj-btn-ghost]=\"viewMode !== 'grid'\"\n (click)=\"viewMode = 'grid'\"\n title=\"Grid View\"\n >\n <i class=\"fa-solid fa-table\"></i>\n </button>\n <button \n class=\"mj-btn mj-btn-icon-only\"\n [class.mj-btn-primary]=\"viewMode === 'cards'\"\n [class.mj-btn-ghost]=\"viewMode !== 'cards'\"\n (click)=\"viewMode = 'cards'\"\n title=\"Card View\"\n >\n <i class=\"fa-solid fa-th-large\"></i>\n </button>\n </div>\n </div>\n </div>\n\n <!-- Loading State -->\n @if (isLoading) {\n <div class=\"loading-container\">\n <div class=\"loading-spinner\">\n <div class=\"spinner-ring\"></div>\n <div class=\"spinner-ring\"></div>\n <div class=\"spinner-ring\"></div>\n </div>\n <div class=\"loading-text\">Loading users...</div>\n </div>\n }\n\n <!-- Error State -->\n @if (error && !isLoading) {\n <div class=\"error-container\">\n <div class=\"error-content\">\n <i class=\"fa-solid fa-exclamation-triangle error-icon\"></i>\n <p class=\"error-message\">{{ error }}</p>\n <button class=\"retry-button\" (click)=\"loadInitialData()\">\n <i class=\"fa-solid fa-refresh\"></i>\n Try Again\n </button>\n </div>\n </div>\n }\n\n <!-- Content Area -->\n @if (!isLoading && !error) {\n <div class=\"content-area\">\n <!-- Grid View -->\n @if (viewMode === 'grid') {\n <div class=\"users-table\">\n <table class=\"modern-table\">\n <thead>\n <tr>\n <th class=\"th-checkbox\">\n <input type=\"checkbox\" class=\"checkbox\" />\n </th>\n <th>User</th>\n <th>Email</th>\n <th>Type</th>\n <th>Status</th>\n <th>Last Updated</th>\n <th class=\"th-actions\">Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (user of filteredUsers; track user.ID) {\n <tr class=\"table-row\" (click)=\"selectUser(user)\">\n <td class=\"td-checkbox\" (click)=\"$event.stopPropagation()\">\n <input type=\"checkbox\" class=\"checkbox\" />\n </td>\n <td>\n <div class=\"user-cell\">\n <div class=\"user-avatar\">\n {{ getUserInitials(user) }}\n </div>\n <div class=\"user-info\">\n <div class=\"user-name\">{{ user.Name }}</div>\n <div class=\"user-fullname\">\n {{ user.FirstName }} {{ user.LastName }}\n </div>\n </div>\n </div>\n </td>\n <td>{{ user.Email }}</td>\n <td>\n <div class=\"user-type\">\n <i [class]=\"'fa-solid ' + getUserTypeIcon(user)\"></i>\n {{ user.Type }}\n </div>\n </td>\n <td>\n <span class=\"status-badge\" [class]=\"getStatusClass(user)\">\n <i [class]=\"'fa-solid ' + getStatusIcon(user)\"></i>\n {{ user.IsActive ? 'Active' : 'Inactive' }}\n </span>\n </td>\n <td>\n <span class=\"last-login\">\n {{ user.__mj_UpdatedAt ? (user.__mj_UpdatedAt | date:'short') : 'Never' }}\n </span>\n </td>\n <td class=\"td-actions\" (click)=\"$event.stopPropagation()\">\n <button class=\"mj-btn mj-btn-ghost mj-btn-sm\" (click)=\"editUser(user)\" title=\"Edit\">\n <i class=\"fa-solid fa-edit\"></i>\n </button>\n <button \n class=\"mj-btn mj-btn-ghost mj-btn-sm\" \n (click)=\"toggleUserStatus(user)\"\n [title]=\"user.IsActive ? 'Deactivate' : 'Activate'\"\n >\n <i [class]=\"user.IsActive ? 'fa-solid fa-toggle-on' : 'fa-solid fa-toggle-off'\"></i>\n </button>\n <button class=\"mj-btn mj-btn-ghost mj-btn-sm text-danger\" (click)=\"confirmDeleteUser(user)\" title=\"Delete\">\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n </td>\n </tr>\n }\n </tbody>\n </table>\n \n @if (filteredUsers.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-users-slash empty-icon\"></i>\n <p class=\"empty-text\">No users found</p>\n <p class=\"empty-subtext\">Try adjusting your filters or add a new user</p>\n </div>\n }\n </div>\n }\n\n <!-- Card View -->\n @if (viewMode === 'cards') {\n <div class=\"users-grid\">\n @for (user of filteredUsers; track user.ID) {\n <div class=\"user-card\" (click)=\"selectUser(user)\">\n <div class=\"card-header\">\n <div class=\"user-avatar-large\">\n {{ getUserInitials(user) }}\n </div>\n <div class=\"card-actions\">\n <button class=\"mj-btn mj-btn-ghost mj-btn-sm\" (click)=\"editUser(user); $event.stopPropagation()\" title=\"Edit\">\n <i class=\"fa-solid fa-edit\"></i>\n </button>\n <button class=\"mj-btn mj-btn-ghost mj-btn-sm text-danger\" (click)=\"confirmDeleteUser(user); $event.stopPropagation()\" title=\"Delete\">\n <i class=\"fa-solid fa-trash\"></i>\n </button>\n </div>\n </div>\n \n <div class=\"card-body\">\n <h3 class=\"user-name\">{{ user.Name }}</h3>\n <p class=\"user-fullname\">{{ user.FirstName }} {{ user.LastName }}</p>\n <p class=\"user-email\">\n <i class=\"fa-solid fa-envelope\"></i>\n {{ user.Email }}\n </p>\n \n <div class=\"card-meta\">\n <div class=\"meta-item\">\n <i [class]=\"'fa-solid ' + getUserTypeIcon(user)\"></i>\n {{ user.Type }}\n </div>\n <span class=\"status-badge\" [class]=\"getStatusClass(user)\">\n <i [class]=\"'fa-solid ' + getStatusIcon(user)\"></i>\n {{ user.IsActive ? 'Active' : 'Inactive' }}\n </span>\n </div>\n \n <div class=\"card-footer\">\n <div class=\"last-login\">\n <i class=\"fa-solid fa-clock\"></i>\n Last updated: {{ user.__mj_UpdatedAt ? (user.__mj_UpdatedAt | date:'short') : 'Never' }}\n </div>\n </div>\n </div>\n </div>\n }\n \n @if (filteredUsers.length === 0) {\n <div class=\"empty-state\">\n <i class=\"fa-solid fa-users-slash empty-icon\"></i>\n <p class=\"empty-text\">No users found</p>\n <p class=\"empty-subtext\">Try adjusting your filters or add a new user</p>\n </div>\n }\n </div>\n }\n </div>\n }\n\n <!-- User Create/Edit Dialog -->\n <mj-user-dialog \n [data]=\"userDialogData\"\n [visible]=\"showUserDialog\"\n (result)=\"onUserDialogResult($event)\">\n </mj-user-dialog>\n\n <!-- Delete Confirmation Dialog -->\n @if (showDeleteConfirm && selectedUser) {\n <div class=\"modal-backdrop\" (click)=\"showDeleteConfirm = false\">\n <div class=\"modal-dialog\" (click)=\"$event.stopPropagation()\">\n <div class=\"modal-header\">\n <h3 class=\"modal-title\">\n <i class=\"fa-solid fa-exclamation-triangle text-danger\"></i>\n Confirm Delete\n </h3>\n <button class=\"modal-close\" (click)=\"showDeleteConfirm = false\">\n <i class=\"fa-solid fa-times\"></i>\n </button>\n </div>\n <div class=\"modal-body\">\n <p>Are you sure you want to delete user <strong>{{ selectedUser.Name }}</strong>?</p>\n <p class=\"text-muted\">This action cannot be undone.</p>\n </div>\n <div class=\"modal-footer\">\n <button class=\"mj-btn mj-btn-secondary\" (click)=\"showDeleteConfirm = false\">Cancel</button>\n <button class=\"mj-btn mj-btn-primary text-danger\" (click)=\"deleteUser()\">\n <i class=\"fa-solid fa-trash\"></i>\n Delete User\n </button>\n </div>\n </div>\n </div>\n }\n</div>", styles: ["@import '../shared/styles/variables';\n@import '../shared/styles/mixins';\n\n.user-management-container {\n @include scrollable-container;\n width: 100%;\n height: 100%;\n}\n\n// Action Buttons\n.action-buttons {\n @include fixed-header;\n display: flex;\n gap: 0.75rem;\n justify-content: flex-end;\n padding: 1rem 2rem;\n background: white;\n border-bottom: 1px solid $border-light;\n\n @media (max-width: 768px) {\n justify-content: center;\n flex-wrap: wrap;\n padding: 1rem;\n }\n}\n\n// Buttons\n.btn-primary {\n @include button-base;\n background-color: #2196f3;\n color: white;\n \n &:hover {\n background-color: #1976d2;\n transform: translateY(-1px);\n box-shadow: 0 4px 12px rgba(33, 150, 243, 0.3);\n }\n\n &:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n transform: none;\n }\n}\n\n.btn-secondary {\n @include button-base;\n background-color: #ffffff;\n color: #374151;\n border: 1px solid #e5e7eb;\n \n &:hover {\n background-color: #f9fafb;\n border-color: #2196f3;\n color: #2196f3;\n }\n}\n\n.btn-danger {\n @include button-base;\n background-color: #f44336;\n color: white;\n \n &:hover {\n background-color: #d32f2f;\n }\n}\n\n// Stats Grid\n.stats-grid {\n @include fixed-header;\n display: grid !important;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 1.5rem;\n padding: 1rem 2rem;\n background: white;\n border-bottom: 1px solid $border-light;\n width: 100%;\n\n @media (max-width: 768px) {\n grid-template-columns: repeat(2, 1fr);\n gap: 1rem;\n padding: 1rem;\n }\n}\n\n.stat-card {\n background: white;\n border-radius: 12px;\n padding: 1.5rem;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);\n display: flex;\n margin-right: 10px;\n align-items: center;\n gap: 1rem;\n transition: all 0.3s ease;\n min-width: 0; // Prevent grid blowout\n\n &:hover {\n transform: translateY(-2px);\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12);\n }\n}\n\n.stat-icon {\n width: 60px;\n height: 60px;\n border-radius: 12px;\n display: flex;\n align-items: center;\n justify-content: center;\n font-size: 1.5rem;\n\n &-total {\n background: rgba(33, 150, 243, 0.1);\n color: #2196f3;\n }\n\n &-active {\n background: rgba(76, 175, 80, 0.1);\n color: #4caf50;\n }\n\n &-inactive {\n background: rgba(244, 67, 54, 0.1);\n color: #f44336;\n }\n\n &-admin {\n background: rgba(156, 39, 176, 0.1);\n color: #9c27b0;\n }\n}\n\n.stat-content {\n flex: 1;\n\n .stat-value {\n font-size: 2rem;\n font-weight: 700;\n color: #1f2937;\n line-height: 1;\n }\n\n .stat-label {\n color: #6b7280;\n font-size: 0.875rem;\n margin-top: 0.25rem;\n }\n}\n\n// Filters Section\n.filters-section {\n @include fixed-header;\n background: white;\n padding: 1.5rem 2rem;\n border-bottom: 1px solid $border-light;\n\n @media (max-width: 768px) {\n padding: 1rem;\n }\n}\n\n.filters-row {\n display: flex;\n gap: 1.5rem;\n align-items: flex-end;\n flex-wrap: wrap;\n\n @media (max-width: 768px) {\n gap: 1rem;\n }\n}\n\n.search-container {\n flex: 1;\n min-width: 250px;\n position: relative;\n\n .search-icon {\n position: absolute;\n left: 1rem;\n top: 50%;\n transform: translateY(-50%);\n color: #6b7280;\n font-size: 1rem;\n }\n\n .search-input {\n width: 100%;\n padding: 0.75rem 1rem 0.75rem 2.75rem;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n font-size: 0.95rem;\n transition: all 0.2s;\n\n &:focus {\n outline: none;\n border-color: #2196f3;\n box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n }\n }\n}\n\n.filter-group {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n\n .filter-label {\n font-size: 0.875rem;\n font-weight: 500;\n color: #374151;\n }\n\n .filter-buttons {\n display: flex;\n background: #f3f4f6;\n border-radius: 8px;\n padding: 4px;\n }\n\n .filter-btn {\n padding: 0.5rem 1rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 0.875rem;\n font-weight: 500;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n color: #374151;\n }\n\n &.active {\n background: white;\n color: #2196f3;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n }\n\n .filter-select {\n padding: 0.625rem 1rem;\n border: 1px solid #e5e7eb;\n border-radius: 8px;\n background: white;\n font-size: 0.875rem;\n color: #374151;\n cursor: pointer;\n transition: all 0.2s;\n\n &:focus {\n outline: none;\n border-color: #2196f3;\n box-shadow: 0 0 0 3px rgba(33, 150, 243, 0.1);\n }\n }\n}\n\n.view-toggle {\n display: flex;\n background: #f3f4f6;\n border-radius: 8px;\n padding: 4px;\n\n .view-btn {\n padding: 0.5rem 0.75rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1rem;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n color: #374151;\n }\n\n &.active {\n background: white;\n color: #2196f3;\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);\n }\n }\n}\n\n// Content Area\n.content-area {\n @include scrollable-content;\n padding: 2rem;\n background: white;\n\n @media (max-width: 768px) {\n padding: 1rem;\n }\n}\n\n// Table View\n.users-table {\n width: 100%;\n overflow-x: auto;\n}\n\n.modern-table {\n width: 100%;\n border-collapse: collapse;\n\n thead {\n background: #f9fafb;\n border-bottom: 1px solid #e5e7eb;\n\n th {\n padding: 1rem;\n text-align: left;\n font-size: 0.875rem;\n font-weight: 600;\n color: #374151;\n white-space: nowrap;\n\n &.th-checkbox {\n width: 50px;\n }\n\n &.th-actions {\n text-align: center;\n width: 150px;\n }\n }\n }\n\n tbody {\n tr {\n border-bottom: 1px solid #f3f4f6;\n transition: background-color 0.2s;\n cursor: pointer;\n\n &:hover {\n background-color: #f9fafb;\n }\n }\n\n td {\n padding: 1rem;\n font-size: 0.875rem;\n color: #374151;\n\n &.td-checkbox {\n width: 50px;\n }\n\n &.td-actions {\n text-align: center;\n }\n }\n }\n}\n\n.checkbox {\n width: 18px;\n height: 18px;\n cursor: pointer;\n}\n\n.user-cell {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n}\n\n.user-avatar {\n width: 40px;\n height: 40px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 0.875rem;\n}\n\n.user-avatar-large {\n width: 80px;\n height: 80px;\n border-radius: 50%;\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\n color: white;\n display: flex;\n align-items: center;\n justify-content: center;\n font-weight: 600;\n font-size: 1.5rem;\n margin: 0 auto 1rem;\n}\n\n.user-info {\n .user-name {\n font-weight: 600;\n color: #1f2937;\n }\n\n .user-fullname {\n font-size: 0.75rem;\n color: #6b7280;\n }\n}\n\n.user-type {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n color: #4b5563;\n\n i {\n font-size: 0.875rem;\n }\n}\n\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 0.375rem;\n padding: 0.25rem 0.75rem;\n border-radius: 20px;\n font-size: 0.75rem;\n font-weight: 500;\n\n &.status-active {\n background: rgba(76, 175, 80, 0.1);\n color: #388e3c;\n }\n\n &.status-inactive {\n background: rgba(244, 67, 54, 0.1);\n color: #d32f2f;\n }\n\n i {\n font-size: 0.625rem;\n }\n}\n\n.last-login {\n color: #6b7280;\n font-size: 0.875rem;\n}\n\n.action-btn {\n padding: 0.5rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1rem;\n border-radius: 6px;\n cursor: pointer;\n transition: all 0.2s;\n\n &:hover {\n background: #f3f4f6;\n color: #2196f3;\n }\n\n &-danger:hover {\n color: #f44336;\n }\n}\n\n// Card View\n.users-grid {\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n gap: 1.5rem;\n padding: 1.5rem;\n\n @media (max-width: 768px) {\n grid-template-columns: 1fr;\n gap: 1rem;\n padding: 1rem;\n }\n}\n\n.user-card {\n background: white;\n border: 1px solid #e5e7eb;\n border-radius: 12px;\n padding: 1.5rem;\n cursor: pointer;\n transition: all 0.3s ease;\n\n &:hover {\n box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1);\n transform: translateY(-2px);\n border-color: #2196f3;\n }\n}\n\n.card-header {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 1rem;\n}\n\n.card-actions {\n display: flex;\n gap: 0.5rem;\n}\n\n.card-body {\n text-align: center;\n\n .user-name {\n font-size: 1.25rem;\n font-weight: 600;\n color: #1f2937;\n margin: 0 0 0.25rem 0;\n }\n\n .user-fullname {\n color: #6b7280;\n margin: 0 0 0.75rem 0;\n }\n\n .user-email {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n color: #4b5563;\n font-size: 0.875rem;\n margin-bottom: 1rem;\n\n i {\n color: #6b7280;\n }\n }\n}\n\n.card-meta {\n display: flex;\n justify-content: center;\n align-items: center;\n gap: 1rem;\n padding: 1rem 0;\n border-top: 1px solid #f3f4f6;\n border-bottom: 1px solid #f3f4f6;\n margin-bottom: 1rem;\n\n .meta-item {\n display: flex;\n align-items: center;\n gap: 0.375rem;\n color: #4b5563;\n font-size: 0.875rem;\n }\n}\n\n.card-footer {\n .last-login {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.375rem;\n color: #6b7280;\n font-size: 0.75rem;\n\n i {\n font-size: 0.875rem;\n }\n }\n}\n\n// Empty State\n.empty-state {\n text-align: center;\n padding: 4rem 2rem;\n\n .empty-icon {\n font-size: 4rem;\n color: #e5e7eb;\n margin-bottom: 1rem;\n }\n\n .empty-text {\n font-size: 1.25rem;\n font-weight: 600;\n color: #374151;\n margin: 0 0 0.5rem 0;\n }\n\n .empty-subtext {\n color: #6b7280;\n margin: 0;\n }\n}\n\n// Loading State\n.loading-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n justify-content: center;\n padding: 4rem 2rem;\n}\n\n.loading-spinner {\n position: relative;\n width: 60px;\n height: 60px;\n margin-bottom: 1rem;\n\n .spinner-ring {\n position: absolute;\n width: 100%;\n height: 100%;\n border: 3px solid transparent;\n border-radius: 50%;\n animation: spin 1.5s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n\n &:nth-child(1) {\n border-color: #2196f3 transparent transparent transparent;\n animation-delay: -0.45s;\n }\n\n &:nth-child(2) {\n border-color: transparent #4caf50 transparent transparent;\n animation-delay: -0.3s;\n }\n\n &:nth-child(3) {\n border-color: transparent transparent #ff9800 transparent;\n animation-delay: -0.15s;\n }\n }\n}\n\n@keyframes spin {\n 0% {\n transform: rotate(0deg);\n }\n 100% {\n transform: rotate(360deg);\n }\n}\n\n.loading-text {\n color: #6b7280;\n font-size: 0.95rem;\n}\n\n// Error State\n.error-container {\n text-align: center;\n padding: 4rem 2rem;\n\n .error-icon {\n font-size: 3rem;\n color: #f44336;\n margin-bottom: 1rem;\n }\n\n .error-message {\n color: #374151;\n margin-bottom: 1.5rem;\n }\n\n .retry-button {\n @include button-base;\n background-color: #2196f3;\n color: white;\n }\n}\n\n// Modal Styles\n.modal-backdrop {\n position: fixed;\n top: 0;\n left: 0;\n right: 0;\n bottom: 0;\n background: rgba(0, 0, 0, 0.5);\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 1000;\n animation: fadeIn 0.2s ease;\n}\n\n.modal-dialog {\n background: white;\n border-radius: 12px;\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\n max-width: 500px;\n width: 90%;\n max-height: 90vh;\n overflow: hidden;\n animation: slideUp 0.3s ease;\n}\n\n.modal-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 1.5rem;\n border-bottom: 1px solid #e5e7eb;\n\n .modal-title {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n font-size: 1.25rem;\n font-weight: 600;\n color: #1f2937;\n margin: 0;\n }\n\n .modal-close {\n padding: 0.5rem;\n border: none;\n background: transparent;\n color: #6b7280;\n font-size: 1.25rem;\n cursor: pointer;\n border-radius: 6px;\n transition: all 0.2s;\n\n &:hover {\n background: #f3f4f6;\n color: #374151;\n }\n }\n}\n\n.modal-body {\n padding: 1.5rem;\n\n p {\n margin: 0 0 1rem 0;\n color: #374151;\n\n &:last-child {\n margin-bottom: 0;\n }\n }\n\n .text-muted {\n color: #6b7280;\n font-size: 0.875rem;\n }\n}\n\n.modal-footer {\n display: flex;\n justify-content: flex-end;\n gap: 0.75rem;\n padding: 1.5rem;\n border-top: 1px solid #e5e7eb;\n background: #f9fafb;\n}\n\n// Animations\n@keyframes fadeIn {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n\n@keyframes slideUp {\n from {\n transform: translateY(20px);\n opacity: 0;\n }\n to {\n transform: translateY(0);\n opacity: 1;\n }\n}\n\n// Utility Classes\n.text-danger {\n color: #f44336;\n}"] }]
|
|
641
756
|
}], () => [], null); })();
|
|
642
|
-
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(UserManagementComponent, { className: "UserManagementComponent", filePath: "src/lib/user-management/user-management.component.ts", lineNumber:
|
|
757
|
+
(() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassDebugInfo(UserManagementComponent, { className: "UserManagementComponent", filePath: "src/lib/user-management/user-management.component.ts", lineNumber: 38 }); })();
|
|
643
758
|
//# sourceMappingURL=user-management.component.js.map
|