| { | { | ||||
| "name": "broker", | "name": "broker", | ||||
| "version": "2.0.39", | |||||
| "version": "2.0.50", | |||||
| "lockfileVersion": 2, | "lockfileVersion": 2, | ||||
| "requires": true, | "requires": true, | ||||
| "packages": { | "packages": { | ||||
| "": { | "": { | ||||
| "name": "broker", | "name": "broker", | ||||
| "version": "2.0.39", | |||||
| "version": "2.0.50", | |||||
| "dependencies": { | "dependencies": { | ||||
| "@angular/animations": "^11.2.9", | "@angular/animations": "^11.2.9", | ||||
| "@angular/common": "^11.2.9", | "@angular/common": "^11.2.9", |
| { | { | ||||
| "name": "broker", | "name": "broker", | ||||
| "version": "2.0.39", | |||||
| "version": "2.0.50", | |||||
| "scripts": { | "scripts": { | ||||
| "ng": "ng", | "ng": "ng", | ||||
| "versionIncrease": "npm --no-git-tag-version version patch", | "versionIncrease": "npm --no-git-tag-version version patch", |
| import { LoanDetailRewardComponent } from './loan-detail/loan-detail-reward/loan-detail-reward.component'; | 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 { LoanDetailProgressComponent } from './loan-detail/loan-detail-progress/loan-detail-progress.component'; | ||||
| import { LoanDetailTrailComponent } from './loan-detail/loan-detail-trail/loan-detail-trail.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'; | |||||
| LoanDetailRewardComponent, | LoanDetailRewardComponent, | ||||
| LoanDetailProgressComponent, | LoanDetailProgressComponent, | ||||
| LoanDetailTrailComponent, | LoanDetailTrailComponent, | ||||
| FileSizePipe, | |||||
| ], | ], | ||||
| imports: [ | imports: [ | ||||
| BrowserModule, | BrowserModule, | ||||
| SessionService, | SessionService, | ||||
| WebSocketService, | WebSocketService, | ||||
| LoanSummaryService, | LoanSummaryService, | ||||
| ToastService, | |||||
| LoanSingleService, | LoanSingleService, | ||||
| LoanStepService, | |||||
| LenderNameService, | LenderNameService, | ||||
| { | { | ||||
| provide: HTTP_INTERCEPTORS, | provide: HTTP_INTERCEPTORS, |
| <div style="height: calc(100vh - 48px);"> | |||||
| <app-chart-type-of-loans></app-chart-type-of-loans> | <app-chart-type-of-loans></app-chart-type-of-loans> | ||||
| <app-chart-amount-of-loans></app-chart-amount-of-loans> | <app-chart-amount-of-loans></app-chart-amount-of-loans> | ||||
| <app-broker-loan-list></app-broker-loan-list> | <app-broker-loan-list></app-broker-loan-list> | ||||
| </div> |
| <kendo-grid | <kendo-grid | ||||
| [data]="brokerLoans | async" | [data]="brokerLoans | async" | ||||
| [height]="610" | |||||
| [loading]="brokerLoans.loading" | [loading]="brokerLoans.loading" | ||||
| class="fullheight_grid" | |||||
| > | > | ||||
| <kendo-grid-column field="Id"></kendo-grid-column> | <kendo-grid-column field="Id"></kendo-grid-column> | ||||
| <ng-template kendoGridCellTemplate let-dataItem> | <ng-template kendoGridCellTemplate let-dataItem> | ||||
| <div *ngFor="let p of dataItem.Client, let idx=index "> | <div *ngFor="let p of dataItem.Client, let idx=index "> | ||||
| <div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.ClientIds[idx])}"></div> | <div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.ClientIds[idx])}"></div> | ||||
| <div class="customer-name"> {{ p }}</div> | |||||
| <div class="customer-name"> {{ p.FullName }}</div> | |||||
| </div> | </div> | ||||
| </ng-template> | </ng-template> | ||||
| </kendo-grid-column> | </kendo-grid-column> | ||||
| <ng-template kendoGridCellTemplate let-dataItem> | <ng-template kendoGridCellTemplate let-dataItem> | ||||
| <div *ngFor="let p of dataItem.Broker, let idx=index "> | <div *ngFor="let p of dataItem.Broker, let idx=index "> | ||||
| <div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.BrokerIds[idx])}"></div> | <div class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.BrokerIds[idx])}"></div> | ||||
| <div class="customer-name"> {{ p }}</div> | |||||
| <div class="customer-name"> {{ p.FullName }}</div> | |||||
| </div> | </div> | ||||
| </ng-template> | </ng-template> | ||||
| </kendo-grid-column> | </kendo-grid-column> | ||||
| <ng-template kendoGridDetailTemplate let-dataItem> | |||||
| <app-loan-steps [Loan]="dataItem"></app-loan-steps> | |||||
| </ng-template> | |||||
| </kendo-grid> | </kendo-grid> |
| filter.filters.push({field:'client_ids', operator: 'contains', value: this.broker.Id, ignoreCase: true}) | 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:'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}) | 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 { | private photoURL(peopleId: any): string { |
| <ng-template kendoGridCellTemplate let-dataItem> | <ng-template kendoGridCellTemplate let-dataItem> | ||||
| <div *ngFor="let p of dataItem.Client, let idx=index "> | <div *ngFor="let p of dataItem.Client, let idx=index "> | ||||
| <div *ngIf="dataItem.ClientIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.ClientIds[idx])}"></div> | <div *ngIf="dataItem.ClientIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.ClientIds[idx])}"></div> | ||||
| <div *ngIf="dataItem.ClientIds.length >0" class="customer-name"> {{ p }}</div> | |||||
| <div *ngIf="dataItem.ClientIds.length >0" class="customer-name"> {{ p.FullName }}</div> | |||||
| </div> | </div> | ||||
| </ng-template> | </ng-template> | ||||
| <ng-template kendoGridFilterCellTemplate let-filter let-column="column"> | <ng-template kendoGridFilterCellTemplate let-filter let-column="column"> | ||||
| <ng-template kendoGridCellTemplate let-dataItem> | <ng-template kendoGridCellTemplate let-dataItem> | ||||
| <div *ngFor="let p of dataItem.Broker, let idx=index "> | <div *ngFor="let p of dataItem.Broker, let idx=index "> | ||||
| <div *ngIf="dataItem.BrokerIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.BrokerIds[idx])}"></div> | <div *ngIf="dataItem.BrokerIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.BrokerIds[idx])}"></div> | ||||
| <div *ngIf="dataItem.BrokerIds.length >0" class="customer-name"> {{ p }}</div> | |||||
| <div *ngIf="dataItem.BrokerIds.length >0" class="customer-name"> {{ p.FullName }}</div> | |||||
| </div> | </div> | ||||
| </ng-template> | </ng-template> | ||||
| <ng-template kendoGridFilterCellTemplate let-filter let-column="column"> | <ng-template kendoGridFilterCellTemplate let-filter let-column="column"> | ||||
| <ng-template kendoGridCellTemplate let-dataItem> | <ng-template kendoGridCellTemplate let-dataItem> | ||||
| <div *ngFor="let p of dataItem.OtherRewarder, let idx=index "> | <div *ngFor="let p of dataItem.OtherRewarder, let idx=index "> | ||||
| <div *ngIf="dataItem.OtherRewarderIds.length > 0 " class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.OtherRewarderIds[idx])}"></div> | <div *ngIf="dataItem.OtherRewarderIds.length > 0 " class="customer-photo" [ngStyle]="{'background-image' : photoURL(dataItem.OtherRewarderIds[idx])}"></div> | ||||
| <div *ngIf="dataItem.OtherRewarderIds.length > 0 " class="customer-name"> {{ p }}</div> | |||||
| <div *ngIf="dataItem.OtherRewarderIds.length > 0 " class="customer-name"> {{ p.FullName }}</div> | |||||
| </div> | </div> | ||||
| </ng-template> | </ng-template> | ||||
| <ng-template kendoGridFilterCellTemplate let-filter let-column="column"> | <ng-template kendoGridFilterCellTemplate let-filter let-column="column"> |
| console.log(filter); | console.log(filter); | ||||
| } | } | ||||
| private loadData(): void { | 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 { | private photoURL(peopleId: any): string { |
| import {Observable} from 'rxjs'; | import {Observable} from 'rxjs'; | ||||
| import {LoanSingleService} from '../../service/loan.single.service'; | import {LoanSingleService} from '../../service/loan.single.service'; | ||||
| import {LenderNameService} from '../../service/lender-name.service'; | import {LenderNameService} from '../../service/lender-name.service'; | ||||
| import { NotificationService } from '@progress/kendo-angular-notification'; | |||||
| import {ToastService} from '../../service/toast.service'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-loan-detail-basic-info', | selector: 'app-loan-detail-basic-info', | ||||
| public errorMessage=''; | public errorMessage=''; | ||||
| constructor( private service: LoanSingleService, | constructor( private service: LoanSingleService, | ||||
| private notificationService: NotificationService, | |||||
| private toast: ToastService, | |||||
| public lenderNameService: LenderNameService) { | public lenderNameService: LenderNameService) { | ||||
| } | } | ||||
| this.Loan.Id = resp.Id; | this.Loan.Id = resp.Id; | ||||
| } | } | ||||
| this.showSuccess(this.Loan.Id + ' saved successfully'); | |||||
| this.toast.showSuccess(this.Loan.Id + ' saved successfully'); | |||||
| }, | }, | ||||
| err => { | err => { | ||||
| this.errorMessage = err instanceof HttpErrorResponse ? err.statusText : 'unknown error occured'; | this.errorMessage = err instanceof HttpErrorResponse ? err.statusText : 'unknown error occured'; | ||||
| this.showError(this.errorMessage); | |||||
| this.toast.showError(this.errorMessage); | |||||
| } | } | ||||
| ); | ); | ||||
| } | } | ||||
| this.Loan.Description = DemoDescription; | 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, | |||||
| }); | |||||
| } | |||||
| } | } |
| <p>loan-detail-people works! {{ Loan.Id}}</p> | |||||
| <form #form="ngForm"> | |||||
| <div class="container"> | |||||
| <div class="row"> | |||||
| <div class="col-sm-6"> | |||||
| <h5 class="title">Select Clients</h5> | |||||
| <kendo-multiselect name="client" [data]="AllPeople" [textField]="'Display'" | |||||
| [valueField]="'Id'" [(ngModel)]="this.Loan.Client" [virtual]="virtual" required="true" | |||||
| (close)="rebuildPeopleMap($event)"> | |||||
| <ng-template kendoMultiSelectItemTemplate let-dataItem> | |||||
| <img class="contact-image" [src]="getContactImageUrl(dataItem.Id)" /> | |||||
| <span>{{ dataItem.Display}}</span> | |||||
| </ng-template> | |||||
| </kendo-multiselect> | |||||
| <div class="container"> | |||||
| <div class="row clientCardContainer justify-content-center"> | |||||
| <div *ngFor="let v of this.Loan.Client" class="col-sm-9"> | |||||
| <app-people-card [PeopleId]="v.Id"></app-people-card> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <div class="col-sm-6"> | |||||
| <h5 class="title">Assign Brokers</h5> | |||||
| <kendo-multiselect name="brokers" [data]="AllBrokers" [textField]="'Display'" | |||||
| [valueField]="'Id'" [(ngModel)]="this.Loan.Broker" [virtual]="virtual" | |||||
| (close)="rebuildPeopleMap($event)" | |||||
| > | |||||
| <ng-template kendoMultiSelectItemTemplate let-dataItem> | |||||
| <img class="contact-image" [src]="getContactImageUrl(dataItem.Id)" /> | |||||
| <span>{{ dataItem.First +' ' + dataItem.Last}}</span> | |||||
| </ng-template> | |||||
| </kendo-multiselect> | |||||
| <div class="container"> | |||||
| <div class="row brokerCardContainer justify-content-center"> | |||||
| <div *ngFor="let v of this.Loan.Broker" class="col-sm-9"> | |||||
| <app-people-card [PeopleId]="v.Id"></app-people-card> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| </div> | |||||
| <h5 class="title">Other Related</h5> | |||||
| <kendo-multiselect name="beneficiaries" [data]="AllPeople" [textField]="'Display'" | |||||
| [valueField]="'Id'" [(ngModel)]="this.Loan.OtherRewarder" [virtual]="virtual" | |||||
| (close)="rebuildPeopleMap($event)"> | |||||
| <ng-template kendoMultiSelectItemTemplate let-dataItem> | |||||
| <img class="contact-image" [src]="getContactImageUrl(dataItem.Id)" /> | |||||
| <span>{{ dataItem.First +' ' + dataItem.Last}}</span> | |||||
| </ng-template> | |||||
| </kendo-multiselect> | |||||
| <div class="container"> | |||||
| <div class="row beneficiaryCardContainer justify-content-center"> | |||||
| <div *ngFor="let v of this.Loan.OtherRewarder, let idx=index" class="col-sm-4"> | |||||
| <app-people-card [PeopleId]="v.Id"></app-people-card> | |||||
| <kendo-combobox [name]="'beneficiary_'+idx" [data]="roles" [allowCustom]="true" [(ngModel)]="role[v.Id]" | |||||
| class="beneficiaryRole" required="true"> </kendo-combobox> | |||||
| </div> | |||||
| </div> | |||||
| </div>` | |||||
| <bkp-divider-shadow-bottom></bkp-divider-shadow-bottom> | |||||
| </form> |
| .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; | |||||
| } |
| import {Component, Input, OnInit} from '@angular/core'; | |||||
| import {Component, Input, OnInit, QueryList, ViewChild, ViewChildren} from '@angular/core'; | |||||
| import {LoanModel} from '../../models/loan.model'; | 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({ | @Component({ | ||||
| selector: 'app-loan-detail-people', | selector: 'app-loan-detail-people', | ||||
| }) | }) | ||||
| export class LoanDetailPeopleComponent implements OnInit { | export class LoanDetailPeopleComponent implements OnInit { | ||||
| @Input() Loan: LoanModel; | @Input() Loan: LoanModel; | ||||
| constructor() { } | |||||
| @ViewChildren(ComboBoxComponent) cb!: QueryList<ComboBoxComponent>; | |||||
| @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 { | 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<ComboBoxComponent> ) => { | |||||
| 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); | |||||
| } | } | ||||
| } | } |
| </div> | </div> | ||||
| </kendo-expansionpanel> | </kendo-expansionpanel> | ||||
| <kendo-expansionpanel | |||||
| <kendo-expansionpanel *ngIf="false" | |||||
| [title]="'People'" | [title]="'People'" | ||||
| [subtitle]="Loan.Amount | currency " | [subtitle]="Loan.Amount | currency " | ||||
| [expanded]="false" | [expanded]="false" | ||||
| </div> | </div> | ||||
| </kendo-expansionpanel> | </kendo-expansionpanel> | ||||
| <kendo-expansionpanel | |||||
| <kendo-expansionpanel *ngIf="false" | |||||
| [title]="'Reward'" | [title]="'Reward'" | ||||
| [subtitle]="Loan.Amount | currency " | [subtitle]="Loan.Amount | currency " | ||||
| [expanded]="false" | [expanded]="false" | ||||
| </div> | </div> | ||||
| </kendo-expansionpanel> | </kendo-expansionpanel> | ||||
| <kendo-expansionpanel | |||||
| <kendo-expansionpanel *ngIf="false" | |||||
| [title]="'Progress'" | [title]="'Progress'" | ||||
| [subtitle]="Loan.Amount | currency " | [subtitle]="Loan.Amount | currency " | ||||
| [expanded]="false" | [expanded]="false" | ||||
| </div> | </div> | ||||
| </kendo-expansionpanel> | </kendo-expansionpanel> | ||||
| <kendo-expansionpanel | |||||
| <kendo-expansionpanel *ngIf="false" | |||||
| [title]="'Trail Income'" | [title]="'Trail Income'" | ||||
| [subtitle]="Loan.Amount | currency " | [subtitle]="Loan.Amount | currency " | ||||
| [expanded]="false" | [expanded]="false" |
| <ng-template kendoListViewHeaderTemplate> | <ng-template kendoListViewHeaderTemplate> | ||||
| <div class="header"> | <div class="header"> | ||||
| <div class="title">All Loans</div> | <div class="title">All Loans</div> | ||||
| <kendo-textbox | |||||
| <kendo-textbox *ngIf="false" | |||||
| placeholder="Filter items..." | placeholder="Filter items..." | ||||
| (valueChange)="handleFilterChange($event)" | (valueChange)="handleFilterChange($event)" | ||||
| > | > |
| const sort: Array <SortDescriptor> = [{dir: 'desc', field: 'Settlement'}]; | const sort: Array <SortDescriptor> = [{dir: 'desc', field: 'Settlement'}]; | ||||
| if ( this.ss.loggedIn.role.toLowerCase() == 'admin' || this.ss.loggedIn.role.toLowerCase() == 'manager' ){ | if ( this.ss.loggedIn.role.toLowerCase() == 'admin' || this.ss.loggedIn.role.toLowerCase() == 'manager' ){ | ||||
| const filter: CompositeFilterDescriptor = {logic: 'and', filters: [] }; | 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 | }else{ // load broker's loans | ||||
| const filter: CompositeFilterDescriptor = {logic: 'or', filters: [] }; | const filter: CompositeFilterDescriptor = {logic: 'or', filters: [] }; | ||||
| filter.filters.push({field:'client_ids', operator: 'contains', value: people.Id, ignoreCase: true}) | 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:'broker_ids', operator: 'contains', value: people.Id, ignoreCase: true}) | ||||
| filter.filters.push({field:'other_rewarder_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}); | |||||
| } | } | ||||
| } | } | ||||
| <div class="container" style="border-top-style: none; border-top-color: #ff0000; border-left: 10px groove #969696; padding: 10px; margin: 5px; background-color: #e9e9e9; box-shadow: inset 1 1 10 10;"> | |||||
| <div class="container single-row"> | |||||
| <div class="row" style="padding-bottom: 10px;"> | <div class="row" style="padding-bottom: 10px;"> | ||||
| <div class="col-md-4"> | <div class="col-md-4"> | ||||
| <h5>Overview<div *ngIf="Loading" class="spinner-border text-success" role="status"> | <h5>Overview<div *ngIf="Loading" class="spinner-border text-success" role="status"> | ||||
| <li class="list-group-item d-flex justify-content-between align-items-center"> | <li class="list-group-item d-flex justify-content-between align-items-center"> | ||||
| <div *ngFor="let p of Loan.Client, let idx=index "> | <div *ngFor="let p of Loan.Client, let idx=index "> | ||||
| <div *ngIf="Loan.ClientIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(Loan.ClientIds[idx])}"></div> | <div *ngIf="Loan.ClientIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(Loan.ClientIds[idx])}"></div> | ||||
| <div *ngIf="Loan.ClientIds.length >0" class="customer-name"> {{ p }}</div> | |||||
| <div *ngIf="Loan.ClientIds.length >0" class="customer-name"> {{ p.FullName }}</div> | |||||
| </div> | </div> | ||||
| <span class="badge badge-primary badge-pill">Client</span> | <span class="badge badge-primary badge-pill">Client</span> | ||||
| </li> | </li> | ||||
| <li class="list-group-item d-flex justify-content-between align-items-center"> | <li class="list-group-item d-flex justify-content-between align-items-center"> | ||||
| <div *ngFor="let p of Loan.Broker, let idx=index "> | <div *ngFor="let p of Loan.Broker, let idx=index "> | ||||
| <div *ngIf="Loan.BrokerIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(Loan.BrokerIds[idx])}"></div> | <div *ngIf="Loan.BrokerIds.length >0" class="customer-photo" [ngStyle]="{'background-image' : photoURL(Loan.BrokerIds[idx])}"></div> | ||||
| <div *ngIf="Loan.BrokerIds.length >0" class="customer-name"> {{ p }}</div> | |||||
| <div *ngIf="Loan.BrokerIds.length >0" class="customer-name"> {{ p.FullName }}</div> | |||||
| </div> | </div> | ||||
| <span class="badge badge-primary badge-pill">BDM</span> | <span class="badge badge-primary badge-pill">BDM</span> | ||||
| </li> | </li> | ||||
| <li class="list-group-item d-flex justify-content-between align-items-center"> | <li class="list-group-item d-flex justify-content-between align-items-center"> | ||||
| <div *ngFor="let p of Loan.OtherRewarder, let idx=index "> | <div *ngFor="let p of Loan.OtherRewarder, let idx=index "> | ||||
| <div *ngIf="Loan.OtherRewarderIds.length > 0 " class="customer-photo" [ngStyle]="{'background-image' : photoURL(Loan.OtherRewarderIds[idx])}"></div> | <div *ngIf="Loan.OtherRewarderIds.length > 0 " class="customer-photo" [ngStyle]="{'background-image' : photoURL(Loan.OtherRewarderIds[idx])}"></div> | ||||
| <div *ngIf="Loan.OtherRewarderIds.length > 0 " class="customer-name"> {{ p }}</div> | |||||
| <div *ngIf="Loan.OtherRewarderIds.length > 0 " class="customer-name"> {{ p.FullName }}</div> | |||||
| </div> | </div> | ||||
| <span class="badge badge-primary badge-pill">Other</span> | <span class="badge badge-primary badge-pill">Other</span> | ||||
| </li> | </li> | ||||
| <span class="badge badge-primary badge-pill">#</span> | <span class="badge badge-primary badge-pill">#</span> | ||||
| </li> | </li> | ||||
| <li class="list-group-item d-flex justify-content-between align-items-center"> | <li class="list-group-item d-flex justify-content-between align-items-center"> | ||||
| <button kendoButton (click)="gotoSteps()">Actions List</button> | |||||
| <button kendoButton (click)="editLoan()">Edit</button> | |||||
| <button kendoButton [togglable]="true" [icon]="editIcon" (selectedChange)="onShowLoanDetails($event)">Edit</button> | |||||
| <button kendoButton [togglable]="true" [icon]="stepIcon" (selectedChange)="onShowSteps($event)" >Steps</button> | |||||
| <button *ngIf="false" kendoButton (click)="editLoan()">Admin Edit</button> | |||||
| </li> | </li> | ||||
| </ul> | </ul> | ||||
| </div> | </div> | ||||
| <div class="col-md-12"> | |||||
| <div class="col-md-12" *ngIf="isShowDetail"> | |||||
| <app-loan-detail [Loan]="Loan"></app-loan-detail> | <app-loan-detail [Loan]="Loan"></app-loan-detail> | ||||
| </div> | </div> | ||||
| <div class="col-md-12" *ngIf="isShowSteps"> | |||||
| <app-loan-steps [Loan]="Loan"> </app-loan-steps> | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | </div> |
| line-height: 16px; | line-height: 16px; | ||||
| padding-left: 10px; | 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; | |||||
| } |
| export class LoanSingleRowComponent implements OnInit { | export class LoanSingleRowComponent implements OnInit { | ||||
| @Input() public Loan: LoanModel = new LoanModel({}); | @Input() public Loan: LoanModel = new LoanModel({}); | ||||
| @Input() public Loading: boolean = false; | @Input() public Loading: boolean = false; | ||||
| @Input() public isShowDetail = false; | |||||
| @Input() public isShowSteps = false; | |||||
| public editIcon = 'edit'; | |||||
| public stepIcon = 'aggregate-fields'; | |||||
| constructor(private service: LoanSummaryService, | constructor(private service: LoanSummaryService, | ||||
| private auth: AuthService, | private auth: AuthService, | ||||
| private router: Router, | private router: Router, | ||||
| private notificationService: NotificationService) { } | private notificationService: NotificationService) { } | ||||
| ngOnInit(): void { | ngOnInit(): void { | ||||
| } | } | ||||
| public editLoan() { | public editLoan() { | ||||
| this.router.navigate(['edit-loan/', this.Loan.Id]).then(); | 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': ''; | |||||
| } | |||||
| } | } |
| {{LoanId}} | |||||
| <kendo-listview | |||||
| [data]="steps" | |||||
| containerClass="k-d-flex k-flex-col k-flex-nowrap" | |||||
| > | |||||
| <ng-template | |||||
| kendoListViewItemTemplate | |||||
| let-dataItem="dataItem" | |||||
| let-index="index" | |||||
| let-isFirst="isFirst" | |||||
| let-isLast="isLast" | |||||
| > | |||||
| <div class="product" [class.border-bottom]="!isLast"> | |||||
| <strong>[{{ index }}]:</strong> | |||||
| <span class="product-name">{{ dataItem.label }}</span> | |||||
| <span class="item-position" | |||||
| >({{ isFirst ? "first" : isLast ? "last" : "mid" }})</span | |||||
| > | |||||
| </div> | |||||
| </ng-template> | |||||
| </kendo-listview> | |||||
| <kendo-tilelayout [columns]="1" [gap]="gap" [reorderable]="true"> | |||||
| <kendo-tilelayout-item [col]="1" title="Tile 1"> | |||||
| <kendo-tilelayout-item-body> | |||||
| {{ firstTileContent }} | |||||
| </kendo-tilelayout-item-body> | |||||
| </kendo-tilelayout-item> | |||||
| <kendo-tilelayout-item [col]="1" title="Tile 2"> | |||||
| <kendo-tilelayout [columns]="1" [gap]="gap" [reorderable]="false"> | |||||
| <span *ngIf="Loan.Steps.length <=0">No action available, administrator will add new actions</span> | |||||
| <kendo-tilelayout-item *ngFor="let step of Loan.Steps; let i=index" [col]="1" | |||||
| [title]="title(step)"> | |||||
| <kendo-tilelayout-item-body> | <kendo-tilelayout-item-body> | ||||
| {{ secondTileContent }} | |||||
| </kendo-tilelayout-item-body> | |||||
| </kendo-tilelayout-item> | |||||
| <form class="k-form" #stepForm="ngForm" > | |||||
| <div class="row" *ngIf="!step.Done"> | |||||
| <div class="col-sm-1"> | |||||
| <kendo-formfield> | |||||
| <kendo-label [for]="done" text="Done"></kendo-label> | |||||
| <kendo-switch [(ngModel)]="step.Done" [ngModelOptions]="{standalone: true}" | |||||
| (valueChange)="onResolveStep(step, $event)" | |||||
| [disabled]="!step.Editable && !isAdmin" | |||||
| onLabel="Yes" | |||||
| offLabel="No"></kendo-switch> | |||||
| </kendo-formfield> | |||||
| </div> | |||||
| <div class="col-sm-3"> | |||||
| <kendo-formfield> | |||||
| <kendo-label [for]="description" text="Description:"></kendo-label> | |||||
| <input kendoTextBox #description name="description" | |||||
| [(ngModel)]="step.Description" [ngModelOptions]="{standalone: true}" | |||||
| [maxlength]="301" [required]="true" | |||||
| [readOnly]="step.Done" | |||||
| [placeholder]="'upload passport'" | |||||
| [disabled]="!step.Editable && !isAdmin" | |||||
| /> | |||||
| <kendo-formerror> required, and 0 < length < 300 </kendo-formerror> | |||||
| </kendo-formfield> | |||||
| </div> | |||||
| <kendo-tilelayout-item title="Tile 3" [col]="1"> | |||||
| <kendo-tilelayout-item-body> | |||||
| {{ thirdTileContent }} | |||||
| </kendo-tilelayout-item-body> | |||||
| </kendo-tilelayout-item> | |||||
| <div class="col-sm-2"> | |||||
| <kendo-formfield> | |||||
| <kendo-label [for]="receviedAt" text="ReceivedAt:"></kendo-label> | |||||
| <kendo-datepicker #receviedAt name="receviedAt" | |||||
| [readOnly]="step.Done" | |||||
| [disabled]="!step.Editable && !isAdmin" | |||||
| [(ngModel)]="step.ReceivedAt" [ngModelOptions]="{standalone: true}" | |||||
| > | |||||
| </kendo-datepicker> | |||||
| </kendo-formfield> | |||||
| </div> | |||||
| <kendo-tilelayout-item title="Tile 4" [col]="1"> | |||||
| <kendo-tilelayout-item-body> | |||||
| {{ fourthTileContent }} | |||||
| <div class="col-sm-2"> | |||||
| <kendo-formfield> | |||||
| <kendo-label [for]="resolvedAt" text="ResolvedAt:"></kendo-label> | |||||
| <kendo-datepicker #resolvedAt name="resolvedAt" | |||||
| [readOnly]="step.Done" | |||||
| [disabled]="!step.Editable && !isAdmin" | |||||
| [(ngModel)]="step.ResolvedAt" [ngModelOptions]="{standalone: true}" | |||||
| > | |||||
| </kendo-datepicker> | |||||
| </kendo-formfield> | |||||
| </div> | |||||
| <div *ngIf="step.FileName !=='' " class="col-sm-4 align-right attachment"><span class="step-text"> | |||||
| {{step.FileName}} <br> | |||||
| Size: ( {{step.FileSize | filesize }} ) Uploaded at: {{step.UploadedAt | date:"yyyy-MMM-dd"}} <br> | |||||
| </span> | |||||
| <button [disabled]="!step.Editable && !isAdmin" kendoButton class="download_attach" (click)="onDownload(step)" [icon]="'download'"></button> | |||||
| <button [disabled]="!step.Editable && !isAdmin" kendoButton class="remove-attach" (click)="onRemoveAttach(step)" [icon]="'trash'"></button> | |||||
| </div> | |||||
| <div *ngIf="step.FileName ==='' " class="col-sm-4"> | |||||
| <kendo-upload class="bottom" [saveUrl]="uploadUrl(step)" | |||||
| [disabled]="!step.Editable && !isAdmin" | |||||
| [showFileList]="false" (success)="onSuccess($event)"> </kendo-upload> | |||||
| </div> | |||||
| </div> | |||||
| <div class="row align-bottom" *ngIf="step.Done"> | |||||
| <div class="col-sm-1"> | |||||
| <kendo-formfield> | |||||
| <kendo-label [for]="done" text="Done"></kendo-label> | |||||
| <kendo-switch [(ngModel)]="step.Done" [ngModelOptions]="{standalone: true}" | |||||
| (valueChange)="onResolveStep(step, $event)" | |||||
| [disabled]="!step.Editable && !isAdmin" | |||||
| onLabel="Yes" | |||||
| offLabel="No"></kendo-switch> | |||||
| </kendo-formfield> | |||||
| </div> | |||||
| <div class="col-sm-3"><span class="step-text"> {{step.Description}} </span></div> | |||||
| <div class="col-sm-2"><span class="step-text"> Receive: {{step.ReceivedAt | date:"yyyy-MMM-dd"}} </span></div> | |||||
| <div class="col-sm-2"><span class="step-text"> Resolve: {{step.ResolvedAt | date:"yyyy-MMM-dd"}} </span></div> | |||||
| <div class="col-sm-4" *ngIf="step.FileName !== ''"> | |||||
| <span class="step-text"> | |||||
| {{step.FileName}} | |||||
| <button kendoButton class="download-readonly" (click)="onDownload(step)" [icon]="'download'"></button> | |||||
| </span> | |||||
| </div> | |||||
| <div class="col-sm-4" *ngIf="step.FileName === ''"> <span class="step-text"> No file attached </span> </div> | |||||
| </div> | |||||
| </form> | |||||
| <button *ngIf="isAdmin" kendoButton [icon]="'close-outline'" class="delete" (click)="onDeleteStep(step)"></button> | |||||
| <button [disabled]="!step.Editable && !isAdmin" kendoButton [icon]="'save'" class="save" (click)="onSaveStep(step)"></button> | |||||
| </kendo-tilelayout-item-body> | </kendo-tilelayout-item-body> | ||||
| </kendo-tilelayout-item> | </kendo-tilelayout-item> | ||||
| <button *ngIf="isAdmin" kendoButton class="add-step" [icon]="'plus'" (click)="onAddNewStep()"> </button> | |||||
| </kendo-tilelayout> | </kendo-tilelayout> | ||||
| <a #downloadLink ></a> |
| 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; | |||||
| } | |||||
| } |
| import { Component, OnInit } from '@angular/core'; | |||||
| import {Component, ElementRef, Input, OnInit, ViewChild} from '@angular/core'; | |||||
| import {LoanSingleService} from '../service/loan.single.service'; | import {LoanSingleService} from '../service/loan.single.service'; | ||||
| import {ActivatedRoute, Router} from '@angular/router'; | import {ActivatedRoute, Router} from '@angular/router'; | ||||
| import {AuthService} from '../service/auth.service'; | import {AuthService} from '../service/auth.service'; | ||||
| import { TileLayoutGap } from '@progress/kendo-angular-layout'; | 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({ | @Component({ | ||||
| selector: 'app-loan-steps', | selector: 'app-loan-steps', | ||||
| styleUrls: ['./loan-steps.component.scss'] | styleUrls: ['./loan-steps.component.scss'] | ||||
| }) | }) | ||||
| export class LoanStepsComponent implements OnInit { | 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<HTMLAnchorElement>; | |||||
| public gap: TileLayoutGap = { | public gap: TileLayoutGap = { | ||||
| rows: 25, | rows: 25, | ||||
| columns: 40 | 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 { | 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; | |||||
| } | |||||
| } | } |
| items: [ | items: [ | ||||
| { text: 'Start New Loan', icon: 'plus', url: './#edit-loan/' }, | { text: 'Start New Loan', icon: 'plus', url: './#edit-loan/' }, | ||||
| { text: 'Export Loans', icon: 'table' , url: './#list-all-loans' }, | { 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: '--', separator: 'true' }, | ||||
| { text: 'list income', icon: 'dollar', url: './#list-income' }, | { text: 'list income', icon: 'dollar', url: './#list-income' }, | ||||
| { text: '--', separator: 'true' }, | { text: '--', separator: 'true' }, |
| import {AuthService} from '../service/auth.service'; | import {AuthService} from '../service/auth.service'; | ||||
| import {LoanSingleService} from '../service/loan.single.service'; | import {LoanSingleService} from '../service/loan.single.service'; | ||||
| import {MilestoneModel} from './milestone.model'; | import {MilestoneModel} from './milestone.model'; | ||||
| import {StepModel} from './step.model'; | |||||
| export interface LoanModelCallBacks { | export interface LoanModelCallBacks { | ||||
| getUserName(userId: string): string; | getUserName(userId: string): string; | ||||
| public SettlementBooked: Date; | public SettlementBooked: Date; | ||||
| public SettlementCompleted: Date; | public SettlementCompleted: Date; | ||||
| public Steps: StepModel[]; | |||||
| constructor(payload: Partial<LoanModel>) { | constructor(payload: Partial<LoanModel>) { | ||||
| this.Id = payload.Id || ''; | this.Id = payload.Id || ''; | ||||
| this.Amount = payload.Amount || 0; | this.Amount = payload.Amount || 0; | ||||
| this.FormalApproved= this.getDate(payload.FormalApproved); | this.FormalApproved= this.getDate(payload.FormalApproved); | ||||
| this.SettlementBooked= this.getDate(payload.SettlementBooked); | this.SettlementBooked= this.getDate(payload.SettlementBooked); | ||||
| this.SettlementCompleted= this.getDate(payload.SettlementCompleted); | 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 { | public static EmptyNew(lss: LoanSingleService, auth: AuthService): LoanModel { |
| 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<StepModel>) { | |||||
| 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 | |||||
| } | |||||
| } |
| <button #enabled *ngIf="contact.Id.length > 10" kendoButton (selectedChange)="onSelectedChange($event)" look="outline" icon="tick" | <button #enabled *ngIf="contact.Id.length > 10" kendoButton (selectedChange)="onSelectedChange($event)" look="outline" icon="tick" | ||||
| [toggleable]="true" [selected]="contact.Enabled" | [toggleable]="true" [selected]="contact.Enabled" | ||||
| >Enable</button> | >Enable</button> | ||||
| <button *ngIf="UserExtra.Login !=''" kendoButton (click)="onEmailPassword()" look="outline" icon="email">Email Password</button> | |||||
| <!-- <button kendoButton (click)="onEditContact()" look="outline" icon="edit">Contacts</button>--> | <!-- <button kendoButton (click)="onEditContact()" look="outline" icon="edit">Contacts</button>--> | ||||
| <div *ngIf="sendingEmail" class="email-sending-indicator"> | |||||
| <span>To...{{UserExtra.Login}}</span> | |||||
| <kendo-loader [type]="'pulsing'" [themeColor]="'primary'" [size]="'small'" ></kendo-loader> | |||||
| </div> | |||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| </div> | </div> | ||||
| flex-direction: row; | flex-direction: row; | ||||
| justify-content: center; | justify-content: center; | ||||
| align-items: center; | align-items: center; | ||||
| div.email-sending-indicator{ | |||||
| text-align:center; | |||||
| font-size: 10px; | |||||
| font-color: black; | |||||
| width: 100% | |||||
| } | |||||
| } | } | ||||
| .social-media-wrapper { | .social-media-wrapper { |
| import {Router} from '@angular/router'; | import {Router} from '@angular/router'; | ||||
| import {UserExtraModel} from '../models/user-extra.model'; | import {UserExtraModel} from '../models/user-extra.model'; | ||||
| import {BadgeAlign} from '@progress/kendo-angular-indicators'; | import {BadgeAlign} from '@progress/kendo-angular-indicators'; | ||||
| import {ToastService} from '../service/toast.service'; | |||||
| @Component({ | @Component({ | ||||
| selector: 'app-people-card', | selector: 'app-people-card', | ||||
| public UserExtra: UserExtraModel = UserExtraModel.EmptyNew(); | public UserExtra: UserExtraModel = UserExtraModel.EmptyNew(); | ||||
| public badgeAlign: BadgeAlign = { vertical: 'bottom', horizontal: 'end' }; | 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 { | ngOnInit(): void { | ||||
| this.ps.getPeopleById(this.PeopleId).subscribe( | this.ps.getPeopleById(this.PeopleId).subscribe( | ||||
| this.contact.Enabled = resp; | 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 ) | |||||
| } | |||||
| ); | |||||
| } | |||||
| } | } |
| 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; | |||||
| } | |||||
| } |
| 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<StepModel>{ | |||||
| return this.http.post<StepModel>(this.cfg.getUrl('step-id/'), {LoanId,StepIndex}); | |||||
| } | |||||
| public SaveStep(step: StepModel): Observable<StepModel>{ | |||||
| return this.http.post<StepModel>(this.cfg.getUrl('step-meta-update/'), step); | |||||
| } | |||||
| public DeleteStep(stepId: string): Observable<boolean> { | |||||
| return this.http.delete<boolean>(this.cfg.getUrl('step/'+ stepId)); | |||||
| } | |||||
| public DeleteStepFileOnly(stepId: string): Observable<boolean> { | |||||
| return this.http.delete<boolean>(this.cfg.getUrl('step-file/'+ stepId)); | |||||
| } | |||||
| } |
| } | } | ||||
| public query(state: any): void { | 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 => { | this.fetch(this.tableName, state).subscribe(x => { | ||||
| var ret: GridDataResult = {total: x.total, data:[]}; | var ret: GridDataResult = {total: x.total, data:[]}; | ||||
| x.data.forEach( v => { | x.data.forEach( v => { |
| return this.http.post<UserExModel>(this.auth.getUrl('user-ex/'), uex); | return this.http.post<UserExModel>(this.auth.getUrl('user-ex/'), uex); | ||||
| } | } | ||||
| public EmailPassword(id: string): Observable<boolean> { | |||||
| return this.http.post<boolean>(this.auth.getUrl('email-password/' + id), id); | |||||
| } | |||||
| } | } |
| 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, | |||||
| }); | |||||
| } | |||||
| } |