| @@ -55,4 +55,21 @@ export class AppConfig { | |||
| return AppConfig.config.Socket; | |||
| } | |||
| public getUrl(key: string): string{ | |||
| const s = this.apiUrl + key; | |||
| const kvPair: {key: string, value: string}[] = [ | |||
| {key: 'login', value: this.apiUrl + 'login'}, | |||
| {key: 'logout', value: this.apiUrl + 'logout'} | |||
| ]; | |||
| kvPair.forEach( item => { | |||
| if ( item.key === key) { | |||
| return item.value; | |||
| } | |||
| }); | |||
| // not found if arrive here | |||
| return s; | |||
| } | |||
| } | |||
| @@ -96,6 +96,7 @@ import { ImagePopupDialogComponent } from './image-popup-dialog/image-popup-dial | |||
| import { PopupIncomeFilterComponent } from './popup-income-filter/popup-income-filter.component'; | |||
| import { LoanCardComponent } from './loan-card/loan-card.component'; | |||
| import {AppConfig} from './app.config'; | |||
| import { UploadingProgressCardComponent } from './uploading-progress-card/uploading-progress-card.component'; | |||
| @@ -162,7 +163,8 @@ export function initializeApp(appConfig: AppConfig): () => Promise<void> { | |||
| SafeUrlPipe, | |||
| ImagePopupDialogComponent, | |||
| PopupIncomeFilterComponent, | |||
| LoanCardComponent | |||
| LoanCardComponent, | |||
| UploadingProgressCardComponent | |||
| ], | |||
| imports: [ | |||
| BrowserModule, | |||
| @@ -23,9 +23,9 @@ export class ChartPastYearMonthlyPerformanceComponent implements OnInit { | |||
| private getPastYearMonthly(): Observable<PastYearMonthlyData[]> { | |||
| return this.http.get<PastYearMonthlyData[]>(this.auth.getUrl('chart/past-year-monthly')).pipe(map( | |||
| data => { | |||
| let ret = []; | |||
| const ret = []; | |||
| data.forEach( (value: PastYearMonthlyData , index: number ) => { | |||
| let a = { | |||
| const a = { | |||
| Period: value.Period, | |||
| Amount: value.Amount / 1000, | |||
| Summary: '', | |||
| @@ -9,8 +9,8 @@ | |||
| </kendo-grid-column> | |||
| <kendo-grid-column field="License" title="License" width="80"> | |||
| </kendo-grid-column> | |||
| <kendo-grid-column field="Login" title="Login" width="80"> | |||
| </kendo-grid-column> | |||
| <!-- <kendo-grid-column field="Login" title="Login" width="80">--> | |||
| <!-- </kendo-grid-column>--> | |||
| <kendo-grid-column field="Organization" title="Organization" width="80"> | |||
| </kendo-grid-column> | |||
| <kendo-grid-column field="NumOfLoans" title="Num Of Loans" width="50" [style]="{'text-align':'center'}" > | |||
| @@ -28,10 +28,10 @@ export class ChartTypeOfLoansComponent implements OnInit { | |||
| return this.http.get<TypeOfLoansModel[]>(this.auth.getUrl('chart/type-of-loans')).pipe( | |||
| map( | |||
| input => { | |||
| let ret = []; | |||
| const ret = []; | |||
| input.forEach( (value, index) =>{ | |||
| ret.push({ | |||
| Kind: value.Kind + ' ' + (value.Share * 100) + '%', | |||
| Kind: value.Kind + ' ' + (value.Share * 100).toFixed(2) + '%', | |||
| Count: value.Count, | |||
| Share: value.Share, | |||
| Amount: value.Amount | |||
| @@ -5,6 +5,7 @@ import {AuthService} from '../service/auth.service'; | |||
| import {PeopleService} from '../service/people.service'; | |||
| import {FileInfo, FileRestrictions, FileSelectComponent, SelectEvent} from '@progress/kendo-angular-upload'; | |||
| import {BrokerModel} from '../models/broker.model'; | |||
| import {AppConfig} from '../app.config'; | |||
| @Component({ | |||
| selector: 'app-client-profile', | |||
| @@ -14,7 +15,7 @@ import {BrokerModel} from '../models/broker.model'; | |||
| export class ClientProfileComponent implements OnInit { | |||
| @Input() User: PeopleModel = PeopleModel.EmptyNew(); | |||
| @ViewChild('fileSelect', {static: true}) fs: FileSelectComponent; | |||
| public avatarUrl = 'url(https://svr2021.lawipac.com:8080/api/v1/avatar/1000)' ; | |||
| public avatarUrl = '' ; | |||
| public myRestrictions: FileRestrictions = { | |||
| allowedExtensions: ['.jpg', '.png', '.jpeg'], | |||
| maxFileSize: 2194304 | |||
| @@ -23,7 +24,9 @@ export class ClientProfileComponent implements OnInit { | |||
| public opened = false; // dialog box | |||
| public Message = ''; // dialog message | |||
| constructor(private auth: AuthService, private ps: PeopleService) { } | |||
| constructor(private auth: AuthService, private ps: PeopleService) { | |||
| this.avatarUrl = 'url("' + location.origin + './assets/img/avatar.png' + '")'; | |||
| } | |||
| ngOnInit(): void { | |||
| @@ -1,32 +1,43 @@ | |||
| <div class="workarea"> | |||
| <div class="file-manager-bar"> | |||
| <div class="upload-area"> | |||
| <kendo-upload class="uploadfiles" [concurrent]="false" [restrictions]="myRestrictions" | |||
| <kendo-upload class="uploadfiles" [concurrent]="true" [restrictions]="myRestrictions" | |||
| (success)="onSuccess($event)" | |||
| (upload)="onUpload($event)" | |||
| (upload)="onStartUpload($event)" | |||
| (error)="onUploadError($event)" | |||
| (complete)="onComplete()" | |||
| (uploadProgress)="uploadProgress($event)" | |||
| [showFileList]="false" | |||
| [saveUrl]="uploadSaveUrl"> | |||
| <ng-template kendoUploadFileInfoTemplate let-files> | |||
| <div *ngIf="files!==undefined && files!== null && files[0] !== null"> | |||
| <div (click)="onClick(files)">Name: {{ files[0].name }}</div> | |||
| <div *ngIf="files[0].validationErrors !== undefined"> Cannot upload this file {{files[0]}}</div> | |||
| <div *ngIf="map.get(files[0].uid) !== undefined"> fuck all {{map.get(files[0].uid).response.body.Funder}} </div> | |||
| </div> | |||
| </ng-template> | |||
| <!-- <ng-template kendoUploadFileInfoTemplate let-files>--> | |||
| <!-- <div *ngIf="files!==undefined && files!== null && files[0] !== null">--> | |||
| <!-- <div (click)="onClick(files)">Name: {{ files[0].name }}</div>--> | |||
| <!-- <div *ngIf="files[0].validationErrors !== undefined"> Cannot upload this file {{files[0]}}</div>--> | |||
| <!-- <div *ngIf="map.get(files[0].uid) !== undefined"> fuck all {{map.get(files[0].uid).response.body.Funder}} </div>--> | |||
| <!-- </div>--> | |||
| <!-- </ng-template>--> | |||
| </kendo-upload> | |||
| </div> | |||
| <div class="search-area"> | |||
| <kendo-textbox class='search-people' [placeholder]="'Type to search/filter'" (valueChange)="onFilterUploads($event)" > </kendo-textbox> | |||
| <kendo-textbox class='search-people' [placeholder]="'Type to search/filter'" | |||
| [(ngModel)]="strFilter" | |||
| (valueChange)="onFilterUploads($event)" > </kendo-textbox> | |||
| <kendo-icon name="search" size="medium"></kendo-icon> | |||
| </div> | |||
| </div> | |||
| <bkp-divider-shadow-bottom></bkp-divider-shadow-bottom> | |||
| <div class="container"> | |||
| <div *ngIf="!loading" class="row justify-content-start"> | |||
| <div class="col-lg-3 text-center" *ngFor="let p of displayedUploads"> | |||
| <app-upload-cards [uploadId]="p"></app-upload-cards> | |||
| <div class="col-lg-3 text-center" *ngFor="let p of allUploads" [ngClass]="{NewUpload: p.IsNew}"> | |||
| <app-upload-cards *ngIf="!p.Uploading" [uploadId]="p.Meta.Id" (deleted)="onDelete($event)"></app-upload-cards> | |||
| <app-uploading-progress-card *ngIf="p.Uploading" | |||
| [MetaId]="p.Info.MetaId" | |||
| [FileName]="p.Info.FileName" | |||
| [Progress]="p.Info.Progress" | |||
| [UniqueId]="p.Info.UniqueId" | |||
| [IsDuplicate]="p.IsDuplicate" | |||
| (Complete)="onCompleteSingle($event)" | |||
| ></app-uploading-progress-card> | |||
| </div> | |||
| </div> | |||
| <div *ngIf="loading" class="row justify-content-center"> | |||
| @@ -1,11 +1,21 @@ | |||
| import {AfterViewInit, Component, ElementRef, EventEmitter, OnInit, Output, ViewChild, ViewEncapsulation} from '@angular/core'; | |||
| import {FileInfo, FileRestrictions, SuccessEvent, UploadComponent, UploadEvent, UploadProgressEvent} from '@progress/kendo-angular-upload'; | |||
| import {Component, EventEmitter, OnInit, Output} from '@angular/core'; | |||
| import {FileInfo, FileRestrictions, SuccessEvent, UploadEvent, UploadProgressEvent} from '@progress/kendo-angular-upload'; | |||
| import {AuthService} from '../service/auth.service'; | |||
| import {range} from '@progress/kendo-angular-dateinputs/dist/es2015/util'; | |||
| import {debounce} from 'ts-debounce'; | |||
| import {Router} from '@angular/router'; | |||
| import {PageChangeEvent} from '@progress/kendo-angular-pager'; | |||
| import {ImagePopupDialogComponent} from '../image-popup-dialog/image-popup-dialog.component'; | |||
| import {UploadAttachService} from '../service/upload.attach.service'; | |||
| import {AppConfig} from '../app.config'; | |||
| import {UploadMetalList, UploadMetaModel} from '../models/uploadMetaModel'; | |||
| import {UploadingInfoModel} from '../models/uploading.info.model'; | |||
| class UploadingCards{ | |||
| Uploading = false; | |||
| IsDuplicate = false; | |||
| IsNew = false; | |||
| Info: UploadingInfoModel = new UploadingInfoModel({}); | |||
| Meta: UploadMetaModel = new UploadMetaModel({}); | |||
| } | |||
| @Component({ | |||
| selector: 'app-lender-uploads', | |||
| @@ -13,79 +23,113 @@ import {UploadAttachService} from '../service/upload.attach.service'; | |||
| styleUrls: ['./lender-uploads.component.scss'], | |||
| }) | |||
| export class LenderUploadsComponent implements OnInit { | |||
| @Output() success: EventEmitter<SuccessEvent> = new EventEmitter<SuccessEvent>(); | |||
| @Output() click: EventEmitter<FileInfo> = new EventEmitter<FileInfo>(); | |||
| @Output() complete: EventEmitter<FileInfo> = new EventEmitter<FileInfo>(); | |||
| @Output() uploadSuccessful: EventEmitter<SuccessEvent> = new EventEmitter<SuccessEvent>(); | |||
| @Output() uploadClick: EventEmitter<FileInfo> = new EventEmitter<FileInfo>(); | |||
| @Output() uploadComplete: EventEmitter<FileInfo> = new EventEmitter<FileInfo>(); | |||
| @Output() upload: EventEmitter<boolean> = new EventEmitter<boolean>(); | |||
| private uploads: SuccessEvent[] = []; | |||
| public value = 0 ; | |||
| public map: Map<string, SuccessEvent> = new Map<string, SuccessEvent>(); | |||
| public loading = false; | |||
| uploadSaveUrl = 'https://svr2021.lawipac.com:8080/api/v1/lender-upload/'; // should represent an actual API endpoint | |||
| uploadRemoveUrl = 'https://svr2021.lawipac.com:8080/api/v1/lender-upload-remove/'; // should represent an actual API endpoint | |||
| uploadSaveUrl = ''; // should represent an actual API endpoint | |||
| uploadRemoveUrl = ''; // should represent an actual API endpoint | |||
| public allUploads = [...range(30, 44 )]; | |||
| public displayedUploads: any[] = []; | |||
| public filteredUploads: any[] = []; | |||
| public allUploads: UploadingCards[] = []; | |||
| public skip = 0; | |||
| public pageSize = 12; | |||
| public total = 0; | |||
| public strFilter = ''; | |||
| private debouncedSearch: any; | |||
| myRestrictions: FileRestrictions = { | |||
| allowedExtensions: ['.pdf', '.xls', '.xlsx'] | |||
| allowedExtensions: ['.pdf', '.xls', '.xlsx'], | |||
| maxFileSize: 30240000, | |||
| minFileSize: 1000, | |||
| }; | |||
| constructor(private auth: AuthService, private router: Router, private uas: UploadAttachService) { | |||
| constructor(private auth: AuthService, private router: Router, private uas: UploadAttachService, private config: AppConfig) { | |||
| this.uploadSaveUrl = this.config.apiUrl + 'lender-upload/'; | |||
| this.uploadRemoveUrl = this.config.apiUrl + 'lender-upload-remove/'; | |||
| } | |||
| ngOnInit(): void { | |||
| this.debouncedSearch = debounce(this.loadDisplayRecords, 500); | |||
| this.uploadSaveUrl = this.auth.getUrl('lender-upload/'); | |||
| this.uploadRemoveUrl = this.auth.getUrl('lender-upload-remove/'); | |||
| this.loadDisplayedUploads(); | |||
| this.loadAllUploads(); | |||
| } | |||
| public onClick( files: any): void{ | |||
| this.click.emit(files[0]); | |||
| this.uploadClick.emit(files[0]); | |||
| } | |||
| public onSuccess(ss: SuccessEvent ): void { | |||
| this.uploads.push(ss); | |||
| this.map.set(ss.files[0].uid, ss); | |||
| this.success.emit(ss); | |||
| this.allUploads.unshift(this.allUploads.length + 100); | |||
| this.uploadSuccessful.emit(ss); | |||
| const idx = this.uid2Idx(ss.files[0].uid); | |||
| this.allUploads[idx].Meta = new UploadMetaModel(ss.response.body); | |||
| this.allUploads[idx].Info.MetaId = this.allUploads[idx].Meta.Id; // trigger analysis | |||
| this.allUploads[idx].IsDuplicate = ss.response.body.IsDuplicate; | |||
| // remove card | |||
| const uid = ss.files[0].uid; | |||
| if (this.allUploads[idx].IsDuplicate ) { | |||
| this.allUploads[idx].Info.Progress = 100; | |||
| const timer = setInterval(() => { | |||
| this.allUploads[idx].Info.Progress -= this.randBetween(1, 10); // when many cards to be removed, it's better not altogether | |||
| if (this.allUploads[idx].Info.Progress <= 0 ) { | |||
| clearInterval(timer); | |||
| this.allUploads = this.allUploads.filter(v => { | |||
| return v.Info.UniqueId !== uid; | |||
| }); | |||
| } | |||
| }, 100); | |||
| } | |||
| } | |||
| public onUpload(ss: UploadEvent ): void { | |||
| this.upload.emit(true); | |||
| private randBetween(start: number, end: number): number { | |||
| return Math.floor(Math.random() * end) + start; | |||
| } | |||
| private uid2Idx(uid: string): number { | |||
| return this.allUploads.findIndex( v => { | |||
| return v.Info.UniqueId === uid; | |||
| }); | |||
| } | |||
| public onStartUpload(ss: UploadEvent ): void { | |||
| const uc = new UploadingCards(); | |||
| uc.Uploading = true; | |||
| uc.IsDuplicate = false; | |||
| uc.IsNew = true; | |||
| uc.Info.UniqueId = ss.files[0].uid; | |||
| uc.Info.FileName = ss.files[0].name; | |||
| uc.Info.Progress = 0; | |||
| uc.Info.MetaId = 0 ; | |||
| this.allUploads.unshift(uc); | |||
| } | |||
| public onComplete(): void { | |||
| this.upload.emit(false); | |||
| console.log('ok completed'); | |||
| } | |||
| public hasResp(uid): boolean { | |||
| let found = false; | |||
| this.uploads.every(v => { | |||
| if ( v.files[0].uid === uid ) { | |||
| found = true; | |||
| return false; // stop search | |||
| } | |||
| }); | |||
| return found; | |||
| public onUploadError(e: any): void { | |||
| console.log(e); | |||
| } | |||
| public uploadProgress(prog: UploadProgressEvent): void { | |||
| this.value = prog.percentComplete.valueOf(); | |||
| prog.files.forEach( v => { | |||
| const idx = this.uid2Idx(v.uid); | |||
| this.allUploads[idx].Info.Progress = prog.percentComplete.valueOf(); | |||
| // console.log(prog.percentComplete.valueOf(), v.uid, v); | |||
| } ); | |||
| } | |||
| public show( i: number): void{ | |||
| this.router.navigate(['/upload-details/' + i]); | |||
| } | |||
| @@ -94,29 +138,49 @@ export class LenderUploadsComponent implements OnInit { | |||
| public onPageChange(e: PageChangeEvent): void { | |||
| this.skip = e.skip; | |||
| this.pageSize = e.take; | |||
| this.loadDisplayedUploads(); | |||
| this.loadDisplayRecords(); | |||
| } | |||
| private loadDisplayedUploads(): void { | |||
| this.filteredUploads = this.allUploads ; | |||
| this.displayedUploads = this.filteredUploads.slice(this.skip, this.skip + this.pageSize); | |||
| this.total = this.displayedUploads.length; | |||
| private loadAllUploads(): void{ | |||
| this.skip = 0; | |||
| this.onFilterUploads(this.strFilter); | |||
| } | |||
| public onFilterUploads(hint: string): void { | |||
| this.skip = 0; | |||
| this.strFilter = hint; | |||
| this.debouncedSearch(); | |||
| } | |||
| private loadAllUploads(): void{ | |||
| private loadDisplayRecords(): void { | |||
| this.allUploads = []; | |||
| this.loading = true; | |||
| // this.uas.getUploadMetaList(this.skip, this.skip + this.pageSize).subscribe( | |||
| // resp => { | |||
| // this.loading = false; | |||
| // } | |||
| // ); | |||
| this.uas.getUploadMetaList(this.skip, this.pageSize, this.strFilter).subscribe( | |||
| (resp: UploadMetalList ) => { | |||
| this.loading = false; | |||
| this.total = resp.Total; | |||
| resp.Data.forEach((v) => { | |||
| const uc = new UploadingCards(); | |||
| uc.Uploading = false; | |||
| uc.Meta = new UploadMetaModel(v); | |||
| this.allUploads.push(uc); | |||
| }); | |||
| }, err => { this.loading = false; }, | |||
| () => { this.loading = false; } | |||
| ); | |||
| } | |||
| public onFilterUploads(hint: string): void { | |||
| this.loadDisplayedUploads(); | |||
| public onDelete(ulMeta: UploadMetaModel): void{ | |||
| this.allUploads = this.allUploads.filter((v) => { | |||
| return v.Meta.Id !== ulMeta.Id; | |||
| }); | |||
| // console.log('delete', ulMeta, this); | |||
| } | |||
| public onCompleteSingle(uid: string): void { | |||
| const idx = this.uid2Idx(uid); | |||
| if (this.allUploads[idx] !== undefined ){ | |||
| this.allUploads[idx].Uploading = false; | |||
| } | |||
| } | |||
| } | |||
| @@ -165,9 +165,9 @@ | |||
| </kendo-grid-numeric-filter-cell> | |||
| </ng-template> | |||
| </kendo-grid-column> | |||
| <kendo-grid-column field="Trail" title="Income Trail($) " width="200" [headerClass]="'colTrail'" [class]="'topAlign colTrail'" filter="numeric"> | |||
| <kendo-grid-column field="IncomeTotal" title="Income Trail($) " width="200" [headerClass]="'colTrail'" [class]="'topAlign colTrail'" filter="numeric"> | |||
| <ng-template kendoGridCellTemplate let-dataItem> | |||
| <span *ngIf="dataItem.Trail > 0" [ngClass]="{'green text-bold': dataItem.Trail < 500000}">{{ dataItem.Trail | currency }}</span> | |||
| <span *ngIf="dataItem.IncomeTotal > 0" [ngClass]="{'green text-bold': dataItem.IncomeTotal < 500000}">{{ dataItem.IncomeTotal | currency }}</span> | |||
| </ng-template> | |||
| <ng-template kendoGridFilterCellTemplate let-filter let-column="column"> | |||
| <kendo-grid-numeric-filter-cell | |||
| @@ -14,7 +14,7 @@ | |||
| <kendo-splitter-pane size="300px"> | |||
| <div class="pane-content"> | |||
| <app-lender-uploads (success)="onSuccess($event)"></app-lender-uploads> | |||
| <app-lender-uploads (uploadSuccessful)="onSuccess($event)"></app-lender-uploads> | |||
| </div> | |||
| </kendo-splitter-pane> | |||
| @@ -6,6 +6,6 @@ export class PastYearMonthlyData { | |||
| Month: number; | |||
| Year: number; | |||
| Amount: number; | |||
| Trail: number; | |||
| IncomeTotal: number; | |||
| Summary: string; | |||
| } | |||
| @@ -22,6 +22,19 @@ export class UploadMetaModel { | |||
| get iconUrl(): string { | |||
| const t = this.Format.replace( '/', '-'); | |||
| return location.origin +'/assets/img/mime/' + t + '.png'; | |||
| return location.origin + '/assets/img/mime/' + t.toLowerCase() + '.png'; | |||
| } | |||
| static iconUrlBySuffix(suffix: string): string { | |||
| return location.origin + '/assets/img/suffix/' + suffix.toLowerCase() + '.png'; | |||
| } | |||
| static thumbDefault(): string { | |||
| return location.origin + '/assets/img/thumb_file_icon.webp'; | |||
| } | |||
| } | |||
| export class UploadMetalList { | |||
| Total: number; | |||
| Data: UploadMetaModel[]; | |||
| } | |||
| @@ -0,0 +1,13 @@ | |||
| export class UploadingInfoModel { | |||
| FileName: string; | |||
| UniqueId: string; | |||
| MetaId: number; | |||
| Progress: number; | |||
| constructor (payload: Partial<UploadingInfoModel>){ | |||
| this.FileName = payload.FileName || ''; | |||
| this.UniqueId = payload.UniqueId || ''; | |||
| this.MetaId = payload.MetaId || 0; | |||
| this.Progress = payload.Progress || 0 ; | |||
| } | |||
| } | |||
| @@ -4,9 +4,7 @@ import { debounce } from 'ts-debounce'; | |||
| import {PeopleModel} from '../models/people.model'; | |||
| import {ControlValueAccessor, FormControl, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; | |||
| import {MultiColumnComboBoxComponent} from '@progress/kendo-angular-dropdowns'; | |||
| import {Observable, of} from 'rxjs'; | |||
| import {PercentPipe} from '@angular/common'; | |||
| import {map} from 'rxjs/operators'; | |||
| import {Observable} from 'rxjs'; | |||
| import {PeopleService} from '../service/people.service'; | |||
| @Component({ | |||
| @@ -9,11 +9,9 @@ import {AppConfig} from '../app.config'; | |||
| @Injectable() | |||
| export class AuthService { | |||
| public apiUrl = ''; | |||
| public apiWsUrl = ''; | |||
| public apiUrl = 'https://svr2021.lawipac.com:8080/api/v1/'; | |||
| 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 <ApiV1LoginResponse>(); | |||
| @@ -2,7 +2,7 @@ import {Injectable} from '@angular/core'; | |||
| import {HttpClient} from '@angular/common/http'; | |||
| import {AuthService} from './auth.service'; | |||
| import {Observable} from 'rxjs'; | |||
| import {UploadMetaModel} from '../models/uploadMetaModel'; | |||
| import {UploadMetalList, UploadMetaModel} from '../models/uploadMetaModel'; | |||
| import {UploadAnalysisModel} from '../models/upload.analysis.model'; | |||
| @@ -19,6 +19,14 @@ export class UploadAttachService { | |||
| return this.http.get<UploadAnalysisModel>(this.auth.getUrl('upload-analysis/' + id)); | |||
| } | |||
| public getUploadMetaList(Skip: number, Take: number, Filter: string): Observable<UploadMetalList> { | |||
| return this.http.post<UploadMetalList>(this.auth.getUrl('upload-meta-list/'), {Skip, Take, Filter}); | |||
| } | |||
| public deleteUpload(id: number): Observable<UploadMetaModel> { | |||
| return this.http.delete<UploadMetaModel>(this.auth.getUrl('upload/' + id)); | |||
| } | |||
| public getUploadAsJpgUrl(id: number): string { | |||
| const ts = Date.now(); | |||
| return this.auth.getUrl('upload-as-image/' + id + '?date=' + ts); | |||
| @@ -51,4 +59,20 @@ export class UploadAttachService { | |||
| return this.auth.getUrl('upload-as-thumbnail/' + id ); | |||
| } | |||
| public createThumb(id: number): Observable<boolean> { | |||
| return this.http.put<boolean>(this.auth.getUrl('upload-as-thumbnail/' + id), {}); | |||
| } | |||
| public createPreview(id: number): Observable<boolean>{ | |||
| return this.http.put<boolean>(this.auth.getUrl('upload-as-image/' + id), {}); | |||
| } | |||
| public createPDF(id: number): Observable<boolean>{ | |||
| return this.http.put<boolean>(this.auth.getUrl('upload-as-pdf/' + id), {}); | |||
| } | |||
| public createAnalysis(id: number): Observable<UploadAnalysisModel> { | |||
| return this.http.put<UploadAnalysisModel>(this.auth.getUrl('upload-analysis/' + id), {}); | |||
| } | |||
| } | |||
| @@ -3,11 +3,14 @@ | |||
| <kendo-card class="upload-card-content upload-thumb" [width]="'200px'" [ngStyle]="{'background-image' : 'url(' + thumbImage + ')'}"> | |||
| <kendo-card-header class="k-hbox"> | |||
| <p> some title </p> | |||
| <button class="action" kendoButton icon="trash" (click)="onDelete(uploadId)"></button> | |||
| <p> {{ ( uploadMeta.FileName.length > 17 ) ? (uploadMeta.FileName | slice:0:17) + "..." : (uploadMeta.FileName)}} </p> | |||
| </kendo-card-header> | |||
| <kendo-card-body> | |||
| <p> {{uploadId}} </p> | |||
| <p class="FileName"> {{uploadMeta.FileName}} </p> | |||
| <p class="ts" > {{uploadMeta.Ts | date:"longDate" }} </p> | |||
| <p class="Id" > Id = {{uploadMeta.Id }} </p> | |||
| <div *ngIf="IsNew" class="isNew">NEW</div> | |||
| </kendo-card-body> | |||
| <kendo-card-footer> | |||
| <button class="action" kendoButton icon="track-changes" (click)="onNavigateTo(uploadId)"></button> | |||
| @@ -23,6 +23,21 @@ div.card-wrapper{ | |||
| background-position: center center; | |||
| } | |||
| div.isNew { | |||
| width: 80px; | |||
| height: 20px; | |||
| color: white; | |||
| background-color: #f50606; | |||
| transform: rotate( | |||
| 45deg | |||
| ); | |||
| position: absolute; | |||
| bottom: 5px; | |||
| left: -25px; | |||
| z-index: 10; | |||
| } | |||
| button.action { | |||
| border-radius: 100%; | |||
| height:32px; | |||
| @@ -92,6 +107,15 @@ div.card-wrapper{ | |||
| transition: all $speed ease-out; | |||
| } | |||
| kendo-card-body { | |||
| p.FileName { | |||
| display:none; | |||
| } | |||
| p.Id { | |||
| display:none; | |||
| } | |||
| } | |||
| &:hover { | |||
| p { | |||
| z-index: 100; | |||
| @@ -124,6 +148,17 @@ div.card-wrapper{ | |||
| } | |||
| } | |||
| kendo-card-body { | |||
| p.FileName { | |||
| display:block; | |||
| word-break: break-all; | |||
| } | |||
| p.Id { | |||
| display:block; | |||
| word-break: break-all; | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @@ -4,6 +4,7 @@ import {ImagePopupDialogComponent} from '../image-popup-dialog/image-popup-dialo | |||
| import {AuthService} from '../service/auth.service'; | |||
| import {UploadMetaModel} from '../models/uploadMetaModel'; | |||
| import {UploadAttachService} from '../service/upload.attach.service'; | |||
| import {AppConfig} from '../app.config'; | |||
| @@ -15,14 +16,19 @@ import {UploadAttachService} from '../service/upload.attach.service'; | |||
| export class UploadCardsComponent implements OnInit { | |||
| @Input() uploadId: number; | |||
| @Input() icon = ''; | |||
| @Output() deleted = new EventEmitter<UploadMetaModel>(); | |||
| @ViewChild('imgBox', {static: true}) imgBox: ImagePopupDialogComponent; | |||
| @ViewChild('downloadLink', {static: true}) downloadLink: ElementRef<HTMLAnchorElement>; | |||
| public uploadMeta: UploadMetaModel = new UploadMetaModel({}); | |||
| public thumbImage = 'https://svr2021.lawipac.com:8080/api/v1/upload-as-thumbnail/31'; | |||
| public thumbImage = ''; | |||
| public fullImage = ''; | |||
| constructor(private router: Router, private auth: AuthService, private uas: UploadAttachService) { } | |||
| public IsNew = false; | |||
| constructor(private router: Router, private auth: AuthService, private uas: UploadAttachService, private config: AppConfig) { | |||
| this.thumbImage = location.origin + '/assets/img/nopreview-thumb.png'; | |||
| } | |||
| ngOnInit(): void { | |||
| let url = this.auth.getUrl('upload-as-thumbnail/' + this.uploadId); | |||
| @@ -33,9 +39,19 @@ export class UploadCardsComponent implements OnInit { | |||
| this.uas.getUploadMeta(this.uploadId).subscribe( resp => { | |||
| this.uploadMeta = new UploadMetaModel(resp); | |||
| this.icon = this.uploadMeta.iconUrl; | |||
| this.IsNew = this.DateDiff(this.uploadMeta.Ts, new Date()) < 1; | |||
| }); | |||
| } | |||
| private DateDiff(date1: Date, date2: Date): number { | |||
| const dt1 = new Date(date1); | |||
| const dt2 = new Date(date2); | |||
| return Math.floor((Date.UTC(dt2.getFullYear(), dt2.getMonth(), dt2.getDate()) | |||
| - Date.UTC(dt1.getFullYear(), dt1.getMonth(), dt1.getDate()) ) | |||
| / (1000 * 60 * 60 * 24)); | |||
| } | |||
| public onNavigateTo(id: number ): void{ | |||
| this.router.navigate(['/upload-details/', id]); | |||
| } | |||
| @@ -59,4 +75,10 @@ export class UploadCardsComponent implements OnInit { | |||
| this.imgBox.showImg(url, this.uploadId + ' - ' + this.uploadMeta.FileName); | |||
| } | |||
| public onDelete(id: number): void { | |||
| this.uas.deleteUpload(id).subscribe( resp => { | |||
| const ulMeta = new UploadMetaModel(resp); | |||
| this.deleted.emit(ulMeta); | |||
| }); | |||
| } | |||
| } | |||
| @@ -21,7 +21,7 @@ export class UploadDetailComponent implements OnInit, AfterViewInit { | |||
| @ViewChild('pdf', {static: false}) pdf: ElementRef; | |||
| @ViewChild('tabs', {static: true}) tab: TabStripComponent; | |||
| // 'http://africau.edu/images/default/sample.pdf'; | |||
| public uploadAsPicUrl = 'https://svr2021.lawipac.com:8080/api/v1/upload-as-pdf/default'; | |||
| public uploadAsPicUrl = ''; | |||
| public uploadAsPdfUrl = ''; | |||
| public initAnimation = false; | |||
| public iframeLoaded = false; | |||
| @@ -42,7 +42,10 @@ export class UploadDetailComponent implements OnInit, AfterViewInit { | |||
| public analysisAAA: AnalysisAaaModel[] = []; | |||
| constructor(private us: UploadAttachService, private actRoute: ActivatedRoute, private router: Router) { } | |||
| constructor(private us: UploadAttachService, private actRoute: ActivatedRoute, private router: Router) { | |||
| this.uploadAsPicUrl = location.origin + 'assets/img/no_preview.jpg'; | |||
| this.uploadAsPdfUrl = location.origin + 'assets/pdf/no_preview.pdf'; | |||
| } | |||
| ngOnInit(): void { | |||
| const id = this.actRoute.snapshot.params.id; | |||
| @@ -118,7 +121,6 @@ export class UploadDetailComponent implements OnInit, AfterViewInit { | |||
| } | |||
| const el = this.pdf.nativeElement; | |||
| if (el !== undefined && el.src === this.uploadAsPdfUrl) { | |||
| console.log('yes true', this); | |||
| this.iframeLoaded = true; | |||
| } | |||
| } | |||
| @@ -0,0 +1,59 @@ | |||
| <div class="card-wrapper"> | |||
| <div class="file-type-image" [ngStyle]="{'background-image': 'url('+ icon +')'}"></div> | |||
| <kendo-card class="upload-card-content upload-thumb" [width]="'200px'" | |||
| [ngStyle]="{'background-image' : 'url(' + thumbImage + ')'}"> | |||
| <kendo-card-header class="k-hbox"> | |||
| <p *ngIf="!IsDuplicate"> {{Operation}} <kendo-loader *ngIf="analyzing" size="small" type="converging-spinner"></kendo-loader></p> | |||
| <p *ngIf="IsDuplicate"> Removing... <kendo-loader *ngIf="analyzing" size="small" type="converging-spinner"></kendo-loader> </p> | |||
| </kendo-card-header> | |||
| <kendo-card-body> | |||
| <p class="FileName"> | |||
| {{ ( FileName.length > 17 ) ? (FileName | slice:0:17) + "..." : (FileName)}} | |||
| </p> | |||
| <p *ngIf="IsDuplicate"> This is Duplicate </p> | |||
| <div *ngIf="!IsDuplicate" class="steps"> | |||
| <div> | |||
| <kendo-icon *ngIf="!pdfSuccess" [name]="'file-pdf'" [themeColor]="'error'" class="k-badge-icon"></kendo-icon> | |||
| <kendo-icon *ngIf="pdfSuccess" [name]="'check-circle'" [themeColor]="'success'" class="k-badge-icon"></kendo-icon> | |||
| <span> | |||
| <kendo-loader *ngIf="currentStep === 'pdf'" size="small" type="pulsing"></kendo-loader> | |||
| 1 pdf convert | |||
| </span> | |||
| </div> | |||
| <div> | |||
| <kendo-icon *ngIf="!previewSuccess" [name]="'file-config'" [themeColor]="'error'" class="k-badge-icon"></kendo-icon> | |||
| <kendo-icon *ngIf="previewSuccess" [name]="'check-circle'" [themeColor]="'success'" class="k-badge-icon"></kendo-icon> | |||
| <span> | |||
| <kendo-loader *ngIf="currentStep === 'preview'" size="small" type="pulsing"></kendo-loader> | |||
| 2 preview | |||
| </span> | |||
| </div> | |||
| <div> | |||
| <kendo-icon *ngIf="!thumbSuccess" [name]="'file-txt'" [themeColor]="'error'" class="k-badge-icon"></kendo-icon> | |||
| <kendo-icon *ngIf="thumbSuccess" [name]="'check-circle'" [themeColor]="'success'" class="k-badge-icon"></kendo-icon> | |||
| <span> | |||
| <kendo-loader *ngIf="currentStep === 'thumb'" size="small" type="pulsing"></kendo-loader> | |||
| 3 miniature | |||
| </span> | |||
| </div> | |||
| <div> | |||
| <kendo-icon *ngIf="!analysisSuccess" [name]="'file-ascx'" [themeColor]="'error'" class="k-badge-icon"></kendo-icon> | |||
| <kendo-icon *ngIf="analysisSuccess" [name]="'check-circle'" [themeColor]="'success'" class="k-badge-icon"></kendo-icon> | |||
| <span> | |||
| <kendo-loader *ngIf="currentStep === 'analysis'" size="small" type="pulsing"></kendo-loader> | |||
| 4 construction | |||
| </span> | |||
| </div> | |||
| </div> | |||
| <div *ngIf="IsDuplicate" class="duplicate">DUP</div> | |||
| </kendo-card-body> | |||
| <kendo-card-footer> | |||
| <div> | |||
| <kendo-progressbar *ngIf=" Progress>0 && Progress <100" [value]="Progress" > </kendo-progressbar> | |||
| </div> | |||
| </kendo-card-footer> | |||
| </kendo-card> | |||
| </div> | |||
| @@ -0,0 +1,110 @@ | |||
| $hoverColor: #d0d9ff; | |||
| $normalHeaderColor: #01838d; | |||
| $normalFooterColor: #10838d; | |||
| $speed: 0.5s; | |||
| div.card-wrapper{ | |||
| width:210px; | |||
| height: 300px; | |||
| padding:10px; | |||
| margin-bottom: 20px; | |||
| min-height:200px; | |||
| display: flex; | |||
| flex-wrap: wrap; | |||
| align-items: center; | |||
| justify-content: center; | |||
| .upload-thumb{ | |||
| min-height: 100px; | |||
| overflow: hidden; | |||
| background-size: contain; | |||
| background-repeat: no-repeat; | |||
| background-position: center center; | |||
| } | |||
| .file-type-image{ | |||
| width: 32px; | |||
| // border-radius:100%; | |||
| background-repeat: no-repeat; | |||
| background-size: cover; | |||
| // border:1px white solid; | |||
| // box-shadow: inset 1px 1px 0 #b7b7b7; | |||
| z-index: 2; | |||
| height: 32px; | |||
| position: absolute; | |||
| margin-right: 10px; | |||
| left : 160px; | |||
| top: 20px; | |||
| } | |||
| kendo-card { | |||
| height: 100%; | |||
| box-shadow: 1px 1px 2px white; | |||
| border-radius: 10px; | |||
| } | |||
| .upload-card-content { | |||
| p { | |||
| z-index: 100; | |||
| transition: all 0.3s ease-out; | |||
| } | |||
| kendo-card-header, | |||
| kendo-card-footer{ | |||
| background-color: $hoverColor; | |||
| } | |||
| kendo-card-header{ | |||
| p { | |||
| color: black; | |||
| } | |||
| .file-type-image{ | |||
| box-shadow: none; | |||
| } | |||
| } | |||
| kendo-card-body { | |||
| p { | |||
| color: black; | |||
| display:block; | |||
| background: $hoverColor; | |||
| word-break: break-all; | |||
| padding: 5px; | |||
| border-radius: 5px; | |||
| } | |||
| } | |||
| } | |||
| kendo-progressbar{ | |||
| width:100%; | |||
| } | |||
| div.duplicate { | |||
| width: 80px; | |||
| height: 20px; | |||
| color: black; | |||
| background-color: yellow; | |||
| -ms-transform: rotate(45deg); | |||
| transform: rotate( | |||
| 45deg | |||
| ); | |||
| position: absolute; | |||
| bottom: 5px ; | |||
| left: -25px; | |||
| z-index: 10; | |||
| } | |||
| kendo-stepper{ | |||
| width: 180px; | |||
| } | |||
| div.steps{ | |||
| text-align:left; | |||
| } | |||
| } | |||
| @@ -0,0 +1,25 @@ | |||
| import { ComponentFixture, TestBed } from '@angular/core/testing'; | |||
| import { UploadingProgressCardComponent } from './uploading-progress-card.component'; | |||
| describe('UploadingProgressCardComponent', () => { | |||
| let component: UploadingProgressCardComponent; | |||
| let fixture: ComponentFixture<UploadingProgressCardComponent>; | |||
| beforeEach(async () => { | |||
| await TestBed.configureTestingModule({ | |||
| declarations: [ UploadingProgressCardComponent ] | |||
| }) | |||
| .compileComponents(); | |||
| }); | |||
| beforeEach(() => { | |||
| fixture = TestBed.createComponent(UploadingProgressCardComponent); | |||
| component = fixture.componentInstance; | |||
| fixture.detectChanges(); | |||
| }); | |||
| it('should create', () => { | |||
| expect(component).toBeTruthy(); | |||
| }); | |||
| }); | |||
| @@ -0,0 +1,108 @@ | |||
| import {Component, EventEmitter, Input, OnInit, Output} from '@angular/core'; | |||
| import {UploadMetaModel} from '../models/uploadMetaModel'; | |||
| import {UploadAttachService} from '../service/upload.attach.service'; | |||
| @Component({ | |||
| selector: 'app-uploading-progress-card', | |||
| templateUrl: './uploading-progress-card.component.html', | |||
| styleUrls: ['./uploading-progress-card.component.scss'] | |||
| }) | |||
| export class UploadingProgressCardComponent implements OnInit { | |||
| @Input() MetaId = 0 ; | |||
| @Input() FileName = ''; | |||
| @Input() UniqueId = ''; | |||
| @Input() Progress = 0 ; | |||
| @Input() IsDuplicate = false ; | |||
| @Output() Complete: EventEmitter<string> = new EventEmitter<string>(); | |||
| public Operation = 'Uploading'; | |||
| public icon = ''; | |||
| public thumbImage = UploadMetaModel.thumbDefault(); | |||
| // sub steps after uploading | |||
| public thumbSuccess = false; | |||
| public pdfSuccess = false; | |||
| public previewSuccess = false; | |||
| public analysisSuccess = false; | |||
| // indicator | |||
| public analyzing = false; | |||
| public currentStep = ''; | |||
| private waitingTimer: any; | |||
| constructor( private uas: UploadAttachService) { } | |||
| ngOnInit(): void { | |||
| const suffix = this.FileName.split('.').pop(); | |||
| this.icon = UploadMetaModel.iconUrlBySuffix(suffix); | |||
| this.prePareToStartAnalysis(); | |||
| return; | |||
| } | |||
| private prePareToStartAnalysis(): void { | |||
| this.waitingTimer = setInterval( | |||
| () => { | |||
| if ( this.Progress >= 100 && this.MetaId !== 0) { | |||
| clearInterval(this.waitingTimer); | |||
| this.startAnalysis(); | |||
| } | |||
| } | |||
| , 500); | |||
| } | |||
| public startAnalysis(): void { | |||
| this.thumbSuccess = false; | |||
| this.pdfSuccess = false; | |||
| this.previewSuccess = false; | |||
| this.analysisSuccess = false; | |||
| this.analyzing = true; | |||
| this.Operation = 'Processing'; | |||
| this.createPdf(); | |||
| } | |||
| private createPdf(): void { | |||
| this.currentStep = 'pdf'; | |||
| this.uas.createPDF(this.MetaId).subscribe( | |||
| resp => { this.pdfSuccess = resp; }, | |||
| error => { this.pdfSuccess = false; this.createPreview(); }, | |||
| () => { this.createPreview(); } | |||
| ); | |||
| } | |||
| private createPreview(): void { | |||
| this.currentStep = 'preview'; | |||
| this.uas.createPreview(this.MetaId).subscribe( | |||
| resp => { this.previewSuccess = resp; }, | |||
| error => { this.previewSuccess = false; this.createThumb(); }, | |||
| () => { this.createThumb(); } | |||
| ); | |||
| } | |||
| private createThumb(): void { | |||
| this.currentStep = 'thumb'; | |||
| this.uas.createThumb(this.MetaId).subscribe( | |||
| resp => { this.thumbSuccess = resp; }, | |||
| error => { this.thumbSuccess = false; this.createAnalysis(); }, | |||
| () => { this.createAnalysis(); } | |||
| ); | |||
| } | |||
| private createAnalysis(): void { | |||
| this.currentStep = 'analysis'; | |||
| this.uas.createAnalysis(this.MetaId).subscribe( | |||
| resp => { this.analysisSuccess = true; }, | |||
| error => { this.analysisSuccess = false; this.endAnalysis(); }, | |||
| () => { this.endAnalysis(); } | |||
| ); | |||
| } | |||
| private endAnalysis(): void{ | |||
| this.analyzing = false; | |||
| this.Operation = 'Done'; | |||
| this.currentStep = ''; | |||
| this.Complete.emit(this.UniqueId); | |||
| } | |||
| } | |||
| @@ -10,13 +10,24 @@ | |||
| </head> | |||
| <body> | |||
| <script id="config" type="text/biukop-config" | |||
| server="https://svr2021.lawipac.com:8080/api/v1/" | |||
| server="https://sc5016.biukop.com.au:8080/" | |||
| version="0.9.1" | |||
| > | |||
| ICB7CiAgICAgICJTZXJ2ZXIiOiAiaHR0cHM6Ly9zdnIyMDIxLmxhd2lwYWMuY29tOjgwODAvYXBpL3YxLyIsCiAgICAgICJTb2NrZXQiOiAid3NzOi8vc3ZyMjAyMS5sYXdpcGFjLmNvbTo4MDgwL2FwaS92MS93cyIKICB9 | |||
| eyJTZXJ2ZXIiOiJodHRwczpcL1wvYzUwMTYuYml1a29wLmNvbS5h | |||
| dTo4MDgwXC9hcGlcL3YxXC8iLCJTb2NrZXQiOiJ3c3M6XC9cL2M1 | |||
| MDE2LmJpdWtvcC5jb20uYXU6ODA4MFwvYXBpXC92MVwvd3MifQ== | |||
| </script> | |||
| <app-root></app-root> | |||
| <script id="config-svr2021" type="text/biukop-config" | |||
| server="https://svr2021.lawipac.com:8080/" | |||
| version="0.9.1" | |||
| > | |||
| eyJTZXJ2ZXIiOiAiaHR0cHM6Ly9zdnIyMDIxLmxhd2lwYWMuY29tO | |||
| jgwODAvYXBpL3YxLyIsICJTb2NrZXQiOiAid3NzOi8vc3ZyMjAyMS | |||
| 5sYXdpcGFjLmNvbTo4MDgwL2FwaS92MS93cyJ9 | |||
| </script> | |||
| </body> | |||
| </html> | |||