refactor(project): Renamed all instances of dafa to msfa or mina-sidor-fa. (TV-379)
Squashed commit of the following: commit d3f52ff6876f6e246c7d3c188e56cc2370289341 Author: Erik Tiekstra <erik.tiekstra@arbetsformedlingen.se> Date: Tue Aug 17 14:10:38 2021 +0200 Renamed all dafa instances to msfa
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { NgModule } from '@angular/core';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'personal',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'personal',
|
||||
loadChildren: () => import('./pages/employees/employees.module').then(m => m.EmployeesModule),
|
||||
},
|
||||
{
|
||||
path: 'personal/:employeeId',
|
||||
loadChildren: () => import('./pages/employee-card/employee-card.module').then(m => m.EmployeeCardModule),
|
||||
},
|
||||
{
|
||||
path: 'skapa-konto',
|
||||
loadChildren: () => import('./pages/employee-form/employee-form.module').then(m => m.EmployeeFormModule),
|
||||
},
|
||||
{
|
||||
path: 'bjuda-in',
|
||||
loadChildren: () => import('./pages/employee-invite/employee-invite.module').then(m => m.EmployeeInviteModule),
|
||||
},
|
||||
{
|
||||
path: 'redigera-konto/:employeeId',
|
||||
loadChildren: () => import('./pages/employee-form/employee-form.module').then(m => m.EmployeeFormModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [RouterModule.forChild(routes)],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class AdministrationRoutingModule {}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { AdministrationComponent } from './administration.component';
|
||||
|
||||
describe('AdministrationComponent', () => {
|
||||
let component: AdministrationComponent;
|
||||
let fixture: ComponentFixture<AdministrationComponent>;
|
||||
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
declarations: [AdministrationComponent],
|
||||
imports: [RouterTestingModule],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AdministrationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-administration',
|
||||
templateUrl: './administration.component.html',
|
||||
styleUrls: ['./administration.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class AdministrationComponent {}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { AdministrationRoutingModule } from './administration-routing.module';
|
||||
import { AdministrationComponent } from './administration.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [AdministrationComponent],
|
||||
imports: [CommonModule, AdministrationRoutingModule],
|
||||
})
|
||||
export class AdministrationModule {}
|
||||
@@ -0,0 +1,93 @@
|
||||
<msfa-layout>
|
||||
<section class="employee-card">
|
||||
<digi-typography *ngIf="detailedEmployeeData$ | async as detailedEmployeeData; else loadingRef">
|
||||
<div class="employee-card__editcontainer">
|
||||
<h1>{{ detailedEmployeeData.fullName }}</h1>
|
||||
<span class="employee-card__editbutton">
|
||||
<a href="./administration/skapa-konto">Redigera</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Lorem ipsum dolor sit amet consectetur adipisicing elit. Accusamus accusantium sit, reprehenderit, esse suscipit
|
||||
quis similique harum est eum eveniet aspernatur delectus magni asperiores porro aliquam voluptate! Architecto,
|
||||
perferendis commodi.
|
||||
</p>
|
||||
|
||||
<div class="employee-card__contents">
|
||||
<div class="employee-card__column">
|
||||
<h2>Kontaktuppgifter</h2>
|
||||
|
||||
<dl>
|
||||
<dt>Namn</dt>
|
||||
<dd *ngIf="detailedEmployeeData.fullName; else emptyDD">{{ detailedEmployeeData.fullName }}</dd>
|
||||
<dt>Personnummer</dt>
|
||||
<dd *ngIf="detailedEmployeeData.ssn; else emptyDD">
|
||||
<msfa-hide-text
|
||||
symbols="********-****"
|
||||
[changingText]="detailedEmployeeData.ssn"
|
||||
ariaLabelType="personnummer"
|
||||
></msfa-hide-text>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
<div class="employee-card__column">
|
||||
<h2>Tjänst</h2>
|
||||
<ul class="employee-card__list">
|
||||
<ng-container *ngIf="detailedEmployeeData.services.length; else emptyDD">
|
||||
<li class="employee-card__column--listitem" *ngFor="let service of detailedEmployeeData.services">
|
||||
{{ service.name }}
|
||||
</li>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="employee-card__organizations">
|
||||
<h2>Utförande verksamheter och utförande adresser</h2>
|
||||
<ul class="employee-card__list" *ngIf="detailedEmployeeData.organizations?.length">
|
||||
<li class="employee-card__list" *ngFor="let organization of detailedEmployeeData.organizations">
|
||||
{{ organization.name }}
|
||||
<ul>
|
||||
<li class="employee-card__listitem--indent">
|
||||
{{ organization.address.street }} {{ organization.address.postalCode }} {{
|
||||
organization.address.houseNumber }} {{ organization.address.city }}
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="employee-card__column">
|
||||
<h2>Behörigheter</h2>
|
||||
<ul class="employee-card__list">
|
||||
<ng-container *ngIf="detailedEmployeeData.authorizations.length; else emptyDD">
|
||||
<li *ngFor="let authorization of detailedEmployeeData.authorizations">{{ authorization.name }}</li>
|
||||
</ng-container>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p></p>
|
||||
</digi-typography>
|
||||
<div class="employee-card__footer">
|
||||
<span class="employee-card__secondarybutton">
|
||||
<a href="./administration/personal">Tillbaka till personallistan</a>
|
||||
</span>
|
||||
|
||||
<span class="employee-card__primarybutton">
|
||||
<a href="./administration/skapa-konto">Skapa nytt konto</a>
|
||||
</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<ng-template #loadingRef>
|
||||
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personalkortet"></digi-ng-skeleton-base>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #emptyDD class="employee-card__list">
|
||||
<dd>
|
||||
<span aria-hidden="true">-</span>
|
||||
<span class="msfa__a11y-sr-only">Info saknas</span>
|
||||
</dd>
|
||||
</ng-template>
|
||||
</msfa-layout>
|
||||
@@ -0,0 +1,92 @@
|
||||
@import 'variables/gutters';
|
||||
@import 'variables/colors';
|
||||
@import 'mixins/buttons';
|
||||
@import 'mixins/list';
|
||||
|
||||
.employee-card {
|
||||
&__contents {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: $digi--layout--gutter--xl $digi--layout--gutter--l;
|
||||
}
|
||||
|
||||
&__editcontainer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
&__column {
|
||||
width: 100%;
|
||||
max-width: var(--digi--typography--text--max-width);
|
||||
}
|
||||
|
||||
&__organizations {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.25rem;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
//LISTS
|
||||
|
||||
&__list {
|
||||
@include msfa__reset-list;
|
||||
}
|
||||
|
||||
&__listitem--indent {
|
||||
@include msfa__reset-list;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
&__description {
|
||||
margin-left: 0.1rem;
|
||||
grid-column: 1;
|
||||
}
|
||||
|
||||
&__term {
|
||||
margin: 0;
|
||||
grid-column: 1;
|
||||
font-weight: var(--digi--typography--font-weight--semibold);
|
||||
}
|
||||
|
||||
//BUTTONS
|
||||
|
||||
&__primarybutton {
|
||||
a {
|
||||
@include msfa_buttontemplate(
|
||||
$msfa-button--background--primary,
|
||||
$msfa-button--text--primary,
|
||||
$msfa-button--hover--primary
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
&__secondarybutton {
|
||||
a {
|
||||
@include msfa_buttontemplate(
|
||||
$msfa-button--background--secondary,
|
||||
$msfa-button--text--secondary,
|
||||
$msfa-button--hover--secondary
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
&__editbutton {
|
||||
a {
|
||||
@include msfa_buttontemplate(
|
||||
$msfa-button--background--secondary,
|
||||
$msfa-button--text--secondary,
|
||||
$msfa-button--hover--secondary
|
||||
);
|
||||
width: var(--digi-button--width);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { EmployeeCardComponent } from './employee-card.component';
|
||||
|
||||
describe('EmployeeCardComponent', () => {
|
||||
let component: EmployeeCardComponent;
|
||||
let fixture: ComponentFixture<EmployeeCardComponent>;
|
||||
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [EmployeeCardComponent],
|
||||
imports: [RouterTestingModule, HttpClientTestingModule],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EmployeeCardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,48 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Employee } from '@msfa-models/employee.model';
|
||||
import { Participant } from '@msfa-models/participant.model';
|
||||
import { EmployeeService } from '@msfa-services/api/employee.service';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { map, switchMap } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-employee-card',
|
||||
templateUrl: './employee-card.component.html',
|
||||
styleUrls: ['./employee-card.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EmployeeCardComponent {
|
||||
private _pendingSelectedParticipants$ = new BehaviorSubject<string[]>([]);
|
||||
private _employeeId$: Observable<string> = this.activatedRoute.params.pipe(
|
||||
map(({ employeeId }) => employeeId as string)
|
||||
);
|
||||
|
||||
detailedEmployeeData$: Observable<Employee> = this._employeeId$.pipe(
|
||||
switchMap(employeeId => this.employeeService.fetchDetailedEmployeeData$(employeeId))
|
||||
);
|
||||
|
||||
constructor(private activatedRoute: ActivatedRoute, private employeeService: EmployeeService) {}
|
||||
|
||||
get pendingSelectedParticipants(): string[] {
|
||||
return this._pendingSelectedParticipants$.getValue();
|
||||
}
|
||||
|
||||
handleChangeEmployee(): void {
|
||||
console.log('change employee: ', this.pendingSelectedParticipants);
|
||||
}
|
||||
|
||||
handleChangeParticipant(id: string, checked: boolean): void {
|
||||
const currentPendingSelectedParticipants = this.pendingSelectedParticipants;
|
||||
|
||||
if (checked) {
|
||||
this._pendingSelectedParticipants$.next([...this.pendingSelectedParticipants, id]);
|
||||
} else {
|
||||
this._pendingSelectedParticipants$.next(currentPendingSelectedParticipants.filter(currentId => currentId !== id));
|
||||
}
|
||||
}
|
||||
|
||||
handleChangeAllParticipants(participants: Participant[], checked: boolean): void {
|
||||
this._pendingSelectedParticipants$.next(checked ? participants.map(participant => participant.id) : []);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { DigiNgLayoutExpansionPanelModule } from '@af/digi-ng/_layout/layout-expansion-panel';
|
||||
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 { HideTextModule } from '@msfa-shared/components/hide-text/hide-text.module';
|
||||
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
|
||||
import { LocalDatePipeModule } from '@msfa-shared/pipes/local-date/local-date.module';
|
||||
import { EmployeeCardComponent } from './employee-card.component';
|
||||
|
||||
@NgModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [EmployeeCardComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{ path: '', component: EmployeeCardComponent }]),
|
||||
LayoutModule,
|
||||
DigiNgSkeletonBaseModule,
|
||||
DigiNgLayoutExpansionPanelModule,
|
||||
LocalDatePipeModule,
|
||||
HideTextModule,
|
||||
],
|
||||
})
|
||||
export class EmployeeCardModule {}
|
||||
@@ -0,0 +1,164 @@
|
||||
<msfa-layout>
|
||||
<section class="employee-form">
|
||||
<digi-typography>
|
||||
<h1>Skapa nytt konto</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-typography>
|
||||
<form [formGroup]="formGroup" (ngSubmit)="submitForm()">
|
||||
<digi-form-error-list
|
||||
class="employee-form__error-list"
|
||||
*ngIf="formGroup.invalid && submitted && formErrors.length"
|
||||
af-heading="Felmeddelanden"
|
||||
>
|
||||
<a *ngFor="let error of formErrors" [routerLink]="" [fragment]="'employee-form-' + error.id"
|
||||
>{{ error.message }}</a
|
||||
>
|
||||
</digi-form-error-list>
|
||||
|
||||
<div class="employee-form__block">
|
||||
<digi-typography>
|
||||
<h2>Personuppgifter</h2>
|
||||
</digi-typography>
|
||||
<digi-ng-form-input
|
||||
afId="employee-form-firstName"
|
||||
class="employee-form__input"
|
||||
formControlName="firstName"
|
||||
afLabel="Förnamn"
|
||||
afInvalidMessage="Förnamn är obligatoriskt"
|
||||
[afDisableValidStyle]="true"
|
||||
[afInvalid]="firstNameControl.invalid && firstNameControl.dirty"
|
||||
></digi-ng-form-input>
|
||||
<digi-ng-form-input
|
||||
afId="employee-form-lastName"
|
||||
class="employee-form__input"
|
||||
formControlName="lastName"
|
||||
afLabel="Efternamn"
|
||||
afInvalidMessage="Efternamn är obligatoriskt"
|
||||
[afDisableValidStyle]="true"
|
||||
[afInvalid]="lastNameControl.invalid && lastNameControl.dirty"
|
||||
></digi-ng-form-input>
|
||||
<digi-ng-form-input
|
||||
afId="employee-form-ssn"
|
||||
class="employee-form__input"
|
||||
formControlName="ssn"
|
||||
afLabel="Personnummer"
|
||||
[afInvalidMessage]="ssnControl.errors?.message || ''"
|
||||
[afDisableValidStyle]="true"
|
||||
[afInvalid]="ssnControl.invalid && ssnControl.dirty"
|
||||
></digi-ng-form-input>
|
||||
</div>
|
||||
<div class="employee-form__block" *ngIf="services$ | async as services">
|
||||
<fieldset class="employee-form__fieldset">
|
||||
<digi-typography>
|
||||
<legend>Tjänster</legend>
|
||||
</digi-typography>
|
||||
|
||||
<ul class="employee-form__services">
|
||||
<li *ngFor="let service of services; let first = first" class="employee-form__service-item">
|
||||
<digi-form-checkbox
|
||||
[afId]="(first && 'employee-form-services') || undefined"
|
||||
af-variation="primary"
|
||||
[afValidation]="servicesControl.invalid && servicesControl.dirty && 'error'"
|
||||
[afLabel]="service.name"
|
||||
[afValue]="service.id"
|
||||
[afChecked]="servicesControl.value.includes(service)"
|
||||
(afOnChange)="toggleService(service, $event.detail.target.checked)"
|
||||
></digi-form-checkbox>
|
||||
</li>
|
||||
</ul>
|
||||
<digi-form-validation-message
|
||||
class="employee-form__validation-message"
|
||||
*ngIf="servicesControl.invalid && servicesControl.dirty"
|
||||
af-variation="error"
|
||||
>
|
||||
{{ servicesControl.errors.message }}
|
||||
</digi-form-validation-message>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="employee-form__block" *ngIf="authorizations$ | async as authorizations">
|
||||
<fieldset class="employee-form__fieldset">
|
||||
<digi-typography>
|
||||
<legend>Tilldela behörigheter</legend>
|
||||
</digi-typography>
|
||||
|
||||
<ul class="employee-form__authorizations">
|
||||
<li
|
||||
*ngFor="let authorization of authorizations; let first = first"
|
||||
class="employee-form__authorization-item"
|
||||
>
|
||||
<digi-form-checkbox
|
||||
class="employee-form__digi-checkbox"
|
||||
[afId]="(first && 'employee-form-authorizations') || undefined"
|
||||
af-variation="primary"
|
||||
[afValidation]="authorizationsControl.invalid && authorizationsControl.dirty && 'error'"
|
||||
[afLabel]="authorization.name"
|
||||
[afValue]="authorization.id"
|
||||
[afChecked]="authorizationsControl.value.includes(authorization)"
|
||||
(afOnChange)="toggleAuthorization(authorization, $event.detail.target.checked)"
|
||||
></digi-form-checkbox>
|
||||
<digi-button
|
||||
af-variation="secondary"
|
||||
[afAriaLabel]="'Läs mer om ' + authorization.name"
|
||||
af-size="s"
|
||||
class="employee-form__read-more"
|
||||
(afOnClick)="openDialog(true, authorization.name)"
|
||||
>
|
||||
Läs mer
|
||||
</digi-button>
|
||||
</li>
|
||||
</ul>
|
||||
<digi-form-validation-message
|
||||
class="employee-form__validation-message"
|
||||
*ngIf="authorizationsControl.invalid && authorizationsControl.dirty"
|
||||
af-variation="error"
|
||||
>
|
||||
{{ authorizationsControl.errors.message }}
|
||||
</digi-form-validation-message>
|
||||
</fieldset>
|
||||
</div>
|
||||
|
||||
<div class="employee-form__footer">
|
||||
<digi-button af-type="reset" af-variation="secondary" (afOnClick)="resetForm($event.detail)"
|
||||
>Avbryt</digi-button
|
||||
>
|
||||
<digi-button af-type="submit">Registrera konto</digi-button>
|
||||
</div>
|
||||
|
||||
<!-- Modal/ Dialog window -->
|
||||
<digi-ng-dialog
|
||||
[afActive]="toggleDialog"
|
||||
(afOnInactive)="openDialog(false)"
|
||||
(afOnPrimaryClick)="openDialog(false)"
|
||||
[afHeading]="modalAuthInfo.name"
|
||||
afHeadingLevel="h3"
|
||||
afPrimaryButtonText="Stäng"
|
||||
>
|
||||
<p>
|
||||
Behörigheten passar personer som arbetar nära deltagare. Behörigheten kan användas av exempelvis handledare,
|
||||
coacher, studie- och yrkesvägledare, lärare eller annan roll som behöver kunna se information om deltager,
|
||||
kontakta deltagare, planera aktiviteter med deltagre och hantera rapporter för deltagre.
|
||||
</p>
|
||||
|
||||
<p>Behörigheten ger tillgång till och utföra aktiviteter i följande funktioner i systemet:</p>
|
||||
|
||||
<p>
|
||||
- Deltagarlista <br />
|
||||
- Information om deltagare <br />
|
||||
- Resultatrapporter <br />
|
||||
- Slutredovisning <br />
|
||||
- Informativ rapport <br />
|
||||
- Skicka välkomstbrev * <br />
|
||||
- Planera deltagares aktiviteter <br />
|
||||
- Deltagares schema <br />
|
||||
- Avvikelserapporter <br />
|
||||
- Närvaro- och frånvarorapporter <br /><br />
|
||||
</p>
|
||||
</digi-ng-dialog>
|
||||
</form>
|
||||
</section>
|
||||
</msfa-layout>
|
||||
@@ -0,0 +1,77 @@
|
||||
@import 'mixins/list';
|
||||
@import 'variables/gutters';
|
||||
|
||||
.employee-form {
|
||||
&__block {
|
||||
max-width: var(--digi--typography--text--max-width);
|
||||
margin-bottom: $digi--layout--gutter--xl;
|
||||
}
|
||||
|
||||
&__input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: var(--digi--layout--gutter);
|
||||
}
|
||||
|
||||
&__fieldset {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
|
||||
legend {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: var(--digi--typography--font-weight--semibold);
|
||||
font-size: var(--digi--typography--font-size--h2--desktop);
|
||||
margin-bottom: var(--digi-typography--margin--h2);
|
||||
}
|
||||
}
|
||||
|
||||
&__validation-message {
|
||||
display: block;
|
||||
margin-top: var(--digi--layout--gutter--s);
|
||||
}
|
||||
|
||||
&__services,
|
||||
&__authorizations {
|
||||
@include msfa__reset-list;
|
||||
margin-bottom: var(--digi--layout--gutter);
|
||||
}
|
||||
|
||||
&__authorization-item {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-areas: 'auth-checkbox read-more';
|
||||
}
|
||||
|
||||
&__service-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&:not(:first-child) {
|
||||
margin-top: var(--digi--layout--gutter);
|
||||
}
|
||||
}
|
||||
|
||||
&__error-list {
|
||||
display: block;
|
||||
margin-top: $digi--layout--gutter--l;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin-top: $digi--layout--gutter--xl;
|
||||
display: flex;
|
||||
gap: var(--digi--layout--gutter);
|
||||
}
|
||||
|
||||
&__digi-checkbox {
|
||||
grid-area: auth-checkbox;
|
||||
align-self: center;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
&__read-more {
|
||||
grid-area: read-more;
|
||||
align-self: center;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import { DigiNgFormCheckboxModule } from '@af/digi-ng/_form/form-checkbox';
|
||||
import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker';
|
||||
import { DigiNgFormInputModule } from '@af/digi-ng/_form/form-input';
|
||||
import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
|
||||
import { DigiNgFormSelectModule } from '@af/digi-ng/_form/form-select';
|
||||
import { DigiNgPopoverModule } from '@af/digi-ng/_popover/popover';
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { EmployeeFormComponent } from './employee-form.component';
|
||||
|
||||
describe('EmployeeFormComponent', () => {
|
||||
let component: EmployeeFormComponent;
|
||||
let fixture: ComponentFixture<EmployeeFormComponent>;
|
||||
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [EmployeeFormComponent],
|
||||
imports: [
|
||||
RouterTestingModule,
|
||||
HttpClientTestingModule,
|
||||
ReactiveFormsModule,
|
||||
DigiNgFormInputModule,
|
||||
DigiNgFormRadiobuttonGroupModule,
|
||||
DigiNgFormDatepickerModule,
|
||||
DigiNgFormSelectModule,
|
||||
DigiNgPopoverModule,
|
||||
DigiNgFormCheckboxModule,
|
||||
],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EmployeeFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,149 @@
|
||||
import { FormSelectBaseItem } from '@af/digi-ng/_form/form-select-base';
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { AbstractControl, FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { Router } from '@angular/router';
|
||||
import { Authorization } from '@msfa-models/authorization.model';
|
||||
import { Service } from '@msfa-models/service.model';
|
||||
import { AuthorizationService } from '@msfa-services/api/authorizations.service';
|
||||
import { EmployeeService } from '@msfa-services/api/employee.service';
|
||||
import { ServiceService } from '@msfa-services/api/service.service';
|
||||
import { SocialSecurityNumberValidator } from '@msfa-utils/validators/social-security-number.validator';
|
||||
import { RequiredValidator } from '@msfa-validators/required.validator';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-employee-form',
|
||||
templateUrl: './employee-form.component.html',
|
||||
styleUrls: ['./employee-form.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EmployeeFormComponent {
|
||||
services$: Observable<Service[]> = this.serviceService.services$;
|
||||
authorizations$: Observable<Authorization[]> = this.authorizationService.authorizations$;
|
||||
servicesSelectItems$: Observable<FormSelectBaseItem[]> = this.services$.pipe(
|
||||
map(services => services.map(({ name, id }) => ({ name, value: id })))
|
||||
);
|
||||
toggleDialog = false;
|
||||
modalAuthInfo: { name: string } = { name: 'Test Behörighetsnamn' };
|
||||
|
||||
formGroup: FormGroup = this.formBuilder.group({
|
||||
firstName: this.formBuilder.control('', [RequiredValidator('Förnamn')]),
|
||||
lastName: this.formBuilder.control('', [RequiredValidator('Efternamn')]),
|
||||
ssn: this.formBuilder.control('', [RequiredValidator('Personnummer'), SocialSecurityNumberValidator()]),
|
||||
services: this.formBuilder.control([], [RequiredValidator('en tjänst')]),
|
||||
authorizations: this.formBuilder.control([], [RequiredValidator('en behörighet')]),
|
||||
});
|
||||
todaysDate = new Date();
|
||||
submitted = false;
|
||||
|
||||
constructor(
|
||||
private formBuilder: FormBuilder,
|
||||
private employeeService: EmployeeService,
|
||||
private serviceService: ServiceService,
|
||||
private authorizationService: AuthorizationService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
get firstNameControl(): AbstractControl {
|
||||
return this.formGroup.get('firstName');
|
||||
}
|
||||
get lastNameControl(): AbstractControl {
|
||||
return this.formGroup.get('lastName');
|
||||
}
|
||||
get ssnControl(): AbstractControl {
|
||||
return this.formGroup.get('ssn');
|
||||
}
|
||||
get servicesControl(): AbstractControl {
|
||||
return this.formGroup.get('services');
|
||||
}
|
||||
get authorizationsControl(): AbstractControl {
|
||||
return this.formGroup.get('authorizations');
|
||||
}
|
||||
|
||||
get formErrors(): { id: string; message: string }[] {
|
||||
const controlsWithErrors = Object.keys(this.formGroup.controls).filter(
|
||||
key => !!this.formGroup.controls[key].errors
|
||||
);
|
||||
|
||||
return controlsWithErrors.map(key => ({
|
||||
id: key,
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
message: this.formGroup.controls[key].errors.message,
|
||||
}));
|
||||
}
|
||||
|
||||
private _markFormAsDirty(): void {
|
||||
Object.keys(this.formGroup.controls).forEach(control => {
|
||||
this.formGroup.get(control).markAsDirty();
|
||||
this.formGroup.get(control).markAsTouched();
|
||||
});
|
||||
}
|
||||
|
||||
toggleAuthorization(authorization: Authorization, checked: boolean): void {
|
||||
const currentAuthorizations = this.authorizationsControl.value as { id: unknown }[];
|
||||
|
||||
if (checked) {
|
||||
this.authorizationsControl.patchValue([...currentAuthorizations, authorization]);
|
||||
} else {
|
||||
this.authorizationsControl.patchValue(
|
||||
currentAuthorizations.filter(currentAuthorization => currentAuthorization.id !== authorization.id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
toggleService(service: Service, checked: boolean): void {
|
||||
const currentServices = this.servicesControl.value as { id: unknown }[];
|
||||
|
||||
if (checked) {
|
||||
this.servicesControl.patchValue([...currentServices, service]);
|
||||
} else {
|
||||
this.servicesControl.patchValue(currentServices.filter(currentService => currentService.id !== service.id));
|
||||
}
|
||||
}
|
||||
|
||||
openDialog(val: boolean, authName?: string): void {
|
||||
if (authName) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
||||
this.modalAuthInfo.name = authName;
|
||||
}
|
||||
this.toggleDialog = val;
|
||||
}
|
||||
|
||||
setFocusOnInvalidInput(event: CustomEvent): void {
|
||||
console.log(event.target);
|
||||
}
|
||||
|
||||
resetForm(event: Event): void {
|
||||
event.preventDefault();
|
||||
this.formGroup.reset({
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
ssn: '',
|
||||
services: [],
|
||||
authorizations: [],
|
||||
});
|
||||
// Object.keys(this.formGroup.controls).forEach(controlKey => this.formGroup.controls[controlKey].markAsPristine());
|
||||
}
|
||||
|
||||
submitForm(): void {
|
||||
this.submitted = true;
|
||||
if (this.formGroup.valid) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||
const submittableValues = {
|
||||
...this.formGroup.value,
|
||||
};
|
||||
const post = this.employeeService.postNewEmployee(submittableValues).subscribe({
|
||||
next: id => {
|
||||
void this.router.navigate(['/administration', 'personal', id]);
|
||||
},
|
||||
complete: () => {
|
||||
post.unsubscribe();
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.error('Form is invalid, do something...');
|
||||
this._markFormAsDirty();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
import { DigiNgDialogModule } from '@af/digi-ng/_dialog/dialog';
|
||||
import { DigiNgFormCheckboxModule } from '@af/digi-ng/_form/form-checkbox';
|
||||
import { DigiNgFormDatepickerModule } from '@af/digi-ng/_form/form-datepicker';
|
||||
import { DigiNgFormInputModule } from '@af/digi-ng/_form/form-input';
|
||||
import { DigiNgFormRadiobuttonGroupModule } from '@af/digi-ng/_form/form-radiobutton-group';
|
||||
import { DigiNgFormSelectModule } from '@af/digi-ng/_form/form-select';
|
||||
import { DigiNgPopoverModule } from '@af/digi-ng/_popover/popover';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
|
||||
import { LocalDatePipeModule } from '@msfa-shared/pipes/local-date/local-date.module';
|
||||
import { EmployeeFormComponent } from './employee-form.component';
|
||||
|
||||
@NgModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [EmployeeFormComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{ path: '', component: EmployeeFormComponent }]),
|
||||
LayoutModule,
|
||||
ReactiveFormsModule,
|
||||
LocalDatePipeModule,
|
||||
DigiNgFormInputModule,
|
||||
DigiNgFormRadiobuttonGroupModule,
|
||||
DigiNgFormDatepickerModule,
|
||||
DigiNgFormSelectModule,
|
||||
DigiNgPopoverModule,
|
||||
DigiNgFormCheckboxModule,
|
||||
DigiNgDialogModule,
|
||||
],
|
||||
})
|
||||
export class EmployeeFormModule {}
|
||||
@@ -0,0 +1,60 @@
|
||||
<msfa-layout>
|
||||
<section class="employee-invite">
|
||||
<digi-typography>
|
||||
<h1>Skapa nytt personalkonto</h1>
|
||||
<p>Såhär skapar du ett nytt personalkonto:</p>
|
||||
<ol>
|
||||
<li>Skicka en inbjudningslänk till personalens e-postadress.</li>
|
||||
<li>Personalen öppnar inbjudningslänken via sin e-post och skapar ett personalkonto med sitt Bank-ID.</li>
|
||||
<li>
|
||||
När kontot är skapat ser du det i personallistan. Det nya personalkontot saknar fortfarande behörigheter.
|
||||
</li>
|
||||
<li>
|
||||
Ge personalkontot behörigheter genom att klicka på namnet i personallistan och ange vilka behörigheter
|
||||
personalen ska ha. Nu kan personalen logga in och arbeta.
|
||||
</li>
|
||||
</ol>
|
||||
</digi-typography>
|
||||
<form [formGroup]="form" (ngSubmit)="submitForm()">
|
||||
<div class="employee-invite__block">
|
||||
<digi-typography>
|
||||
<h2>Skicka en inbjudningslänk</h2>
|
||||
<p>
|
||||
Skicka en inbjudningslänk till personalen du vill lägga till som systemanvändare. Ange personalens
|
||||
e-postadress nedan och tryck på skicka inbjudningslänk.
|
||||
</p>
|
||||
</digi-typography>
|
||||
<div class="employee-invite__input-section">
|
||||
<digi-ng-form-input
|
||||
afId="employee-invite-email"
|
||||
class="employee-invite__input"
|
||||
formControlName="email"
|
||||
afLabel="E-postadress"
|
||||
afType="email"
|
||||
[afRequired]="true"
|
||||
[afInvalidMessage]="email.errors?.message || 'Ogiltig e-postadress'"
|
||||
[afDisableValidStyle]="true"
|
||||
[afInvalid]="email.invalid && email.dirty"
|
||||
></digi-ng-form-input>
|
||||
<digi-button af-size="m" af-type="submit" class="employee-invite__invitation-btn"
|
||||
>Skicka inbjudningslänk</digi-button
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<digi-notification-alert
|
||||
*ngIf="(latestSubmittedInvite$ | async) as latestSubmittedInvite"
|
||||
af-variation="success"
|
||||
af-heading="Allt gick bra"
|
||||
af-heading-level="h3"
|
||||
af-closeable="true"
|
||||
(click)="onCloseAlert()"
|
||||
>
|
||||
<p>Inbjudan har skickats till {{latestSubmittedInvite.email}}</p>
|
||||
</digi-notification-alert>
|
||||
|
||||
<footer class="employee-invite__footer">
|
||||
<msfa-back-link [route]="['/administration/personal']">Tillbaka till personallistan</msfa-back-link>
|
||||
</footer>
|
||||
</form>
|
||||
</section>
|
||||
</msfa-layout>
|
||||
@@ -0,0 +1,31 @@
|
||||
@import 'variables/gutters';
|
||||
|
||||
.employee-invite {
|
||||
&__block {
|
||||
max-width: var(--digi--typography--text--max-width);
|
||||
margin-top: $digi--layout--gutter--xl;
|
||||
margin-bottom: $digi--layout--gutter--xl;
|
||||
}
|
||||
|
||||
&__input-section {
|
||||
display: flex;
|
||||
margin-top: $digi--layout--gutter--xl;
|
||||
}
|
||||
|
||||
&__input {
|
||||
display: block;
|
||||
min-width: 240px;
|
||||
margin-bottom: var(--digi--layout--gutter);
|
||||
}
|
||||
&__invitation-btn {
|
||||
margin-top: 31px;
|
||||
margin-left: 16px;
|
||||
}
|
||||
|
||||
&__footer {
|
||||
margin-top: $digi--layout--gutter--xl;
|
||||
display: flex;
|
||||
gap: var(--digi--layout--gutter);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import { DigiNgFormInputModule } from '@af/digi-ng/_form/form-input';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
|
||||
import { EmployeeInviteComponent } from './employee-invite.component';
|
||||
|
||||
describe('EmployeeInviteComponent', () => {
|
||||
let component: EmployeeInviteComponent;
|
||||
let fixture: ComponentFixture<EmployeeInviteComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [ EmployeeInviteComponent ],
|
||||
imports: [
|
||||
ReactiveFormsModule,
|
||||
DigiNgFormInputModule
|
||||
]
|
||||
})
|
||||
.compileComponents();
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EmployeeInviteComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,54 @@
|
||||
import { ChangeDetectionStrategy, Component, OnInit } from '@angular/core';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { EmployeeService } from '@msfa-services/api/employee.service';
|
||||
import { RequiredValidator } from '@msfa-utils/validators/required.validator';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-employee-invite',
|
||||
templateUrl: './employee-invite.component.html',
|
||||
styleUrls: ['./employee-invite.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EmployeeInviteComponent implements OnInit {
|
||||
form: FormGroup;
|
||||
private latestSubmittedInvite_ = new BehaviorSubject<string>('');
|
||||
latestSubmittedInvite$: Observable<string> = this.latestSubmittedInvite_.asObservable();
|
||||
|
||||
constructor(private employeeService: EmployeeService) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.form = new FormGroup({
|
||||
email: new FormControl('', [
|
||||
RequiredValidator('E-postadress'),
|
||||
Validators.pattern('^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,4}$'),
|
||||
]),
|
||||
});
|
||||
}
|
||||
|
||||
get email() {
|
||||
return this.form.get('email');
|
||||
}
|
||||
|
||||
submitForm(): void {
|
||||
if (this.form.invalid) {
|
||||
this.email.markAsDirty();
|
||||
this.email.markAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
const post = this.employeeService.postEmployeeInvitation(this.form.value).subscribe({
|
||||
next: () => {
|
||||
this.latestSubmittedInvite_.next(this.form.value);
|
||||
this.form.reset();
|
||||
},
|
||||
complete: () => {
|
||||
post.unsubscribe();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
onCloseAlert(): void {
|
||||
this.latestSubmittedInvite_.next('');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { DigiNgFormInputModule } from '@af/digi-ng/_form/form-input';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { ReactiveFormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { BackLinkModule } from '@msfa-shared/components/back-link/back-link.module';
|
||||
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
|
||||
import { EmployeeInviteComponent } from './employee-invite.component';
|
||||
|
||||
@NgModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [EmployeeInviteComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{ path: '', component: EmployeeInviteComponent }]),
|
||||
LayoutModule,
|
||||
BackLinkModule,
|
||||
ReactiveFormsModule,
|
||||
DigiNgFormInputModule,
|
||||
],
|
||||
})
|
||||
export class EmployeeInviteModule {}
|
||||
@@ -0,0 +1,64 @@
|
||||
<div class="employees-list">
|
||||
<digi-table af-variation="secondary">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col" class="employees-list__column-head" *ngFor="let column of columnHeaders">
|
||||
<button
|
||||
class="employees-list__sort-button"
|
||||
[attr.id]="'sort-button-' + column.key"
|
||||
(click)="handleSort(column.key)"
|
||||
>
|
||||
{{column.label}}
|
||||
<ng-container *ngIf="sort.key === column.key">
|
||||
<digi-icon-caret-up
|
||||
class="employees-list__sort-icon"
|
||||
*ngIf="sort.order === orderType.ASC"
|
||||
></digi-icon-caret-up>
|
||||
<digi-icon-caret-down
|
||||
class="employees-list__sort-icon"
|
||||
*ngIf="sort.order === orderType.DESC"
|
||||
></digi-icon-caret-down>
|
||||
</ng-container>
|
||||
</button>
|
||||
</th>
|
||||
<th scope="col" class="employees-list__column-head">Redigera</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr class="employees-list__row" *ngFor="let employee of employees">
|
||||
<th scope="row">
|
||||
<a [routerLink]="employee.id" class="employees-list__link">{{ employee.fullName }}</a>
|
||||
</th>
|
||||
<td>
|
||||
<ng-container *ngFor="let service of employee.services; let last = last">
|
||||
{{ service.name }}<ng-container *ngIf="!last">, </ng-container>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<ng-container *ngFor="let organization of employee.organizations; let last = last">
|
||||
{{ organization.address.city }}<ng-container *ngIf="!last">, </ng-container>
|
||||
</ng-container>
|
||||
</td>
|
||||
<td>
|
||||
<digi-button af-variation="tertiary">
|
||||
<digi-icon-edit style="--digi--ui--width--icon: 1.25rem" slot="icon"></digi-icon-edit>
|
||||
</digi-button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</digi-table>
|
||||
|
||||
<digi-navigation-pagination
|
||||
*ngIf="totalPages > 1"
|
||||
class="employees-list__pagination"
|
||||
[afTotalPages]="totalPages"
|
||||
[afCurrentResultStart]="currentResultStart"
|
||||
[afCurrentResultEnd]="currentResultEnd"
|
||||
[afTotalResults]="count"
|
||||
(afOnPageChange)="setNewPage($event.detail)"
|
||||
af-result-name="medarbetare"
|
||||
>
|
||||
</digi-navigation-pagination>
|
||||
</div>
|
||||
@@ -0,0 +1,35 @@
|
||||
@import 'variables/gutters';
|
||||
|
||||
.employees-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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
import { By } from '@angular/platform-browser';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { SortOrder } from '@msfa-enums/sort-order.enum';
|
||||
import { Employee } from '@msfa-models/employee.model';
|
||||
import { EmployeesListComponent } from './employees-list.component';
|
||||
import { employeesMock } from './employees-list.mock';
|
||||
|
||||
describe('EmployeesListComponent', () => {
|
||||
let component: EmployeesListComponent;
|
||||
let fixture: ComponentFixture<EmployeesListComponent>;
|
||||
const getEmployeeRows = () => fixture.debugElement.queryAll(By.css('.employees-list__row'));
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [EmployeesListComponent],
|
||||
imports: [RouterTestingModule],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EmployeesListComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
|
||||
describe('20 employees sorted by Full name Ascending', () => {
|
||||
beforeEach(() => {
|
||||
component.employees = employeesMock;
|
||||
component.paginationMeta = { count: employeesMock.length, limit: 50, page: 1, totalPages: 3 };
|
||||
component.sort = { key: <keyof Employee>'fullName', order: SortOrder.ASC };
|
||||
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should display the rows from employees object 20 rows regardless of pagination', () => {
|
||||
expect(getEmployeeRows().length).toBe(20);
|
||||
});
|
||||
|
||||
it('should display the up caret next to Full name to indicate that it´s sorted by full name Ascending', () => {
|
||||
const fullNameUpCaret = fixture.debugElement.query(By.css('#sort-button-fullName > digi-icon-caret-up'));
|
||||
expect(fullNameUpCaret).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should only display one caret', () => {
|
||||
const upCarets = fixture.debugElement.queryAll(By.css('digi-icon-caret-up'));
|
||||
const downCarets = fixture.debugElement.queryAll(By.css('digi-icon-caret-down'));
|
||||
expect(upCarets.length + downCarets.length).toBe(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,62 @@
|
||||
import { ChangeDetectionStrategy, Component, EventEmitter, Input, Output } from '@angular/core';
|
||||
import { SortOrder } from '@msfa-enums/sort-order.enum';
|
||||
import { Employee } from '@msfa-models/employee.model';
|
||||
import { PaginationMeta } from '@msfa-models/pagination-meta.model';
|
||||
import { Sort } from '@msfa-models/sort.model';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-employees-list',
|
||||
templateUrl: './employees-list.component.html',
|
||||
styleUrls: ['./employees-list.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EmployeesListComponent {
|
||||
@Input() employees: Employee[];
|
||||
@Input() paginationMeta: PaginationMeta;
|
||||
@Input() sort: Sort<keyof Employee>;
|
||||
@Output() sorted = new EventEmitter<keyof Employee>();
|
||||
@Output() paginated = new EventEmitter<number>();
|
||||
|
||||
columnHeaders: { label: string; key: keyof Employee }[] = [
|
||||
{ label: 'Namn', key: 'fullName' },
|
||||
{
|
||||
label: 'Tjänst',
|
||||
key: 'services',
|
||||
},
|
||||
{
|
||||
label: 'Utförandeverksamheter',
|
||||
key: 'organizations',
|
||||
},
|
||||
];
|
||||
|
||||
orderType = SortOrder;
|
||||
|
||||
get currentPage(): number {
|
||||
return this.paginationMeta.page;
|
||||
}
|
||||
|
||||
get totalPages(): number {
|
||||
return this.paginationMeta?.totalPages;
|
||||
}
|
||||
|
||||
get count(): number {
|
||||
return this.paginationMeta.count;
|
||||
}
|
||||
|
||||
get currentResultStart(): number {
|
||||
return (this.currentPage - 1) * this.paginationMeta.limit + 1;
|
||||
}
|
||||
|
||||
get currentResultEnd(): number {
|
||||
const end = this.currentResultStart + this.paginationMeta.limit - 1;
|
||||
return end < this.count ? end : this.count;
|
||||
}
|
||||
|
||||
handleSort(key: keyof Employee): void {
|
||||
this.sorted.emit(key);
|
||||
}
|
||||
|
||||
setNewPage(page: number): void {
|
||||
this.paginated.emit(page);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,565 @@
|
||||
import { Service } from '@msfa-enums/service.enum';
|
||||
import { Employee } from '@msfa-models/employee.model';
|
||||
|
||||
export const employeesMock: Employee[] = [
|
||||
{
|
||||
id: 'b136f30a-3997-4fdd-8c02-2415ee9c6d83',
|
||||
firstName: 'Jayson',
|
||||
lastName: 'Karlsson',
|
||||
ssn: '19951019-7751',
|
||||
organizations: [
|
||||
{
|
||||
id: 'd5b9d727-4473-47be-bdc0-cc3d6ed85934',
|
||||
name: 'Svensson, Olsson and Nilsson',
|
||||
kaNumber: 999419,
|
||||
address: {
|
||||
street: 'Eriksson gatan',
|
||||
houseNumber: 85,
|
||||
postalCode: '13202',
|
||||
city: 'Columbia',
|
||||
kommun: 'Halmstads kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628956,
|
||||
},
|
||||
{
|
||||
id: 'c3359c5d-e0ff-4792-a3f3-7142fef932e5',
|
||||
firstName: 'Elbert',
|
||||
lastName: 'Andersson',
|
||||
ssn: '19701221-4753',
|
||||
organizations: [
|
||||
{
|
||||
id: 'fc42fe9c-ad06-46df-9c33-9e61b5c3f881',
|
||||
name: 'Nilsson, Svensson and Johansson',
|
||||
kaNumber: 578637,
|
||||
address: {
|
||||
street: 'Olsson gatan',
|
||||
houseNumber: 33,
|
||||
postalCode: '98821',
|
||||
city: 'Hacienda Heights',
|
||||
kommun: 'Motala kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628956,
|
||||
},
|
||||
{
|
||||
id: '28cc9679-bf5e-4066-900f-0866710ebdbc',
|
||||
firstName: 'Tyreek',
|
||||
lastName: 'Larsson',
|
||||
ssn: '19530826-5774',
|
||||
organizations: [
|
||||
{
|
||||
id: '11da9de5-2ce2-4364-a1b2-08a263bfc248',
|
||||
name: 'Eriksson Group',
|
||||
kaNumber: 975639,
|
||||
address: {
|
||||
street: 'Vito allén',
|
||||
houseNumber: 82,
|
||||
postalCode: '61048',
|
||||
city: 'Helsing Consuelo',
|
||||
kommun: 'Finspångs kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628956,
|
||||
},
|
||||
{
|
||||
id: '6fae0a53-fd04-4ca0-b099-933161c920b8',
|
||||
firstName: 'Jarret',
|
||||
lastName: 'Eriksson',
|
||||
ssn: '19731207-7794',
|
||||
organizations: [
|
||||
{
|
||||
id: 'b8011410-d7a8-4163-98c8-3262cf0681b9',
|
||||
name: 'Svensson - Svensson',
|
||||
kaNumber: 815388,
|
||||
address: {
|
||||
street: 'Delphine allén',
|
||||
houseNumber: 26,
|
||||
postalCode: '26994',
|
||||
city: 'En Akeem',
|
||||
kommun: 'Smedjebackens kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: '20e09e98-c744-45b3-95ef-54ef51af32c0',
|
||||
name: 'KVL' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628957,
|
||||
},
|
||||
{
|
||||
id: '6fd136ad-f51a-4a30-b6e9-dd1116cf90d6',
|
||||
firstName: 'Bradley',
|
||||
lastName: 'Svensson',
|
||||
ssn: '19831128-5775',
|
||||
organizations: [
|
||||
{
|
||||
id: '53d64944-040c-44ee-9505-879ae05f660e',
|
||||
name: 'Persson, Andersson and Karlsson',
|
||||
kaNumber: 234733,
|
||||
address: {
|
||||
street: 'Althea allén',
|
||||
houseNumber: 42,
|
||||
postalCode: '76986',
|
||||
city: 'Myrtisby',
|
||||
kommun: 'Olofströms kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628957,
|
||||
},
|
||||
{
|
||||
id: '0f8445b0-9eb6-432d-9967-0541ea74d9c6',
|
||||
firstName: 'Heath',
|
||||
lastName: 'Karlsson',
|
||||
ssn: '19821114-5302',
|
||||
organizations: [
|
||||
{
|
||||
id: '6bd3806d-b49d-412d-96b7-76ff4ec27a44',
|
||||
name: 'Olsson, Andersson and Andersson',
|
||||
kaNumber: 902976,
|
||||
address: {
|
||||
street: 'Johansson gatan',
|
||||
houseNumber: 92,
|
||||
postalCode: '65702',
|
||||
city: 'Lessieland',
|
||||
kommun: 'Motala kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: '20e09e98-c744-45b3-95ef-54ef51af32c0',
|
||||
name: 'KVL' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628957,
|
||||
},
|
||||
{
|
||||
id: '29cfccc4-bf1d-4eaa-88d9-b86e22203bc7',
|
||||
firstName: 'Mitchel',
|
||||
lastName: 'Andersson',
|
||||
ssn: '19680607-4896',
|
||||
organizations: [
|
||||
{
|
||||
id: '11da9de5-2ce2-4364-a1b2-08a263bfc248',
|
||||
name: 'Eriksson Group',
|
||||
kaNumber: 975639,
|
||||
address: {
|
||||
street: 'Vito allén',
|
||||
houseNumber: 82,
|
||||
postalCode: '61048',
|
||||
city: 'Helsing Consuelo',
|
||||
kommun: 'Finspångs kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: '20e09e98-c744-45b3-95ef-54ef51af32c0',
|
||||
name: 'KVL' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628957,
|
||||
},
|
||||
{
|
||||
id: '412a7141-82fa-4b98-812f-e092910663af',
|
||||
firstName: 'Raheem',
|
||||
lastName: 'Andersson',
|
||||
ssn: '19820609-8453',
|
||||
organizations: [
|
||||
{
|
||||
id: 'e2d4f74f-c1da-478d-a116-d6dfa1b0183c',
|
||||
name: 'Larsson - Gustafsson',
|
||||
kaNumber: 852472,
|
||||
address: {
|
||||
street: 'Gust gatan',
|
||||
houseNumber: 77,
|
||||
postalCode: '52349',
|
||||
city: 'Katelynnmora',
|
||||
kommun: 'Hofors kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628957,
|
||||
},
|
||||
{
|
||||
id: 'a4df2f97-fdaf-4b41-8793-dd84ea631502',
|
||||
firstName: 'Ricky',
|
||||
lastName: 'Johansson',
|
||||
ssn: '19980903-7392',
|
||||
organizations: [
|
||||
{
|
||||
id: 'd5b9d727-4473-47be-bdc0-cc3d6ed85934',
|
||||
name: 'Svensson, Olsson and Nilsson',
|
||||
kaNumber: 999419,
|
||||
address: {
|
||||
street: 'Eriksson gatan',
|
||||
houseNumber: 85,
|
||||
postalCode: '13202',
|
||||
city: 'Columbia',
|
||||
kommun: 'Halmstads kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628957,
|
||||
},
|
||||
{
|
||||
id: '1a4cbf66-14f7-45c1-ad64-0df6ec3bdc2c',
|
||||
firstName: 'Billie',
|
||||
lastName: 'Andersson',
|
||||
ssn: '19710304-8866',
|
||||
organizations: [
|
||||
{
|
||||
id: 'fc42fe9c-ad06-46df-9c33-9e61b5c3f881',
|
||||
name: 'Nilsson, Svensson and Johansson',
|
||||
kaNumber: 578637,
|
||||
address: {
|
||||
street: 'Olsson gatan',
|
||||
houseNumber: 33,
|
||||
postalCode: '98821',
|
||||
city: 'Hacienda Heights',
|
||||
kommun: 'Motala kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628958,
|
||||
},
|
||||
{
|
||||
id: 'b3703a76-474d-4cb3-b74f-fa41ffe158b5',
|
||||
firstName: 'Lizzie',
|
||||
lastName: 'Karlsson',
|
||||
ssn: '19890729-2332',
|
||||
organizations: [
|
||||
{
|
||||
id: 'd5b9d727-4473-47be-bdc0-cc3d6ed85934',
|
||||
name: 'Svensson, Olsson and Nilsson',
|
||||
kaNumber: 999419,
|
||||
address: {
|
||||
street: 'Eriksson gatan',
|
||||
houseNumber: 85,
|
||||
postalCode: '13202',
|
||||
city: 'Columbia',
|
||||
kommun: 'Halmstads kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628958,
|
||||
},
|
||||
{
|
||||
id: 'd1523e59-b400-4b6d-8a4b-f393d70bf2d5',
|
||||
firstName: 'Cruz',
|
||||
lastName: 'Gustafsson',
|
||||
ssn: '19861226-1321',
|
||||
organizations: [
|
||||
{
|
||||
id: '11da9de5-2ce2-4364-a1b2-08a263bfc248',
|
||||
name: 'Eriksson Group',
|
||||
kaNumber: 975639,
|
||||
address: {
|
||||
street: 'Vito allén',
|
||||
houseNumber: 82,
|
||||
postalCode: '61048',
|
||||
city: 'Helsing Consuelo',
|
||||
kommun: 'Finspångs kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628958,
|
||||
},
|
||||
{
|
||||
id: '1340f322-7e80-4f59-926a-bde9fc6621fc',
|
||||
firstName: 'Jeremie',
|
||||
lastName: 'Svensson',
|
||||
ssn: '19681107-4830',
|
||||
organizations: [
|
||||
{
|
||||
id: 'fc42fe9c-ad06-46df-9c33-9e61b5c3f881',
|
||||
name: 'Nilsson, Svensson and Johansson',
|
||||
kaNumber: 578637,
|
||||
address: {
|
||||
street: 'Olsson gatan',
|
||||
houseNumber: 33,
|
||||
postalCode: '98821',
|
||||
city: 'Hacienda Heights',
|
||||
kommun: 'Motala kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628958,
|
||||
},
|
||||
{
|
||||
id: 'bd207a4b-ab8a-41e9-8492-ddc580dc2cfd',
|
||||
firstName: 'Mae',
|
||||
lastName: 'Olsson',
|
||||
ssn: '19980630-7229',
|
||||
organizations: [
|
||||
{
|
||||
id: '6bd3806d-b49d-412d-96b7-76ff4ec27a44',
|
||||
name: 'Olsson, Andersson and Andersson',
|
||||
kaNumber: 902976,
|
||||
address: {
|
||||
street: 'Johansson gatan',
|
||||
houseNumber: 92,
|
||||
postalCode: '65702',
|
||||
city: 'Lessieland',
|
||||
kommun: 'Motala kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: '20e09e98-c744-45b3-95ef-54ef51af32c0',
|
||||
name: 'KVL' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628958,
|
||||
},
|
||||
{
|
||||
id: '5ab9ad0f-fabc-4916-9d9c-7559100866cd',
|
||||
firstName: 'Mable',
|
||||
lastName: 'Gustafsson',
|
||||
ssn: '19821217-3880',
|
||||
organizations: [
|
||||
{
|
||||
id: 'e2d4f74f-c1da-478d-a116-d6dfa1b0183c',
|
||||
name: 'Larsson - Gustafsson',
|
||||
kaNumber: 852472,
|
||||
address: {
|
||||
street: 'Gust gatan',
|
||||
houseNumber: 77,
|
||||
postalCode: '52349',
|
||||
city: 'Katelynnmora',
|
||||
kommun: 'Hofors kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628958,
|
||||
},
|
||||
{
|
||||
id: '996c421e-4e93-4d04-961e-5d1268840a2e',
|
||||
firstName: 'Lonie',
|
||||
lastName: 'Nilsson',
|
||||
ssn: '19920429-1095',
|
||||
organizations: [
|
||||
{
|
||||
id: 'b8011410-d7a8-4163-98c8-3262cf0681b9',
|
||||
name: 'Svensson - Svensson',
|
||||
kaNumber: 815388,
|
||||
address: {
|
||||
street: 'Delphine allén',
|
||||
houseNumber: 26,
|
||||
postalCode: '26994',
|
||||
city: 'En Akeem',
|
||||
kommun: 'Smedjebackens kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628959,
|
||||
},
|
||||
{
|
||||
id: '24e28f54-8bdd-4ee5-b072-37e25feba220',
|
||||
firstName: 'Albertha',
|
||||
lastName: 'Olsson',
|
||||
ssn: '19900128-8896',
|
||||
organizations: [
|
||||
{
|
||||
id: 'd75cad98-75b5-40e6-8674-765121938928',
|
||||
name: 'Karlsson, Gustafsson and Svensson',
|
||||
kaNumber: 619459,
|
||||
address: {
|
||||
street: 'Svensson gärdet',
|
||||
houseNumber: 41,
|
||||
postalCode: '16444',
|
||||
city: 'Gustafssonberg',
|
||||
kommun: 'Hallsbergs kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: '20e09e98-c744-45b3-95ef-54ef51af32c0',
|
||||
name: 'KVL' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628959,
|
||||
},
|
||||
{
|
||||
id: '4de5abb8-bda6-40e2-9b26-6a834e61b543',
|
||||
firstName: 'Giovanny',
|
||||
lastName: 'Nilsson',
|
||||
ssn: '19620130-3009',
|
||||
organizations: [
|
||||
{
|
||||
id: '53d64944-040c-44ee-9505-879ae05f660e',
|
||||
name: 'Persson, Andersson and Karlsson',
|
||||
kaNumber: 234733,
|
||||
address: {
|
||||
street: 'Althea allén',
|
||||
houseNumber: 42,
|
||||
postalCode: '76986',
|
||||
city: 'Myrtisby',
|
||||
kommun: 'Olofströms kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628959,
|
||||
},
|
||||
{
|
||||
id: '61ff95f7-175c-48df-94d2-e5e368ba116c',
|
||||
firstName: 'Meta',
|
||||
lastName: 'Olsson',
|
||||
ssn: '19790727-4413',
|
||||
organizations: [
|
||||
{
|
||||
id: '6bd3806d-b49d-412d-96b7-76ff4ec27a44',
|
||||
name: 'Olsson, Andersson and Andersson',
|
||||
kaNumber: 902976,
|
||||
address: {
|
||||
street: 'Johansson gatan',
|
||||
houseNumber: 92,
|
||||
postalCode: '65702',
|
||||
city: 'Lessieland',
|
||||
kommun: 'Motala kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: '20e09e98-c744-45b3-95ef-54ef51af32c0',
|
||||
name: 'KVL' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628959,
|
||||
},
|
||||
{
|
||||
id: '3909e35e-22be-4d95-b4f4-a6f34309c7b8',
|
||||
firstName: 'Candelario',
|
||||
lastName: 'Svensson',
|
||||
ssn: '19741125-2817',
|
||||
organizations: [
|
||||
{
|
||||
id: 'd7ba7bb8-2946-4444-b60e-edf4e0cf27dd',
|
||||
name: 'Eriksson - Gustafsson',
|
||||
kaNumber: 393573,
|
||||
address: {
|
||||
street: 'Juvenal vägen',
|
||||
houseNumber: 92,
|
||||
postalCode: '53784',
|
||||
city: 'Alenaland',
|
||||
kommun: 'Bromölla kommun',
|
||||
},
|
||||
},
|
||||
],
|
||||
services: [
|
||||
{
|
||||
id: 'a33515e7-045a-4da5-8646-9eed160b18d1',
|
||||
name: 'KROM' as Service,
|
||||
},
|
||||
],
|
||||
authorizations: [],
|
||||
createdAt: 1623655628959,
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,12 @@
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { EmployeesListComponent } from './employees-list.component';
|
||||
|
||||
@NgModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [EmployeesListComponent],
|
||||
imports: [CommonModule, RouterModule],
|
||||
exports: [EmployeesListComponent],
|
||||
})
|
||||
export class EmployeesListModule {}
|
||||
@@ -0,0 +1,45 @@
|
||||
<msfa-layout>
|
||||
<section class="employees">
|
||||
<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="employees__cta-wrapper">
|
||||
<digi-ng-link-button afText="Skapa nytt konto" afRoute="/administration/bjuda-in"></digi-ng-link-button>
|
||||
</div>
|
||||
|
||||
<h2>Personallista</h2>
|
||||
|
||||
<form class="employees__search-wrapper" (ngSubmit)="setSearchFilter()">
|
||||
<digi-form-input-search
|
||||
af-label="Sök personal"
|
||||
af-label-description="Sök på namn"
|
||||
(afOnInput)="setSearchValue($event)"
|
||||
></digi-form-input-search>
|
||||
<digi-form-checkbox
|
||||
class="employees__only-employees-without-authorization"
|
||||
af-label="Visa endast personer utan behörigheter"
|
||||
(afOnChange)="setOnlyEmployeesWithoutAuthorization($event)"
|
||||
></digi-form-checkbox>
|
||||
</form>
|
||||
|
||||
<msfa-employees-list
|
||||
*ngIf="employeesData$ | async as employeesData; else loadingRef"
|
||||
[employees]="employeesData.data"
|
||||
[paginationMeta]="employeesData.meta"
|
||||
[sort]="sort$ | async"
|
||||
[order]="order$ | async"
|
||||
(sorted)="handleEmployeesSort($event)"
|
||||
(paginated)="setNewPage($event)"
|
||||
></msfa-employees-list>
|
||||
</digi-typography>
|
||||
|
||||
<ng-template #loadingRef>
|
||||
<digi-ng-skeleton-base [afCount]="3" afText="Laddar personal"></digi-ng-skeleton-base>
|
||||
</ng-template>
|
||||
</section>
|
||||
</msfa-layout>
|
||||
@@ -0,0 +1,19 @@
|
||||
@import 'variables/gutters';
|
||||
|
||||
.employees {
|
||||
&__cta-wrapper {
|
||||
margin-top: var(--digi--layout--gutter);
|
||||
}
|
||||
|
||||
&__search-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
max-width: var(--digi--typography--text--max-width);
|
||||
margin-top: $digi--layout--gutter--l;
|
||||
margin-bottom: $digi--layout--gutter--xl;
|
||||
}
|
||||
|
||||
&__only-employees-without-authorization {
|
||||
margin-top: $digi--layout--gutter--l;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { HttpClientTestingModule } from '@angular/common/http/testing';
|
||||
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
|
||||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
import { RouterTestingModule } from '@angular/router/testing';
|
||||
import { EmployeesComponent } from './employees.component';
|
||||
|
||||
describe('EmployeesComponent', () => {
|
||||
let component: EmployeesComponent;
|
||||
let fixture: ComponentFixture<EmployeesComponent>;
|
||||
|
||||
beforeEach(
|
||||
waitForAsync(() => {
|
||||
void TestBed.configureTestingModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [EmployeesComponent],
|
||||
imports: [RouterTestingModule, HttpClientTestingModule],
|
||||
}).compileComponents();
|
||||
})
|
||||
);
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(EmployeesComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
void expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,51 @@
|
||||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { IconType } from '@msfa-enums/icon-type.enum';
|
||||
import { Employee, EmployeesData } from '@msfa-models/employee.model';
|
||||
import { Sort } from '@msfa-models/sort.model';
|
||||
import { EmployeeService } from '@msfa-services/api/employee.service';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
|
||||
@Component({
|
||||
selector: 'msfa-employees',
|
||||
templateUrl: './employees.component.html',
|
||||
styleUrls: ['./employees.component.scss'],
|
||||
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||
})
|
||||
export class EmployeesComponent {
|
||||
private _searchValue$ = new BehaviorSubject<string>('');
|
||||
private _onlyEmployeesWithoutAuthorization$ = new BehaviorSubject<boolean>(false);
|
||||
employeesData$: Observable<EmployeesData> = this.employeeService.employeesData$;
|
||||
sort$: Observable<Sort<keyof Employee>> = this.employeeService.sort$;
|
||||
iconType = IconType;
|
||||
|
||||
constructor(private employeeService: EmployeeService) {}
|
||||
|
||||
get searchValue(): string {
|
||||
return this._searchValue$.getValue();
|
||||
}
|
||||
|
||||
setSearchFilter(): void {
|
||||
this.employeeService.setSearchFilter(this.searchValue);
|
||||
}
|
||||
|
||||
setSearchValue($event: CustomEvent<{ target: { value: string } }>): void {
|
||||
this._searchValue$.next($event.detail.target.value);
|
||||
}
|
||||
|
||||
handleEmployeesSort(key: keyof Employee): void {
|
||||
this.employeeService.setSort(key);
|
||||
}
|
||||
|
||||
setNewPage(page: number): void {
|
||||
this.employeeService.setPage(page);
|
||||
}
|
||||
|
||||
get onlyEmployeesWithoutAuthorization(): boolean {
|
||||
return this._onlyEmployeesWithoutAuthorization$.getValue();
|
||||
}
|
||||
|
||||
setOnlyEmployeesWithoutAuthorization(event: CustomEvent<{ target: { checked: boolean } }>): void {
|
||||
this._onlyEmployeesWithoutAuthorization$.next(event.detail.target.checked);
|
||||
this.employeeService.setOnlyEmployeesWithoutAuthorization(this.onlyEmployeesWithoutAuthorization);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { DigiNgLinkButtonModule } from '@af/digi-ng/_link/link-button';
|
||||
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 { FormsModule } from '@angular/forms';
|
||||
import { RouterModule } from '@angular/router';
|
||||
import { LayoutModule } from '@msfa-shared/components/layout/layout.module';
|
||||
import { EmployeesListModule } from './components/employees-list/employees-list.module';
|
||||
import { EmployeesComponent } from './employees.component';
|
||||
|
||||
@NgModule({
|
||||
schemas: [CUSTOM_ELEMENTS_SCHEMA],
|
||||
declarations: [EmployeesComponent],
|
||||
imports: [
|
||||
CommonModule,
|
||||
RouterModule.forChild([{ path: '', component: EmployeesComponent }]),
|
||||
LayoutModule,
|
||||
DigiNgLinkInternalModule,
|
||||
DigiNgSkeletonBaseModule,
|
||||
EmployeesListModule,
|
||||
DigiNgLinkButtonModule,
|
||||
FormsModule,
|
||||
],
|
||||
})
|
||||
export class EmployeesModule {}
|
||||
Reference in New Issue
Block a user