Skip to content

Commit 1ab9e2f

Browse files
fix/content-render-timing
1 parent 30dd814 commit 1ab9e2f

File tree

7 files changed

+596
-85
lines changed

7 files changed

+596
-85
lines changed
Lines changed: 86 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,86 +1,95 @@
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+
{{ profileData.authenticated ? 'Authenticated' : 'Not Authenticated' }}
60+
</div>
5761
</div>
58-
<div>
59-
{{ language }}
62+
<div class="info-box">
63+
<div class="header">
64+
{{ 'labels.inputs.Language' | translate }}
65+
</div>
66+
<div>
67+
{{ language }}
68+
</div>
6069
</div>
6170
</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>
71+
</mat-card>
72+
<mat-card>
73+
<table class="mat-elevation-z1" mat-table [dataSource]="dataSource">
74+
<ng-container matColumnDef="role">
75+
<th mat-header-cell *matHeaderCellDef>
76+
{{ 'labels.inputs.Role' | translate }}
77+
</th>
78+
<td mat-cell *matCellDef="let role">
79+
{{ role.name }}
80+
</td>
81+
</ng-container>
82+
<ng-container matColumnDef="description">
83+
<th mat-header-cell *matHeaderCellDef>
84+
{{ 'labels.inputs.Description' | translate }}
85+
</th>
86+
<td mat-cell *matCellDef="let role">
87+
{{ role.description }}
88+
</td>
89+
</ng-container>
90+
<tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
91+
<tr mat-row *matRowDef="let row; columns: displayedColumns"></tr>
92+
</table>
93+
</mat-card>
94+
</div>
8695
</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: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<!-- Profile Type Skeleton -->
2+
<div *ngIf="type === 'profile'" class="skeleton-loader-profile">
3+
<!-- Buttons -->
4+
<div class="skeleton-buttons" *ngIf="showButtons">
5+
<div class="skeleton-button" *ngFor="let i of [].constructor(buttonCount); let idx = index"></div>
6+
</div>
7+
8+
<!-- Info Boxes Card -->
9+
<div class="skeleton-card">
10+
<div class="layout-row-wrap">
11+
<div class="skeleton-info-box" *ngFor="let i of [].constructor(6); let idx = index">
12+
<div class="skeleton-header"></div>
13+
<div class="skeleton-text"></div>
14+
</div>
15+
</div>
16+
</div>
17+
18+
<!-- Table Card -->
19+
<div class="skeleton-card">
20+
<div class="skeleton-table" [style.--columns]="tableColumns">
21+
<div class="skeleton-table-header">
22+
<div class="skeleton-cell" *ngFor="let i of [].constructor(tableColumns); let idx = index"></div>
23+
</div>
24+
<div class="skeleton-table-row" *ngFor="let i of [].constructor(tableRows); let idx = index">
25+
<div class="skeleton-cell" *ngFor="let j of [].constructor(tableColumns); let idx2 = index"></div>
26+
</div>
27+
</div>
28+
</div>
29+
</div>
30+
31+
<!-- Table Type Skeleton -->
32+
<div *ngIf="type === 'table'" class="skeleton-loader-table">
33+
<div class="skeleton-table" [style.--columns]="tableColumns">
34+
<div class="skeleton-table-header">
35+
<div class="skeleton-cell" *ngFor="let i of [].constructor(tableColumns); let idx = index"></div>
36+
</div>
37+
<div class="skeleton-table-row" *ngFor="let i of [].constructor(tableRows); let idx = index">
38+
<div class="skeleton-cell" *ngFor="let j of [].constructor(tableColumns); let idx2 = index"></div>
39+
</div>
40+
</div>
41+
</div>
42+
43+
<!-- Card Type Skeleton -->
44+
<div *ngIf="type === 'card'" class="skeleton-loader-card">
45+
<div class="skeleton-card-item" *ngFor="let i of [].constructor(items); let idx = index">
46+
<div class="skeleton-card-header">
47+
<div class="skeleton-avatar"></div>
48+
<div class="skeleton-card-title"></div>
49+
</div>
50+
<div class="skeleton-card-content">
51+
<div class="skeleton-line skeleton-line-90"></div>
52+
<div class="skeleton-line skeleton-line-70"></div>
53+
<div class="skeleton-line skeleton-line-85"></div>
54+
</div>
55+
<div class="skeleton-card-footer"></div>
56+
</div>
57+
</div>
58+
59+
<!-- List Type Skeleton -->
60+
<div *ngIf="type === 'list'" class="skeleton-loader-list">
61+
<div class="skeleton-list-item" *ngFor="let i of [].constructor(items); let idx = index">
62+
<div class="skeleton-avatar"></div>
63+
<div class="skeleton-list-content">
64+
<div class="skeleton-line skeleton-line-60"></div>
65+
<div class="skeleton-line skeleton-line-40"></div>
66+
</div>
67+
</div>
68+
</div>
69+
70+
<!-- Custom Type Skeleton -->
71+
<div *ngIf="type === 'custom'" class="skeleton-loader-custom" [ngClass]="cssClass">
72+
<ng-content></ng-content>
73+
</div>

0 commit comments

Comments
 (0)