Added better staff mock data and updated several pages

This commit is contained in:
Erik Tiekstra
2021-04-09 08:48:22 +02:00
committed by Erik Tiekstra
parent cee9168c3c
commit 1ee6ca9251
27 changed files with 385 additions and 56 deletions

View File

@@ -2,6 +2,7 @@ import { NavigationBreadcrumbsItem } from '@af/digi-ng/_navigation/navigation-br
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { User } from '@dafa-models/user.model';
import { mapPathToPageName } from '@dafa-utils/map-path-to-page-name.util';
import { BehaviorSubject } from 'rxjs';
import { filter } from 'rxjs/operators';
import { UnsubscribeDirective } from './directives/unsubscribe.directive';
@@ -41,7 +42,7 @@ export class AppComponent extends UnsubscribeDirective {
...[...paths]
.filter(path => !!path)
.map(path => ({
text: `${path.charAt(0).toUpperCase()}${path.slice(1)}`,
text: mapPathToPageName(path),
routerLink: paths.slice(0, paths.length - 1).join('/'),
})),
]);

View File

@@ -0,0 +1,10 @@
export const Navigation = {
administration: 'Administration',
'skapa-konto': 'Skapa nytt konto',
personal: 'Personal',
'mina-deltagare': 'Mina deltagare',
avrop: 'Avrop',
meddelanden: 'Meddelanden',
statistik: 'Statistik',
installningar: 'Inställningar',
};

View File

