Skip to content

Commit 0b8fa32

Browse files
fix/content-render-timing
fix/i18n-and-darkmode-skeleton-profile fix: content rendering timing and i18n dark mode skeleton issue in profile
1 parent 30dd814 commit 0b8fa32

File tree

8 files changed

+633
-85
lines changed

8 files changed

+633
-85
lines changed
Lines changed: 90 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,99 @@
1-
<div class="container m-b-10 layout-row layout-lt-md-column align-end gap-1percent">
2-
<button mat-raised-button color="primary" class="m-r-10" [routerLink]="['/system', 'roles-and-permissions']">
3-
<fa-icon icon="check" class="m-r-10"></fa-icon>
4-
{{ 'labels.buttons.Permissions' | translate }}
5-
</button>
6-
<button mat-raised-button color="primary" class="m-r-10" (click)="changeUserPassword()">
7-
<fa-icon icon="cog" class="m-r-10"></fa-icon>
8-
{{ 'labels.buttons.Change Password' | translate }}
9-
</button>
1+
<!-- Skeleton Loader -->
2+
<div *ngIf="isLoadingTranslations" class="skeleton-wrapper">
3+
<mifosx-skeleton-loader type="profile" [showButtons]="true" [buttonCount]="2" [tableRows]="3" [tableColumns]="2">
4+
</mifosx-skeleton-loader>
105
</div>
11-
<div class="container layout-column gap-1percent">
12-
<mat-card>
13-
<div class="layout-row-wrap">
14-
<div class="info-box">
15-
<div class="header">
16-
{{ 'labels.inputs.Tenant Id' | translate }}
6+
7+
<!-- Actual Content -->
8+
<div *ngIf="!isLoadingTranslations">
9+
<div class="container m-b-10 layout-row layout-lt-md-column align-end gap-1percent">
10+
<button mat-raised-button color="primary" class="m-r-10" [routerLink]="['/system', 'roles-and-permissions']">
11+
<fa-icon icon="check" class="m-r-10"></fa-icon>
12+
{{ 'labels.buttons.Permissions' | translate }}
13+
</button>
14+
<button mat-raised-button color="primary" class="m-r-10" (click)="changeUserPassword()">
15+
<fa-icon icon="cog" class="m-r-10"></fa-icon>
16+
{{ 'labels.buttons.Change Password' | translate }}
17+
</button>
18+
</div>
19+
<div class="container layout-column gap-1percent">
20+
<mat-card>
21+
<div class="layout-row-wrap">
22+
<div class="info-box">
23+
<div class="header">
24+
{{ 'labels.inputs.Tenant Id' | translate }}
25+
</div>
26+
<div>
27+
{{ tenantIdentifier }}
28+
</div>
1729
</div>
18-
<div>
19-
{{ tenantIdentifier }}
30+
<div class="info-box">
31+
<div class="header">
32+
{{ 'labels.inputs.User Id' | translate }}
33+
</div>
34+
<div>
35+
{{ profileData.userId }}
36+
</div>
2037
</div>
21-
</div>
22-
<div class="info-box">
23-
<div class="header">
24-
{{ 'labels.inputs.User Id' | translate }}
25-
</div>
26-
<div>
27-
{{ profileData.userId }}
28-
</div>
29-
</div>
30-
<div class="info-box">
31-
<div class="header">
32-
{{ 'labels.inputs.User Name' | translate }}
33-
</div>
34-
<div>
35-
{{ profileData.username }}
38+
<div class="info-box">
39+
<div class="header">
40+
{{ 'labels.inputs.User Name' | translate }}
41+
</div>
42+
<div>
43+
{{ profileData.username }}
44+
</div>
3645
</div>
37-
</div>
38-
<div class="info-box">
39-
<div class="header">
40-
{{ 'labels.inputs.Office' | translate }}
46+
<div class="info-box">
47+
<div class="header">
48+
{{ 'labels.inputs.Office' | translate }}
49+
</div>
50+
<div>
51+
{{ profileData.officeName }}
52+
</div>
4153
</div>
42-
<div>
43-
{{ profileData.officeName }}
44-
</div>
45-
</div>
46-
<div class="info-box">
47-
<div class="header">
48-
{{ 'labels.inputs.Status' | translate }}
49-
</div>
50-
<div>
51-
{{ profileData.authenticated ? 'Authenticated' : 'Not Authenticated' }}
52-
</div>
53-
</div>
54-
<div class="info-box">
55-
<div class="header">
56-
{{ 'labels.inputs.Language' | translate }}
54+
<div class="info-box">
55+
<div class="header">
56+
{{ 'labels.inputs.Status' | translate }}
57+
</div>
58+
<div>
59+
{{
60+
profileData.authenticated
61+
? ('labels.states.Authenticated' | translate)
62+
: ('labels.states.Not Authenticated' | translate)
63+
}}
64+
</div>
5765
</div>
58-
<div>
59-
{{ language }}
66+
<div class="info-box">
67+
<div class="header">
68+
{{ 'labels.inputs.Language' | translate }}
69+
</div>
70+
<div>
71+
{{ language }}
72+
</div>
6073
</div>
6174
</div>
62-
</div>
63-
</mat-card>
64-
<mat-card>
65-
<table class="mat-elevation-z1" mat-table [dataSource]="dataSource">
66-
<ng-container matColumnDef="role">
67-
<th mat-header-cell *matHeaderCellDef>
68-
{{ 'labels.inputs.Role' | translate }}
69-
</th>
70-
<td mat-cell *matCellDef="let role">
71-
{{ role.name }}
72-
</td>
73-
</ng-container>
74-
<ng-container matColumnDef="description">
75-
<th mat-header-cell *matHeaderCellDef>
76-
{{ 'labels.inputs.Description' | translate }}
77-
</th>
78-
<td mat-cell *matCellDef="let role">
79-
{{ role.description }}
80-
</td>
81-
</ng-container>
82-
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
83-
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
84-
</table>
85-
</mat-card>
75+
</mat-card>
76+
<mat-card>
77+
<table class="mat-elevation-z1" mat-table [dataSource]="dataSource">
78+
<ng-container matColumnDef="role">
79+
<th mat-header-cell *matHeaderCellDef>
80+
{{ 'labels.inputs.Role' | translate }}
81+
</th>
82+
<td mat-cell *matCellDef="let role">
83+
{{ role.name }}
84+
</td>
85+
</ng-container>
86+
<ng-container matColumnDef="description">
87+
<th mat-header-cell *matHeaderCellDef>
88+
{{ 'labels.inputs.Description' | translate }}
89+
</th>
90+
<td mat-cell *matCellDef="let role">
91+
{{ role.description }}
92+
</td>
93+
</ng-container>
94+
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
95+
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
96+
</table>
97+
</mat-card>
98+
</div>
8699
</div>

src/app/profile/profile.component.scss

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
.skeleton-wrapper {
2+
display: block;
3+
width: 100%;
4+
}
5+
6+
/* Generic table styles should come before specific ones */
7+
table th,
8+
table td {
9+
border-top: 1px solid var(--border-color, rgb(0 0 0 / 12%));
10+
}
11+
112
.container {
213
max-width: 37rem;
314
padding: 1rem;
@@ -76,11 +87,6 @@
7687
}
7788
}
7889

79-
.container table th,
80-
.container table td {
81-
border-top: 1px solid var(--border-color, rgb(0 0 0 / 12%));
82-
}
83-
8490
th.mat-header-cell:not(:first-of-type),
8591
td.mat-cell:not(:first-of-type) {
8692
border-left: 1px solid var(--border-color, rgb(0 0 0 / 12%));
@@ -92,7 +98,6 @@ td.mat-cell:not(:first-of-type) {
9298
border-radius: 6px;
9399
}
94100

95-
// Dark mode styles
96101
:host-context(.dark-theme) {
97102
--border-color: #444;
98103
--card-background: #2d2d2d;
@@ -107,10 +112,27 @@ td.mat-cell:not(:first-of-type) {
107112
.container {
108113
mat-card {
109114
box-shadow: 0 2px 4px rgb(0 0 0 / 30%);
115+
background-color: var(--card-background);
116+
color: var(--text-color);
110117
}
111118

112119
table {
113120
box-shadow: 0 1px 3px rgb(0 0 0 / 30%);
114121
}
115122
}
123+
124+
.skeleton-wrapper {
125+
background-color: transparent;
126+
}
127+
128+
button {
129+
&.mat-raised-button {
130+
background-color: var(--primary-color, #1976d2);
131+
color: #fff;
132+
133+
&:hover {
134+
background-color: var(--primary-hover-color, #1565c0);
135+
}
136+
}
137+
}
116138
}

src/app/profile/profile.component.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import { ChangePasswordDialogComponent } from 'app/shared/change-password-dialog
2222
import { SettingsService } from 'app/settings/settings.service';
2323
import { FaIconComponent } from '@fortawesome/angular-fontawesome';
2424
import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
25+
import { TranslateService } from '@ngx-translate/core';
26+
import { SkeletonLoaderComponent } from 'app/shared/skeleton-loader';
2527

2628
/**
2729
* Profile Component.
@@ -42,7 +44,8 @@ import { STANDALONE_SHARED_IMPORTS } from 'app/standalone-shared.module';
4244
MatHeaderRowDef,
4345
MatHeaderRow,
4446
MatRowDef,
45-
MatRow
47+
MatRow,
48+
SkeletonLoaderComponent
4649
]
4750
})
4851
export class ProfileComponent implements OnInit {
@@ -59,23 +62,45 @@ export class ProfileComponent implements OnInit {
5962
'description'
6063
];
6164

65+
/** Loading state for translations */
66+
isLoadingTranslations = true;
67+
6268
/**
6369
* @param {AuthenticationService} authenticationService Authentication Service
6470
* @param {UserService} userService Users Service
6571
* @param {Router} router Router
6672
* @param {MatDialog} dialog Mat Dialog
73+
* @param {TranslateService} translateService Translate Service
6774
*/
6875
constructor(
6976
private authenticationService: AuthenticationService,
7077
private settingsService: SettingsService,
7178
private router: Router,
72-
public dialog: MatDialog
79+
public dialog: MatDialog,
80+
private translateService: TranslateService
7381
) {
7482
this.profileData = authenticationService.getCredentials();
7583
}
7684

7785
ngOnInit() {
7886
this.dataSource = new MatTableDataSource(this.profileData.roles);
87+
88+
// Show skeleton for at least 600ms and wait for translations
89+
const startTime = Date.now();
90+
const minDisplayTime = 600;
91+
92+
this.translateService.get('labels.inputs.Tenant Id').subscribe(() => {
93+
const elapsedTime = Date.now() - startTime;
94+
const remainingTime = Math.max(0, minDisplayTime - elapsedTime);
95+
96+
if (remainingTime > 0) {
97+
setTimeout(() => {
98+
this.isLoadingTranslations = false;
99+
}, remainingTime);
100+
} else {
101+
this.isLoadingTranslations = false;
102+
}
103+
});
79104
}
80105

81106
/**
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { SkeletonLoaderComponent } from './skeleton-loader.component';
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<!-- Profile Type Skeleton -->
2+
<div *ngIf="type === 'profile'" class="skeleton-loader-profile">
3+
<!-- Buttons -->
4+
<div class="skeleton-buttons" *ngIf="showButtons">
5+
<div
6+
class="skeleton-button"
7+
*ngFor="let i of createArray(buttonCount); let idx = index; trackBy: trackByIndex"
8+
></div>
9+
</div>
10+
11+
<!-- Info Boxes Card -->
12+
<div class="skeleton-card">
13+
<div class="layout-row-wrap">
14+
<div class="skeleton-info-box" *ngFor="let i of createArray(6); let idx = index; trackBy: trackByIndex">
15+
<div class="skeleton-header"></div>
16+
<div class="skeleton-text"></div>
17+
</div>
18+
</div>
19+
</div>
20+
21+
<!-- Table Card -->
22+
<div class="skeleton-card">
23+
<div class="skeleton-table" [style.--columns]="tableColumns">
24+
<div class="skeleton-table-header">
25+
<div
26+
class="skeleton-cell"
27+
*ngFor="let i of createArray(tableColumns); let idx = index; trackBy: trackByIndex"
28+
></div>
29+
</div>
30+
<div class="skeleton-table-row" *ngFor="let i of createArray(tableRows); let idx = index; trackBy: trackByIndex">
31+
<div
32+
class="skeleton-cell"
33+
*ngFor="let j of createArray(tableColumns); let idx2 = index; trackBy: trackByIndex"
34+
></div>
35+
</div>
36+
</div>
37+
</div>
38+
</div>
39+
40+
<!-- Table Type Skeleton -->
41+
<div *ngIf="type === 'table'" class="skeleton-loader-table">
42+
<div class="skeleton-table" [style.--columns]="tableColumns">
43+
<div class="skeleton-table-header">
44+
<div
45+
class="skeleton-cell"
46+
*ngFor="let i of createArray(tableColumns); let idx = index; trackBy: trackByIndex"
47+
></div>
48+
</div>
49+
<div class="skeleton-table-row" *ngFor="let i of createArray(tableRows); let idx = index; trackBy: trackByIndex">
50+
<div
51+
class="skeleton-cell"
52+
*ngFor="let j of createArray(tableColumns); let idx2 = index; trackBy: trackByIndex"
53+
></div>
54+
</div>
55+
</div>
56+
</div>
57+
58+
<!-- Card Type Skeleton -->
59+
<div *ngIf="type === 'card'" class="skeleton-loader-card">
60+
<div class="skeleton-card-item" *ngFor="let i of createArray(items); let idx = index; trackBy: trackByIndex">
61+
<div class="skeleton-card-header">
62+
<div class="skeleton-avatar"></div>
63+
<div class="skeleton-card-title"></div>
64+
</div>
65+
<div class="skeleton-card-content">
66+
<div class="skeleton-line skeleton-line-90"></div>
67+
<div class="skeleton-line skeleton-line-70"></div>
68+
<div class="skeleton-line skeleton-line-85"></div>
69+
</div>
70+
<div class="skeleton-card-footer"></div>
71+
</div>
72+
</div>
73+
74+
<!-- List Type Skeleton -->
75+
<div *ngIf="type === 'list'" class="skeleton-loader-list">
76+
<div class="skeleton-list-item" *ngFor="let i of createArray(items); let idx = index; trackBy: trackByIndex">
77+
<div class="skeleton-avatar"></div>
78+
<div class="skeleton-list-content">
79+
<div class="skeleton-line skeleton-line-60"></div>
80+
<div class="skeleton-line skeleton-line-40"></div>
81+
</div>
82+
</div>
83+
</div>
84+
85+
<!-- Custom Type Skeleton -->
86+
<div *ngIf="type === 'custom'" class="skeleton-loader-custom" [ngClass]="cssClass">
87+
<ng-content></ng-content>
88+
</div>

0 commit comments

Comments
 (0)