[HIMA-710] Implemented reminder popup that a remission process is started every 20 minutes or 40 inutes when user is idle

This commit is contained in:
Eraldo Hasanaj
2019-12-03 17:31:55 +01:00
parent a922912308
commit 64fef0a48e
17 changed files with 296 additions and 10 deletions

View File

@@ -43,6 +43,7 @@ import { GoodsInState } from './core/store/state/goods-in.state';
import { BranchProcessState } from './core/store/state/branch-process.state';
import { RemissionModule } from '@isa/remission';
import { RemissionState } from './core/store/state/remission.state';
import { NgIdleKeepaliveModule } from '@ng-idle/keepalive';
const states = [
AppState,
@@ -108,6 +109,7 @@ export function _feedServiceEndpointProviderFactory(conf: ConfigService) {
animationDuration: 300,
clockwise: true
}),
NgIdleKeepaliveModule.forRoot(),
RemissionModule.forRoot({
useMock: false,
endpoints: {

View File

@@ -18,4 +18,5 @@ export interface Remission {
shippingDocument?: ShippingDocument;
shippingDocumentCreated?: boolean;
searchedProduct?: RemissionProduct;
blockReminder?: boolean;
}

View File

@@ -6,7 +6,7 @@ import { LoadCountries } from '../store/actions/countries.actions';
import { AppState } from '../store/state/app.state';
import { LoadBranches, LoadUserBranch } from '../store/actions/branch.actions';
import { AppUserDataSync } from '../store/actions/app.actions';
import { Subject, of } from 'rxjs';
import { Subject, of, Observable, timer } from 'rxjs';
import { takeUntil, catchError, take, filter, pairwise } from 'rxjs/operators';
import { UserStateService } from './user-state.service';
import { Meta, MetaDefinition } from '@angular/platform-browser';
@@ -18,8 +18,11 @@ import { WindowRef } from './window-ref.service';
import { LoadVats } from '../store/actions/vat.actions';
import { LoadSuppliers } from '../store/actions/suppliers.actions';
import { RemissionSelectors } from '../store/selectors/remission.selectors';
import { Idle, DEFAULT_INTERRUPTSOURCES } from '@ng-idle/core';
declare let ga: Function;
export const NOT_IDLE_REMISSION_REMINDER_MINUTES = 20;
export const IDLE_REMISSION_REMINDER_MINUTES = 40;
@Injectable({
providedIn: 'root'
@@ -28,6 +31,8 @@ export class AppService implements OnDestroy {
destroy$ = new Subject();
loader$ = new Subject();
levingRemission$ = new Subject();
remissionReminder$ = new Subject();
timerClear$ = new Subject();
constructor(
private ssoService: SsoService,
private store: Store,
@@ -36,7 +41,8 @@ export class AppService implements OnDestroy {
private route: ActivatedRoute,
private http: HttpClient,
public router: Router,
private windowRef: WindowRef
private windowRef: WindowRef,
private idle: Idle
) {}
public appLoadInitialisations() {
@@ -63,6 +69,8 @@ export class AppService implements OnDestroy {
this.loadCountries();
this.loadVats();
this.loadSuppliers();
this.shouldRemindForRemissionAtAppStart();
this.registerIdleManager();
});
});
}
@@ -145,11 +153,76 @@ export class AppService implements OnDestroy {
const isRemissionCompleated = this.store.selectSnapshot(RemissionSelectors.getRemissionCompleatedStatus);
isRemissionCompleated.ifFalse(() => {
this.levingRemission$.next();
this.registerTimedRemissionReminder(NOT_IDLE_REMISSION_REMINDER_MINUTES);
});
});
(isCommingFromRemission && isGoingAwayFromRemission).ifFalse(() => {
this.timerClear$.next();
});
}
}
private shouldRemindForRemissionAtAppStart() {
this.store
.select(RemissionSelectors.shouldRemindForRemission)
.pipe(
filter(data => !isNullOrUndefined(data)),
take(1)
)
.subscribe(status => {
const isNotAtRemission = !this.router.url.includes('remission');
(status && isNotAtRemission).ifTrue(() => {
this.remissionReminder$.next();
this.registerTimedRemissionReminder(NOT_IDLE_REMISSION_REMINDER_MINUTES);
});
isNotAtRemission.ifFalse(() => {
this.timerClear$.next();
});
});
}
private registerTimedRemissionReminder(minutes: number) {
const emittingTime = 1000 * 60 * minutes;
const timer$: Observable<number> = timer(0, emittingTime);
timer$.pipe(takeUntil(this.timerClear$)).subscribe(interval => {
(interval > 0).ifTrue(() => {
this.remissionReminder$.next();
});
});
}
private registerIdleManager() {
this.idle.setIdle(60);
this.idle.setInterrupts(DEFAULT_INTERRUPTSOURCES);
this.idle.onIdleStart.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.timerClear$.next();
this.registerTimedRemissionReminder(IDLE_REMISSION_REMINDER_MINUTES);
});
this.idle.onIdleEnd.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.timerClear$.next();
this.returToActiveStateReminder();
});
this.idle.watch();
}
private returToActiveStateReminder() {
this.store
.select(RemissionSelectors.shouldRemindForRemission)
.pipe(
filter(data => !isNullOrUndefined(data)),
take(1)
)
.subscribe(status => {
const isNotAtRemission = !this.router.url.includes('remission');
(status && isNotAtRemission).ifTrue(() => {
this.registerTimedRemissionReminder(NOT_IDLE_REMISSION_REMINDER_MINUTES);
});
isNotAtRemission.ifFalse(() => {
this.timerClear$.next();
});
});
}
private sendNavigationEventsToGoogleAnalytics(url: string) {
ga('set', 'page', url);
ga('send', 'pageview');
@@ -157,6 +230,7 @@ export class AppService implements OnDestroy {
ngOnDestroy() {
this.destroy$.next();
this.timerClear$.next();
}
private testHttpCall() {

View File

@@ -17,6 +17,7 @@ export const SET_REMISSION_TARGET = '[REMISSION] Set target';
export const SET_REMISSION_PRODUCTS = '[REMISSION] Set products';
export const SET_SHIPPING_DOCUMENT = '[REMISSION] Set shipping document';
export const SET_REMISSION_SEARCHED_PRODUCT = '[REMISSION] Set remission searched product';
export const ACTIVATE_REMISSION_REMINDER = '[REMISSION] activate reminder';
export class SetRemissionCreated {
static readonly type = SET_REMISSION_CREATED;
@@ -95,3 +96,7 @@ export class SetRemissionSearchedProduct {
constructor(public product?: RemissionProduct) {}
}
export class ActivateRemissionReminder {
static readonly type = ACTIVATE_REMISSION_REMINDER;
}

View File

@@ -35,6 +35,12 @@ export class RemissionSelectors {
return remission.remissionProcessCompleted;
}
@Selector([RemissionState])
static shouldRemindForRemission(remissionState: RemissionStateModel) {
const remission = remissionState.remission;
return remission.remissionProcessCreated && !remission.remissionProcessCompleted && !remission.blockReminder;
}
@Selector([RemissionState])
static getRemissionShippingDocumentstatus(remissionState: RemissionStateModel) {
const remission = remissionState.remission;

View File

@@ -22,7 +22,7 @@ import { ReloadGoodsIn } from '../actions/goods-in.actions';
import { ReloadBranchProcess } from '../actions/branch-process.actions';
import { ReloadRemission } from '../actions/remission.actions';
export const SYNC_DATA_VERSION = 110;
export const SYNC_DATA_VERSION = 117;
export class AppStateModel {
currentProcesssId: number;

View File

@@ -122,6 +122,15 @@ export class RemissionState {
this.syncApiState(remission);
}
@Action(actions.ActivateRemissionReminder)
activateRemissionReminder(ctx: StateContext<RemissionStateModel>) {
const state = ctx.getState();
const currentRemission = state.remission;
const remission: Remission = { ...currentRemission, blockReminder: false };
ctx.patchState({ remission });
this.syncApiState(remission);
}
@Action(actions.SetRemissionShippingDocument)
setRemissionShippingDocument(ctx: StateContext<RemissionStateModel>, { shippingDocument }: actions.SetRemissionShippingDocument) {
const state = ctx.getState();
@@ -176,7 +185,8 @@ export class RemissionState {
centralTarget: Side.LEFT,
overflowTarget: Side.LEFT,
source: RemissionResourceType.Central,
shippingDocumentCreated: false
shippingDocumentCreated: false,
blockReminder: true
};
ctx.patchState({ remission });
this.syncApiState(remission);
@@ -192,7 +202,8 @@ export class RemissionState {
centralTarget: Side.LEFT,
overflowTarget: Side.LEFT,
source: RemissionResourceType.Central,
shippingDocumentCreated: false
shippingDocumentCreated: false,
blockReminder: true
};
ctx.patchState({ remission });
this.syncApiState(remission);
@@ -207,7 +218,8 @@ export class RemissionState {
centralTarget: Side.LEFT,
overflowTarget: Side.LEFT,
source: RemissionResourceType.Central,
shippingDocumentCreated: false
shippingDocumentCreated: false,
blockReminder: true
};
ctx.patchState({ remission });
this.syncApiState(remission);

View File

@@ -29,6 +29,7 @@ import { LogOutComponent } from '../components/log-out/log-out.component';
import { ErrorModule } from '../core/error/component/error.module';
import { DoubleChoiceSwitchModule } from '@libs/ui/lib/double-choice-switch';
import { RemissionLeaveDialogComponent } from './remission/components/remission-leave-dialog/remission-leave-dialog.component';
import { RemissionReminderDialogComponent } from './remission/components/remission-reminder-dialog/remission-reminder-dialog.component';
@NgModule({
declarations: [
@@ -39,7 +40,8 @@ import { RemissionLeaveDialogComponent } from './remission/components/remission-
MenuComponent,
BreadcrumbsComponent,
LogOutComponent,
RemissionLeaveDialogComponent
RemissionLeaveDialogComponent,
RemissionReminderDialogComponent
],
imports: [
CommonModule,
@@ -73,7 +75,8 @@ import { RemissionLeaveDialogComponent } from './remission/components/remission-
InfiniteScrollModule,
ProcessModule,
LogOutComponent,
RemissionLeaveDialogComponent
RemissionLeaveDialogComponent,
RemissionReminderDialogComponent
]
})
export class ComponentsModule {}

View File

@@ -0,0 +1,16 @@
<app-modal id="remission-reminder-modal" branch="true">
<div class="modal-wrapper">
<div class="header">
<lib-icon (click)="closeDialog()" height="21px" class="close-icon" name="close-branch" alt="close"></lib-icon>
</div>
<div class="title">
<span>Offene Remi Liste</span>
</div>
<div class="sub-title">
<span class="align-center">Sie haben eine offene Remi Liste. Kehren Sie zurück zur Remission, um die Remi Liste abzuschließen.</span>
</div>
<div class="actions">
<app-button (click)="toRemission()" primaryBorders="true" primary="true">Zur Remission</app-button>
</div>
</div>
</app-modal>

View File

@@ -0,0 +1,57 @@
.modal-wrapper {
font-family: 'Open Sans';
line-height: 21px;
margin: 16px 0 40px 0;
display: flex;
flex-direction: column;
height: 210px;
.header {
.close-icon {
position: absolute;
top: 25px;
right: 25px;
}
.close-icon:hover {
cursor: pointer;
}
}
.title {
display: flex;
flex-direction: column;
justify-content: center;
margin-top: 36px;
span {
font-size: 20px;
font-weight: bold;
color: #000000;
text-align: center;
}
}
.sub-title {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 26px;
span {
font-family: 'Open Sans';
font-size: 16px;
font-weight: 600;
max-width: 546px;
}
}
.actions {
display: flex;
flex-direction: row;
justify-content: center;
margin-top: 26px;
.close-button {
margin-left: 20px;
}
}
}

View File

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

View File

@@ -0,0 +1,53 @@
import { Component, OnInit } from '@angular/core';
import { ModalService } from '@libs/ui';
import { Router } from '@angular/router';
import { Store } from '@ngxs/store';
import { ModuleSwitcherService } from 'apps/sales/src/app/core/services/module-switcher.service';
import { ResetBreadcrumbsTo } from 'apps/sales/src/app/core/store/actions/breadcrumb.actions';
import { SetBranchProcessCurrentPath } from 'apps/sales/src/app/core/store/actions/branch-process.actions';
@Component({
selector: 'app-remission-reminder-dialog',
templateUrl: './remission-reminder-dialog.component.html',
styleUrls: ['./remission-reminder-dialog.component.scss']
})
export class RemissionReminderDialogComponent implements OnInit {
id = 'remission-reminder-modal';
constructor(
private modalService: ModalService,
private router: Router,
private store: Store,
private moduleSwitcher: ModuleSwitcherService
) {}
ngOnInit() {}
public openDialog() {
this.modalService.open(this.id);
}
closeDialog() {
this.modalService.close(this.id);
}
toRemission() {
this.closeDialog();
this.moduleSwitcher.switchToBranch();
this.navigateRemissionCreateList();
}
navigateRemissionCreateList() {
const path = '/remission/create';
this.store.dispatch(
new ResetBreadcrumbsTo(
{
path: path,
name: 'Remission'
},
'remission'
)
);
this.router.navigate([path]);
this.store.dispatch(new SetBranchProcessCurrentPath(path, true));
}
}

View File

@@ -26,7 +26,8 @@ import {
SetRemissionSource,
SetRemissionTarget,
SetRemissionStarted,
SetRemissionSearchedProduct
SetRemissionSearchedProduct,
ActivateRemissionReminder
} from 'apps/sales/src/app/core/store/actions/remission.actions';
import { RemissionHelperService } from '../../services/remission-helper.service';
import { UpdateFilter } from '../../models/update-filter.model';
@@ -261,7 +262,12 @@ export class RemissionListCreateComponent implements OnInit, OnDestroy {
(this.selectedRemissionResourceType === RemissionResourceType.Overflow).ifTrue(() =>
this.shouldShowOverflowInitialMessage(remissionProcess.filter)
);
this.store.dispatch(new SetRemissionProcess(remissionProcess));
this.store
.dispatch(new SetRemissionProcess(remissionProcess))
.toPromise()
.then(() => {
this.store.dispatch(new ActivateRemissionReminder());
});
};
shouldShowOverflowInitialMessage(remissionFilter: RemissionFilter) {

View File

@@ -8,3 +8,4 @@
</div>
<app-error></app-error>
<app-remission-leave-dialog></app-remission-leave-dialog>
<app-remission-reminder-dialog></app-remission-reminder-dialog>

View File

@@ -8,6 +8,8 @@ import { ModuleSwitcher } from '../../core/models/app-switcher.enum';
import { ModuleSwitcherService } from '../../core/services/module-switcher.service';
import { RemissionLeaveDialogComponent } from '../../modules/remission/components/remission-leave-dialog/remission-leave-dialog.component';
import { AppService } from '../../core/services/app.service';
// tslint:disable-next-line: max-line-length
import { RemissionReminderDialogComponent } from '../../modules/remission/components/remission-reminder-dialog/remission-reminder-dialog.component';
@Component({
selector: 'app-content',
@@ -17,6 +19,7 @@ import { AppService } from '../../core/services/app.service';
export class ContentPageComponent implements OnInit, OnDestroy {
@ViewChild(ErrorComponent) element: ErrorComponent;
@ViewChild(RemissionLeaveDialogComponent) remissionLeavingDialog: RemissionLeaveDialogComponent;
@ViewChild(RemissionReminderDialogComponent) remissionReminderDialog: RemissionReminderDialogComponent;
module: ModuleSwitcher = ModuleSwitcher.Customer;
destroy$ = new Subject();
constructor(
@@ -35,6 +38,10 @@ export class ContentPageComponent implements OnInit, OnDestroy {
this.appService.levingRemission$.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.remissionLeavingDialog.openDialog();
});
this.appService.remissionReminder$.pipe(takeUntil(this.destroy$)).subscribe(() => {
this.remissionReminderDialog.openDialog();
});
}
ngOnInit() {

16
package-lock.json generated
View File

@@ -1102,6 +1102,22 @@
"tslib": "^1.9.0"
}
},
"@ng-idle/core": {
"version": "8.0.0-beta.4",
"resolved": "https://registry.npmjs.org/@ng-idle/core/-/core-8.0.0-beta.4.tgz",
"integrity": "sha512-bI/tt7F5+z/w6b6SzNPc0+9rJ1knWgLzQweLd6xUQ7HHFrHsmBlJYUGiSv0cYUY+2RZAmIDbevwUDKLWmdE55w==",
"requires": {
"tslib": "^1.9.0"
}
},
"@ng-idle/keepalive": {
"version": "8.0.0-beta.4",
"resolved": "https://registry.npmjs.org/@ng-idle/keepalive/-/keepalive-8.0.0-beta.4.tgz",
"integrity": "sha512-yj7Y0pu2C7LhcjTcBE9DK5mFPFVHDj3vIVPTDzK3dN+51WoDQMrELl7BMuvzmTViat7mjsSP+00EwFvg6+FDEQ==",
"requires": {
"tslib": "^1.9.0"
}
},
"@ngtools/json-schema": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@ngtools/json-schema/-/json-schema-1.1.0.tgz",

View File

@@ -39,6 +39,8 @@
"@isa/print-api": "^0.0.25",
"@isa/remi-api": "^0.0.25",
"@isa/remission": "^0.1.15",
"@ng-idle/core": "^8.0.0-beta.4",
"@ng-idle/keepalive": "^8.0.0-beta.4",
"@ngxs/store": "^3.4.1",
"@types/faker": "^4.1.5",
"@zxing/ngx-scanner": "^1.3.0",