Commit 8d11110c authored by Samart's avatar Samart
Browse files

Merge branch 'dev' into 'master'

Dev

See merge request !732
1 merge request!732Dev
Pipeline #2499 canceled with stage
in 4 seconds
Showing with 5535 additions and 2669 deletions
+5535 -2669
......@@ -21,6 +21,11 @@
"tsConfig": "tsconfig.app.json",
"aot": true,
"assets": [
{
"glob": "**/*",
"input": "node_modules/tinymce",
"output": "/tinymce/"
},
"src/favicon.ico",
"src/assets"
],
......@@ -29,7 +34,11 @@
"./node_modules/bootstrap/dist/css/bootstrap.min.css",
"src/styles.scss"
],
"scripts": []
"scripts": [
"./node_modules/tinymce/tinymce.min.js",
"./node_modules/jquery/dist/jquery.js",
"./node_modules/signalr/jquery.signalR.js"
]
},
"configurations": {
"production": {
......
This diff is collapsed.
......@@ -15,41 +15,47 @@
},
"private": true,
"dependencies": {
"@angular/animations": "~10.2.1",
"@angular/animations": "~10.2.5",
"@angular/cdk": "~10.2.7",
"@angular/common": "~10.2.1",
"@angular/compiler": "~10.2.1",
"@angular/core": "~10.2.1",
"@angular/forms": "~10.2.1",
"@angular/common": "~10.2.5",
"@angular/compiler": "~10.2.5",
"@angular/core": "~10.2.5",
"@angular/forms": "~10.2.5",
"@angular/material": "~10.2.7",
"@angular/material-moment-adapter": "~10.2.7",
"@angular/platform-browser": "~10.2.1",
"@angular/platform-browser-dynamic": "~10.2.1",
"@angular/platform-server": "~10.2.1",
"@angular/router": "~10.2.1",
"@auth0/angular-jwt": "~4.0.0",
"aspnet-prerendering": "^3.0.1",
"@angular/platform-browser": "~10.2.5",
"@angular/platform-browser-dynamic": "~10.2.5",
"@angular/platform-server": "~10.2.5",
"@angular/router": "~10.2.5",
"@auth0/angular-jwt": "^5.0.2",
"@tinymce/tinymce-angular": "^4.2.4",
"bootstrap": "^4.3.1",
"core-js": "^3.6.4",
"d3": "^7.3.0",
"file-saver": "^2.0.5",
"install": "^0.13.0",
"jquery": "^3.6.0",
"material-design-icons": "^3.0.1",
"moment": "^2.24.0",
"moment": "^2.29.4",
"ngx-currency": "^2.2.2",
"npm": "^8.14.0",
"rxjs": "~6.5.4",
"signalr": "^2.4.3",
"signalr-hub": "^1.3.1",
"tinymce": "^6.1.0",
"tslib": "^2.0.0",
"xlsx": "^0.17.3",
"zone.js": "~0.10.3"
},
"devDependencies": {
"@angular-devkit/build-angular": "^0.1002.0",
"@angular/cli": "~10.2.0",
"@angular/compiler-cli": "~10.2.1",
"@angular/language-service": "~10.2.1",
"@angular-devkit/build-angular": "^0.1002.4",
"@angular/cli": "~10.2.4",
"@angular/compiler-cli": "~10.2.5",
"@angular/language-service": "~10.2.5",
"@types/jasmine": "~3.5.0",
"@types/jasminewd2": "~2.0.3",
"@types/node": "^12.11.1",
"chromedriver": "^87.0.5",
"codelyzer": "^5.1.2",
"jasmine-core": "~3.5.0",
"jasmine-spec-reporter": "~5.0.0",
"karma": "~5.0.0",
......@@ -59,8 +65,7 @@
"karma-jasmine-html-reporter": "^1.5.0",
"protractor": "~7.0.0",
"ts-node": "~8.3.0",
"tslint": "~6.1.0",
"typescript": "~4.0.5",
"webpack-bundle-analyzer": "^3.9.0"
}
"typescript": "~4.0.5"
},
"resolutions": {}
}
......@@ -130,6 +130,14 @@ const routes: Routes = [
),
canLoad: [AuthLoad],
},
{
path: "PartsCenter",
loadChildren: () =>
import("./modules/partscenter/partscenter.module").then(
(m) => m.PartsCenterModule
),
canLoad: [AuthLoad],
},
{
path: "ExportBiz",
loadChildren: () =>
......@@ -178,15 +186,47 @@ const routes: Routes = [
import("./modules/report-viewer/report-viewer.module").then((m) => m.ReportViewerModule),
canActivate: [AuthGuard],
},
{
path: "Monitoring",
loadChildren: () =>
import("./modules/monitoring/monitoring.module").then(
(m) => m.MonitoringModule
),
//canLoad: [AuthLoad],
},
{
path: "document",
loadChildren: () =>
import("./modules/document/document.module").then(
(m) => m.DocumentModule
),
canLoad: [AuthLoad],
},
{
path: "icms",
loadChildren: () =>
import("./modules/icms/icms.module").then(
(m) => m.ICMSModule
),
canLoad: [AuthLoad],
},
{
path: "demo",
loadChildren: () =>
import("./demo/demo.module").then(
(m) => m.DemoModule
),
canLoad: [AuthLoad],
},
{
path: "**",
redirectTo: "/404",
pathMatch: "full"
}
pathMatch: "full",
},
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
imports: [RouterModule.forRoot(routes, {useHash: true})],
exports: [RouterModule],
})
export class AppRoutingModule {}
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { ReactiveFormsModule } from '@angular/forms';
import { BrowserModule } from "@angular/platform-browser";
import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
import { HttpClientModule, HTTP_INTERCEPTORS } from "@angular/common/http";
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from "@angular/core";
import { ReactiveFormsModule } from "@angular/forms";
import { AppRoutingModule } from './app-routing.module';
import { AppRoutingModule } from "./app-routing.module";
import { AuthLoggedIn, AuthGuard, AuthLoad, AuthGuardWithRefresh } from './core/auth-guard.service';
import { Globals } from './core/global';
import { HttpService } from './core/http.service';
import { JwtModule, JwtModuleOptions } from '@auth0/angular-jwt';
import { ModalService } from './core/modal.service';
import {
AuthLoggedIn,
AuthGuard,
AuthLoad,
AuthGuardWithRefresh,
} from "./core/auth-guard.service";
import { Globals } from "./core/global";
import { HttpService } from "./core/http.service";
import { JwtModule, JwtModuleOptions } from "@auth0/angular-jwt";
import { ModalService } from "./core/modal.service";
import { SharedComponentModule } from './modules/shared/shared-component.module';
import { SharedComponentModule } from "./modules/shared/shared-component.module";
import { AppComponent } from './app.component';
import { LoginComponent } from './pages/login/login.component';
import { MainComponent } from './pages/main/main.component';
import { PortalComponent } from './pages/portal/portal.component';
import { RoleListComponent } from './pages/portal/role-list.component';
import { AppComponent } from "./app.component";
import { LoginComponent } from "./pages/login/login.component";
import { MainComponent } from "./pages/main/main.component";
import { PortalComponent } from "./pages/portal/portal.component";
import { RoleListComponent } from "./pages/portal/role-list.component";
import { MatSidenavModule } from '@angular/material/sidenav';
import { MaterialImportModule } from './modules/shared/material.module';
import { ChangePasswordComponent } from './pages/changepwd/changepwd.component';
import { NotFoundComponent } from './pages/404/notfound.component';
import { MaintenanceComponent } from './pages/maintenance/maintenance.component';
import { MatSidenavModule } from "@angular/material/sidenav";
import { MaterialImportModule } from "./modules/shared/material.module";
import { ChangePasswordComponent } from "./pages/changepwd/changepwd.component";
import { NotFoundComponent } from "./pages/404/notfound.component";
import { MaintenanceComponent } from "./pages/maintenance/maintenance.component";
import { APP_BASE_HREF } from "@angular/common";
export function jwtTokenGetter() {
return localStorage.getItem("jwtToken") || '';
return localStorage.getItem("jwtToken") || "";
}
const JWT_Module_Options: JwtModuleOptions = {
config: {
tokenGetter: jwtTokenGetter,
whitelistedDomains: ['']
}
},
};
@NgModule({
......@@ -46,7 +51,7 @@ const JWT_Module_Options: JwtModuleOptions = {
PortalComponent,
RoleListComponent,
NotFoundComponent,
MaintenanceComponent
MaintenanceComponent,
],
imports: [
BrowserModule,
......@@ -72,6 +77,6 @@ const JWT_Module_Options: JwtModuleOptions = {
ModalService,
],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule { }
export class AppModule {}
......@@ -137,6 +137,6 @@
<mat-progress-bar mode="indeterminate" *ngIf="showProgressBar"></mat-progress-bar>
<div class="norecfound" *ngIf="showNoRecord">No record found</div>
<mat-paginator [hidden]="!showPagination" [pageSizeOptions]="[minpage, 20, 50, 100]" showFirstLastButtons>
<mat-paginator [hidden]="!showPagination" [pageSizeOptions]="[minpage, 20, 50, 100, 500]" showFirstLastButtons>
</mat-paginator>
</div>
......@@ -74,6 +74,7 @@ export class AutoTableHighlightComponent implements OnChanges {
@Input("stickyEndCol") stickyEndCol: string | Array<string>;
@Input("hideColumns") hideColumns: Array<string> = [];
@Input("minpage") minpage: number = 10;
@Output("filteredData") filteredData = new EventEmitter<any>();
@ContentChildren(TemplateRef) templateRef;
@ViewChild("table") table: any;
......@@ -131,6 +132,8 @@ export class AutoTableHighlightComponent implements OnChanges {
.reduce((acc: boolean, curr: boolean) => (acc == curr) && acc, true);
};
this.dataList.filter = JSON.stringify(this._filter);
this.filteredData.emit(this.dataList.filteredData);
this.ArrangeShowColumn(this.headerList);
}
......@@ -246,6 +249,8 @@ export class AutoTableHighlightComponent implements OnChanges {
if (isClear || this._filter[column] === '') delete this._filter[column];
this.dataList.filter = JSON.stringify(this._filter);
this.filteredData.emit(this.dataList.filteredData);
}
sortUp(column: string) {
......
<div class="mat-elevation-z2">
<div class="table-container">
<table mat-table #table [dataSource]="dataList" cdkDropList
[cdkDropListData]="dataList"
(cdkDropListDropped)="dropTable($event)" >
<ng-container [matColumnDef]="header.FIELD_NAME" *ngFor="let header of headerList; let hIdx = index">
<th mat-header-cell *matHeaderCellDef [ngClass]="{'tblHeader': true}" style="line-height:normal;"
[ngStyle]="{'min-width': header.WIDTH,
'flex-basis': header.WIDTH,
'white-space': 'pre-line'}">
{{header.TITLE}}
</th>
<td mat-cell *matCellDef="let row; let i = index;" [ngStyle]="{
'text-align': header.ALIGN,
'justify-content': (header.ALIGN.toUpperCase() === 'RIGHT' ? 'flex-end' :
header.ALIGN.toUpperCase() === 'LEFT' ? 'flex-start' : header.ALIGN),
'min-width': header.WIDTH,
'flex-basis': header.WIDTH
}">
<ng-template [ngIf]="header.DATA_TYPE == 'number'">
{{row[header.FIELD_NAME] | number: '1.0-2'}}
</ng-template>
<ng-template [ngIf]="header.DATA_TYPE == 'date'">
{{row[header.FIELD_NAME] | dateFormat}}
</ng-template>
<ng-template [ngIf]="header.DATA_TYPE != 'date'
&& header.DATA_TYPE != 'number'">
{{row[header.FIELD_NAME]}}
</ng-template>
</td>
</ng-container>
<tr mat-header-row *matHeaderRowDef="displayedColumns; sticky: true"></tr>
<tr mat-row *matRowDef="let row; columns: displayedColumns; let i = index;" cdkDrag [cdkDragData]="row"></tr>
</table>
</div>
</div>
\ No newline at end of file
.grid-content {
position: absolute;
top: 49px;
}
.cdk-drag-preview {
box-sizing: border-box;
border-radius: 4px;
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2), 0 8px 10px 1px
rgba(0, 0, 0, 0.14), 0 3px 14px 2px
rgba(0, 0, 0, 0.12);
height: 48px;
background-color: white;
display: table;
}
.cdk-drag-preview td {
flex-grow: 2;
font-size: 14px;
padding: 4px;
vertical-align: middle;
}
.table-container {
width: 100%;
overflow: hidden;
}
.mat-table {
min-width: 100%;
width: auto;
table-layout: fixed;
display: inline-table;
}
.mat-row,
.mat-header-row,
.mat-footer-row {
display: flex;
height: 46px;
}
.mat-header-row {
position: sticky;
top: 0;
z-index: 101;
}
.mat-row:hover,
.mat-row.row-highlight {
background: #afebf9;
}
.mat-footer-row {
margin-bottom: 1px;
}
.mat-header-cell,
.mat-cell,
.mat-footer-cell {
display: inline-flex;
flex-grow: 1;
flex-basis: 0;
flex-shrink: 0;
padding: 5px !important;
align-self: stretch;
align-items: center;
}
.mat-header-cell {
text-align: center;
justify-content: center;
font-size: 10pt;
border-right: 1px solid rgba(250, 250, 250, 0.08);
line-height: 14px;
}
.mat-cell {
font-size: 9pt;
/* border-right: 1px solid rgba(0, 0, 0, .12); */
}
.mat-row>.mat-header-cell:last-child,
.mat-row>.mat-cell:last-child {
border-right: none;
}
.norecfound {
padding: 0.75rem;
text-align: center;
color: #9d9d9d;
text-transform: uppercase;
}
td {
font-size: 9pt;
}
import { Component, OnInit, Input, OnChanges, ContentChild, TemplateRef, Output, EventEmitter, ViewChild, SimpleChanges, ContentChildren, NgZone } from '@angular/core';
import { HttpService } from 'src/app/core/http.service';
import { MatTable, MatTableDataSource } from '@angular/material/table';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort, Sort } from '@angular/material/sort';
import { CdkDragDrop, moveItemInArray } from '@angular/cdk/drag-drop';
import { Globals } from 'src/app/core/global';
import { filter, take } from 'rxjs/operators';
import { Subject } from 'rxjs';
/**
* How to use the table?
* you can just add element into yout html
* @param {string} tableName Field TABLE_NAME.
* @param {Array<any>} tableData Detail row list in table.
* @param {Array<any>} tableHeader
* @param {Array<any>} tableFooter show footer
* @param {boolean} showPagination
* @param {boolean} sortTable
* @param {string} stickyCol
* @param {string} stickyEndCol
* @event editClick($event) Send function name to this for Edit button action.
*
* @tableFooter param list
* 1. fieldName: show under the header fieldName
* 2. show: true | false (show column in footer)
* 3. value: fix value of the field (If not defined, table will sum field value)
*
* you can custom condition to show another value inside the column
* @var {object} header Current header row
* @var {object} data Current data row
* @description<ng-template let-header="header" let-data="data">
* @description--> insert your condition
* @description</ng-template>
*/
@Component({
selector: 'auto-table-reorder',
templateUrl: './auto-table-reorder.component.html',
styleUrls: ['./auto-table-reorder.component.scss']
})
export class AutoTableeorderComponent implements OnChanges {
public headerList: Array<any> = [];
public dataList: any;
public displayedColumns: string[] = [];
public displayFooter: Array<any> = [];
public isCustomContent: boolean = false;
public isCustomHeader: boolean = false;
public templateBody: TemplateRef<any>;
public templateHeader: TemplateRef<any>;
public progressBar: any;
public showProgressBar: boolean = false;
public showNoRecord: boolean = false;
private dataTypes: Array<string> = [];
public _filter: any = {};
public i: number;
private tableFooterChanged: Subject<Array<any>> = new Subject<Array<any>>();
@Input("tableName") tableName: string;
@Input("tableData") tableData: Array<any> = [];
@Input("tableHeader") tableHeader: Array<any> = [];
@Input("tableFooter") tableFooter: Array<any> = [];
@Input("maxHeight") maxHeight: string = "45vh";
//@Input("editIcon") editIcon: string = "edit";
//@Output("editClick") editClick = new EventEmitter<any>();
@ContentChildren(TemplateRef) templateRef;
//@ViewChild("table") table: any;
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild(MatSort, { static: true }) sort: MatSort;
@ViewChild('table') table: MatTable<any>;
constructor(
private http: HttpService,
public global: Globals,
private ngZone: NgZone
) { }
ngOnInit(): void {
if (this.tableName) {
this.getTableHeader();
} else if (this.tableHeader.length > 0) {
this.headerList = this.tableHeader;
this.ArrangeShowColumn(this.headerList);
}
}
ngOnChanges(changes: SimpleChanges): void {
if (!changes["tableData"].isFirstChange() && this.tableData) {
if (this.tableData.length === 0) {
if (!this.showProgressBar) {
this.loading();
} else {
this.unloading();
this.showNoRecord = true;
}
} else {
this.unloading();
}
}
this.dataList = this.tableData//new MatTableDataSource<any>(this.tableData || []);
// this.tableFooterChanged.next(this.tableFooter);
// this.dataList.filterPredicate = (data, filterObject: string): boolean => {
// const filters: any = JSON.parse(filterObject);
// return Object.keys(filters)
// .map(column => (data[column] || "").toString().includes(filters[column].toString()))
// .reduce((acc: boolean, curr: boolean) => (acc == curr) && acc, true);
// };
// this.dataList.filter = JSON.stringify(this._filter);
this.ArrangeShowColumn(this.headerList);
}
ngAfterContentInit(): void {
this.isCustomContent = false;
this.isCustomHeader = false;
if (this.templateRef) {
if (this.templateRef.length == 1) {
this.isCustomContent = true;
this.templateBody = this.templateRef._results[0];
} else if (this.templateRef.length == 2
&& this.templateRef._results.find(t => t._declarationTContainer?.localNames?.includes("tblHeader"))
&& this.templateRef._results.find(t => t._declarationTContainer?.localNames?.includes("tblBody"))) {
let headerIndex = this.templateRef._results.findIndex(t => t._declarationTContainer?.localNames?.includes("tblHeader"));
let bodyIndex = this.templateRef._results.findIndex(t => t._declarationTContainer?.localNames?.includes("tblBody"));
this.isCustomContent = true;
this.isCustomHeader = true;
this.templateHeader = this.templateRef._results[headerIndex];
this.templateBody = this.templateRef._results[bodyIndex];
}
}
}
ngAfterViewChecked() {
this.ngZone.onMicrotaskEmpty.pipe(take(3)).subscribe(() => this.table.updateStickyColumnStyles());
}
public AutodropTable() {
moveItemInArray(this.dataList, 0, this.dataList.length);
this.table.renderRows();
}
dropTable(event: CdkDragDrop<any[]>) {
const prevIndex = this.dataList.findIndex((d) => d === event.item.data);
moveItemInArray(this.dataList, prevIndex, event.currentIndex);
this.table.renderRows();
}
getTableHeader() {
this.http.get("System", "GetHeaderGridView", this.tableName)
.subscribe(result => {
this.headerList = result.filter(v => v.VISIBILITY === true);
//Change footer for header column map
this.tableFooterChanged.next(this.tableFooter);
this.ArrangeShowColumn(this.headerList);
})
}
// editBtnClick(item: any) {
// this.editClick.emit(item);
// }
loading(isFirstCall: boolean = false) {
this.showProgressBar = true;
this.showNoRecord = false;
clearTimeout(this.progressBar);
this.progressBar = setTimeout(() => {
this.showProgressBar = false;
if (!isFirstCall && this.tableData.length === 0) {
this.showNoRecord = true;
}
}, 5000);
}
unloading() {
clearTimeout(this.progressBar);
this.showProgressBar = false;
this.showNoRecord = false;
}
ArrangeShowColumn(data) {
this.displayedColumns = data.map(v => v.FIELD_NAME);
}
public CheckIsFinite(value: number) {
return Number.isFinite(value);
}
}
......@@ -8,6 +8,7 @@
<th mat-header-cell *matHeaderCellDef [ngClass]="{'tblHeader': true}" style="line-height:normal;"
[ngStyle]="{'min-width': header.WIDTH,
'flex-basis': header.WIDTH,
'background-color':(themeColor == '1' ? '#2a6ca9' :themeColor == '2' ? '#C71444' : '#007f7f'),
'white-space': 'pre-line'}">
<ng-template [ngIf]="isCustomHeader">
<ng-template
......@@ -73,7 +74,7 @@
<div *ngIf="header.FIELD_NAME == 'EDIT'; then showEdit; else showData"></div>
<ng-template #showEdit>
<button mat-icon-button (click)="editBtnClick(row)">
<i class="material-icons">edit</i>
<i class="material-icons">{{editIcon}}</i>
</button>
</ng-template>
<ng-template #showData>
......@@ -95,11 +96,15 @@
<ng-template [ngIf]="header.DATA_TYPE == 'dateTime'">
{{row[header.FIELD_NAME] | dateTimeFormat}}
</ng-template>
<ng-template [ngIf]="header.DATA_TYPE == 'datetime'">
{{row[header.FIELD_NAME] | dateTimeFormat}}
</ng-template>
<ng-template [ngIf]="header.DATA_TYPE == 'time'">
{{row[header.FIELD_NAME] | date:"HH:mm:ss"}}
</ng-template>
<ng-template [ngIf]="header.DATA_TYPE != 'date'
&& header.DATA_TYPE != 'dateTime'
&& header.DATA_TYPE != 'datetime'
&& header.DATA_TYPE != 'number2'
&& header.DATA_TYPE != 'number3'
&& header.DATA_TYPE != 'number5'
......@@ -123,6 +128,7 @@
<div *ngSwitchCase="header.DATA_TYPE === 'number3' && CheckIsFinite(getFooterValue(header.FIELD_NAME))">{{ getFooterValue(header.FIELD_NAME) | number: '1.3-3'}}</div>
<div *ngSwitchCase="header.DATA_TYPE === 'date'">{{ getFooterValue(header.FIELD_NAME) | dateFormat}}</div>
<div *ngSwitchCase="header.DATA_TYPE === 'dateTime'">{{ getFooterValue(header.FIELD_NAME) | dateTimeFormat}}</div>
<div *ngSwitchCase="header.DATA_TYPE === 'datetime'">{{ getFooterValue(header.FIELD_NAME) | dateTimeFormat}}</div>
<div *ngSwitchCase="header.DATA_TYPE === 'time'">{{ getFooterValue(header.FIELD_NAME) | date:"HH:mm:ss"}}</div>
<div *ngSwitchCase="header.DATA_TYPE === 'text'">{{ getFooterValue(header.FIELD_NAME) }}</div>
<div *ngSwitchDefault>{{getFooterValue(header.FIELD_NAME)}}</div>
......@@ -163,6 +169,6 @@
<mat-progress-bar mode="indeterminate" *ngIf="showProgressBar"></mat-progress-bar>
<div class="norecfound" *ngIf="showNoRecord">No record found</div>
<mat-paginator [hidden]="!showPagination" [pageSizeOptions]="[minpage, 20, 50, 100]" showFirstLastButtons>
<mat-paginator [hidden]="!showPagination" [pageSizeOptions]="[minpage, 20, 50, 100, 500]" showFirstLastButtons>
</mat-paginator>
</div>
......@@ -69,7 +69,10 @@ export class AutoTableComponent implements OnChanges {
@Input("stickyEndCol") stickyEndCol: string | Array<string>;
@Input("hideColumns") hideColumns: Array<string> = [];
@Input("minpage") minpage: number = 10;
@Input("editIcon") editIcon: string = "edit";
@Output("editClick") editClick = new EventEmitter<any>();
@Output("filteredData") filteredData = new EventEmitter<any>();
@Input("themeColor") themeColor: string = '1';
@ContentChildren(TemplateRef) templateRef;
@ViewChild("table") table: any;
......@@ -127,7 +130,7 @@ export class AutoTableComponent implements OnChanges {
.reduce((acc: boolean, curr: boolean) => (acc == curr) && acc, true);
};
this.dataList.filter = JSON.stringify(this._filter);
this.ArrangeShowColumn(this.headerList);
this.filteredData.emit(this.dataList.filteredData);
this.ArrangeShowColumn(this.headerList);
......@@ -237,6 +240,8 @@ export class AutoTableComponent implements OnChanges {
if (isClear || this._filter[column] === '') delete this._filter[column];
this.dataList.filter = JSON.stringify(this._filter);
this.filteredData.emit(this.dataList.filteredData);
}
sortUp(column: string) {
......
body {
font-family: 'Open Sans', sans-serif;
}
/*div#layout {
text-align: center;
}*/
:host ::ng-deep path {
stroke: gray;
}
:host ::ng-deep .grid path {
stroke-width: 0;
}
:host ::ng-deep .grid .tick line {
stroke: #9FAAAE;
stroke-opacity: 0.3;
}
:host ::ng-deep text.divergence {
font-size: 12px;
fill: #2F4A6D;
}
:host ::ng-deep text.value {
font-size: 12px;
}
:host ::ng-deep text.title {
font-size: 22px;
font-weight: 600;
}
:host ::ng-deep text.label {
font-size: 12px;
font-weight: 400;
}
:host ::ng-deep text.source {
font-size: 10px;
}
:host ::ng-deep line {
stroke: gray;
}
:host ::ng-deep line#limit {
stroke: #fe7a66;
stroke-width: 1;
/*stroke-dasharray: 3 6;*/
}
<div>
<div [id]="ID"></div>
</div>
<!--<svg [id]="ID" width="650" height="400"></svg>-->
<style>
text {
font: 10px sans-serif;
}
text{
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.legendLinear text.label{
fill: #fff;
}
.legend {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 60%;
}
</style>
import { Component, OnInit, OnChanges, ElementRef, Input, ViewChild, ViewEncapsulation, SimpleChanges } from '@angular/core';
import { HttpService } from 'src/app/core/http.service';
import * as d3 from 'd3';
@Component({
selector: 'stack-bar',
templateUrl: './stack-bar.component.html',
styleUrls: ['./stack-bar.component.css']
})
export class StackBarChartComponent implements OnInit, OnChanges {
constructor(
private http: HttpService
) { }
@Input() title: string = "Simple";
@Input("ID") ID: string;
@Input() groupKey : Array<any> = ["PASS","FAIL"];//["PASS","FAIL"]
@Input() LegenList: any = [{ legend: "PASSsssss", color: '#99cc00' }, { legend: "FAILlllll", color: '#c00000'}]
@Input() Data : any =[];
@Input() Width : any;
@Input() Heigth : any;
private svg: any;
private yScale: any;
private xScale: any;
private xAxis: any;
private yAxis: any;
private chart: any;
private z: any;
private barGroups: any;
private data: any;
private margin = { top: 30, right: 20, bottom: 20, left: 35 }
private width = 600 - this.margin.left - this.margin.right
private height = 400 - this.margin.top - this.margin.bottom;
public layers: any;
public delay: number = 0
ngOnInit() {
console.log('datalenght')
if (this.Width) {
this.width = this.Width - this.margin.left - this.margin.right
}
if (this.Heigth) {
this.height = this.Heigth - this.margin.top - this.margin.bottom
}
if(this.Data.length == 0){
// this.Data = [{'CATAGORY': 'Apr','PASS': 150,'FAIL': 20,'PERCENT': 75,'total': 200},
// {'CATAGORY': 'May','PASS': 180,'FAIL': 20,'PERCENT': 90,'total': 200},
// {'CATAGORY': 'Jun','PASS': 189,'FAIL': 11,'PERCENT': 94,'total': 200},
// {'CATAGORY': 'Jul','PASS': 182,'FAIL': 18,'PERCENT': 91,'total': 200},
// {'CATAGORY': 'Aug','PASS': 167,'FAIL': 33,'PERCENT': 83,'total': 200},
// {'CATAGORY': 'Sep','PASS': 178,'FAIL': 22,'PERCENT': 89,'total': 200},
// {'CATAGORY': 'Oct','PASS': 160,'FAIL': 40,'PERCENT': 80,'total': 200},
// {'CATAGORY': 'Nov','PASS': 169,'FAIL': 31,'PERCENT': 84,'total': 200},
// {'CATAGORY': 'Dec','PASS': 175,'FAIL': 25,'PERCENT': 87,'total': 200},
// {'CATAGORY': 'Jan','PASS': 195,'FAIL': 5,'PERCENT': 97,'total': 200},
// {'CATAGORY': 'Feb','PASS': 166,'FAIL': 34,'PERCENT': 83,'total': 200},
// {'CATAGORY': 'Mar','PASS': 183,'FAIL': 17,'PERCENT': 91,'total': 200}
// ]}
}
}
ngOnChanges(changes: SimpleChanges): void {
if (!changes["Data"].isFirstChange()) {
//this.delay = 100
//this.getData()
d3.select("#" + 'stack' + this.ID).remove();
this.setUpChart(this.ID)
//this.createChart(this.Data)
//this.delay = 1500
//this.Redraw(this.Data);
}
}
ngAfterViewInit() {
console.log(this.Data,'ngAfterViewInitData');
//if(this.Data.lenght > 0){
console.log('ngAfterViewInit')
this.setUpChart(this.ID)
//}
}
ngAfterContentInit(): void {
console.log('ngAfterContentInit',this.Data)
if(this.Data.lenght == 0){
}
//this.setUpChart(this.ID)
}
// getData() {
// this.http.get("System")
// .subscribe(result => {
// this.data = result.Table;
// console.log(this.data)
// this.createChart(this.data)
// })
// }
setUpChart(id) {
//var group = ["PASS","FAIL"];
this.svg = d3.select("#" + id).append('svg')
.attr("id", 'stack' + id)
.attr('width', this.width + this.margin.left + this.margin.right)
.attr('height', this.height + this.margin.top + this.margin.bottom + 20)
.style("background", "rgb(255, 255, 255");
this.xScale = d3.scaleBand()
.range([this.margin.left, this.width - this.margin.right])
.padding(0.3)
this.yScale = d3.scaleLinear()
.rangeRound([this.height - this.margin.bottom, this.margin.top])
this.xAxis = d3.axisBottom(this.xScale);
this.yAxis = d3.axisLeft(this.yScale);
this.chart = this.svg.append('g')
.attr('transform', `translate(${this.margin.left / 2}, ${this.margin.top + 30})`);
this.chart.append('g')
.attr("class", "x-axis")
.attr("transform", `translate(0,${this.height - this.margin.bottom})`)
.call(this.xAxis)
this.chart.append('g')
.attr("class", "y-axis")
.attr("transform", `translate(${this.margin.left},0)`)
.call(this.yAxis)
this.z = d3.scaleOrdinal()
.range([this.LegenList[0].color, this.LegenList[1].color])
.domain(this.groupKey);
//this.getData();
this.createChart(this.Data)
}
createChart(gData) {
var max = this.generateYmaxDomain(gData)
this.yScale.domain([0, d3.max(gData, d => d3.sum(this.groupKey, k => +d[k]) + 10)]).nice();
this.chart.selectAll(".y-axis").transition().duration(5)
.call(d3.axisLeft(this.yScale).ticks(5))
this.xScale.domain(gData.map(d => d.CATAGORY));
this.chart.selectAll(".x-axis").transition().duration(5)
.call(d3.axisBottom(this.xScale).tickSizeOuter(0))
var bargroup = this.chart.selectAll("g.layer")
.data(d3.stack().keys(this.groupKey)(gData), d => d.key)
bargroup.exit().remove();
bargroup.enter().insert("g", ".y-axis").append("g")
.classed("layer", true)
.attr("fill", d => this.z(d.key));
// bargroup.enter().append("g")
// .classed("layer", true)
// .attr("fill", d => this.z(d.key));
var bars = this.chart.selectAll("g.layer").selectAll("rect")
.data(d => d, e => e.data.CATAGORY);
bars.exit().remove();
bars.enter().append("rect")
.merge(bars)
.transition().duration(this.delay)
.attr("x", d => this.xScale(d.data.CATAGORY))
.attr("y", d => this.yScale(d[1]))
.attr("width", this.xScale.bandwidth())
.attr("height", d => {
return (this.yScale(d[0]) - this.yScale(d[1]))
})
bars.enter().append("text")
.text((d) => d3.format("")(d[1] - d[0]))
.transition().duration(this.delay)
//.attr("class", "text2")
.attr("y", (d) => (this.yScale(d[1]) + (this.yScale(d[0]) - this.yScale(d[1])) / 2) + 7 )
.attr('x', (d) => (this.xScale(d.data.CATAGORY) + this.xScale.bandwidth() / 2))
.attr('text-anchor', 'middle')
.attr("font-size", "8pt")
.style("fill", (d) => "white");
var text = this.svg.selectAll(".text")
.data(gData, d => d.CATAGORY);
text.exit().remove();
text.enter().append("text")
.attr("class", "text")
.attr("text-anchor", "middle")
.attr("font-size", "8pt")
.merge(text)
.transition().duration(5)
.attr("x", d => (this.xScale(d.CATAGORY) + this.xScale.bandwidth() / 2) + 20)
.attr("y", d => this.yScale(max) + 40)
.text(d => d.PERCENT)
this.svg.select('.texttitle').remove()
this.chart.append('text')
.attr('class', 'texttitle')
.attr('x', this.width / 2)
.attr('y', -35)
.attr("font-size", "20px")
.attr('text-anchor', 'middle')
.text(this.title)
let legendList = this.LegenList
let height = this.height;
let width = this.width;
var nodeWidth = (d) => d.getBBox().width;
//const cScale = d3.scaleOrdinal();
const legend = this.svg.append('g')
.attr('class', 'legend')
.attr('transform', 'translate(0,0)');
const lg = legend.selectAll('g')
.data(legendList)
.enter()
.append('g')
.attr('transform', (d, i) => `translate(${i * 100},${height + 15})`);
lg.append('rect')
.style('fill', d => d.color)
.attr('x', -100)
.attr('y', 90)
.attr('width', 50)
.attr('height', 10);
lg.append('text')
.style('font-family', 'Georgia')
.style('font-size', '15px')
.attr('x', -40)
.attr('y', 100)
.text(d => d.legend);
let offset = 0;
lg.attr('transform', function (d, i) {
let x = offset;
offset += nodeWidth(this) + 10;
return `translate(${x},${-40})`;
});
legend.attr('transform', function () {
return `translate(${(width - nodeWidth(this)) / 2},${0})`
});
}
private Redraw(gData){
var max = this.generateYmaxDomain(gData)
this.yScale.domain([0, d3.max(gData, d => d3.sum(this.groupKey, k => +d[k]) + 10)]).nice();
this.chart.selectAll(".y-axis").transition().duration(5)
.call(d3.axisLeft(this.yScale).ticks(5))
var bargroup = this.chart.selectAll("g.layer")
.data(d3.stack().keys(this.groupKey)(gData), d => d.key)
//bargroup.exit().remove();
bargroup.enter().insert("g", ".y-axis").append("g")
.classed("layer", true)
.attr("fill", d => this.z(d.key));
var bars = this.chart.selectAll("g.layer").selectAll("rect")
.data(d => d, e => e.data.CATAGORY);
//bars.exit().remove();
bars.enter().append("rect")
.merge(bars)
.transition().duration(this.delay)
.attr("x", d => this.xScale(d.data.CATAGORY))
.attr("y", d => this.yScale(d[1]))
.attr("width", this.xScale.bandwidth())
.attr("height", d => {
return (this.yScale(d[0]) - this.yScale(d[1]))
})
var barstext = this.chart.selectAll("text")
.data(d => d, e => e.data.CATAGORY);
barstext.enter().append("text")
.merge(bars)
.text((d) => d3.format("")(d[1] - d[0]))
.transition().duration(this.delay)
.attr("y", (d) => (this.yScale(d[1]) + (this.yScale(d[0]) - this.yScale(d[1])) / 2) + 7 )
.attr('x', (d) => (this.xScale(d.data.CATAGORY) + this.xScale.bandwidth() / 2))
.attr('text-anchor', 'middle')
.attr("font-size", "8pt")
.style("fill", (d) => "black");
}
generateYmaxDomain(data: any) {
var yMaxDomain: number;
//if (this.TypeChart == 'Value') {
// //yMaxDomain = 100
// yMaxDomain = d3.max(data, d => Number(d.Value))
// //yMaxDomain = yMaxDomain + ((yMaxDomain * 5) / 100);
//} else {
// yMaxDomain = 100;
//}
yMaxDomain = d3.max(data, d => Number(d.PERCENT))
//yMaxDomain = yMaxDomain + ((yMaxDomain * 5) / 100);
return yMaxDomain
}
generateYminDomain(data: any) {
var yMinDomain: number = 0;
// var yMinDomain = d3.min(data, d => Number(d.Value))
// yMinDomain = yMinDomain - ((yMinDomain * 5) / 100)
return yMinDomain
}
generateHearderList(data) {
let newData = [];
for (var key in data[0]) {
if (key != 'FY' && key != 'CATAGORY' && key != 'Value') {
newData.push(key)
}
}
return newData
}
}
export interface ChartData {
Values: Array<valueModel>;
Legend: Array<legendModel>
}
export interface valueModel {
CATAGORY: string;
PERCENT: number;
//Value2: number;
//Value3: number;
}
export interface legendModel {
legend: string;
color: string;
}
<div class="dialog-container">
<div class="dialog-header d-flex align-items-center justify-content-between">
<h3 class="mb-0" mat-dialog-title [ngStyle]="{'color':colors}">{{title}}</h3>
<button type=" button" mat-icon-button (click)="OnCloseClick()">
<button type=" button" mat-icon-button (click)="OnCloseClick()" tabindex="-1">
<i class="material-icons">
close
</i>
......
import { Component, ContentChild, TemplateRef, ViewChild, Inject, Input } from '@angular/core';
import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Overlay } from '@angular/cdk/overlay';
import {
Component,
ContentChild,
TemplateRef,
ViewChild,
Inject,
Input,
Output,
EventEmitter,
} from "@angular/core";
import {
MatDialog,
MatDialogRef,
MAT_DIALOG_DATA,
} from "@angular/material/dialog";
import { Overlay } from "@angular/cdk/overlay";
@Component({
selector: 'dialog-template',
template: '<button mat-icon-button (click)="OpenDialog()"><i class="material-icons">{{iconName}}</i></button>'
selector: "dialog-template",
template:
'<button mat-icon-button (click)="OpenDialog()"><i class="material-icons">{{iconName}}</i></button>',
})
export class DialogTemplateComponent {
@Input() iconName: string = "edit";
@Input() title: string = "Dialog";
@Input() width: string = "75%";
@Input() minWidth: string = "auto";
@Input() maxWidth: string = "auto";
@Input() height: string = "100%";
@Input() minHeight: string = "auto";
@Input() maxHeight: string = "auto";
@Input() colors: string = "#000000";
@Output() onClose: EventEmitter<any> = new EventEmitter();
@Input() iconName: string = "edit";
@Input() title: string = "Dialog";
@Input() width: string = "75%";
@Input() minWidth: string = "auto";
@Input() maxWidth: string = "auto";
@Input() height: string = "100%";
@Input() minHeight: string = "auto";
@Input() maxHeight: string = "auto";
@Input() colors : string = "#000000";
@ContentChild("content", { static: false }) templateRefContent;
@ContentChild("action", { static: false }) templateRefAction;
@ContentChild("content", { static: false }) templateRefContent;
@ContentChild("action", { static: false }) templateRefAction;
public isOpen: boolean = false;
constructor(
private dialog: MatDialog,
private overlay: Overlay
) { }
private dialogRef: any;
constructor(private dialog: MatDialog, private overlay: Overlay) {}
private dialogRef: any;
ngOnDestroy() {
setTimeout(() => {
this.dialogRef ? this.dialogRef.close() : null;
}, 100)
}
ngOnDestroy() {
setTimeout(() => {
this.dialogRef ? this.dialogRef.close() : null;
}, 100);
}
OpenDialog() {
this.dialogRef = this.dialog.open(DialogTemplateDialog, {
panelClass: "dialog-panel",
autoFocus: false,
width: this.width,
height: this.height,
minHeight: this.minHeight,
maxHeight: this.maxHeight,
minWidth: this.minWidth,
maxWidth: this.maxWidth,
data: {
title: this.title,
colors : this.colors,
templateRefContent: this.templateRefContent,
templateRefAction: this.templateRefAction
}
})
OpenDialog() {
this.dialogRef = this.dialog.open(DialogTemplateDialog, {
panelClass: "dialog-panel",
autoFocus: true,
width: this.width,
height: this.height,
minHeight: this.minHeight,
maxHeight: this.maxHeight,
minWidth: this.minWidth,
maxWidth: this.maxWidth,
data: {
title: this.title,
colors: this.colors,
templateRefContent: this.templateRefContent,
templateRefAction: this.templateRefAction,
onClose: this.onClose,
},
});
this.dialogRef.afterClosed().subscribe(result => { })
this.isOpen = true;
}
Close() {
this.dialogRef.close();
}
this.dialogRef.afterClosed().subscribe((result) => {
this.isOpen = false;
});
}
Close() {
this.dialogRef.close();
}
}
@Component({
selector: 'dialog-template-dialog',
templateUrl: './dialog-template.component.html',
styleUrls: ['./dialog-template.component.css']
selector: "dialog-template-dialog",
templateUrl: "./dialog-template.component.html",
styleUrls: ["./dialog-template.component.css"],
})
export class DialogTemplateDialog {
public templateRefContent: TemplateRef<any>;
public templateRefAction: TemplateRef<any>;
public title: string;
public colors : string;
public templateRefContent: TemplateRef<any>;
public templateRefAction: TemplateRef<any>;
public title: string;
public colors: string;
public onClose: EventEmitter<any> = new EventEmitter();
constructor(
public dialogRef: MatDialogRef<DialogTemplateDialog>,
@Inject(MAT_DIALOG_DATA) public dialogData: any
) { }
constructor(
public dialogRef: MatDialogRef<DialogTemplateDialog>,
@Inject(MAT_DIALOG_DATA) public dialogData: any
) {}
ngOnInit() {
this.colors = this.dialogData.colors;
this.title = this.dialogData.title;
this.templateRefContent = this.dialogData.templateRefContent;
this.templateRefAction = this.dialogData.templateRefAction;
}
ngOnInit() {
this.colors = this.dialogData.colors;
this.title = this.dialogData.title;
this.templateRefContent = this.dialogData.templateRefContent;
this.templateRefAction = this.dialogData.templateRefAction;
this.onClose = this.dialogData.onClose;
}
OnCloseClick() {
this.dialogRef.close();
OnCloseClick() {
if (this.onClose.observers.length > 0) {
this.onClose.emit("closed");
} else {
this.dialogRef.close();
}
}
\ No newline at end of file
}
}
import { PickToken } from "./PickTokenExtractor";
type propName = "QR" | "partNo" | "caseNo" | "baBy";
export class InvoiceControl {
public reset(): void {
this.QR = null;
this.caseNo = null;
this.partNo = null;
this.latestScanned = null;
this.baBy = null;
}
public set(args: InvoiceControlSetValue) {
if (args.propName == "QR" && args.value instanceof PickToken)
this.QR = args.value;
if (args.propName == "caseNo" && args.value instanceof CaseNo)
this.caseNo = args.value;
if (args.propName == "partNo" && typeof args.value == "string")
this.partNo = args.value;
if (args.propName == "baBy" && typeof args.value == "string")
this.baBy = args.value;
this.latestScanned = args.propName
}
public QR: PickToken;
public caseNo: CaseNo;
public partNo: string;
public latestScanned: propName;
public baBy : string;
}
export class InvoiceControlSetValue {
public propName: propName;
public value: PickToken | CaseNo | string;
}
export class CaseNo {
public constructor(caseno: string, casedate: string, casetype?:'BOX' | 'BAG') {
this.caseNo = caseno;
this.caseDate = casedate;
if(casetype)
this.caseType = casetype;
}
public caseNo: string;
public caseDate: string;
public caseType?: 'BOX' | 'BAG';
}
\ No newline at end of file
import { Injectable } from "@angular/core";
import * as moment from "moment";
export class PickToken {
public constructor() {}
public COMPANY_CODE: string;
public PICKING_LIST_NO: string;
public CUSTOMER_CODE: string;
public BRANCH_CODE: string;
public INSTRUCTION_NO: string;
public setDetail(
companyCode: string,
pickListNo: string,
customerCode: string,
branchCode: string,
instructionNo: string
) {
if (!companyCode) throw "undefined";
if (!pickListNo) throw "undefined";
if (!customerCode) throw "undefined";
if (!branchCode) throw "undefined";
if (!instructionNo) throw "undefined";
this.COMPANY_CODE = companyCode;
this.PICKING_LIST_NO = pickListNo;
this.CUSTOMER_CODE = customerCode;
this.BRANCH_CODE = branchCode;
this.INSTRUCTION_NO = instructionNo;
}
}
@Injectable()
export class PickTokenExtractor {
private token: PickToken = new PickToken();
public INVALID_TOKEN: string = "Invalid QR";
public getToken(tokenString: string): PickToken {
this.token = new PickToken();
this.extractToken(tokenString);
return this.token;
}
private extractToken(tokenString: string) {
try {
const tokenDetail = tokenString.split("|");
this.token.setDetail.apply(this.token, tokenDetail);
} catch (e) {
throw this.INVALID_TOKEN;
}
}
}
import { Pipe, PipeTransform } from '@angular/core';
import { DatePipe } from '@angular/common';
import { DatePipe, DecimalPipe } from '@angular/common';
import { Globals } from './global';
import { DomSanitizer } from '@angular/platform-browser';
import { orderBy } from 'lodash';
@Pipe({
name: 'dateFormat'
......@@ -30,6 +32,17 @@ export class dateTimeFormatPipe extends DatePipe implements PipeTransform {
}
@Pipe({
name: 'dateTimeFormat24H'
})
export class dateTimeFormat24HPipe extends DatePipe implements PipeTransform {
constructor(private global: Globals) {
super('en-us');
}
transform(value: any, args?: any): any {
return value && super.transform(value, this.global.DateTimeFormat24H);
}
}
@Pipe({
name: 'filterBytext'
})
......@@ -56,4 +69,73 @@ export class UniquePipe implements PipeTransform {
return array;
}
}
\ No newline at end of file
}
@Pipe({
name: 'safe'
})
export class SafePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) {}
/**
*
* @param value
* @param securityContext
* NONE: 0
* HTML: 1
* STYLE: 2
* SCRIPT: 3
* URL: 4
* RESOURCE_URL: 5
* @returns
*/
transform(value: any, securityContext: 'HTML' | 'STYLE' | 'SCRIPT' | 'URL' | 'RESOURCE_URL'): any {
switch(securityContext) {
case 'HTML': return this.sanitizer.bypassSecurityTrustHtml(value);
case 'STYLE': return this.sanitizer.bypassSecurityTrustStyle(value);
case 'SCRIPT': return this.sanitizer.bypassSecurityTrustScript(value);
case 'URL': return this.sanitizer.bypassSecurityTrustUrl(value);
case 'RESOURCE_URL': return this.sanitizer.bypassSecurityTrustResourceUrl(value);
default: return this.sanitizer.sanitize(0, value);
}
}
}
@Pipe({
name: "orderBy"
})
export class OrderByPipe implements PipeTransform {
transform(array: any, sortBy: string, order?: string): any[] {
const sortOrder = order ? order : 'asc'; // setting default ascending order
return orderBy(array, [sortBy], [sortOrder]);
}
}
@Pipe({
name: 'dataType',
})
export class DataTypePipe implements PipeTransform {
constructor(
private numberFormat: DecimalPipe,
private dateFormat: dateFormatPipe,
private dateTimeFormat: dateTimeFormatPipe,
private timeFormat: DatePipe
) {}
transform(value: any, format: string): any {
if (format === 'number') return this.numberFormat.transform(value, '1.0-2');
if (format === 'number2')
return this.numberFormat.transform(value, '1.2-2');
if (format === 'number3')
return this.numberFormat.transform(value, '1.3-3');
if (format === 'number5')
return this.numberFormat.transform(value, '1.5-5');
if (format === 'date') return this.dateFormat.transform(value);
if (format === 'dateTime') return this.dateTimeFormat.transform(value);
if (format === 'time')
return this.dateTimeFormat.transform(value, 'HH:mm:ss');
return value;
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment