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:
Erik Tiekstra
2021-08-18 07:10:28 +02:00
parent 6d29baa533
commit 03a2c7a42f
349 changed files with 720 additions and 734 deletions

View File

@@ -0,0 +1,4 @@
<a class="back-link" [routerLink]="route">
<msfa-icon [icon]="iconType.ARROW_LEFT"></msfa-icon>
<ng-content></ng-content>
</a>

View File

@@ -0,0 +1,11 @@
.back-link {
display: inline-flex;
align-items: center;
text-decoration: none;
font-weight: var(--digi--typography--font-weight--semibold);
gap: var(--digi--layout--gutter--xs);
&:hover {
text-decoration: underline;
}
}

View File

@@ -0,0 +1,26 @@
/* tslint:disable:no-unused-variable */
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { BackLinkComponent } from './back-link.component';
describe('BackLinkComponent', () => {
let component: BackLinkComponent;
let fixture: ComponentFixture<BackLinkComponent>;
beforeEach(async(() => {
void TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [BackLinkComponent],
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(BackLinkComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,13 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { IconType } from '@msfa-enums/icon-type.enum';
@Component({
selector: 'msfa-back-link',
templateUrl: './back-link.component.html',
styleUrls: ['./back-link.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class BackLinkComponent {
@Input() route: string[];
iconType = IconType;
}

View File

@@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { IconModule } from '../icon/icon.module';
import { BackLinkComponent } from './back-link.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [BackLinkComponent],
imports: [CommonModule, RouterModule, IconModule],
exports: [BackLinkComponent],
})
export class BackLinkModule {}

View File

@@ -0,0 +1,8 @@
<span class="hide-text">
{{ transformedText }}
<button class="hide-text__button" type="button" [attr.aria-label]="ariaLabel" (click)="toggleText()">
<msfa-icon *ngIf="!hideText" [icon]="iconType.EYESLASH" size="l"></msfa-icon>
<msfa-icon *ngIf="hideText" [icon]="iconType.EYE" size="l"></msfa-icon>
<span class="hide-text__button-text">{{ hideText ? 'Visa' : 'Dölj'}}</span>
</button>
</span>

View File

@@ -0,0 +1,22 @@
.hide-text {
display: flex;
align-items: center;
gap: var(--digi--layout--gutter);
&__button {
background-color: transparent;
color: var(--digi--typography--color--link);
font-size: var(--digi--typography--font-size--desktop);
border-width: 0;
display: flex;
align-items: center;
justify-content: center;
&:hover {
color: var(--digi--typography--color--link--active);
}
}
&__button-text {
margin-left: var(--digi--layout--gutter--s);
}
}

View File

@@ -0,0 +1,23 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { HideTextComponent } from './hide-text.component';
describe('HideTextComponent', () => {
let component: HideTextComponent;
let fixture: ComponentFixture<HideTextComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [HideTextComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(HideTextComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,30 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { IconType } from '@msfa-enums/icon-type.enum';
@Component({
selector: 'msfa-hide-text',
templateUrl: './hide-text.component.html',
styleUrls: ['./hide-text.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class HideTextComponent {
@Input() changingText: string;
@Input() symbols: string;
@Input() ariaLabelType = 'text';
hideText = true;
iconType = IconType;
get transformedText(): string {
return this.hideText ? this.symbols : this.changingText;
}
toggleText(): void {
this.hideText = !this.hideText;
}
get ariaLabel(): string {
const buttonText = this.hideText ? 'Visa' : 'Dölj';
return `${buttonText} ${this.ariaLabelType}`;
}
}

View File

@@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { HideTextComponent } from './hide-text.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [HideTextComponent],
imports: [CommonModule, IconModule],
exports: [HideTextComponent],
})
export class HideTextModule {}

View File

@@ -0,0 +1,63 @@
<digi-ng-icon-custom *ngIf="isCustomIcon; else DigiNgIcon" aria-hidden="true" class="icon" [ngClass]="[iconClass]">
<svg *ngIf="icon === iconType.HOME" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512" width="25" height="25">
<path
d="M280.37 148.26L96 300.11V464a16 16 0 0 0 16 16l112.06-.29a16 16 0 0 0 15.92-16V368a16 16 0 0 1 16-16h64a16 16 0 0 1 16 16v95.64a16 16 0 0 0 16 16.05L464 480a16 16 0 0 0 16-16V300L295.67 148.26a12.19 12.19 0 0 0-15.3 0zM571.6 251.47L488 182.56V44.05a12 12 0 0 0-12-12h-56a12 12 0 0 0-12 12v72.61L318.47 43a48 48 0 0 0-61 0L4.34 251.47a12 12 0 0 0-1.6 16.9l25.5 31A12 12 0 0 0 45.15 301l235.22-193.74a12.19 12.19 0 0 1 15.3 0L530.9 301a12 12 0 0 0 16.9-1.6l25.5-31a12 12 0 0 0-1.7-16.93z"
fill="currentColor"
></path>
</svg>
<svg
*ngIf="icon === iconType.SETTINGS"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 512 512"
width="25"
height="25"
>
<path
d="M487.4 315.7l-42.6-24.6c4.3-23.2 4.3-47 0-70.2l42.6-24.6c4.9-2.8 7.1-8.6 5.5-14-11.1-35.6-30-67.8-54.7-94.6-3.8-4.1-10-5.1-14.8-2.3L380.8 110c-17.9-15.4-38.5-27.3-60.8-35.1V25.8c0-5.6-3.9-10.5-9.4-11.7-36.7-8.2-74.3-7.8-109.2 0-5.5 1.2-9.4 6.1-9.4 11.7V75c-22.2 7.9-42.8 19.8-60.8 35.1L88.7 85.5c-4.9-2.8-11-1.9-14.8 2.3-24.7 26.7-43.6 58.9-54.7 94.6-1.7 5.4.6 11.2 5.5 14L67.3 221c-4.3 23.2-4.3 47 0 70.2l-42.6 24.6c-4.9 2.8-7.1 8.6-5.5 14 11.1 35.6 30 67.8 54.7 94.6 3.8 4.1 10 5.1 14.8 2.3l42.6-24.6c17.9 15.4 38.5 27.3 60.8 35.1v49.2c0 5.6 3.9 10.5 9.4 11.7 36.7 8.2 74.3 7.8 109.2 0 5.5-1.2 9.4-6.1 9.4-11.7v-49.2c22.2-7.9 42.8-19.8 60.8-35.1l42.6 24.6c4.9 2.8 11 1.9 14.8-2.3 24.7-26.7 43.6-58.9 54.7-94.6 1.5-5.5-.7-11.3-5.6-14.1zM256 336c-44.1 0-80-35.9-80-80s35.9-80 80-80 80 35.9 80 80-35.9 80-80 80z"
fill="currentColor"
></path>
</svg>
<svg *ngIf="icon === iconType.PLUS" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512" width="25" height="25">
<path
fill="currentColor"
d="M416 208H272V64c0-17.67-14.33-32-32-32h-32c-17.67 0-32 14.33-32 32v144H32c-17.67 0-32 14.33-32 32v32c0 17.67 14.33 32 32 32h144v144c0 17.67 14.33 32 32 32h32c17.67 0 32-14.33 32-32V304h144c17.67 0 32-14.33 32-32v-32c0-17.67-14.33-32-32-32z"
></path>
</svg>
<svg
*ngIf="icon === iconType.CLIPBOARD"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 384 512"
width="25"
height="25"
>
<path
fill="currentColor"
d="M384 112v352c0 26.51-21.49 48-48 48H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h80c0-35.29 28.71-64 64-64s64 28.71 64 64h80c26.51 0 48 21.49 48 48zM192 40c-13.255 0-24 10.745-24 24s10.745 24 24 24 24-10.745 24-24-10.745-24-24-24m96 114v-20a6 6 0 0 0-6-6H102a6 6 0 0 0-6 6v20a6 6 0 0 0 6 6h180a6 6 0 0 0 6-6z"
></path>
</svg>
</digi-ng-icon-custom>
<ng-template #DigiNgIcon>
<ng-container [ngSwitch]="icon">
<digi-icon-user-alt *ngSwitchCase="iconType.USER" [ngClass]="iconClass"></digi-icon-user-alt>
<digi-icon-users-solid *ngSwitchCase="iconType.USERS" [ngClass]="iconClass"></digi-icon-users-solid>
<digi-icon-bell *ngSwitchCase="iconType.BELL" [ngClass]="iconClass"></digi-icon-bell>
<digi-icon-calendar-alt *ngSwitchCase="iconType.CALENDAR" [ngClass]="iconClass"></digi-icon-calendar-alt>
<digi-icon-envelope-filled *ngSwitchCase="iconType.ENVELOPE" [ngClass]="iconClass"></digi-icon-envelope-filled>
<digi-icon-sokkandidat *ngSwitchCase="iconType.SOK_KANDIDAT" [ngClass]="iconClass"></digi-icon-sokkandidat>
<digi-icon-edit *ngSwitchCase="iconType.EDIT" [ngClass]="iconClass"></digi-icon-edit>
<digi-icon-exclamation-circle *ngSwitchCase="iconType.INFO" [ngClass]="iconClass"></digi-icon-exclamation-circle>
<digi-icon-exclamation-triangle
*ngSwitchCase="iconType.WARNING"
[ngClass]="iconClass"
></digi-icon-exclamation-triangle>
<digi-icon-check-circle-reg *ngSwitchCase="iconType.APPROVED" [ngClass]="iconClass"></digi-icon-check-circle-reg>
<digi-icon-x *ngSwitchCase="iconType.X" [ngClass]="iconClass"></digi-icon-x>
<digi-icon-arrow-left *ngSwitchCase="iconType.ARROW_LEFT" [ngClass]="iconClass"></digi-icon-arrow-left>
<digi-icon-arrow-right *ngSwitchCase="iconType.ARROW_RIGHT" [ngClass]="iconClass"></digi-icon-arrow-right>
<digi-icon-eye *ngSwitchCase="iconType.EYE" [ngClass]="iconClass"></digi-icon-eye>
<digi-icon-eye-slash *ngSwitchCase="iconType.EYESLASH" [ngClass]="iconClass"></digi-icon-eye-slash>
</ng-container>
</ng-template>

View File

@@ -0,0 +1,23 @@
@import 'mixins/icon';
:host {
display: inline-flex;
}
.icon {
&--s {
@include msfa__digi-ng-icon(0.875em);
}
&--m {
@include msfa__digi-ng-icon(1em);
}
&--l {
@include msfa__digi-ng-icon(1.25em);
}
&--xl {
@include msfa__digi-ng-icon(1.5em);
}
&--xxl {
@include msfa__digi-ng-icon(2em);
}
}

View File

@@ -0,0 +1,26 @@
/* tslint:disable:no-unused-variable */
import { CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { IconComponent } from './icon.component';
describe('IconComponent', () => {
let component: IconComponent;
let fixture: ComponentFixture<IconComponent>;
beforeEach(() => {
void TestBed.configureTestingModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [IconComponent],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(IconComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,27 @@
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
import { IconSize } from '@msfa-enums/icon-size.enum';
import { IconType } from '@msfa-enums/icon-type.enum';
const CUSTOM_ICONS: IconType[] = [IconType.HOME, IconType.SETTINGS, IconType.PLUS, IconType.CLIPBOARD];
@Component({
selector: 'msfa-icon',
templateUrl: './icon.component.html',
styleUrls: ['./icon.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class IconComponent implements OnChanges {
@Input() icon: IconType;
@Input() size: IconSize = IconSize.M;
iconClass: string;
iconType = IconType;
ngOnChanges(): void {
this.iconClass = `icon--${this.size}`;
}
get isCustomIcon(): boolean {
return CUSTOM_ICONS.includes(this.icon);
}
}

View File

@@ -0,0 +1,11 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { IconComponent } from './icon.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [IconComponent],
imports: [CommonModule],
exports: [IconComponent],
})
export class IconModule {}

View File

@@ -0,0 +1,7 @@
<footer class="footer">
<div class="footer__logo-wrapper">
<a class="footer__logo-link" href="/">
<img class="footer__logo" src="/assets/logo/arbetsformedlingen-light.svg" alt="Arbetsförmedlingen" />
</a>
</div>
</footer>

View File

@@ -0,0 +1,15 @@
.footer {
background-color: var(--digi--ui--color--background--profile);
padding: var(--digi--layout--gutter);
&__logo-wrapper {
height: 100%;
display: flex;
align-items: center;
}
&__logo {
height: 2rem;
vertical-align: middle;
}
}

View File

@@ -0,0 +1,28 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { FooterComponent } from './footer.component';
describe('FooterComponent', () => {
let component: FooterComponent;
let fixture: ComponentFixture<FooterComponent>;
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [FooterComponent],
imports: [RouterTestingModule, IconModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(FooterComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,9 @@
import { ChangeDetectionStrategy, Component } from '@angular/core';
@Component({
selector: 'msfa-footer',
templateUrl: './footer.component.html',
styleUrls: ['./footer.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FooterComponent {}

View File

@@ -0,0 +1,11 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { FooterComponent } from './footer.component';
@NgModule({
declarations: [FooterComponent],
imports: [CommonModule, RouterModule],
exports: [FooterComponent],
})
export class FooterModule {}

View File

@@ -0,0 +1,7 @@
@media print {
.footer {
border-bottom-width: 0;
padding: var(--digi--layout--gutter) 0;
display: flex;
}
}

View File

@@ -0,0 +1,21 @@
<div class="navigation">
<div class="navigation__logo-wrapper">
<a [routerLink]="['/']" aria-label="Till startsidan för FA Mina sidor">
<digi-logo af-system-name="Mina sidor för fristående aktörer" af-color="secondary"></digi-logo>
</a>
</div>
<ul class="navigation__list msfa__hide-on-print">
<li *ngIf="user" class="navigation__item">
<div class="navigation__user">
<msfa-icon [icon]="iconType.USER" size="l"></msfa-icon>
<span class="navigation__text">{{ user.fullName }}</span>
</div>
</li>
<li class="navigation__item">
<a routerLink="/" class="navigation__link">
<msfa-icon [icon]="iconType.BELL" size="l"></msfa-icon>
<span class="navigation__text">Notiser</span>
</a>
</li>
</ul>
</div>

View File

@@ -0,0 +1,67 @@
@import 'mixins/list';
@import 'variables/breakpoints';
@import 'variables/colors';
@import 'variables/gutters';
@import 'variables/navigation';
.navigation {
background-color: var(--digi--ui--color--background--profile);
border-bottom: 1px solid var(--digi--ui--color--background--off);
position: relative;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 var(--digi--layout--gutter);
height: $msfa__navigation-height;
@media (min-width: $digi--layout--breakpoint--m) {
height: $msfa__navigation-height-large;
}
&__logo-wrapper {
height: 100%;
display: flex;
align-items: center;
}
&__logo {
height: $msfa__navigation-height / 2.5;
vertical-align: middle;
@media (min-width: $digi--layout--breakpoint--m) {
height: $msfa__navigation-height-large / 2.5;
}
}
&__list {
@include msfa__reset-list;
display: flex;
height: 100%;
gap: $digi--layout--gutter--l;
color: var(--digi--typography--color--text--light);
margin-right: var(--digi--layout--gutter);
}
&__item {
display: flex;
}
&__user,
&__link {
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
padding: var(--digi--layout--gutter--s) var(--digi--layout--gutter);
}
&__link {
text-decoration: none;
color: var(--digi--typography--color--text--light);
}
&__text {
font-size: 0.75rem;
margin-top: var(--digi--layout--gutter--s);
}
}

View File

@@ -0,0 +1,28 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { NavigationComponent } from './navigation.component';
describe('NavigationComponent', () => {
let component: NavigationComponent;
let fixture: ComponentFixture<NavigationComponent>;
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [NavigationComponent],
imports: [RouterTestingModule, IconModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(NavigationComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { IconType } from '@msfa-enums/icon-type.enum';
import { User } from '@msfa-models/user.model';
@Component({
selector: 'msfa-navigation',
templateUrl: './navigation.component.html',
styleUrls: ['./navigation.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NavigationComponent {
@Input() user: User;
iconType = IconType;
}

View File

@@ -0,0 +1,13 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { NavigationComponent } from './navigation.component';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
declarations: [NavigationComponent],
imports: [CommonModule, RouterModule, IconModule],
exports: [NavigationComponent],
})
export class NavigationModule {}

View File

@@ -0,0 +1,7 @@
@media print {
.navigation {
border-bottom-width: 0;
padding: var(--digi--layout--gutter) 0;
display: flex;
}
}

View File

@@ -0,0 +1,45 @@
<nav class="sidebar" aria-label="Sidofält">
<ul class="sidebar__list">
<li class="sidebar__item">
<a
[routerLink]="['/']"
[routerLinkActive]="['sidebar__link--active']"
[routerLinkActiveOptions]="{ exact: true }"
class="sidebar__link"
>
<msfa-icon class="sidebar__icon" [icon]="iconType.HOME" size="xl"></msfa-icon>
Hem
</a>
</li>
<li class="sidebar__item">
<a [routerLink]="['/avrop']" [routerLinkActive]="['sidebar__link--active']" class="sidebar__link">
<msfa-icon class="sidebar__icon" [icon]="iconType.CLIPBOARD" size="xl"></msfa-icon>
Avrop
</a>
</li>
<li class="sidebar__item">
<a [routerLink]="['/deltagare']" [routerLinkActive]="['sidebar__link--active']" class="sidebar__link">
<msfa-icon class="sidebar__icon" [icon]="iconType.SOK_KANDIDAT" size="xl"></msfa-icon>
Mina deltagare
</a>
</li>
<li class="sidebar__item">
<a [routerLink]="['/planera-aktiviteter']" [routerLinkActive]="['sidebar__link--active']" class="sidebar__link">
<msfa-icon class="sidebar__icon" [icon]="iconType.USERS" size="xxl"></msfa-icon>
Planera aktiviteter
</a>
</li>
<li class="sidebar__item">
<a [routerLink]="['/rapportera']" [routerLinkActive]="['sidebar__link--active']" class="sidebar__link">
<msfa-icon class="sidebar__icon" [icon]="iconType.EDIT" size="xl"></msfa-icon>
Rapportera
</a>
</li>
<li class="sidebar__item">
<a [routerLink]="['/administration']" [routerLinkActive]="['sidebar__link--active']" class="sidebar__link">
<msfa-icon class="sidebar__icon" [icon]="iconType.SETTINGS" size="xl"></msfa-icon>
Administration
</a>
</li>
</ul>
</nav>

View File

@@ -0,0 +1,53 @@
@import 'variables/colors';
@import 'variables/gutters';
@import 'variables/navigation';
@import 'mixins/list';
.sidebar {
display: flex;
flex-direction: column;
position: sticky;
top: $msfa__navigation-height-large;
&__list {
@include msfa__reset-list;
}
&__link {
position: relative;
display: flex;
flex-direction: column;
gap: var(--digi--layout--gutter--xs);
align-items: center;
width: 100%;
padding: $digi--layout--gutter--m var(--digi--layout--gutter);
border-bottom: 1px solid var(--digi--ui--color--background--off);
color: var(--digi--typography--color--text) !important;
text-decoration: none;
font-weight: var(--digi--typography--font-weight);
font-size: var(--digi--typography--font-size--s);
text-align: center;
&:hover {
background-color: var(--digi--ui--color--background--tertiary);
}
&--active {
background-color: var(--digi--ui--color--background--tertiary);
&::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
width: 5px;
background-color: var(--digi--ui--color--primary);
}
}
}
&__icon {
color: var(--digi--typography--color--link);
}
}

View File

@@ -0,0 +1,26 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { SidebarComponent } from './sidebar.component';
describe('SidebarComponent', () => {
let component: SidebarComponent;
let fixture: ComponentFixture<SidebarComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [SidebarComponent],
imports: [RouterTestingModule, IconModule],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SidebarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

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

View File

@@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { IconModule } from '@msfa-shared/components/icon/icon.module';
import { SidebarComponent } from './sidebar.component';
@NgModule({
declarations: [SidebarComponent],
imports: [CommonModule, RouterModule, IconModule],
exports: [SidebarComponent],
})
export class SidebarModule {}

View File

@@ -0,0 +1 @@
<a [attr.href]="skipLinkPath" class="skip-to-content">Gå till sidans innehåll</a>

View File

@@ -0,0 +1,29 @@
@import 'variables/colors';
.skip-to-content {
position: absolute;
top: -1000px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
overflow: hidden;
z-index: 1000;
text-align: center;
padding: var(--digi--layout--gutter--xs) var(--digi--layout--gutter);
display: block;
background-color: var(--digi--ui--color--complementary-alt);
color: var(--digi--typography--color--text--light);
&:focus {
position: fixed;
top: 0;
width: auto;
height: auto;
text-decoration: none;
}
&:hover {
text-decoration: underline;
}
}

View File

@@ -0,0 +1,27 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { SkipToContentComponent } from './skip-to-content.component';
describe('SkipToContentComponent', () => {
let component: SkipToContentComponent;
let fixture: ComponentFixture<SkipToContentComponent>;
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [SkipToContentComponent],
imports: [RouterTestingModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(SkipToContentComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,29 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input } from '@angular/core';
import { NavigationEnd, Router } from '@angular/router';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { filter } from 'rxjs/operators';
@Component({
selector: 'msfa-skip-to-content',
templateUrl: './skip-to-content.component.html',
styleUrls: ['./skip-to-content.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SkipToContentComponent extends UnsubscribeDirective {
@Input() mainContentId: string;
skipLinkPath: string;
constructor(private router: Router, private changeDetectorRef: ChangeDetectorRef) {
super();
super.unsubscribeOnDestroy(
this.router.events.pipe(filter((event: NavigationEnd) => event instanceof NavigationEnd)).subscribe(({ url }) => {
const mainContentId = `#${this.mainContentId}`;
// Check if the current URL already includes the mainContentId.
const existsInUrl: boolean = url.substring(url.length - mainContentId.length, url.length) === mainContentId;
this.skipLinkPath = existsInUrl ? url : `${url}${mainContentId}`;
this.changeDetectorRef.markForCheck();
})
);
}
}

View File

@@ -0,0 +1,10 @@
import { CommonModule } from '@angular/common';
import { NgModule } from '@angular/core';
import { SkipToContentComponent } from './skip-to-content.component';
@NgModule({
declarations: [SkipToContentComponent],
imports: [CommonModule],
exports: [SkipToContentComponent],
})
export class SkipToContentModule {}

View File

@@ -0,0 +1,19 @@
<div class="msfa" *ngIf="isLoggedIn$ | async">
<msfa-skip-to-content mainContentId="msfa-main-content"></msfa-skip-to-content>
<header class="msfa__header">
<msfa-navigation [user]="user$ | async"></msfa-navigation>
</header>
<msfa-sidebar class="msfa__sidebar"></msfa-sidebar>
<main id="msfa-main-content" class="msfa__content">
<digi-ng-navigation-breadcrumbs
class="msfa__breadcrumbs"
[afItems]="breadcrumbsItems"
></digi-ng-navigation-breadcrumbs>
<ng-content></ng-content>
</main>
<msfa-footer class="msfa__footer"></msfa-footer>
</div>

View File

@@ -0,0 +1,48 @@
@import 'variables/navigation';
@import 'variables/breakpoints';
@import 'variables/gutters';
.msfa {
display: grid;
height: 100vh;
grid-template-columns: 12rem 1fr;
grid-template-rows: auto 1fr auto;
grid-template-areas:
'header header'
'sidebar content'
'footer footer';
// @media (min-width: $digi--layout--breakpoint--m) {
// grid-template-rows: $msfa__navigation-height-large 1fr auto;
// }
&__header {
grid-area: header;
position: sticky;
top: 0;
z-index: 1;
}
&__sidebar {
grid-area: sidebar;
background-color: var(--digi--ui--color--background--secondary);
border-right: 1px solid var(--digi--ui--color--background--off);
}
&__content {
grid-area: content;
max-width: $digi--layout--breakpoint--l;
padding: var(--digi--layout--gutter) $digi--layout--gutter--l $digi--layout--gutter--xxl;
}
&__breadcrumbs {
display: block;
margin-bottom: var(--digi--layout--gutter);
}
&__footer {
grid-area: footer;
background-color: var(--digi--ui--color--primary);
min-height: 10rem;
}
}

View File

@@ -0,0 +1,28 @@
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { LayoutComponent } from './Layout.component';
describe('LayoutComponent', () => {
let component: LayoutComponent;
let fixture: ComponentFixture<LayoutComponent>;
beforeEach(
waitForAsync(() => {
void TestBed.configureTestingModule({
declarations: [LayoutComponent],
imports: [RouterTestingModule, HttpClientTestingModule],
}).compileComponents();
})
);
beforeEach(() => {
fixture = TestBed.createComponent(LayoutComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -0,0 +1,61 @@
import { NavigationBreadcrumbsItem } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { ChangeDetectionStrategy, Component } from '@angular/core';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { UnsubscribeDirective } from '@msfa-directives/unsubscribe.directive';
import { User } from '@msfa-models/user.model';
import { AuthenticationService } from '@msfa-services/api/authentication.service';
import { UserService } from '@msfa-services/api/user.service';
import { mapPathsToBreadcrumbs } from '@msfa-utils/map-paths-to-breadcrumbs.util';
import { BehaviorSubject, Observable } from 'rxjs';
import { filter, switchMap } from 'rxjs/operators';
@Component({
selector: 'msfa-layout',
templateUrl: './layout.component.html',
styleUrls: ['./layout.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class LayoutComponent extends UnsubscribeDirective {
private startBreadcrumb: NavigationBreadcrumbsItem = {
text: 'Start',
routerLink: '/',
};
private _breadcrumbsItems$ = new BehaviorSubject<NavigationBreadcrumbsItem[]>([this.startBreadcrumb]);
isLoggedIn$: Observable<boolean> = this.authService.isLoggedIn$;
user$: Observable<User> = this.isLoggedIn$.pipe(
filter(loggedIn => !!loggedIn),
switchMap(() => this.userService.user$)
);
get breadcrumbsItems(): NavigationBreadcrumbsItem[] {
return this._breadcrumbsItems$.getValue();
}
constructor(
private router: Router,
private activatedRoute: ActivatedRoute,
private authService: AuthenticationService,
private userService: UserService
) {
super();
super.unsubscribeOnDestroy(
this.router.events.pipe(filter(event => event instanceof NavigationEnd)).subscribe(() => {
const urlTree = this.router.parseUrl(this.router.url);
if (urlTree.queryParams.code) {
void this.router.navigate([], {
relativeTo: this.activatedRoute,
queryParams: { code: null },
queryParamsHandling: 'merge',
replaceUrl: true,
});
}
urlTree.queryParams = {};
const paths = urlTree
.toString()
.split('/')
.filter(path => !!path);
this._breadcrumbsItems$.next(mapPathsToBreadcrumbs(paths, this.startBreadcrumb));
})
);
}
}

View File

@@ -0,0 +1,27 @@
import { DigiNgNavigationBreadcrumbsModule } from '@af/digi-ng/_navigation/navigation-breadcrumbs';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { MarkdownModule } from 'ngx-markdown';
import { FooterModule } from './components/footer/footer.module';
import { NavigationModule } from './components/navigation/navigation.module';
import { SidebarModule } from './components/sidebar/sidebar.module';
import { SkipToContentModule } from './components/skip-to-content/skip-to-content.module';
import { LayoutComponent } from './layout.component';
@NgModule({
declarations: [LayoutComponent],
imports: [
CommonModule,
RouterModule,
SkipToContentModule,
NavigationModule,
SidebarModule,
FooterModule,
MarkdownModule.forRoot({ loader: HttpClient }),
DigiNgNavigationBreadcrumbsModule,
],
exports: [LayoutComponent],
})
export class LayoutModule {}

View File

@@ -0,0 +1,17 @@
export const DRIVERS_LICENSES = [
'AM',
'A1',
'A2',
'A',
'B',
'BE',
'B96',
'C1',
'C',
'C1E',
'CE',
'D1',
'D',
'D1E',
'DE',
];

View File

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

View File

@@ -0,0 +1,15 @@
import { Directive, OnDestroy } from '@angular/core';
import { Subscription } from 'rxjs';
@Directive()
export class UnsubscribeDirective implements OnDestroy {
private subscriptions: Subscription[] = [];
ngOnDestroy(): void {
this.subscriptions.forEach(sub => sub.unsubscribe());
}
unsubscribeOnDestroy(...sub: Subscription[]): void {
this.subscriptions.push(...sub);
}
}

View File

@@ -0,0 +1,16 @@
export enum AddressType {
OFFICIAL = 'Folkbokföringsadress',
POSTAL = 'Postadress',
UNSPECIFIED = 'Ospecificerad adress',
}
export function getAddressType(addressType: 'FBF' | 'EgenAngiven'): AddressType {
switch (addressType) {
case 'FBF':
return AddressType.OFFICIAL;
case 'EgenAngiven':
return AddressType.POSTAL;
default:
return AddressType.UNSPECIFIED;
}
}

View File

@@ -0,0 +1,6 @@
export enum Authorization {
UserManagement = 'UserManagement',
Economy = 'Economy',
Reports = 'Reports',
ParticipantManagement = 'ParticipantManagement',
}

View File

@@ -0,0 +1,5 @@
export enum ErrorSeverity {
HIGH = 'High',
MEDIUM = 'Medium',
LOW = 'Low'
}

View File

@@ -0,0 +1,6 @@
export enum ErrorType {
API = 'API Error',
NETWORK = 'Network Error',
APP = 'Application Error',
UNKNOWN = 'Unknown Error'
}

View File

@@ -0,0 +1,7 @@
export enum IconSize {
S = 's',
M = 'm',
L = 'l',
XL = 'xl',
XXL = 'xxl',
}

View File

@@ -0,0 +1,21 @@
export enum IconType {
HOME = 'home', // Custom
SETTINGS = 'settings', // Custom
PLUS = 'plus', // Custom
CLIPBOARD = 'clipboard', // Custom
USER = 'user',
USERS = 'users',
BELL = 'bell',
CALENDAR = 'calendar',
ENVELOPE = 'envelope',
SOK_KANDIDAT = 'sok-kandidat',
EDIT = 'edit',
INFO = 'info',
WARNING = 'warning',
APPROVED = 'approved',
X = 'x',
ARROW_LEFT = 'arrow-left',
ARROW_RIGHT = 'arrow-right',
EYE = 'eye',
EYESLASH = 'eyeslash',
}

View File

@@ -0,0 +1,4 @@
export enum KeyboardCodes {
ENTER = 'Enter',
ESCAPE = 'Escape',
}

View File

@@ -0,0 +1,4 @@
export enum ParticipantStatus {
ACTIVE = 'active',
FOLLOW_UP = 'follow-up',
}

View File

@@ -0,0 +1,17 @@
export enum PhoneNumberType {
CELL = 'Mobil',
DOMESTIC = 'Hem',
WORK = 'Arbete',
UNKNOWN = 'Okänd',
}
export function getPhoneNumberType(phoneNumberType: 'Mobil' | 'Bostad'): PhoneNumberType {
switch (phoneNumberType) {
case 'Mobil':
return PhoneNumberType.CELL;
case 'Bostad':
return PhoneNumberType.DOMESTIC;
default:
return PhoneNumberType.UNKNOWN;
}
}

View File

@@ -0,0 +1,5 @@
export enum Service {
KVL = 'KVL',
KROM = 'KROM',
STOM = 'STOM',
}

View File

@@ -0,0 +1,4 @@
export enum SortOrder {
ASC = 'asc',
DESC = 'desc',
}

View File

@@ -0,0 +1,11 @@
export enum ToastPosition {
TOP_RIGHT = 'top-right',
TOP_CENTER = 'top-center',
TOP_LEFT = 'top-left',
BOTTOM_RIGHT = 'bottom-right',
BOTTOM_CENTER = 'bottom-center',
BOTTOM_LEFT = 'bottom-left',
CENTER_RIGHT = 'center-right',
CENTER_CENTER = 'center-center',
CENTER_LEFT = 'center-left',
}

View File

@@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { ActivatedRouteSnapshot, CanActivate, Router } from '@angular/router';
import { environment } from '@msfa-environment';
import { AuthenticationService } from '@msfa-services/api/authentication.service';
import { Observable, of } from 'rxjs';
import { map, switchMap } from 'rxjs/operators';
@Injectable()
export class AuthGuard implements CanActivate {
constructor(private authenticationService: AuthenticationService, private router: Router) {}
canActivate(route: ActivatedRouteSnapshot): Observable<boolean> {
return this.authenticationService.isLoggedIn$.pipe(
switchMap(loggedIn => {
if (loggedIn) {
return of(true);
} else if (route.queryParams.code) {
return this.authenticationService.login$(route.queryParams.code).pipe(map(result => !!result));
}
if (environment.environment === 'local') {
void this.router.navigateByUrl(environment.loginUrl);
} else {
document.location.href = `${environment.loginUrl}&client_id=${environment.clientId}&redirect_uri=${window.location.origin}`;
}
return of(false);
})
);
}
}

View File

@@ -0,0 +1,14 @@
import { ErrorHandler, Injectable } from '@angular/core';
import { CustomError, errorToCustomError } from '@msfa-models/error/custom-error';
import { ErrorService } from '@msfa-services/error.service';
@Injectable()
export class CustomErrorHandler implements ErrorHandler {
constructor(private errorService: ErrorService) {}
handleError(error: Error & { ngDebugContext: unknown }): void {
const customError: CustomError = errorToCustomError(error);
console.error(error);
this.errorService.add(customError);
}
}

View File

@@ -0,0 +1,20 @@
import { AddressType, getAddressType } from '@msfa-enums/address-type.enum';
import { AddressResponse } from './api/address.response.model';
export interface Address {
type: AddressType;
street: string;
postalCode: string;
city: string;
country: string;
}
export function mapResponseToAddress(data: AddressResponse): Address {
return {
type: getAddressType(data.adresstyp),
street: data.gatuadress,
postalCode: data.postnummer,
city: data.postort,
country: data.land,
};
}

View File

@@ -0,0 +1,7 @@
export interface AddressResponse {
adresstyp: 'FBF' | 'EgenAngiven';
gatuadress: string;
postnummer: string;
postort: string;
land: string;
}

View File

@@ -0,0 +1,8 @@
export interface AuthenticationResponse {
id: string;
access_token: string;
scope: string;
id_token: string;
token_type: string;
expires_in: number;
}

View File

@@ -0,0 +1,30 @@
export interface AvropResponse {
id: string;
deltagare: string;
genomforandeReferens: number;
orgId: string;
leverantorId: number;
organisationsnummer: string;
utforandeVerksamhetId: number;
utforandeverksamhet: string;
kommunKod: string;
kommun: string;
utforandeAdressId: number;
adress: string;
ordernummer: string;
bokningsId: number;
personnummer: string;
sokandeId: number;
tjanstekod: string;
tjansteNamn: string;
deltagandeGrad: number;
startdatumAvrop: string;
slutdatumAvrop: string;
aktnummerDiariet: string;
tolkbehov: string;
sprakstod: string;
sparkod: string;
sparNamn: string;
supervisorId: number;
recievedTimestamp: string;
}

View File

@@ -0,0 +1,11 @@
import { AddressResponse } from './address.response.model';
import { PhoneNumberResponse } from './phonenumber.response.model';
export interface ContactInformationResponse {
fornamn: string;
efternamn: string;
personnummer: string;
epost: string;
telekomadresser: PhoneNumberResponse[];
adresser: AddressResponse[];
}

View File

@@ -0,0 +1,28 @@
import { AvropResponse } from './avrop.response.model';
import { ContactInformationResponse } from './contact-information.response.model';
import { DisabilitiesResponse } from './disabilities.response.model';
import { DriversLicenseResponse } from './drivers-license.response.model';
import { EducationsResponse } from './educations.response.model';
import { HighestEducationResponse } from './highest-education.response.model';
import { TranslatorResponse } from './translator.response.model';
import { WorkExperiencesResponse } from './work-experiences.response.model';
import { WorkLanguagesResponse } from './work-languages.response.model';
export interface DeltagareCompactResponse {
sokandeId: string;
deltagare: string;
kommun: string;
}
export interface DeltagareResponse {
id: string;
contact: ContactInformationResponse;
driverlicense: DriversLicenseResponse;
highestEducation: HighestEducationResponse;
educations: EducationsResponse;
translator: TranslatorResponse;
workLanguages: WorkLanguagesResponse;
disabilities: DisabilitiesResponse;
workExperiences: WorkExperiencesResponse;
avropInformation: AvropResponse;
}

View File

@@ -0,0 +1,5 @@
import { DisabilityResponse } from './disability.response.model';
export interface DisabilitiesResponse {
funktionsnedsattningar: DisabilityResponse[];
}

View File

@@ -0,0 +1,7 @@
export interface DisabilityResponse {
kod: string;
funktionsnedsattning: string;
beskrivning: string;
utgatt: boolean;
}

View File

@@ -0,0 +1,4 @@
export interface DriversLicenseResponse {
korkort: { behorighet: string };
tillgang_till_bil: boolean;
}

View File

@@ -0,0 +1,5 @@
export interface EducationLevelResponse {
utbildningsniva: string;
beskrivning: string;
taxonomy_id: number;
}

View File

@@ -0,0 +1,7 @@
export interface EducationResponse {
utbildning: string;
beskrivning: string;
anordnare: string;
period_from: string;
period_tom: string;
}

View File

@@ -0,0 +1,5 @@
import { EducationResponse } from './education.response.model';
export interface EducationsResponse {
utbildningar: EducationResponse[];
}

View File

@@ -0,0 +1,4 @@
export interface EmployeeInviteMockApiResponse {
id: number,
createdAt: number
}

View File

@@ -0,0 +1,4 @@
export interface HandledareResponse {
id: number;
name: string
}

View File

@@ -0,0 +1,6 @@
export interface HighestEducationResponse {
utbildningsniva: string;
beskrivning_utbildningsniva: string;
sun_kod: string;
beskrivning_sun_kod: string;
}

View File

@@ -0,0 +1,4 @@
export interface KommunResponse {
kommunCode: number;
kommun: string;
}

View File

@@ -0,0 +1,3 @@
export interface LanguageResponse {
beskrivning: string;
}

View File

@@ -0,0 +1,4 @@
export interface OrganizationResponse {
name: string;
organizationnumber: string;
}

View File

@@ -0,0 +1,5 @@
export interface PhoneNumberResponse {
landskod: number;
nummer_utan_inledande_nolla: number;
telekomtyp: 'Mobil' | 'Bostad';
}

View File

@@ -0,0 +1,4 @@
export interface SunKodResponse {
sun_kod: string;
beskrivning: string;
}

View File

@@ -0,0 +1,4 @@
export interface TjanstResponse {
code: string;
name: string;
}

View File

@@ -0,0 +1,5 @@
import { LanguageResponse } from './language.response.model';
export interface TranslatorResponse {
sprak: LanguageResponse;
}

View File

@@ -0,0 +1,6 @@
export interface UserInfoResponse {
id: string;
firstName: string;
lastName: string;
roles: string[];
}

View File

@@ -0,0 +1,4 @@
export interface UtforandeVerksamhetResponse {
id: number;
name: string;
}

View File

@@ -0,0 +1,7 @@
export interface WorkExperienceResponse {
yrke: string;
beskrivning: string;
arbetsgivare: string;
period_from: string;
period_tom: string;
}

View File

@@ -0,0 +1,5 @@
import { WorkExperienceResponse } from './work-experience.response.model';
export interface WorkExperiencesResponse {
arbetslivserfarenheter: WorkExperienceResponse[];
}

View File

@@ -0,0 +1,5 @@
import { LanguageResponse } from './language.response.model';
export interface WorkLanguagesResponse {
sprak: LanguageResponse[];
}

View File

@@ -0,0 +1,14 @@
import { AuthenticationResponse } from './api/authentication.response.model';
export interface Authentication {
idToken: string;
expiresIn: number;
}
export function mapAuthApiResponseToAuthenticationResult(data: AuthenticationResponse): Authentication {
const { id_token, expires_in } = data;
return {
idToken: id_token,
expiresIn: expires_in,
};
}

View File

@@ -0,0 +1,23 @@
import { Authorization as AuthorizationEnum } from '@msfa-enums/authorization.enum';
export interface Authorization {
id: string;
name: AuthorizationEnum;
}
export interface AuthorizationApiResponse {
data: AuthorizationApiResponseData[];
}
export interface AuthorizationApiResponseData {
id: string;
name: AuthorizationEnum;
}
export function mapAuthorizationApiResponseToAuthorization(data: AuthorizationApiResponseData): Authorization {
const { id, name } = data;
return {
id,
name,
};
}

View File

@@ -0,0 +1,57 @@
import { AvropResponse } from './api/avrop.response.model';
export interface AvropCompact {
id: string; // id
sokandeId: number; // sokandeId
fullName: string; // deltagare
tjanst: string; // tjansteNamn
startDate: Date; // startdatumAvrop
endDate: Date; // slutdatumAvrop
tolkbehov: string; // tolkbehov
sprakstod: string; // sprakstod
utforandeAdress: string; // adress
trackCode: string; // sparkod
trackName: string; // sparNamn
}
export interface Avrop extends AvropCompact {
genomforandeReferens: number; // genomforandeReferens
participationFrequency: number; // deltagandeGrad
utforandeVerksamhet: string; // utforandeverksamhet
}
export function mapAvropResponseToAvrop(data: AvropResponse): Avrop {
const {
id,
sokandeId,
deltagare,
tjansteNamn,
startdatumAvrop,
slutdatumAvrop,
tolkbehov,
sprakstod,
adress,
sparkod,
sparNamn,
genomforandeReferens,
deltagandeGrad,
utforandeverksamhet,
} = data;
return {
id,
sokandeId,
fullName: deltagare,
tjanst: tjansteNamn,
startDate: new Date(startdatumAvrop),
endDate: new Date(slutdatumAvrop),
tolkbehov: tolkbehov,
sprakstod: sprakstod,
utforandeAdress: adress,
trackCode: sparkod,
trackName: sparNamn,
genomforandeReferens,
participationFrequency: deltagandeGrad,
utforandeVerksamhet: utforandeverksamhet,
};
}

View File

@@ -0,0 +1,34 @@
import { Address, mapResponseToAddress } from './address.model';
import { ContactInformationResponse } from './api/contact-information.response.model';
import { mapResponseToPhoneNumber, PhoneNumber } from './phonenumber.model';
export interface ContactInformation {
firstName: string;
lastName: string;
fullName: string;
ssn: string;
email: string;
phoneNumbers: PhoneNumber[];
addresses: Address[];
}
export function mapResponseToContactInformation(contactInformation: ContactInformationResponse): ContactInformation {
return {
firstName: contactInformation.fornamn,
lastName: contactInformation.efternamn,
fullName: `${contactInformation.fornamn} ${contactInformation.efternamn}`,
ssn: `${contactInformation.personnummer.substring(
0,
contactInformation.personnummer.length - 4
)}-${contactInformation.personnummer.substring(contactInformation.personnummer.length - 4)}`,
email: contactInformation.epost,
phoneNumbers: contactInformation.telekomadresser
? contactInformation.telekomadresser.map(phoneNumber => {
return mapResponseToPhoneNumber(phoneNumber);
})
: [],
addresses: contactInformation.adresser
? contactInformation.adresser.map(address => mapResponseToAddress(address))
: null,
};
}

View File

@@ -0,0 +1,6 @@
export interface DateFormatOptions {
year?: 'short' | 'long' | 'numeric';
month?: 'short' | 'long' | 'numeric';
day?: 'short' | 'long' | 'numeric';
weekday?: 'short' | 'long' | 'numeric';
}

View File

@@ -0,0 +1,78 @@
import { Address } from './address.model';
import { AvropResponse } from './api/avrop.response.model';
import { DeltagareResponse } from './api/deltagare.response.model';
import { Avrop, mapAvropResponseToAvrop } from './avrop.model';
import { mapResponseToContactInformation } from './contact-information.model';
import { Disability, mapResponseToDisability } from './disability.model';
import { DriversLicense, mapResponseToDriversLicense } from './drivers-license.model';
import { Education, mapResponseToEducation } from './education.model';
import { HighestEducation, mapResponseToHighestEducation } from './highest-education.model';
import { PhoneNumber } from './phonenumber.model';
import { mapResponseToWorkExperience, WorkExperience } from './work-experience.model';
export interface DeltagareCompact {
id: string;
fullName: string;
utforandeVerksamhet: string;
utforandeAdress: string;
}
export interface Deltagare {
id: string;
fullName: string;
firstName: string;
lastName: string;
ssn: string;
email: string;
phoneNumbers: PhoneNumber[];
addresses: Address[];
driversLicense: DriversLicense;
educations: Education[];
highestEducation: HighestEducation;
translator: string;
disabilities: Disability[];
workLanguages: string[];
workExperiences: WorkExperience[];
avropInformation: Avrop;
}
export function mapResponseToDeltagareCompact(data: AvropResponse): DeltagareCompact {
const { sokandeId, deltagare, adress, utforandeverksamhet } = data;
return {
id: sokandeId.toString(),
fullName: deltagare,
utforandeVerksamhet: utforandeverksamhet,
utforandeAdress: adress,
};
}
export function mapResponseToDeltagare(data: DeltagareResponse): Deltagare {
const {
id,
contact,
driverlicense,
highestEducation,
educations,
translator,
workLanguages,
disabilities,
workExperiences,
avropInformation,
} = data;
return {
id,
...mapResponseToContactInformation(contact),
driversLicense: mapResponseToDriversLicense(driverlicense),
highestEducation: highestEducation && mapResponseToHighestEducation(highestEducation),
educations: educations && educations.utbildningar.map(education => mapResponseToEducation(education)),
translator: translator && translator.sprak.beskrivning,
workLanguages: workLanguages && workLanguages.sprak.map(language => language.beskrivning),
disabilities:
disabilities && disabilities.funktionsnedsattningar.map(disability => mapResponseToDisability(disability)),
workExperiences:
workExperiences &&
workExperiences.arbetslivserfarenheter.map(workExperience => mapResponseToWorkExperience(workExperience)),
avropInformation: avropInformation && mapAvropResponseToAvrop(avropInformation),
};
}

View File

@@ -0,0 +1,16 @@
import { DisabilityResponse } from './api/disability.response.model';
export interface Disability {
code: string;
title: string;
description: string;
}
export function mapResponseToDisability(data: DisabilityResponse): Disability {
const { kod, funktionsnedsattning, beskrivning } = data;
return {
code: kod,
title: funktionsnedsattning,
description: beskrivning,
};
}

View File

@@ -0,0 +1,29 @@
import { DRIVERS_LICENSES } from '@msfa-constants/drivers-licenses';
import { DriversLicenseResponse } from './api/drivers-license.response.model';
export interface DriversLicense {
licenses: string[];
accessToCar: boolean;
}
function mapLicensesAsStringToArray(licenses = ''): string[] {
const allLicenses = DRIVERS_LICENSES.sort((a, b) => b.length - a.length);
let search = licenses;
const found: string[] = [];
allLicenses.forEach(license => {
if (search.indexOf(license) >= 0) {
found.push(license);
search = search.replace(license, '');
}
});
return found;
}
export function mapResponseToDriversLicense(data: DriversLicenseResponse): DriversLicense {
return {
licenses: mapLicensesAsStringToArray(data.korkort.behorighet),
accessToCar: data.tillgang_till_bil,
};
}

View File

@@ -0,0 +1,22 @@
import { formatToDate } from '@msfa-utils/format-to-date.util';
import { EducationResponse } from './api/education.response.model';
export interface Education {
education: string;
description: string;
organizer: string;
dateFrom: Date;
dateTo: Date;
}
export function mapResponseToEducation(data: EducationResponse): Education {
const { utbildning, beskrivning, anordnare, period_from, period_tom } = data;
return {
education: utbildning,
description: beskrivning,
organizer: anordnare,
dateFrom: formatToDate(period_from),
dateTo: formatToDate(period_tom),
};
}

View File

@@ -0,0 +1,3 @@
export interface EmployeeInviteMockaData {
id: number
}

View File

@@ -0,0 +1,75 @@
import { Authorization } from '@msfa-enums/authorization.enum';
import { Organization } from './organization.model';
import { PaginationMeta } from './pagination-meta.model';
import { Service } from './service.model';
export interface Employee {
id: string;
firstName: string;
lastName: string;
fullName?: string;
ssn: string;
organizations: Organization[];
services: Service[];
authorizations: Authorization[];
createdAt?: number;
}
export interface EmployeesApiResponse {
data: Employee[];
meta: PaginationMeta;
}
export interface EmployeesData {
data: Employee[];
meta: PaginationMeta;
}
export interface EmployeeApiResponse {
data: Employee;
}
export interface EmployeeApiResponseData {
id: string;
firstName: string;
lastName: string;
ssn: string;
organizations: Organization[];
authorizations: Authorization[];
services: Service[];
}
export interface EmployeeApiRequestData {
firstName: string;
lastName: string;
ssn: string;
organizations: Organization[];
services: Service[];
authorizations: Authorization[];
}
export function mapEmployeeToEmployeeApiRequestData(data: Employee): EmployeeApiRequestData {
const { firstName, lastName, ssn, services, organizations, authorizations } = data;
return {
firstName,
lastName,
ssn,
services,
organizations,
authorizations,
};
}
export function mapEmployeeReponseToEmployee(data: EmployeeApiResponseData): Employee {
const { id, firstName, lastName, ssn, services, organizations, authorizations } = data;
return {
id,
firstName,
lastName,
fullName: `${firstName} ${lastName}`,
organizations,
authorizations,
services,
ssn,
};
}

View File

@@ -0,0 +1,10 @@
export interface Environment {
environment: 'api' | 'local' | 'prod';
clientId: string;
loginUrl: string;
production: boolean;
api: {
url: string;
headers: { [key: string]: string };
};
}

View File

@@ -0,0 +1,71 @@
import { ErrorSeverity } from '@msfa-enums/error-severity.enum';
import { ErrorType } from '@msfa-enums/error-type.enum';
export class CustomError implements Error {
id: string;
name: string;
message: string;
stack: string;
type: ErrorType;
severity: ErrorSeverity;
timestamp: Date;
error: Error;
removeAfter: number;
constructor(args: { error: Error; type?: ErrorType; message?: string; severity?: ErrorSeverity; stack?: string }) {
this.timestamp = new Date();
this.id = this.timestamp.getTime().toString();
this.type = this.name = args.type || ErrorType.UNKNOWN;
this.message = args.message || args.error.message;
this.severity = args.severity || ErrorSeverity.HIGH;
this.stack = args.stack || CustomError.getStack(args.error);
this.error = args.error;
this.removeAfter =
this.severity === ErrorSeverity.LOW ? 5000 : this.severity === ErrorSeverity.MEDIUM ? 10000 : 20000;
}
static getStack(error: Error | { error: Error }): string {
if (!error) {
return '';
}
if (typeof error === 'string') {
return error;
}
if ('stack' in error) {
return error.stack;
} else if ('error' in error) {
return this.getStack(error.error);
} else {
return error as never;
}
}
static getErrorType(error: Error | (Error & { type: ErrorType })): ErrorType {
if (typeof error === 'object' && 'type' in error) {
return error.type;
} else if (error.name === 'HttpErrorResponse') {
return ErrorType.API;
}
return ErrorType.UNKNOWN;
}
}
export function errorToCustomError(error: Error & { ngDebugContext: unknown }): CustomError {
const type = CustomError.getErrorType(error);
const message = error.message || error;
const severity = ErrorSeverity.HIGH;
// this is done to avoid circular references while running in debug mode
if ('ngDebugContext' in error) {
error.ngDebugContext = {};
}
return new CustomError({
error,
type,
severity,
message: message as string,
});
}

View File

@@ -0,0 +1,17 @@
import { HandledareResponse } from './api/handledare.response.model';
export interface Handledare {
id: number;
name: string;
}
export function mapHandledareResponseToHandledare(
data: HandledareResponse
): Handledare {
const { id, name } = data;
return {
id,
name
}
}

Some files were not shown because too many files have changed in this diff Show More