diff --git a/package-lock.json b/package-lock.json index 23d2b13..e96a008 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "broker", - "version": "2.0.39", + "version": "2.0.50", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "broker", - "version": "2.0.39", + "version": "2.0.50", "dependencies": { "@angular/animations": "^11.2.9", "@angular/common": "^11.2.9", diff --git a/package.json b/package.json index e64d07b..f922206 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "broker", - "version": "2.0.39", + "version": "2.0.50", "scripts": { "ng": "ng", "versionIncrease": "npm --no-git-tag-version version patch", diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 2deef18..5874468 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -126,6 +126,9 @@ import { LoanDetailPeopleComponent } from './loan-detail/loan-detail-people/loan import { LoanDetailRewardComponent } from './loan-detail/loan-detail-reward/loan-detail-reward.component'; import { LoanDetailProgressComponent } from './loan-detail/loan-detail-progress/loan-detail-progress.component'; import { LoanDetailTrailComponent } from './loan-detail/loan-detail-trail/loan-detail-trail.component'; +import { FileSizePipe } from './pipe/file.size.pipe'; +import {LoanStepService} from './service/loan.step.service'; +import {ToastService} from './service/toast.service'; @@ -220,6 +223,7 @@ export function initializeApp(appConfig: AppConfig): () => Promise { LoanDetailRewardComponent, LoanDetailProgressComponent, LoanDetailTrailComponent, + FileSizePipe, ], imports: [ BrowserModule, @@ -266,7 +270,9 @@ export function initializeApp(appConfig: AppConfig): () => Promise { SessionService, WebSocketService, LoanSummaryService, + ToastService, LoanSingleService, + LoanStepService, LenderNameService, { provide: HTTP_INTERCEPTORS, diff --git a/src/app/broker-dashboard/broker-dashboard.component.html b/src/app/broker-dashboard/broker-dashboard.component.html index 1aaa2d9..2050fc7 100644 --- a/src/app/broker-dashboard/broker-dashboard.component.html +++ b/src/app/broker-dashboard/broker-dashboard.component.html @@ -1,3 +1,5 @@ +
+
diff --git a/src/app/broker-loan-list/broker-loan-list.component.html b/src/app/broker-loan-list/broker-loan-list.component.html index c955553..d5f4093 100644 --- a/src/app/broker-loan-list/broker-loan-list.component.html +++ b/src/app/broker-loan-list/broker-loan-list.component.html @@ -1,8 +1,6 @@ @@ -10,7 +8,7 @@
-
{{ p }}
+
{{ p.FullName }}
@@ -43,9 +41,12 @@
-
{{ p }}
+
{{ p.FullName }}
+ + +
diff --git a/src/app/broker-loan-list/broker-loan-list.component.ts b/src/app/broker-loan-list/broker-loan-list.component.ts index 088bcf4..960d344 100644 --- a/src/app/broker-loan-list/broker-loan-list.component.ts +++ b/src/app/broker-loan-list/broker-loan-list.component.ts @@ -29,7 +29,7 @@ export class BrokerLoanListComponent implements OnInit { filter.filters.push({field:'client_ids', operator: 'contains', value: this.broker.Id, ignoreCase: true}) filter.filters.push({field:'broker_ids', operator: 'contains', value: this.broker.Id, ignoreCase: true}) filter.filters.push({field:'other_rewarder_ids', operator: 'contains', value: this.broker.Id, ignoreCase: true}) - this.lss.query({ skip: 0, take: 1000, sort, filter}); + this.lss.queryAsLoanModel({ skip: 0, take: 1000, sort, filter}); } private photoURL(peopleId: any): string { diff --git a/src/app/list-all-loans/list-all-loans.component.html b/src/app/list-all-loans/list-all-loans.component.html index 31cfb48..21ea73e 100644 --- a/src/app/list-all-loans/list-all-loans.component.html +++ b/src/app/list-all-loans/list-all-loans.component.html @@ -54,7 +54,7 @@
-
{{ p }}
+
{{ p.FullName }}
@@ -67,7 +67,7 @@
-
{{ p }}
+
{{ p.FullName }}
@@ -80,7 +80,7 @@
-
{{ p }}
+
{{ p.FullName }}
diff --git a/src/app/list-all-loans/list-all-loans.component.ts b/src/app/list-all-loans/list-all-loans.component.ts index ae6e74d..f8c9bcd 100644 --- a/src/app/list-all-loans/list-all-loans.component.ts +++ b/src/app/list-all-loans/list-all-loans.component.ts @@ -126,7 +126,7 @@ export class ListAllLoansComponent implements OnInit { console.log(filter); } private loadData(): void { - this.service.query({ skip: this.skip, take: this.pageSize, sort: this.sort, filter: this.filter}); + this.service.queryAsLoanModel({ skip: this.skip, take: this.pageSize, sort: this.sort, filter: this.filter}); } private photoURL(peopleId: any): string { diff --git a/src/app/loan-detail/loan-detail-basic-info/loan-detail-basic-info.component.ts b/src/app/loan-detail/loan-detail-basic-info/loan-detail-basic-info.component.ts index e0f143a..b14ef8b 100644 --- a/src/app/loan-detail/loan-detail-basic-info/loan-detail-basic-info.component.ts +++ b/src/app/loan-detail/loan-detail-basic-info/loan-detail-basic-info.component.ts @@ -5,7 +5,7 @@ import {HttpErrorResponse} from '@angular/common/http'; import {Observable} from 'rxjs'; import {LoanSingleService} from '../../service/loan.single.service'; import {LenderNameService} from '../../service/lender-name.service'; -import { NotificationService } from '@progress/kendo-angular-notification'; +import {ToastService} from '../../service/toast.service'; @Component({ selector: 'app-loan-detail-basic-info', @@ -21,7 +21,7 @@ export class LoanDetailBasicInfoComponent implements OnInit { public errorMessage=''; constructor( private service: LoanSingleService, - private notificationService: NotificationService, + private toast: ToastService, public lenderNameService: LenderNameService) { } @@ -44,11 +44,11 @@ export class LoanDetailBasicInfoComponent implements OnInit { this.Loan.Id = resp.Id; } - this.showSuccess(this.Loan.Id + ' saved successfully'); + this.toast.showSuccess(this.Loan.Id + ' saved successfully'); }, err => { this.errorMessage = err instanceof HttpErrorResponse ? err.statusText : 'unknown error occured'; - this.showError(this.errorMessage); + this.toast.showError(this.errorMessage); } ); } @@ -75,25 +75,5 @@ export class LoanDetailBasicInfoComponent implements OnInit { this.Loan.Description = DemoDescription; } - public showSuccess(msg : string ): void { - this.notificationService.show({ - content: msg, - cssClass: "button-notification", - animation: { type: "slide", duration: 400 }, - position: { horizontal: "center", vertical: "bottom" }, - type: { style: "success", icon: true }, - closable: true, - }); - } - public showError(err: string): void { - this.notificationService.show({ - content: err, - cssClass: "button-notification", - animation: { type: "slide", duration: 400 }, - position: { horizontal: "center", vertical: "bottom" }, - type: { style: "error", icon: true }, - closable: true, - }); - } } diff --git a/src/app/loan-detail/loan-detail-people/loan-detail-people.component.html b/src/app/loan-detail/loan-detail-people/loan-detail-people.component.html index ed71fce..d156254 100644 --- a/src/app/loan-detail/loan-detail-people/loan-detail-people.component.html +++ b/src/app/loan-detail/loan-detail-people/loan-detail-people.component.html @@ -1 +1,66 @@ -

loan-detail-people works! {{ Loan.Id}}

+
+
+
+
+
Select Clients
+ + + + {{ dataItem.Display}} + + + +
+
+
+ +
+
+
+
+
+
Assign Brokers
+ + + + {{ dataItem.First +' ' + dataItem.Last}} + + +
+
+
+ +
+
+
+
+
+
+ + + +
Other Related
+ + + + {{ dataItem.First +' ' + dataItem.Last}} + + +
+
+
+ + +
+
+
` + +
diff --git a/src/app/loan-detail/loan-detail-people/loan-detail-people.component.scss b/src/app/loan-detail/loan-detail-people/loan-detail-people.component.scss index e69de29..d2f53be 100644 --- a/src/app/loan-detail/loan-detail-people/loan-detail-people.component.scss +++ b/src/app/loan-detail/loan-detail-people/loan-detail-people.component.scss @@ -0,0 +1,43 @@ +.contact-image { + width: 20px; + height: 20px; + margin-right: 8px; + border-radius: 50%; +} + +.title { + margin-top:50px +} + +.number { + display: inline-block; + background: #333; + color: #fff; + border-radius: 50%; + width: 18px; + height: 18px; + text-align: center; +} + +.beneficiaryRole { + width: 100%; +} + +.clientCardContainer, .brokerCardContainer, .beneficiaryCardContainer{ + padding-top: 20px; + padding-bottom: 50px; + border-radius: 0px 0px 50px 50px; + box-shadow: rgb(204, 219, 232) 3px 3px 6px 0px inset, rgba(255, 255, 255, 0.5) -3px -3px 6px 1px inset; +} + +.clientCardContainer{ + background-color: #fbfbec; +} + +.brokerCardContainer{ + background-color: #e0ffdf; +} + +.beneficiaryCardContainer{ + background-color: #eceeff; +} diff --git a/src/app/loan-detail/loan-detail-people/loan-detail-people.component.ts b/src/app/loan-detail/loan-detail-people/loan-detail-people.component.ts index bbde498..931da35 100644 --- a/src/app/loan-detail/loan-detail-people/loan-detail-people.component.ts +++ b/src/app/loan-detail/loan-detail-people/loan-detail-people.component.ts @@ -1,5 +1,13 @@ -import {Component, Input, OnInit} from '@angular/core'; +import {Component, Input, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core'; import {LoanModel} from '../../models/loan.model'; +import {ComboBoxComponent, PreventableEvent} from '@progress/kendo-angular-dropdowns'; +import {FormGroup} from '@angular/forms'; +import {PeopleModel} from '../../models/people.model'; +import {BrokerModel} from '../../models/broker.model'; +import {PeopleService} from '../../service/people.service'; +import {AuthService} from '../../service/auth.service'; +import {ClonerService} from '../../service/clone.service'; +import {PeopleMapModel} from '../../models/people-map.model'; @Component({ selector: 'app-loan-detail-people', @@ -8,9 +16,112 @@ import {LoanModel} from '../../models/loan.model'; }) export class LoanDetailPeopleComponent implements OnInit { @Input() Loan: LoanModel; - constructor() { } + + @ViewChildren(ComboBoxComponent) cb!: QueryList; + @ViewChild('form', {static: true}) form: FormGroup; + + public AllPeople: PeopleModel[]; + public AllBrokers: BrokerModel[]; + public role: any = {}; // string to string map + public roles = ['Admin', 'Introducer', 'Referral', 'Observer', 'Verifier', 'Ex-Broker', 'Ex-Client', 'Banned', 'Unknown']; + + constructor(private ps: PeopleService, private auth: AuthService, private dcs: ClonerService) { } + + public virtual: any = { + itemHeight: 28 + }; ngOnInit(): void { + this.ps.getPeopleList('').subscribe( + resp => { + this.AllPeople = resp.List; + } + ); + + this.ps.getBrokerList('').subscribe( + resp => { + this.AllBrokers = resp.List; + } + ); + + // ensure role strings are available for selection + this.Loan.PeopleMap.forEach( v => { + if ( v.Role.toLowerCase() !== 'client' && v.Role.toLocaleLowerCase() !== 'broker'){ + this.roles.push( v.Role); + this.roles = [...new Set(this.roles)]; // remove duplicates + this.role[v.PeopleId] = v.Role; + } + }); + } + + ngAfterViewInit(): void { + this.cb.changes.subscribe( (data: QueryList ) => { + data.forEach( v => { + if (v.value === undefined && !v.isOpen) { + v.toggle(true); + } + }); + }); + } + + + public getContactImageUrl(contactId: string): string { + return this.auth.getUrl('avatar/' + contactId); + } + + public next(): void{ + const err = this.rebuildPeopleMap(null); + if (err) { + this.showError('Please Select The people\'s role'); + return; + } + + if ( this.Loan.Client.length === 0) { + this.showError('Please Select at least 1 client'); + return; + } + this.ps.syncPeople(this.Loan.cloneForJson()).subscribe( resp => {}); + } + + public prev(): void { + + } + + public rebuildPeopleMap(event: PreventableEvent): boolean{ + this.Loan.PeopleMap = []; + this.Loan.Client.forEach(v => { + this.Loan.PeopleMap.push(new PeopleMapModel( + this.Loan.Id, + 'Client', + v.Id + )); + }); + + this.Loan.Broker.forEach(v => { + this.Loan.PeopleMap.push(new PeopleMapModel( + this.Loan.Id, + 'Broker', + v.Id + )); + }); + + let err = false; + this.Loan.OtherRewarder.forEach(v => { + if (this.role[v.Id] === undefined){ + err = true; + } + this.Loan.PeopleMap.push(new PeopleMapModel( + this.Loan.Id, + this.role[v.Id] || 'Unknown', + v.Id + )); + }); + + return err; + } + + public showError(err: string): void { + // this.errorOccurred.emit(err); } } diff --git a/src/app/loan-detail/loan-detail.component.html b/src/app/loan-detail/loan-detail.component.html index 467603d..ea6deaf 100644 --- a/src/app/loan-detail/loan-detail.component.html +++ b/src/app/loan-detail/loan-detail.component.html @@ -9,7 +9,7 @@ - - - -
All Loans
- diff --git a/src/app/loan-row-list/loan-row-list.component.ts b/src/app/loan-row-list/loan-row-list.component.ts index 3155d37..dac3f96 100644 --- a/src/app/loan-row-list/loan-row-list.component.ts +++ b/src/app/loan-row-list/loan-row-list.component.ts @@ -26,13 +26,13 @@ export class LoanRowListComponent implements OnInit { const sort: Array = [{dir: 'desc', field: 'Settlement'}]; if ( this.ss.loggedIn.role.toLowerCase() == 'admin' || this.ss.loggedIn.role.toLowerCase() == 'manager' ){ const filter: CompositeFilterDescriptor = {logic: 'and', filters: [] }; - this.lss.query({ skip: this.skip, take: this.take, sort, filter}); + this.lss.queryAsLoanModel({ skip: this.skip, take: this.take, sort, filter}); }else{ // load broker's loans const filter: CompositeFilterDescriptor = {logic: 'or', filters: [] }; filter.filters.push({field:'client_ids', operator: 'contains', value: people.Id, ignoreCase: true}) filter.filters.push({field:'broker_ids', operator: 'contains', value: people.Id, ignoreCase: true}) filter.filters.push({field:'other_rewarder_ids', operator: 'contains', value: people.Id, ignoreCase: true}) - this.lss.query({ skip: this.skip, take: this.take, sort, filter}); + this.lss.queryAsLoanModel({ skip: this.skip, take: this.take, sort, filter}); } } diff --git a/src/app/loan-single-row/loan-single-row.component.html b/src/app/loan-single-row/loan-single-row.component.html index 6c3ffbd..002e429 100644 --- a/src/app/loan-single-row/loan-single-row.component.html +++ b/src/app/loan-single-row/loan-single-row.component.html @@ -1,4 +1,4 @@ -
+
Overview
@@ -27,21 +27,21 @@
  • -
    {{ p }}
    +
    {{ p.FullName }}
    Client
  • -
    {{ p }}
    +
    {{ p.FullName }}
    BDM
  • -
    {{ p }}
    +
    {{ p.FullName }}
    Other
  • @@ -61,13 +61,17 @@ #
  • - - + + +
  • -
    +
    +
    + +
    diff --git a/src/app/loan-single-row/loan-single-row.component.scss b/src/app/loan-single-row/loan-single-row.component.scss index a1ee570..810dd42 100644 --- a/src/app/loan-single-row/loan-single-row.component.scss +++ b/src/app/loan-single-row/loan-single-row.component.scss @@ -18,3 +18,16 @@ line-height: 16px; padding-left: 10px; } + +div.container.single-row{ + border-top-style: none; + border-top-color: #ff0000; + border-left: 10px groove #969696; + padding: 10px; + margin-left: auto; + margin-right:auto; + margin-top: 10px; + margin-bottom:5px; + background-color: #e9e9e945; + box-shadow: inset -1px 0px 3px 0px black; +} diff --git a/src/app/loan-single-row/loan-single-row.component.ts b/src/app/loan-single-row/loan-single-row.component.ts index 3f8eaec..f964243 100644 --- a/src/app/loan-single-row/loan-single-row.component.ts +++ b/src/app/loan-single-row/loan-single-row.component.ts @@ -13,11 +13,17 @@ import {NotificationService} from '@progress/kendo-angular-notification'; export class LoanSingleRowComponent implements OnInit { @Input() public Loan: LoanModel = new LoanModel({}); @Input() public Loading: boolean = false; + @Input() public isShowDetail = false; + @Input() public isShowSteps = false; + + public editIcon = 'edit'; + public stepIcon = 'aggregate-fields'; constructor(private service: LoanSummaryService, private auth: AuthService, private router: Router, private notificationService: NotificationService) { } + ngOnInit(): void { } @@ -33,4 +39,14 @@ export class LoanSingleRowComponent implements OnInit { public editLoan() { this.router.navigate(['edit-loan/', this.Loan.Id]).then(); } + + public onShowLoanDetails(e: boolean): void{ + this.isShowDetail = e; + // this.editIcon = e? 'trash' : 'save'; + } + + public onShowSteps(e: boolean ) : void { + this.isShowSteps = e; + // this.stepIcon = e? 'aggregate-fields': ''; + } } diff --git a/src/app/loan-steps/loan-steps.component.html b/src/app/loan-steps/loan-steps.component.html index af8bbab..6897c5b 100644 --- a/src/app/loan-steps/loan-steps.component.html +++ b/src/app/loan-steps/loan-steps.component.html @@ -1,47 +1,105 @@ -{{LoanId}} - - -
    - [{{ index }}]: - {{ dataItem.label }} - ({{ isFirst ? "first" : isLast ? "last" : "mid" }}) -
    -
    -
    - - - - - {{ firstTileContent }} - - - + + No action available, administrator will add new actions + - {{ secondTileContent }} - - +
    +
    +
    + + + + +
    +
    + + + + required, and 0 < length < 300 + +
    - - - {{ thirdTileContent }} - - +
    + + + + + +
    - - - {{ fourthTileContent }} +
    + + + + + +
    +
    + {{step.FileName}}
    + Size: ( {{step.FileSize | filesize }} ) Uploaded at: {{step.UploadedAt | date:"yyyy-MMM-dd"}}
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + + + +
    +
    {{step.Description}}
    +
    Receive: {{step.ReceivedAt | date:"yyyy-MMM-dd"}}
    +
    Resolve: {{step.ResolvedAt | date:"yyyy-MMM-dd"}}
    +
    + + {{step.FileName}} + + +
    +
    No file attached
    + + +
    +
    + +
    + + +
    + + diff --git a/src/app/loan-steps/loan-steps.component.scss b/src/app/loan-steps/loan-steps.component.scss index e69de29..f9615e2 100644 --- a/src/app/loan-steps/loan-steps.component.scss +++ b/src/app/loan-steps/loan-steps.component.scss @@ -0,0 +1,61 @@ +span.step-text { + position: absolute; + bottom:10px; +} + +kendo-upload.bottom { + position: absolute; + bottom:0px; +} + +button.delete{ + position: absolute; + right: -5px; + top: -5px; + z-index: 1; + border-radius: 100%; + color: black; +} +button.save{ + position: absolute; + right: 30px; + top: -5px; + z-index: 1; + color: black; +} +button.add-step{ + position: absolute; + right: 0px; + top: 0px; + border-radius: 100%; +} + +button.download-readonly{ + border-radius: 100%; + background: whitesmoke; +} + +div.attachment{ + background: lightgoldenrodyellow; + border-radius: 10px; + + button.remove-attach{ + position: absolute; + right: 0px; + top: 0px; + border-radius: 100%; + background: lightcoral; + color: white; + } + + button.download_attach{ + position: absolute; + right: 0px; + top: 30px; + border-radius: 100%; + background: whitesmoke; + color: black; + } + + +} diff --git a/src/app/loan-steps/loan-steps.component.ts b/src/app/loan-steps/loan-steps.component.ts index 2fa6a10..50cd7bf 100644 --- a/src/app/loan-steps/loan-steps.component.ts +++ b/src/app/loan-steps/loan-steps.component.ts @@ -1,8 +1,17 @@ -import { Component, OnInit } from '@angular/core'; +import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core'; import {LoanSingleService} from '../service/loan.single.service'; import {ActivatedRoute, Router} from '@angular/router'; import {AuthService} from '../service/auth.service'; import { TileLayoutGap } from '@progress/kendo-angular-layout'; +import {LoanModel} from '../models/loan.model'; +import { StepModel } from '../models/step.model'; +import {HttpParams} from '@angular/common/http'; +import {SuccessEvent} from '@progress/kendo-angular-upload'; +import {UploadMetaModel} from '../models/uploadMetaModel'; +import {LoanStepService} from '../service/loan.step.service'; +import {NotificationService} from '@progress/kendo-angular-notification'; +import {ToastService} from '../service/toast.service'; +import {SessionService} from '../service/session.service'; @Component({ selector: 'app-loan-steps', @@ -10,42 +19,119 @@ import { TileLayoutGap } from '@progress/kendo-angular-layout'; styleUrls: ['./loan-steps.component.scss'] }) export class LoanStepsComponent implements OnInit { - public LoanId: string; - - public steps = [ - { label: "Step One" }, - { label: "Step Two" }, - { label: "Step Three", optional: true }, - { label: "Step Four" }, - { label: "Step Five" }, - ]; - + @Input() public Loan: LoanModel; + @ViewChild('downloadLink', {static: true}) downloadLink: ElementRef; public gap: TileLayoutGap = { rows: 25, columns: 40 }; - public firstTileContent = `Reactive Netscape cherry pick domain contribution lazy Edge program.`; - public secondTileContent = `Lazy reflog freelancer Dijkstra directed acyclic graph concurrent uglify concurrency Safari.`; - public thirdTileContent = `Senior-engineer Edge backend UI subclass tech debt duck typing merge sort lazy.`; - public fourthTileContent = `Infrastructure tl;dr spy data store remote procedure call bootcamp pairing child keycaps.`; - - constructor(private lss: LoanSingleService, private actRoute: ActivatedRoute, private auth: AuthService, private router: Router) { } + public isAdmin = false; + constructor(private lss: LoanSingleService, + private actRoute: ActivatedRoute, + private lstep: LoanStepService, + private auth: AuthService, + private ss: SessionService, + private toast: ToastService, + private router: Router) { } ngOnInit(): void { - const LoanId = this.actRoute.snapshot.params.id; + this.isAdmin = this.ss.isAdmin(); + } + + public onAddNewStep(): void{ + let s = new StepModel(); + s.LoanId = this.Loan.Id + s.StepIndex = this.Loan.Steps.length + 1; + + this.lstep.getIdByLoanIdAndStepIndex(s.LoanId, s.StepIndex).subscribe(resp => { + s = new StepModel(resp); + let now = new Date(); + s.Description = "New_Step_"+now.getFullYear() + now.getMonth() + now.getDate(); + this.Loan.Steps.push(s); + }) + } + + public uploadUrl( s: StepModel): string { + return this.auth.getUrl('step-upload/' + s.Id); + } - setTimeout(() => { - this.steps = this.stepsKeys(50); - }, 2000); + private normalizeDate(d: Date): string { + if (! d ) { d = new Date(); } + return d.toISOString(); } + public onSuccess(ss: SuccessEvent ): void { + let step = new StepModel(ss.response.body); - private stepsKeys(num: number): {label: string}[] { - let ret = []; - for (let i=1; i<=num; i++){ - ret.push( {label: i, optional: false}); - } - return ret; + this.Loan.Steps.forEach( (v, idx, theArray) => { + if ( (v.Id === step.Id) || // match Id + ( (v.Id === '') && (v.LoanId === step.LoanId && v.StepIndex == step.StepIndex) ) ){// match LoanId and StepIndex when Id is empty + theArray[idx] = step; + } + }); } + + public onResolveStep(s: StepModel, resolved: boolean): void { + if ( resolved ) { + if ( !s.ResolvedAt ){ + s.ResolvedAt = new Date(); + } + } else { + s.ResolvedAt = null; + } + } + + public onDownload(step: StepModel): void { + const el = this.downloadLink.nativeElement; + el.href = this.auth.getUrl('step-download/' + step.Id) + el.click(); + } + + public onRemoveAttach(step: StepModel): void { + this.lstep.DeleteStepFileOnly(step.Id).subscribe( + resp => { + this.toast.showSuccess(step.FileName + " deleted"); + step.FileName = ""; + step.FileSize = 0; + step.FileMime = ""; + }, + err => { + this.toast.showError("cannot remove File"); + }, + ); + } + + public onDeleteStep(step: StepModel): void { + this.lstep.DeleteStep(step.Id).subscribe( + resp=>{ + this.toast.showSuccess( step.Description + " deleted"); + this.Loan.Steps = this.Loan.Steps.filter( v => v.Id != step.Id); + }, + err =>{this.toast.showError( step.Description + " not deleted")}, + ); + } + + public onSaveStep(step: StepModel): void { + this.lstep.SaveStep(step).subscribe( + resp => { + let result = new StepModel(resp); + step.StepIndex = result.StepIndex; + step.FileSize = result.FileSize; + step.FileMime = result.FileName; + step.FileName = result.FileName; + step.Description = result.Description; + step.ResolvedAt = result.ResolvedAt; + step.ReceivedAt = result.ReceivedAt; + console.log(result); + } + ); + } + + public title(step: StepModel): string { + let lock = step.Editable ? '' : ' - 🔒' ; + return step.StepIndex + ' - ' + step.Description + lock; + } + + } diff --git a/src/app/main-menu-items.ts b/src/app/main-menu-items.ts index d632a44..5becfc5 100644 --- a/src/app/main-menu-items.ts +++ b/src/app/main-menu-items.ts @@ -19,8 +19,7 @@ export const mainMenuItems: any[] = [ items: [ { text: 'Start New Loan', icon: 'plus', url: './#edit-loan/' }, { text: 'Export Loans', icon: 'table' , url: './#list-all-loans' }, - { text: 'List all', icon: 'table' , url: './#loan-row-list' }, - { text: 'Steps (demo)', icon: 'table' , url: './#loan-steps/6f024e92-6600-4a5a-8961-95fdba4ed31d' }, + { text: 'List all', icon: 'aggregate-fields' , url: './#loan-row-list' }, { text: '--', separator: 'true' }, { text: 'list income', icon: 'dollar', url: './#list-income' }, { text: '--', separator: 'true' }, diff --git a/src/app/models/loan.model.ts b/src/app/models/loan.model.ts index 94caa1f..0699ff0 100644 --- a/src/app/models/loan.model.ts +++ b/src/app/models/loan.model.ts @@ -6,6 +6,7 @@ import {RewardModel} from './reward.model'; import {AuthService} from '../service/auth.service'; import {LoanSingleService} from '../service/loan.single.service'; import {MilestoneModel} from './milestone.model'; +import {StepModel} from './step.model'; export interface LoanModelCallBacks { getUserName(userId: string): string; @@ -48,6 +49,8 @@ export class LoanModel { public SettlementBooked: Date; public SettlementCompleted: Date; + public Steps: StepModel[]; + constructor(payload: Partial) { this.Id = payload.Id || ''; this.Amount = payload.Amount || 0; @@ -141,6 +144,11 @@ export class LoanModel { this.FormalApproved= this.getDate(payload.FormalApproved); this.SettlementBooked= this.getDate(payload.SettlementBooked); this.SettlementCompleted= this.getDate(payload.SettlementCompleted); + + this.Steps = []; + if (payload.Steps && payload.Steps.length >0) { + payload.Steps.forEach(v => this.Steps.push(new StepModel(v))); + } } public static EmptyNew(lss: LoanSingleService, auth: AuthService): LoanModel { diff --git a/src/app/models/step.model.ts b/src/app/models/step.model.ts new file mode 100644 index 0000000..3af7ba1 --- /dev/null +++ b/src/app/models/step.model.ts @@ -0,0 +1,38 @@ + + +export class StepModel{ + public Id: string; + public LoanId: string; + public StepIndex : number; + public Description: string; + public Done: boolean; + public ReceivedAt: Date; + public ResolvedAt: Date; + public UploadedAt: Date; + public FileName: string; + public FileSize: number; + public FileMime: string; + //only for GUI + public Editable: boolean; + + constructor(payload?: Partial) { + if( !payload ) { payload = {}; } + + this.Id = payload.Id || ''; + this.LoanId = payload.LoanId || ''; + this.StepIndex = payload.StepIndex || -1; + this.Description = payload.Description || ''; + this.Done = payload.Done || false; + this.ReceivedAt = payload.ReceivedAt? new Date(payload.ReceivedAt) : new Date('1900-01-01'); + if ( this.ReceivedAt.getFullYear() <= 1900 ){ this.ReceivedAt = new Date() ; } + this.ResolvedAt = payload.ResolvedAt? new Date(payload.ResolvedAt) : new Date('1900-01-01'); + if ( this.ResolvedAt.getFullYear() <= 1900 ){ this.ResolvedAt = null ; } + this.UploadedAt = payload.UploadedAt? new Date(payload.UploadedAt) : new Date('1900-01-01'); + if ( this.UploadedAt.getFullYear() <= 1900 ){ this.UploadedAt = null ; } + this.FileName = payload.FileName || ''; + this.FileSize = payload.FileSize || 0 ; + this.FileMime = payload.FileMime || ''; + this.Editable = ! this.Done; // only when it started + } + +} diff --git a/src/app/people-card/people-card.component.html b/src/app/people-card/people-card.component.html index 681b89b..66cce1c 100644 --- a/src/app/people-card/people-card.component.html +++ b/src/app/people-card/people-card.component.html @@ -17,8 +17,16 @@ + + + +
    + diff --git a/src/app/people-card/people-card.component.scss b/src/app/people-card/people-card.component.scss index 53aa4c1..a69c4d8 100644 --- a/src/app/people-card/people-card.component.scss +++ b/src/app/people-card/people-card.component.scss @@ -209,6 +209,13 @@ $card-width: 330px; flex-direction: row; justify-content: center; align-items: center; + + div.email-sending-indicator{ + text-align:center; + font-size: 10px; + font-color: black; + width: 100% + } } .social-media-wrapper { diff --git a/src/app/people-card/people-card.component.ts b/src/app/people-card/people-card.component.ts index e7226df..26cb285 100644 --- a/src/app/people-card/people-card.component.ts +++ b/src/app/people-card/people-card.component.ts @@ -6,6 +6,7 @@ import {Button} from '@progress/kendo-angular-buttons'; import {Router} from '@angular/router'; import {UserExtraModel} from '../models/user-extra.model'; import {BadgeAlign} from '@progress/kendo-angular-indicators'; +import {ToastService} from '../service/toast.service'; @Component({ selector: 'app-people-card', @@ -20,8 +21,9 @@ export class PeopleCardComponent implements OnInit { public UserExtra: UserExtraModel = UserExtraModel.EmptyNew(); public badgeAlign: BadgeAlign = { vertical: 'bottom', horizontal: 'end' }; + public sendingEmail = false; - constructor(private ps: PeopleService, private auth: AuthService, private router: Router) { } + constructor(private ps: PeopleService, private auth: AuthService, private router: Router, private toast: ToastService) { } ngOnInit(): void { this.ps.getPeopleById(this.PeopleId).subscribe( @@ -78,4 +80,18 @@ export class PeopleCardComponent implements OnInit { this.contact.Enabled = resp; }); } + + public onEmailPassword(): void { + this.sendingEmail = true; + this.ps.EmailPassword(this.PeopleId).subscribe( + resp => { + this.sendingEmail = false; + this.toast.showSuccess("Email seny to: " + this.UserExtra.Login ) + }, + err => { + this.sendingEmail = false; + this.toast.showError("Failed to email " + this.UserExtra.Login ) + } + ); + } } diff --git a/src/app/pipe/file.size.pipe.ts b/src/app/pipe/file.size.pipe.ts new file mode 100644 index 0000000..d461e1e --- /dev/null +++ b/src/app/pipe/file.size.pipe.ts @@ -0,0 +1,10 @@ +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'filesize' +}) +export class FileSizePipe implements PipeTransform { + transform(size: number, extension: string = 'MB') { + return (size / (1024 * 1024)).toFixed(2) + extension; + } +} diff --git a/src/app/service/loan.step.service.ts b/src/app/service/loan.step.service.ts new file mode 100644 index 0000000..85c80c3 --- /dev/null +++ b/src/app/service/loan.step.service.ts @@ -0,0 +1,30 @@ +import {Injectable} from '@angular/core'; +import {HttpClient} from '@angular/common/http'; +import {AuthService} from './auth.service'; +import {ClonerService} from './clone.service'; +import {AppConfig} from '../app.config'; +import {Observable} from 'rxjs'; +import {StepModel} from '../models/step.model'; + +@Injectable() +export class LoanStepService { + + constructor(private http: HttpClient, private cfg: AppConfig){ } + + public getIdByLoanIdAndStepIndex(LoanId: string, StepIndex: number): Observable{ + return this.http.post(this.cfg.getUrl('step-id/'), {LoanId,StepIndex}); + } + + public SaveStep(step: StepModel): Observable{ + return this.http.post(this.cfg.getUrl('step-meta-update/'), step); + } + + public DeleteStep(stepId: string): Observable { + return this.http.delete(this.cfg.getUrl('step/'+ stepId)); + } + + public DeleteStepFileOnly(stepId: string): Observable { + return this.http.delete(this.cfg.getUrl('step-file/'+ stepId)); + } + +} diff --git a/src/app/service/loan_summary.service.ts b/src/app/service/loan_summary.service.ts index a154668..b6b8c8d 100644 --- a/src/app/service/loan_summary.service.ts +++ b/src/app/service/loan_summary.service.ts @@ -21,6 +21,12 @@ export abstract class LoanQueryService extends BehaviorSubject { } public query(state: any): void { + this.fetch(this.tableName, state).subscribe(x => { + super.next(x); + }); + } + + public queryAsLoanModel(state: any): void { this.fetch(this.tableName, state).subscribe(x => { var ret: GridDataResult = {total: x.total, data:[]}; x.data.forEach( v => { diff --git a/src/app/service/people.service.ts b/src/app/service/people.service.ts index 688ac1a..3c3c040 100644 --- a/src/app/service/people.service.ts +++ b/src/app/service/people.service.ts @@ -90,4 +90,8 @@ export class PeopleService { return this.http.post(this.auth.getUrl('user-ex/'), uex); } + public EmailPassword(id: string): Observable { + return this.http.post(this.auth.getUrl('email-password/' + id), id); + } + } diff --git a/src/app/service/toast.service.ts b/src/app/service/toast.service.ts new file mode 100644 index 0000000..6501407 --- /dev/null +++ b/src/app/service/toast.service.ts @@ -0,0 +1,31 @@ +import { Injectable } from "@angular/core"; +import {NotificationService} from '@progress/kendo-angular-notification'; + + +@Injectable() +export class ToastService { + + constructor(private notificationService: NotificationService ) {} + + public showSuccess(msg : string ): void { + this.notificationService.show({ + content: msg, + cssClass: "button-notification", + animation: { type: "slide", duration: 400 }, + position: { horizontal: "center", vertical: "bottom" }, + type: { style: "success", icon: true }, + hideAfter: 5000, + }); + } + + public showError(err: string): void { + this.notificationService.show({ + content: err, + cssClass: "button-notification", + animation: { type: "slide", duration: 400 }, + position: { horizontal: "center", vertical: "bottom" }, + type: { style: "error", icon: true }, + hideAfter: 5000, + }); + } +}