diff --git a/package-lock.json b/package-lock.json
index a966d78..1b9531a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2433,6 +2433,22 @@
}
}
},
+ "@progress/kendo-angular-upload": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/@progress/kendo-angular-upload/-/kendo-angular-upload-7.1.0.tgz",
+ "integrity": "sha512-oezUH2BWZguJ6YunBHs5J4SLGWcbt87RTWp6AHlwaopgSALsu5VXpptoLjcrkVXska+nkiyuLY9zpT4HBoGgfQ==",
+ "requires": {
+ "@progress/kendo-schematics": "^1.0.0",
+ "tslib": "^1.9.0"
+ },
+ "dependencies": {
+ "tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg=="
+ }
+ }
+ },
"@progress/kendo-charts": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@progress/kendo-charts/-/kendo-charts-1.17.1.tgz",
diff --git a/package.json b/package.json
index 188adc6..7216649 100644
--- a/package.json
+++ b/package.json
@@ -48,9 +48,10 @@
"@progress/kendo-angular-progressbar": "^2.0.0",
"@progress/kendo-angular-toolbar": "^4.0.0",
"@progress/kendo-angular-treeview": "^5.1.0",
+ "@progress/kendo-angular-upload": "^7.1.0",
"@progress/kendo-data-query": "^1.5.4",
"@progress/kendo-drawing": "^1.9.4",
- "@progress/kendo-licensing": "^1.1.3",
+ "@progress/kendo-licensing": "^1.0.2",
"@progress/kendo-svg-icons": "^0.1.2",
"@progress/kendo-theme-default": "latest",
"bootstrap": "^4.6.0",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 4db05bf..8ae82ad 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -70,6 +70,8 @@ import { BrokerProfileComponent } from './broker-profile/broker-profile.componen
import { ClientLoanListComponent } from './client-loan-list/client-loan-list.component';
import { ClientProfileComponent } from './client-profile/client-profile.component';
import { E403Component } from './e403/e403.component';
+import {FileSelectModule, UploadModule} from '@progress/kendo-angular-upload';
+
@@ -140,7 +142,9 @@ import { E403Component } from './e403/e403.component';
DateInputsModule,
DropDownsModule,
ExcelExportModule,
- EditorModule
+ EditorModule,
+ UploadModule,
+ FileSelectModule
],
providers: [
MenuService,
diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts
index d607086..ac0aa1a 100644
--- a/src/app/auth/auth.component.ts
+++ b/src/app/auth/auth.component.ts
@@ -4,7 +4,7 @@ import { Router } from '@angular/router';
import { NotificationService } from '@progress/kendo-angular-notification';
import { Subscription } from 'rxjs';
import { AuthService } from '../service/auth.service';
-import {apiV1LoginResponse} from '../models/api-v1-login-response';
+import {ApiV1LoginResponse} from '../models/api-v1-login-response';
@Component({
selector: 'app-auth',
@@ -42,7 +42,7 @@ export class AuthComponent implements OnInit, OnDestroy{
this.authService.login(this.userForm.value.email, this.userForm.value.password);
}
- public onLogin(rsp: apiV1LoginResponse): void {
+ public onLogin(rsp: ApiV1LoginResponse): void {
this.loading = false;
// console.log ('found login ' , rsp );
if (rsp.login) {
diff --git a/src/app/broker-profile/broker-profile.component.html b/src/app/broker-profile/broker-profile.component.html
index 777b41e..09dcd25 100644
--- a/src/app/broker-profile/broker-profile.component.html
+++ b/src/app/broker-profile/broker-profile.component.html
@@ -1 +1,139 @@
-
broker-profile works!
+
+
+
+
+
+
Edit : {{broker.First + ' ' + broker.Last}}
+
+
+
+
+
+
+
+ only jpg and png are allowed
+
+
+
+
+
+
+
+
Change Password
+
+
+
+
+
+
+
+
+
+
+ {{ Message }}
+
+
+
+
diff --git a/src/app/broker-profile/broker-profile.component.scss b/src/app/broker-profile/broker-profile.component.scss
index e69de29..7d990e4 100644
--- a/src/app/broker-profile/broker-profile.component.scss
+++ b/src/app/broker-profile/broker-profile.component.scss
@@ -0,0 +1,33 @@
+div.container {
+ max-width: 500px;
+}
+
+div.vertical-spacer {
+ height:1px;
+ margin-bottom: 30px;
+}
+
+
+.dropzoneInvisible{
+ opacity:0.1;
+}
+
+.avatar {
+ width: 100%;
+}
+
+.broker-photo {
+ display: inline-block;
+ width: 256px;
+ height: 256px;
+ border-radius: 50%;
+ background-size: 256px 256px;
+ background-position: center center;
+ vertical-align: middle;
+ line-height: 132px;
+ box-shadow: inset 0 0 1px #999, inset 0 0 10px rgba(0,0,0,.2);
+ margin-left: 5px;
+ margin-bottom: 10px;
+ background-repeat: no-repeat;
+ box-shadow: 1px 1px 10px black;
+}
diff --git a/src/app/broker-profile/broker-profile.component.ts b/src/app/broker-profile/broker-profile.component.ts
index 508e3ab..576c585 100644
--- a/src/app/broker-profile/broker-profile.component.ts
+++ b/src/app/broker-profile/broker-profile.component.ts
@@ -1,4 +1,13 @@
-import { Component, OnInit } from '@angular/core';
+import {Component, Input, OnInit, ViewChild} from '@angular/core';
+import {PeopleModel} from '../models/people.model';
+import {LoanSummaryService} from '../service/loan_summary.service';
+import {AuthService} from '../service/auth.service';
+import {FormControl, FormGroup, NgForm} from '@angular/forms';
+import {FileInfo, FileRestrictions, FileSelectComponent, SelectEvent} from '@progress/kendo-angular-upload';
+import {PeopleService} from '../service/people.service';
+import {BrokerModel} from '../models/broker.model';
+import {ConfirmedValidator} from '../validator/confirmed.validator';
+
@Component({
selector: 'app-broker-profile',
@@ -7,9 +16,94 @@ import { Component, OnInit } from '@angular/core';
})
export class BrokerProfileComponent implements OnInit {
- constructor() { }
+ @Input() public broker: BrokerModel = BrokerModel.EmptyNew();
+ @ViewChild('fileSelect', {static: true}) fs: FileSelectComponent;
+ public avatarUrl = 'url(https://svr2021.lawipac.com:8080/api/v1/avatar/1000)' ;
+ public myRestrictions: FileRestrictions = {
+ allowedExtensions: ['.jpg', '.png', '.jpeg'],
+ maxFileSize: 2194304
+ };
+
+ public opened = false; // dialog box
+ public Message = ''; // dialog message
+
+ public changePassword = false;
+ public ChangePassForm: FormGroup = new FormGroup({
+ OldPassword: new FormControl(),
+ NewPass: new FormControl(),
+ NewPass1: new FormControl(),
+ });
+
+ constructor( private auth: AuthService, private ps: PeopleService) { }
ngOnInit(): void {
+ this.broker = BrokerModel.getFromUserAndExtra(this.auth.loggedIn.user, this.auth.loggedIn.userExtra);
+ this.avatarUrl = 'url(' + this.auth.getUrl('avatar/' + this.broker.Id) + ')';
+ }
+
+ public save(brokerForm: NgForm): void{
+ if (! brokerForm.touched || brokerForm.form.pristine) {
+ return;
+ }
+ this.ps.saveBroker(this.broker).subscribe( () => {
+ this.showDialog('updated successfully ');
+ brokerForm.form.markAsPristine();
+ }, err => {
+ this.showDialog('Failed to Update: ' + err.toString());
+ });
+ }
+
+ public savePassword(): void{
+ this.ps.savePassword(this.broker.Id, this.ChangePassForm.value).subscribe(
+ () => {
+ this.showDialog('Password Changed');
+ }, err => {
+ this.showDialog('Failed to Change Password :' + err.toString());
+ }
+ );
}
+ public passEqual(): boolean{
+ const v = this.ChangePassForm.value;
+ if ( this.ChangePassForm.valid && v.NewPass === v.NewPass1 && this.ChangePassForm.touched) {
+ return true;
+ }else{
+ this.ChangePassForm.controls['NewPass1'].setErrors({'incorrect': true});
+ }
+ return false;
+ }
+
+ public onSelect(ev: SelectEvent): void {
+ if (ev.files) {
+ ev.files.every((file: FileInfo) => {
+ if (file.rawFile && !file.validationErrors) {
+ const reader = new FileReader();
+
+ reader.onloadend = () => {
+ const str = reader.result as string;
+ this.ps.updateAvatar(str, this.broker.Id).subscribe( resp => {
+ this.avatarUrl = 'url(' + str + ' )';
+ }, err => {
+ this.showDialog('Failed to Update Avatar: ' + err.toString());
+ });
+ this.fs.clearFiles();
+ };
+ reader.readAsDataURL(file.rawFile);
+ }else{
+ this.showDialog('Only jpg, and png are supported (max 2MB)');
+ setTimeout(() => { this.fs.clearFiles(); }, 10);
+ }
+ return false; // we only take first file
+ });
+ }
+ }
+
+ public showDialog(msg: string): void {
+ this.Message = msg;
+ this.opened = true; // open dialog
+ }
+
+ public close(status): void {
+ this.opened = false;
+ }
}
diff --git a/src/app/broker-reward/broker-reward.component.html b/src/app/broker-reward/broker-reward.component.html
index fee1874..d38a67b 100644
--- a/src/app/broker-reward/broker-reward.component.html
+++ b/src/app/broker-reward/broker-reward.component.html
@@ -10,10 +10,10 @@
{{ dataItem.Amount | currency }}
-
+
-
-
+
+
{{dataItem.Status}}
@@ -28,6 +28,6 @@
-
+
diff --git a/src/app/dashboard/dashboard.component.scss b/src/app/dashboard/dashboard.component.scss
index 1305331..4343ee4 100644
--- a/src/app/dashboard/dashboard.component.scss
+++ b/src/app/dashboard/dashboard.component.scss
@@ -14,4 +14,3 @@
/* background-color:chartreuse; */
}
-
diff --git a/src/app/models/api-v1-login-response.ts b/src/app/models/api-v1-login-response.ts
index c0b3bee..d3fc83d 100644
--- a/src/app/models/api-v1-login-response.ts
+++ b/src/app/models/api-v1-login-response.ts
@@ -2,8 +2,9 @@
// tslint:disable-next-line:class-name
import {PeopleModel} from './people.model';
+import {UserExtraModel} from './user-extra.model';
-export class apiV1LoginResponse {
+export class ApiV1LoginResponse {
constructor(
public login: boolean,
@@ -11,7 +12,8 @@ export class apiV1LoginResponse {
public session: string,
public sessionExpire: number, // unix timestamp
public role: string,
- public user: PeopleModel
+ public user: PeopleModel,
+ public userExtra?: UserExtraModel // extra user informaiton
) {
this.login = login;
this.machineId = machineId;
@@ -19,8 +21,8 @@ export class apiV1LoginResponse {
this.sessionExpire = sessionExpire;
}
- public static EmptyNew(): apiV1LoginResponse{
- return new apiV1LoginResponse(
+ public static EmptyNew(): ApiV1LoginResponse{
+ return new ApiV1LoginResponse(
false, '', '', 0, '', PeopleModel.EmptyNew() );
}
diff --git a/src/app/models/broker.model.ts b/src/app/models/broker.model.ts
index f6232dd..2058dc0 100644
--- a/src/app/models/broker.model.ts
+++ b/src/app/models/broker.model.ts
@@ -1,4 +1,5 @@
import {PeopleModel} from './people.model';
+import {UserExtraModel} from './user-extra.model';
export class BrokerModel{
constructor(
@@ -25,4 +26,10 @@ export class BrokerModel{
return new BrokerModel('', '', '', '', '', '', '',
'', false, '', '', '', '');
}
+
+ public static getFromUserAndExtra(u: PeopleModel, ex: UserExtraModel): BrokerModel {
+ return new BrokerModel(
+ u.Id, u.First, u.Last, u.Middle, u.Title, u.Display, u.Nick, ex.Login, ex.Enabled, ex.BSB, ex.ACC, ex.License, ex.Organization
+ );
+ }
}
diff --git a/src/app/models/change-password.model.ts b/src/app/models/change-password.model.ts
new file mode 100644
index 0000000..a753a4b
--- /dev/null
+++ b/src/app/models/change-password.model.ts
@@ -0,0 +1,8 @@
+
+
+
+export class ChangePassword {
+ public OldPassword: string;
+ public NewPass: string;
+ public NewPass1: string;
+}
diff --git a/src/app/models/user-extra.model.ts b/src/app/models/user-extra.model.ts
new file mode 100644
index 0000000..4e68539
--- /dev/null
+++ b/src/app/models/user-extra.model.ts
@@ -0,0 +1,15 @@
+
+export class UserExtraModel {
+ public BSB: string;
+ public ACC: string;
+ public License: string;
+ public Organization: string;
+ public Enabled: boolean;
+ public Login: string;
+
+ constructor(payload: Partial) {
+ Object.assign(this, payload);
+ }
+
+}
+
diff --git a/src/app/service/auth.service.ts b/src/app/service/auth.service.ts
index 46ed0c8..30e0909 100644
--- a/src/app/service/auth.service.ts
+++ b/src/app/service/auth.service.ts
@@ -1,8 +1,9 @@
import {EventEmitter, Injectable, OnDestroy, OnInit} from '@angular/core';
import {HttpClient, HttpEvent, HttpHandler, HttpInterceptor, HttpParams, HttpRequest} from '@angular/common/http';
-import {apiV1LoginResponse} from '../models/api-v1-login-response';
+import {ApiV1LoginResponse} from '../models/api-v1-login-response';
import {Router} from '@angular/router';
import {PeopleModel} from '../models/people.model';
+import {UserExtraModel} from '../models/user-extra.model';
@Injectable()
export class AuthService {
@@ -11,8 +12,8 @@ export class AuthService {
public apiWsUrl = 'wss://svr2021.lawipac.com:8080/api/v1/ws';
// public apiUrl = 'https://c5016.biukop.com.au:8080/api/v1/';
// public apiWsUrl = 'wss://c5016.biukop.com.au:8080/api/v1/ws';
- public loggedIn = apiV1LoginResponse.EmptyNew();
- loginSuccess = new EventEmitter ();
+ public loggedIn = ApiV1LoginResponse.EmptyNew();
+ loginSuccess = new EventEmitter ();
constructor( private http: HttpClient ,
private router: Router) {
@@ -20,21 +21,24 @@ export class AuthService {
public AutoLogin(): void {
- const sfm: apiV1LoginResponse = JSON.parse(localStorage.getItem('sfm'));
+ const sfm: ApiV1LoginResponse = JSON.parse(localStorage.getItem('sfm'));
if (!sfm) {
console.log('no auto login');
return;
}
- this.loggedIn = new apiV1LoginResponse(
+ this.loggedIn = new ApiV1LoginResponse(
sfm.login,
sfm.machineId,
sfm.session,
sfm.sessionExpire,
sfm.role,
- new PeopleModel(
- sfm.user.Id, sfm.user.First, sfm.user.Last, sfm.user.Middle, sfm.user.Title, sfm.user.Display, sfm.user.Nick
- )
+ new PeopleModel(sfm.user.Id, sfm.user.First, sfm.user.Last, sfm.user.Middle, sfm.user.Title, sfm.user.Display, sfm.user.Nick)
);
+
+ if ( sfm.userExtra !== undefined ) {
+ this.loggedIn.userExtra = new UserExtraModel(sfm.userExtra);
+ }
+
this.loginSuccess.emit(this.loggedIn);
// console.log ( 'auto login emit events', this.loggedIn);
}
@@ -55,7 +59,7 @@ export class AuthService {
login(email: string, password: string): void {
- this.http.post(this.apiUrl + 'login', {u: email, p: password}).subscribe(
+ this.http.post(this.apiUrl + 'login', {u: email, p: password}).subscribe(
responseData => {
this.loggedIn.session = responseData['Biukop-Session'];
this.loggedIn.login = responseData.login;
@@ -72,14 +76,19 @@ export class AuthService {
responseData.user.Display,
responseData.user.Nick
);
+
+ if (responseData.userExtra !== undefined ) {
+ this.loggedIn.userExtra = new UserExtraModel(responseData.userExtra);
+ }
}else{
this.loggedIn.user = PeopleModel.EmptyNew();
}
+
this.saveSessionInfo();
this.loginSuccess.emit(responseData);
},
error => {
- const fail = apiV1LoginResponse.EmptyNew();
+ const fail = ApiV1LoginResponse.EmptyNew();
this.loggedIn = fail;
console.log('login error', error);
this.loginSuccess.emit(this.loggedIn);
@@ -89,7 +98,7 @@ export class AuthService {
}
logout(): void {
- this.loggedIn = apiV1LoginResponse.EmptyNew();
+ this.loggedIn = ApiV1LoginResponse.EmptyNew();
localStorage.removeItem('sfm');
this.loginSuccess.emit(this.loggedIn);
this.router.navigate(['/login']).then(r => {
diff --git a/src/app/service/people.service.ts b/src/app/service/people.service.ts
index bb574bd..5884c16 100644
--- a/src/app/service/people.service.ts
+++ b/src/app/service/people.service.ts
@@ -5,6 +5,7 @@ import {Observable} from 'rxjs';
import {PeopleModel} from '../models/people.model';
import {BrokerModel} from '../models/broker.model';
import {LoanModel} from '../models/loan.model';
+import {ChangePassword} from '../models/change-password.model';
@Injectable({providedIn: 'root'})
export class PeopleService {
@@ -28,4 +29,16 @@ export class PeopleService {
return this.http.post(this.auth.getUrl('sync-people/'), loan);
}
+ public updateAvatar(avatar: string, id: string ): Observable {
+ return this.http.post(this.auth.getUrl('avatar/' + id), avatar);
+ }
+
+ public savePassword(id: string, change: ChangePassword): Observable{
+ return this.http.post(this.auth.getUrl('change-pass/' + id), change);
+ }
+
+ public saveBroker(broker: BrokerModel): Observable{
+ return this.http.post(this.auth.getUrl('broker/' + broker.Id), broker);
+ }
+
}
diff --git a/src/app/top-bar/top-bar.component.ts b/src/app/top-bar/top-bar.component.ts
index 7836675..1726b28 100644
--- a/src/app/top-bar/top-bar.component.ts
+++ b/src/app/top-bar/top-bar.component.ts
@@ -2,7 +2,7 @@ import {Component, OnDestroy, OnInit} from '@angular/core';
import {MenuService} from '../service/menu.service';
import {AuthService} from '../service/auth.service';
import {brokerMenuItems, mainMenuItems, peopleMenuItems, userMenuItems} from '../main-menu-items';
-import {apiV1LoginResponse} from '../models/api-v1-login-response';
+import {ApiV1LoginResponse} from '../models/api-v1-login-response';
import {Subscription} from 'rxjs';
import {PeopleModel} from '../models/people.model';
@@ -38,7 +38,7 @@ export class TopBarComponent implements OnInit , OnDestroy {
this.selectMenuShow(this.authService.loggedIn.role);
this.loginSub = this.authService.loginSuccess.subscribe(
- (rsp: apiV1LoginResponse) => {
+ (rsp: ApiV1LoginResponse) => {
this.login = rsp.login;
this.LoggedInUser = this.authService.loggedIn.user;
this.selectMenuShow(rsp.role);
diff --git a/src/app/validator/confirmed.validator.ts b/src/app/validator/confirmed.validator.ts
new file mode 100644
index 0000000..571a830
--- /dev/null
+++ b/src/app/validator/confirmed.validator.ts
@@ -0,0 +1,16 @@
+import { FormGroup } from '@angular/forms';
+
+export function ConfirmedValidator(controlName: string, matchingControlName: string) {
+ return (formGroup: FormGroup) => {
+ const control = formGroup.controls[controlName];
+ const matchingControl = formGroup.controls[matchingControlName];
+ if (matchingControl.errors && !matchingControl.errors.confirmedValidator) {
+ return;
+ }
+ if (control.value !== matchingControl.value) {
+ matchingControl.setErrors({ confirmedValidator: true });
+ } else {
+ matchingControl.setErrors(null);
+ }
+ };
+}