Skip to content

Commit

Permalink
feat(front-admin-users)#131: convert HTML to dumb component
Browse files Browse the repository at this point in the history
  • Loading branch information
spy4x committed Dec 7, 2022
1 parent de3b181 commit dca9251
Show file tree
Hide file tree
Showing 30 changed files with 570 additions and 433 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div *ngIf="user$ | async as user">
<div *ngIf="user$ | async as user" class="h-full">
<!-- TODO: implement show/hide animations using "@angular/animations" -->
<!-- Off-canvas menu for mobile, show/hide based on off-canvas menu state. -->
<div *ngIf="isMobileMenuOpened" class="relative z-40 md:hidden" role="dialog" aria-modal="true">
Expand Down Expand Up @@ -157,7 +157,7 @@
</div>
</div>
</div>
<div class="flex flex-1 flex-col md:pl-64">
<div class="flex h-full flex-1 flex-col overflow-hidden md:pl-64">
<div class="sticky top-0 z-10 bg-gray-100 pl-1 pt-1 sm:pl-3 sm:pt-3 md:hidden">
<button
(click)="toggleMobileMenu(true)"
Expand All @@ -179,7 +179,7 @@
</svg>
</button>
</div>
<main class="flex-1">
<main class="max-h-[calc(100%-50px)] flex-1 md:max-h-full">
<router-outlet></router-outlet>
</main>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { UserDetailsComponent } from './user-details.component';
import { DetailComponent } from './detail.component';

describe('UserDetailsComponent', () => {
let component: UserDetailsComponent;
let fixture: ComponentFixture<UserDetailsComponent>;
let component: DetailComponent;
let fixture: ComponentFixture<DetailComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [UserDetailsComponent],
declarations: [DetailComponent],
}).compileComponents();