@@ -2,7 +2,7 @@ import { ParticipantStatus } from '@dafa-enums/participant-status.enum';
import { Service } from '@dafa-enums/service.enum';
export interface Participant {
id: number;
id: string;
firstName: string;
lastName: string;
status: ParticipantStatus;

View File

@@ -1,6 +1,7 @@
import { Participant } from './participant.model';
import { Staff } from './staff.model';
export interface SortBy {
key: keyof Participant;
key: keyof Participant | keyof Staff;
reverse: boolean;
}

View File

@@ -1,16 +1,10 @@
import { ParticipantStatus } from '@dafa-enums/participant-status.enum';
import { Service } from '@dafa-enums/service.enum';
export interface Participant {
id: number;
export interface Staff {
id: string;
staffId: string;
firstName: string;
lastName: string;
status: ParticipantStatus;
nextStep: string;
service: Service;
errandNumber: number;
startDate: Date;
endDate: Date;
handleBefore: Date;
kommun: string;
active: boolean;
service: string;
fullName?: string;
}

View File

@@ -11,7 +11,7 @@ const routes: Routes = [
},
{
path: 'personal',
loadChildren: () => import('./pages/staff-list/staff-list.module').then(m => m.StaffListModule),
loadChildren: () => import('./pages/staff/staff.module').then(m => m.StaffModule),
},
{
path: 'skapa-konto',

View File

@@ -1,14 +0,0 @@
<section class="staff-list">
<digi-typography>
<h1>Personallista</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam magna neque, interdum vel massa eget, condimentum
rutrum velit. Sed vitae ullamcorper sem. Aliquam malesuada nunc sed purus mollis scelerisque. Curabitur bibendum
leo quis ante porttitor tincidunt. Nam tincidunt imperdiet tortor eu suscipit. Maecenas ut dui est.
</p>
<digi-button>
Skapa nytt konto
<dafa-icon [icon]="iconType.PLUS"></dafa-icon>
</digi-button>
</digi-typography>
</section>

View File

@@ -1,12 +0,0 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IconType } from '@dafa-enums/icon-type.enum';
@Component({
selector: 'dafa-staff-list',
templateUrl: './staff-list.component.html',
styleUrls: ['./staff-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StaffListComponent {
iconType = IconType;
}

View File

@@ -0,0 +1,73 @@
<digi-ng-table>
<table>
<thead>
<tr>
<th scope="col" class="staff-list__column-head">
<button class="staff-list__sort-button" (click)="handleSort('fullName')">
Namn
<ng-container *ngIf="sortBy?.key === 'fullName'">
<digi-icon-caret-up class="staff-list__sort-icon" *ngIf="!sortBy.reverse"></digi-icon-caret-up>
<digi-icon-caret-down class="staff-list__sort-icon" *ngIf="sortBy.reverse"></digi-icon-caret-down>
</ng-container>
</button>
</th>
<th scope="col" class="staff-list__column-head">
<button class="staff-list__sort-button" (click)="handleSort('staffId')">
Personal-ID
<ng-container *ngIf="sortBy?.key === 'staffId'">
<digi-icon-caret-up class="staff-list__sort-icon" *ngIf="!sortBy.reverse"></digi-icon-caret-up>
<digi-icon-caret-down class="staff-list__sort-icon" *ngIf="sortBy.reverse"></digi-icon-caret-down>
</ng-container>
</button>
</th>
<th scope="col" class="staff-list__column-head">
<button class="staff-list__sort-button" (click)="handleSort('kommun')">
Kommun
<ng-container *ngIf="sortBy?.key === 'kommun'">
<digi-icon-caret-up class="staff-list__sort-icon" *ngIf="!sortBy.reverse"></digi-icon-caret-up>
<digi-icon-caret-down class="staff-list__sort-icon" *ngIf="sortBy.reverse"></digi-icon-caret-down>
</ng-container>
</button>
</th>
<th scope="col" class="staff-list__column-head">
<button class="staff-list__sort-button" (click)="handleSort('active')">
Aktiv i tjänst
<ng-container *ngIf="sortBy?.key === 'active'">
<digi-icon-caret-up class="staff-list__sort-icon" *ngIf="!sortBy.reverse"></digi-icon-caret-up>
<digi-icon-caret-down class="staff-list__sort-icon" *ngIf="sortBy.reverse"></digi-icon-caret-down>
</ng-container>
</button>
</th>
<th scope="col" class="staff-list__column-head">
<button class="staff-list__sort-button" (click)="handleSort('service')">
Tjänst
<ng-container *ngIf="sortBy?.key === 'service'">
<digi-icon-caret-up class="staff-list__sort-icon" *ngIf="!sortBy.reverse"></digi-icon-caret-up>
<digi-icon-caret-down class="staff-list__sort-icon" *ngIf="sortBy.reverse"></digi-icon-caret-down>
</ng-container>
</button>
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let staff of pagedStaff">
<th scope="row">{{ staff.firstName }} {{ staff.lastName }}</th>
<td>{{ staff.staffId }}</td>
<td>{{ staff.kommun }}</td>
<td>{{ staff.active ? 'Ja' : 'Nej' }}</td>
<td>{{ staff.service }}</td>
</tr>
</tbody>
</table>
</digi-ng-table>
<digi-navigation-pagination
*ngIf="staff.length > pagedStaff.length"
class="staff-list__pagination"
[afTotalPages]="totalPages"
[afCurrentResultStart]="currentResultStart"
[afCurrentResultEnd]="currentResultEnd"
[afTotalResults]="staff.length"
(afOnPageChange)="handlePagination($event.detail)"
af-result-name="deltagare"
></digi-navigation-pagination>

View File

@@ -0,0 +1,35 @@
@import 'variables/gutters';
.staff-list {
&__column-head {
padding: 0;
}
&__sort-button {
position: relative;
background-color: transparent;
border-width: 0;
width: 100%;
text-align: left;
padding: var(--digi--layout--gutter--s) $digi--layout--gutter--l var(--digi--layout--gutter--s)
var(--digi--layout--gutter);
margin: 0;
font-size: inherit;
font-weight: inherit;
display: flex;
align-items: center;
gap: var(--digi--layout--gutter);
cursor: pointer;
}
&__sort-icon {
position: absolute;
display: inline-flex;
right: 0.5rem;
}
&__pagination {
display: block;
margin-top: var(--digi--layout--gutter);
}
}

View File

@@ -0,0 +1,27 @@
import { DigiNgTableModule } from '@af/digi-ng/_table/table';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { StaffListComponent } from './staff-list.component';
describe('StaffListComponent', () => {
let component: StaffListComponent;
let fixture: ComponentFixture<StaffListComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [StaffListComponent],
imports: [RouterTestingModule, DigiNgTableModule],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(StaffListComponent);
component = fixture.componentInstance;
component.staff = [];
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,47 @@
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
import { SortBy } from '@dafa-models/sort-by.model';
import { Staff } from '@dafa-models/staff.model';
import { BehaviorSubject } from 'rxjs';
@Component({
selector: 'dafa-staff-list',
templateUrl: './staff-list.component.html',
styleUrls: ['./staff-list.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StaffListComponent {
@Input() staff: Staff[];
@Input() sortBy: SortBy | null;
@Output() sorted = new EventEmitter<keyof Staff>();
private _currentPage$ = new BehaviorSubject<number>(1);
private _staffPerPage = 10;
get currentPage(): number {
return this._currentPage$.getValue();
}
get totalPages(): number {
return Math.ceil(this.staff.length / this._staffPerPage);
}
get pagedStaff(): Staff[] {
return [...this.staff].slice(this.currentResultStart - 1, this.currentResultEnd - 1);
}
get currentResultStart(): number {
return (this.currentPage - 1) * this._staffPerPage + 1;
}
get currentResultEnd(): number {
return this.currentResultStart + this._staffPerPage;
}
handleSort(key: keyof Staff): void {
this.sorted.emit(key);
}
handlePagination(page: number): void {
this._currentPage$.next(page);
}
}

View File

@@ -1,12 +1,12 @@
import { DigiNgTableModule } from '@af/digi-ng/_table/table';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { IconModule } from '@dafa-shared/components/icon/icon.module';
import { StaffListComponent } from './staff-list.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [StaffListComponent],
imports: [CommonModule, RouterModule.forChild([{ path: '', component: StaffListComponent }]), IconModule],
imports: [CommonModule, DigiNgTableModule],
exports: [StaffListComponent],
})
export class StaffListModule {}

View File

@@ -0,0 +1,31 @@
<section class="staff">
<digi-typography>
<h1>Personal</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam magna neque, interdum vel massa eget, condimentum
rutrum velit. Sed vitae ullamcorper sem. Aliquam malesuada nunc sed purus mollis scelerisque. Curabitur bibendum
leo quis ante porttitor tincidunt. Nam tincidunt imperdiet tortor eu suscipit. Maecenas ut dui est.
</p>
<div class="staff__cta-wrapper">
<digi-ng-link-internal afText="Skapa nytt konto" afRoute="/administration/skapa-konto"></digi-ng-link-internal>
</div>
<h2>Personallista</h2>
<p>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Earum, officia perferendis? Excepturi animi rem culpa
facere, laboriosam vel, quia eos eligendi, cupiditate doloribus aspernatur unde nihil iste earum corrupti illo.
</p>
<dafa-staff-list
*ngIf="filteredStaff$ | async as staff; else loadingRef"
[staff]="staff"
[sortBy]="staffSortBy$ | async"
(sorted)="handleStaffSort($event)"
></dafa-staff-list>
</digi-typography>
<ng-template #loadingRef>
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base>
</ng-template>
</section>

View File

@@ -0,0 +1,11 @@
.staff {
&__cta-wrapper {
margin-top: var(--digi--layout--gutter);
}
&__link {
display: inline-flex;
align-items: center;
gap: var(--digi--layout--gutter--s);
}
}

View File

@@ -1,22 +1,22 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { StaffListComponent } from './staff-list.component';
import { StaffComponent } from './staff.component';
describe('StaffListComponent', () => {
let component: StaffListComponent;
let fixture: ComponentFixture<StaffListComponent>;
describe('StaffComponent', () => {
let component: StaffComponent;
let fixture: ComponentFixture<StaffComponent>;
beforeEach(
waitForAsync(() => {
TestBed.configureTestingModule({
declarations: [StaffListComponent],
declarations: [StaffComponent],
imports: [RouterTestingModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(StaffListComponent);
fixture = TestBed.createComponent(StaffComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

View File

@@ -0,0 +1,37 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { IconType } from '@dafa-enums/icon-type.enum';
import { SortBy } from '@dafa-models/sort-by.model';
import { Staff } from '@dafa-models/staff.model';
import { StaffService } from '@dafa-services/api/staff.service';
import { BehaviorSubject, Observable } from 'rxjs';
@Component({
selector: 'dafa-staff',
templateUrl: './staff.component.html',
styleUrls: ['./staff.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class StaffComponent {
private _searchValue$ = new BehaviorSubject<string>('');
filteredStaff$: Observable<Staff[]> = this.staffService.filteredStaff$;
staffSortBy$: Observable<SortBy | null> = this.staffService.staffSortBy$;
iconType = IconType;
constructor(private staffService: StaffService) {}
get searchValue(): string {
return this._searchValue$.getValue();
}
handleSearchSubmit(): void {
this.staffService.setSearchFilter(this.searchValue);
}
handleSearchInput($event: CustomEvent): void {
this._searchValue$.next($event.detail.target.value);
}
handleStaffSort(key: keyof Staff): void {
this.staffService.setStaffSortKey(key);
}
}

View File

@@ -0,0 +1,20 @@
import { DigiNgLinkInternalModule } from '@af/digi-ng/_link/link-internal';
import { DigiNgSkeletonBaseModule } from '@af/digi-ng/_skeleton/skeleton-base';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { StaffListModule } from './components/staff-list/staff-list.module';
import { StaffComponent } from './staff.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [StaffComponent],
imports: [
CommonModule,
RouterModule.forChild([{ path: '', component: StaffComponent }]),
DigiNgLinkInternalModule,
DigiNgSkeletonBaseModule,
StaffListModule,
],
})
export class StaffModule {}

View File

@@ -0,0 +1,56 @@
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { environment } from '@dafa-environment';
import { SortBy } from '@dafa-models/sort-by.model';
import { Staff } from '@dafa-models/staff.model';
import { sort } from '@dafa-utils/sort.util';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
function filterStaff(staff: Staff[], searchFilter: string): Staff[] {
return staff.filter(person => {
const searchValueExistsInName = person.fullName.toLowerCase().includes(searchFilter.toLowerCase());
return searchValueExistsInName;
});
}
@Injectable({
providedIn: 'root',
})
export class StaffService {
private _allStaff$: Observable<Staff[]> = this.httpClient.get<Staff[]>(`${environment.apiBase}/staff`).pipe(
map(staff =>
staff.map(person => ({
...person,
fullName: `${person.firstName} ${person.lastName}`,
}))
)
);
private _staffSortBy$ = new BehaviorSubject<SortBy | null>({ key: 'fullName', reverse: false });
public staffSortBy$: Observable<SortBy> = this._staffSortBy$.asObservable();
private _searchFilter$ = new BehaviorSubject<string>('');
public searchFilter$: Observable<string> = this._searchFilter$.asObservable();
private _filteredStaff$: Observable<Staff[]> = combineLatest([this._allStaff$, this._searchFilter$]).pipe(
map(([staff, searchFilter]) => filterStaff(staff, searchFilter))
);
public filteredStaff$: Observable<Staff[]> = combineLatest([this._filteredStaff$, this._staffSortBy$]).pipe(
map(([staff, sortBy]) => {
return sortBy ? sort(staff, sortBy) : staff;
})
);
public setSearchFilter(value: string) {
this._searchFilter$.next(value);
}
public setStaffSortKey(key: keyof Staff) {
const currentSortBy = this._staffSortBy$.getValue();
const reverse = currentSortBy?.key === key ? !currentSortBy.reverse : false;
this._staffSortBy$.next({ key, reverse });
}
constructor(private httpClient: HttpClient) {}
}

View File

@@ -0,0 +1,5 @@
import { Navigation } from '@dafa-constants/navigation';
export function mapPathToPageName(path: string): string {
return Navigation[path] || `${path.charAt(0).toUpperCase()}${path.slice(1)}`;
}

View File

@@ -15,6 +15,7 @@
body {
margin: 0;
font-weight: var(--digi--typography--font-weight);
overflow: hidden;
}
button {

View File

@@ -7,3 +7,7 @@ Run `npm install` to install all dependencies.
## Get the mock-api up and running
Run `npm start` and navigate to `localhost:8000` to see a simple overview of the API. Navigate to [localhost:8000](localhost:8000) to see a quick explaination on which recources and routes are available inside the API.
### Mock delayed response
Run `npm run start:delay` to use the API with a delayed response.

View File

@@ -4,7 +4,8 @@
"description": "A mock api implementing all needed endpoints for dafa-web",
"scripts": {
"generate-api": "node ./scripts/generate-api.js",
"start": "npm run generate-api && json-server --watch api.json --port 8000 --routes routes.json"
"start": "npm run generate-api && json-server --watch api.json --port 8000 --routes routes.json",
"start:delay": "npm start -- --delay 500"
},
"author": "Erik Tiekstra (erik.tiekstra@arbetsformedlingen.se)",
"license": "MIT",

View File

@@ -10,9 +10,9 @@ const STEPS = ['Gemensam planering', 'Periodisk rapport', 'Resultatrapport', 'Sl
function generateParticipants(amount = 10) {
const participants = [];
for (let id = 1; id <= amount; ++id) {
for (let i = 1; i <= amount; ++i) {
participants.push({
id,
id: faker.random.uuid(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
status: STATUSES[Math.floor(Math.random() * STATUSES.length)],

View File

@@ -11,9 +11,10 @@ const STATUSES = [true, false];
function generateStaff(amount = 10) {
const staff = [];
for (let id = 1; id <= amount; ++id) {
for (let i = 1; i <= amount; ++i) {
staff.push({
id,
id: faker.random.uuid(),
staffId: faker.random.number(),
firstName: faker.name.firstName(),
lastName: faker.name.lastName(),
kommun: KOMMUN[Math.floor(Math.random() * KOMMUN.length)].kommun,