fixture = TestBed.createComponent(UserDetailsComponent);
fixture = TestBed.createComponent(DetailComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,8 @@ import { ChangeDetectionStrategy, Component, ViewEncapsulation } from '@angular/

@Component({
selector: 'seed-admin-users-details',
templateUrl: './user-details.component.html',
styles: [],
templateUrl: './detail.component.html',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class UserDetailsComponent {}
export class DetailComponent {}
44 changes: 44 additions & 0 deletions libs/front/admin/users/src/lib/list/filters/filters.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<!-- Tabs -->
<div [ngClass]="{ 'pointer-events-none opacity-50': isLoading }">
<!-- Mobile -->
<div class="p-4 sm:hidden">
<label for="tabs" class="sr-only">Select a tab</label>
<select
id="tabs"
name="tabs"
class="block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-purple-500 focus:outline-none focus:ring-purple-500 sm:text-sm"
[formControl]="tabControl"
>
<option *ngFor="let r of roleTitles" [ngValue]="r.value">
{{ r.title }} {{ !isLoading && r.value === role ? '(' + total + ')' : '' }}
</option>
</select>
</div>

<!-- Desktop -->
<div class="hidden sm:block">
<div class="border-b border-gray-200">
<nav class="-mb-px flex space-x-8 px-4" aria-label="Tabs">
<a
*ngFor="let r of roleTitles"
(click)="changeRole(r.value)"
href="javascript:;"
class="whitespace-nowrap border-b-2 py-4 px-1 text-sm font-medium"
[ngClass]="{
'border-purple-500 text-purple-600': r.value === role,
'border-transparent text-gray-500 hover:border-gray-200 hover:text-gray-700': r.value !== role
}"
>
{{ r.title }}
<span
*ngIf="r.value === role"
class="ml-2 inline-flex rounded-full bg-purple-100 py-0.5 px-2.5 text-xs font-medium text-purple-600"
>
<i *ngIf="isLoading" class="feather-loader animate-spin"></i>
<span *ngIf="!isLoading">{{ total }}</span>
</span>
</a>
</nav>
</div>
</div>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { FiltersComponent } from './filters.component';

describe('FiltersComponent', () => {
let component: FiltersComponent;
let fixture: ComponentFixture<FiltersComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [FiltersComponent],
}).compileComponents();

fixture = TestBed.createComponent(FiltersComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
58 changes: 58 additions & 0 deletions libs/front/admin/users/src/lib/list/filters/filters.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import {
ChangeDetectionStrategy,
Component,
EventEmitter,
Input,
OnChanges,
OnInit,
Output,
SimpleChanges,
} from '@angular/core';
import { UserRole } from '@prisma/client';
import { FormControl } from '@angular/forms';
import { UntilDestroy, untilDestroyed } from '@ngneat/until-destroy';
import { ZERO } from '@seed/shared/constants';

@UntilDestroy()
@Component({
selector: 'seed-admin-users-filters',
templateUrl: './filters.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FiltersComponent implements OnInit, OnChanges {
UserRole = UserRole;

roleTitles = [
{ value: UserRole.USER, title: 'Users' },
{ value: UserRole.ADMIN, title: 'Admins' },
{ value: UserRole.MODERATOR, title: 'Moderators' },
];

@Input() isLoading = true;

@Input() role: UserRole = UserRole.USER;

@Input() total = ZERO;

@Output() roleChange = new EventEmitter<UserRole>();

tabControl = new FormControl<UserRole>(this.role);

ngOnInit(): void {
this.tabControl.valueChanges.pipe(untilDestroyed(this)).subscribe(role => {
this.changeRole(role as UserRole);
});
}

ngOnChanges(changes: SimpleChanges): void {
if (changes['role'].currentValue !== changes['role'].previousValue) {
this.tabControl.setValue(this.role);
}
}

changeRole(role: UserRole): void {
if (role !== this.role) {
this.roleChange.emit(role);
}
}
}
25 changes: 25 additions & 0 deletions libs/front/admin/users/src/lib/list/list.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<div class="flex h-full max-h-full flex-col justify-start overflow-hidden">
<seed-admin-users-filters
[role]="(role$ | async)!"
[isLoading]="(isLoading$ | async)!"
[total]="(total$ | async)!"
(roleChange)="onRoleChange($event)"
></seed-admin-users-filters>
<div class="relative flex flex-1 overflow-auto">
<seed-admin-users-table
[users]="(users$ | async)!"
[isLoading]="(isLoading$ | async)!"
class="flex-1"
></seed-admin-users-table>
<div seed-loading *ngIf="isLoading$ | async" class="absolute"></div>
</div>
<ng-container *ngIf="(total$ | async)! > (limit$ | async)!">
<seed-pagination
[total]="(total$ | async)!"
[page]="(page$ | async)!"
[limit]="(limit$ | async)!"
[isLoading]="(isLoading$ | async)!"
(pageChange)="onPageChange($event)"
></seed-pagination>
</ng-container>
</div>
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { UsersListComponent } from './users-list.component';
import { ListComponent } from './list.component';

describe(UsersListComponent.name, () => {
let component: UsersListComponent;
let fixture: ComponentFixture<UsersListComponent>;
describe(ListComponent.name, () => {
let component: ListComponent;
let fixture: ComponentFixture<ListComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [UsersListComponent],
declarations: [ListComponent],
}).compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(UsersListComponent);
fixture = TestBed.createComponent(ListComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
Expand Down
66 changes: 66 additions & 0 deletions libs/front/admin/users/src/lib/list/list.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { ChangeDetectionStrategy, Component, OnInit, ViewEncapsulation } from '@angular/core';
import { User, UserRole } from '@prisma/client';
import { BehaviorSubject } from 'rxjs';
import { mockUsers } from '@seed/shared/mock-data';
import { ONE, PAGINATION_DEFAULTS, THOUSAND, ZERO } from '@seed/shared/constants';

@Component({
selector: 'seed-admin-users-list',
templateUrl: './list.component.html',
encapsulation: ViewEncapsulation.None,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ListComponent implements OnInit {
users$ = new BehaviorSubject<User[]>([]);

isLoading$ = new BehaviorSubject<boolean>(true);

page$ = new BehaviorSubject<number>(ONE);

total$ = new BehaviorSubject<number>(ZERO);

limit$ = new BehaviorSubject<number>(PAGINATION_DEFAULTS.limit);

role$ = new BehaviorSubject<UserRole>(UserRole.USER);

ngOnInit(): void {
setTimeout(() => {
const allUsersOfThisRole = mockUsers.filter(user => user.role === this.role$.value);
const users = allUsersOfThisRole.slice(
(this.page$.value - ONE) * this.limit$.value,
this.page$.value * this.limit$.value,
);
this.users$.next(users);
this.isLoading$.next(false);
this.page$.next(ONE);
this.total$.next(allUsersOfThisRole.length);
}, THOUSAND);
}

onPageChange(page: number): void {
this.isLoading$.next(true);
setTimeout(() => {
const allUsersOfThisRole = mockUsers.filter(user => user.role === this.role$.value);
const users = allUsersOfThisRole.slice((page - ONE) * this.limit$.value, page * this.limit$.value);
this.page$.next(page);
this.users$.next(users);
this.isLoading$.next(false);
}, THOUSAND);
}

onRoleChange(role: UserRole): void {
this.isLoading$.next(true);
this.role$.next(role);
setTimeout(() => {
this.page$.next(ONE);
const allUsersOfThisRole = mockUsers.filter(user => user.role === this.role$.value);
const users = allUsersOfThisRole.slice(
(this.page$.value - ONE) * this.limit$.value,
this.page$.value * this.limit$.value,
);
this.users$.next(users);
this.total$.next(allUsersOfThisRole.length);
this.isLoading$.next(false);
}, THOUSAND);
}
}
48 changes: 48 additions & 0 deletions libs/front/admin/users/src/lib/list/table/table.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<div *ngIf="!isLoading && !users.length" class="flex h-full w-full items-center justify-center">
<div class="rounded-md p-6 text-center text-gray-400 shadow-md">
<i class="feather-users mx-auto text-3xl"></i>
<h3 class="mt-2 font-medium">No users yet.</h3>
</div>
</div>

<ul role="list" class="divide-y divide-gray-200">
<li *ngFor="let user of users; trackBy: trackByFn" class="odd:bg-gray-100">
<a [routerLink]="'/users/' + user.id" class="block hover:bg-gray-50">
<div class="flex items-center px-4 py-4 sm:px-6">
<div class="flex min-w-0 flex-1 items-center text-sm">
<div class="flex-shrink-0">
<img class="h-12 w-12 rounded-full" [src]="user.photoURL" [alt]="user.firstName + ' ' + user.lastName" />
</div>
<div class="min-w-0 flex-1 px-4 md:grid md:grid-cols-2 md:gap-4">
<div>
<p class="truncate font-medium text-indigo-600">{{ user.firstName + ' ' + user.lastName }}</p>
<p class="mt-2 text-gray-500">
<span class="truncate">[email protected]</span>
<!-- TODO: <span class="truncate">{{user.email}}</span> -->
</p>
</div>
<div class="hidden text-center md:block">
<p
class="truncate text-gray-600"
[attr.title]="'Signed in on ' + (user.lastTimeSignedIn | date : dateTimeFormat)"
>
<i class="feather-log-in mr-1"></i>
<time [attr.datetime]="user.lastTimeSignedIn">{{ user.lastTimeSignedIn | date : dateFormat }}</time>
</p>
<p
class="mt-2 truncate text-gray-500"
[attr.title]="'Signed up on ' + (user.createdAt | date : dateTimeFormat)"
>
<i class="feather-user-plus mr-1"></i>
<time [attr.datetime]="user.createdAt">{{ user.createdAt | date : dateFormat }}</time>
</p>
</div>
</div>
</div>
<div>
<i class="feather-chevron-right"></i>
</div>
</div>
</a>
</li>
</ul>
22 changes: 22 additions & 0 deletions libs/front/admin/users/src/lib/list/table/table.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { TableComponent } from './table.component';

describe('TableComponent', () => {
let component: TableComponent;
let fixture: ComponentFixture<TableComponent>;

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [TableComponent],
}).compileComponents();

fixture = TestBed.createComponent(TableComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});
});
22 changes: 22 additions & 0 deletions libs/front/admin/users/src/lib/list/table/table.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { User } from '@prisma/client';
import { dateFormat, dateTimeFormat } from '@seed/shared/constants';

@Component({
selector: 'seed-admin-users-table',
templateUrl: './table.component.html',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TableComponent {
@Input() users: User[] = [];

@Input() isLoading = true;

dateFormat = dateFormat;

dateTimeFormat = dateTimeFormat;

trackByFn(_index: number, item: User): string {
return item.id;
}
}
Loading

0 comments on commit dca9251

Please sign in to comment.