[init] init version

This commit is contained in:
2023-10-10 03:58:47 +07:00
commit 73359d5f24
299 changed files with 55909 additions and 0 deletions

View File

@@ -0,0 +1,23 @@
import { Component } from "@angular/core";
import { CDialogConfig, IDialogConfig } from "../interface/Dialog";
@Component({ template: "", standalone: true })
export class BaseFormComponent {
dataForm: any = {};
dataFilter: any = {};
ids: any;
action: any;
params: any = {};
dialogConfig: IDialogConfig = CDialogConfig;
public isFieldValid(form: any, field: any) {
return field.errors && (field.dirty || field.touched || form.submitted);
}
}

View File

@@ -0,0 +1,123 @@
import {Component, ViewChild} from '@angular/core';
import {MatPaginator} from '@angular/material/paginator';
import {MatSort} from '@angular/material/sort';
import generateParamsValue from '../utils/GenerateParamsValue';
import {MatTableDataSource} from '@angular/material/table';
import {IListPageDefault, IListResponse} from "../interface/ListResponse";
import {CDialogConfig, IDialogConfig} from "../interface/Dialog";
import { STORAGE } from "../../@config/app";
@Component({template: ''})
export abstract class BaseListComponent {
dataFilter: any = {};
dataSource: any = [];
dataSourceList = new MatTableDataSource<any>([]);
dataSourceCount = 0;
pageSizeOptions = [10, 20, 50];
pageIndex: number = 1;
pageSize: number = 10;
totalItem: any = 0;
totalOfElement: number = 10;
isTab: string = '';
storage: any = STORAGE;
dialogConfig: IDialogConfig = CDialogConfig;
default: IListPageDefault = {
pageIndex: 1,
pageSize: 10,
};
@ViewChild(MatSort, {static: true}) sort: MatSort | any;
@ViewChild(MatPaginator, {static: true}) paginator: MatPaginator | any;
protected setParams(url: string, $event? : any) {
this.pageIndex = $event ? $event.pageIndex + 1 : this.default.pageIndex;
this.pageSize = $event ? $event.pageSize : this.default.pageSize;
const filter = generateParamsValue(this.dataFilter);
return `${url}?page=${this.pageIndex}&perPage=${this.pageSize}${filter ? '&' + filter : '' }`;
}
protected setDataSource<T>(data: IListResponse<T>) {
this.totalItem = data ? data.totalItem : 0;
this.totalOfElement = data ? data.totalOfElement : 0;
this.paginator.length = data ? data.totalItem : 0;
this.dataSourceCount = data ? data.totalItem : 0;
return data ? data.data : [];
}
protected getCurrentPage() {
return {pageIndex: this.pageIndex - 1, pageSize: this.pageSize};
}
protected setDataSourceList<T>(data : any) {
this.dataSourceList = new MatTableDataSource<T>(data);
this.dataSourceList.paginator = this.paginator;
this.dataSourceCount = data ? data.length : 0;
}
protected setFilterPredicate<T>() {
this.dataSourceList.filterPredicate = (data: T, filtersJson: string) => {
const matchFilter : any = [];
const filters = JSON.parse(filtersJson);
filters.forEach((filter : any) => {
// @ts-ignore
const val = data[filter.id] ? data[filter.id] : '';
filter.value = filter.value ? filter.value : '';
matchFilter.push(val.toLowerCase().includes(filter.value.toLowerCase()));
});
return matchFilter.every(Boolean);
};
}
protected onFilterPredicate() {
const tableFilters : any = [];
Object.keys(this.dataFilter).forEach(key => {
if (key) tableFilters.push({id: key, value: this.dataFilter[key]});
});
this.dataSourceList.filter = JSON.stringify(tableFilters);
if (this.dataSourceList.paginator) {
this.dataSourceList.paginator.firstPage();
}
}
protected setFilterLocal(page : any) {
const data = {
page,
filter: this.dataFilter
};
const filterLocalData = [];
const filterLocal: any = localStorage.getItem('filterLocal');
if (filterLocal) {
const parse = JSON.parse(filterLocal);
const mapData = parse.map((item : any) => {
if (item.page === page) return data;
return item;
});
return localStorage.setItem('filterLocal', JSON.stringify(mapData));
}
filterLocalData.push(data);
localStorage.setItem('filterLocal', JSON.stringify(filterLocalData));
}
protected getFilterLocal(page : any) {
const filterLocal: any = localStorage.getItem('filterLocal');
if (!filterLocal) return;
const parse = JSON.parse(filterLocal);
const isPage = parse.filter((f : any) => f.page === page);
if (!isPage[0]) return;
this.dataFilter = isPage[0].filter;
}
}

View File

@@ -0,0 +1,15 @@
import {Component} from '@angular/core';
import {CDialogConfig, IDialogConfig} from "../interface/Dialog";
@Component({template: ''})
export abstract class BasePopupComponent {
ids: any;
dataForm: any = {};
dialogConfig: IDialogConfig = CDialogConfig;
public isFieldValid(form : any, field : any) {
return field.errors && (field.dirty || field.touched || form.submitted);
}
}

View File

@@ -0,0 +1,21 @@
import {EAction} from "../../@config/app";
export const CDialogConfig: IDialogConfig = {
width: '80%',
height: '80%',
data: {}
};
export interface IDialogConfig {
width: string;
height: string;
data: IDialogConfigData;
}
export interface IDialogConfigData {
action?: EAction;
ids?: string;
datas?: any;
dataForm?: any;
}

View File

@@ -0,0 +1,12 @@
export interface IListResponse<T> {
data: T[];
totalItem: number;
totalOfElement: number;
page: number;
rowsPerPage: number;
lastPage: number;
}
export interface IListPageDefault {
pageIndex: number;
pageSize: number;
}

View File

@@ -0,0 +1,201 @@
export interface IQuotation {
id?: number
step?: number
quotationNo?: string
productId?: any
typeCode?: any
productNo?: string
productName?: string
productBrandName?: string
productSize?: string
productColor?: string
productWeight?: any
productYear?: string
productPrice?: string
productLatestPrice?: string
productImages?: any
images?: any
openingVideo?: any
coverImage?: string
startDate?: string
deposit?: string
cmfsDeposit?: string
price?: string
lessSellerDeposit?: string
sellerDeposit?: string
sellerDeposit2ndTime?: any
sellerDeposit3rdTime?: any
sellerDepositSum?: any
plusPacking?: string
plusLuxuryHandbag?: string
principalBalanceTotal?: string
wantToInstallmentTerm?: number
installment?: any
plusBankFee?: string
transferSummary?: string
customerId?: number
sellerId?: any
customerPrefix?: any
customerFirstName?: string
customerLastName?: string
customerPhone?: string
customerIdCard?: any
customerIdCardImage?: any
customerAddress?: any
customerEmail?: any
customerLine?: any
customerLineShop?: any
customerFacebook?: any
customerOccupation?: any
customerIg?: any
userFullName?: string
sellerName?: string
sellerPhone?: string
sellerEmail?: any
sellerLine?: any
sellerLineShop?: any
sellerFacebook?: any
sellerIg?: any
sellerSnProduct?: any
status?: string
status2ndTime?: string
status3rdTime?: string
statusContract?: string
contractDate?: any
contractApprovedDate?: any
contractCancelDate?: any
contractBankName?: any
contractAccountName?: any
contractAccountNumber?: any
contractDetail?: any
isContractClosing?: any
contractPrice?: any
contractInterest?: any
contractPriceSum?: any
receivedDate?: any
statusWarehouse?: any
areaId?: any
areaCode?: any
areaName?: any
areaRoomName?: any
areaLocation?: any
areaFloor?: any
areaRoomSize?: any
storageBoxId?: any
storageBoxCode?: any
storageBoxName?: any
storageBoxSize?: any
disbursementDate?: any
disbursementType?: any
deliveryWorker?: any
isCheckInspection?: any
isReceived?: any
type?: string
pickType?: any
settingInterestRate?: any
settingCmfsDeposit?: any
priceDisbursement?: any
sellerPaymentAccountName?: string
sellerPaymentPayee?: string
sellerPaymentMethod?: string
sellerPaymentDate?: string
sellerPaymentAmount?: string
sellerPaymentAccountNumber?: string
isInvoice?: string
isPaying?: string
createdDate?: string
createdBy?: any
updatedDate?: string
updatedBy?: number
productMeasurement?: IQuotationProductMeasurement[]
quotationDetail?: IQuotationDetail[]
customer?: IQuotationCustomer
seller?: any
discount?: any
}
export interface IQuotationCustomer {
id?: number
code?: any
prefix?: string
gender?: string
firstName?: string
lastName?: string
phone?: string
idCard?: string
idCardImage?: string
address?: string
isAddress?: boolean
deliveryAddress?: string
email?: string
line?: string
lineShop?: any
facebook?: string
occupation?: string
ig?: string
status?: any
createdDate?: string
createdBy?: any
updatedDate?: string
updatedBy?: any
}
export interface IQuotationProductMeasurement {
id?: number
productId?: any
masterProductMeasurementId?: any
quotationId?: number
code?: any
name?: string
size?: string
unitId?: number
status?: any
createdDate?: string
createdBy?: any
updatedDate?: string
updatedBy?: any
}
export interface IQuotationDetail {
id?: number
quotationId?: number
installment?: number
dueDate?: string
principle?: string
interestTotal?: string
fee?: string
totalPayment?: string
principleTotal?: string
status?: string
createdDate?: string
createdBy?: any
updatedDate?: string
updatedBy?: any
}
export interface IQuotationPayment {
id?: number
quotationId?: number
quotationNo?: string
productNo?: string
productName?: string
customerPrefix?: string
customerFirstName?: string
customerLastName?: string
customerPhone?: string
price?: string
sellerDeposit?: string
deposit?: string
paymentDate?: string
paymentType?: string
paymentAmount?: string
paymentAmountAll?: string
paymentMethod?: string
status?: string
createdDate?: string
createdBy?: any
updatedDate?: string
updatedBy?: any
quotation?: IQuotation
}

View File

@@ -0,0 +1,199 @@
import { OnInit, Directive, HostListener, ElementRef, forwardRef } from "@angular/core";
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from "@angular/forms";
import { DecimalPipe } from "@angular/common";
export const CURRENCY_INPUT_MASK_DIRECTIVE_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CurrencyInputMaskDirective),
multi: true
};
@Directive({
selector: "[appCurrencyInputMask]",
providers: [
CURRENCY_INPUT_MASK_DIRECTIVE_VALUE_ACCESSOR,
DecimalPipe
]
})
export class CurrencyInputMaskDirective implements ControlValueAccessor, OnInit {
private el: HTMLInputElement | any;
private onModelChange: Function | any;
private onModelTouched: Function | any;
private lastNumVal: number | any;
private DECIMAL_MARK = ".";
constructor(
private elementRef: ElementRef,
private decimalPipe: DecimalPipe
) {
}
ngOnInit() {
this.el = this.elementRef.nativeElement;
}
@HostListener("focus", ["$event"])
handleFocus(event: any) {
const strVal: string = this.getInputValue();
const unmaskedStr: string = this.getUnmaskedValue(strVal);
this.updateInputValue(unmaskedStr);
}
@HostListener("cut", ["$event"])
handleCut(event: any) {
setTimeout(() => {
this.inputUpdated();
}, 0);
}
@HostListener("keypress", ["$event"])
handleKeypress(event: any) {
// Restrict characters
const newChar: string = String.fromCharCode(event.which);
const allowedChars: RegExp = /^[\d.]+$/;
if (!allowedChars.test(newChar)) {
event.preventDefault();
return;
}
// Handle decimal mark input
const currentValue: string = event.target.value;
const separatorIdx: number = currentValue.indexOf(this.DECIMAL_MARK);
const hasFractionalPart: boolean = (separatorIdx >= 0);
if (!hasFractionalPart || newChar !== this.DECIMAL_MARK) {
return;
}
const isOutsideSelection = !this.isIdxBetweenSelection(separatorIdx);
if (isOutsideSelection) {
const positionAfterMark = separatorIdx + 1;
this.setCursorPosition(positionAfterMark);
event.preventDefault();
return;
}
}
@HostListener("input", ["$event"])
handleInput(event: any) {
this.inputUpdated();
}
@HostListener("paste", ["$event"])
handlePaste(event: any) {
setTimeout(() => {
this.inputUpdated();
}, 1);
}
@HostListener("blur", ["$event"])
handleBlur(event: any) {
const strVal: string = this.getInputValue();
const numVal: any = this.convertStrToDecimal(strVal);
this.maskInput(numVal);
this.onModelTouched.apply(event);
}
registerOnChange(callbackFunction: Function): void {
this.onModelChange = callbackFunction;
}
registerOnTouched(callbackFunction: Function): void {
this.onModelTouched = callbackFunction;
}
setDisabledState(value: boolean): void {
this.el.disabled = value;
}
writeValue(numValue: number): void {
this.maskInput(numValue);
}
private maskInput(numVal: number): void {
if (!this.isNumeric(numVal)) {
this.updateInputValue("");
return;
}
const strVal: string = this.convertDecimalToStr(numVal);
const newVal: any = this.transformWithPipe(strVal);
this.updateInputValue(newVal);
}
private inputUpdated() {
this.restrictDecimalValue();
const strVal: string = this.getInputValue();
const unmaskedVal: string = this.getUnmaskedValue(strVal);
const numVal: any = this.convertStrToDecimal(unmaskedVal);
if (numVal !== this.lastNumVal) {
this.lastNumVal = numVal;
this.onModelChange(numVal);
}
}
private restrictDecimalValue(): void {
const strVal: string = this.getInputValue();
const dotIdx: number = strVal.indexOf(this.DECIMAL_MARK);
const hasFractionalPart: boolean = (dotIdx >= 0);
if (hasFractionalPart) {
const fractionalPart: string = strVal.substring(dotIdx + 1);
if (fractionalPart.length > 2) {
const choppedVal: string = strVal.substring(0, dotIdx + 3);
this.updateInputValue(choppedVal, true);
return;
}
}
}
private transformWithPipe(str: any) {
return this.decimalPipe.transform(str, "1.2-2");
}
private getUnmaskedValue(value: string): string {
return value.replace(/[^-\d\\.]/g, "");
}
private updateInputValue(value: string, savePosition = false) {
if (savePosition) {
this.saveCursorPosition();
}
this.el.value = value;
}
private getInputValue(): string {
return this.el.value;
}
private convertStrToDecimal(str: any) {
return (this.isNumeric(str)) ? parseFloat(str) : null;
}
private convertDecimalToStr(n: number): string {
return (this.isNumeric(n)) ? n + "" : "";
}
private isNumeric(n: any): boolean {
return !isNaN(parseFloat(n)) && isFinite(n);
}
private saveCursorPosition() {
const position: number = this.el.selectionStart;
setTimeout(() => {
this.setCursorPosition(position);
}, 1);
}
private setCursorPosition(position: number) {
this.el.selectionStart = position;
this.el.selectionEnd = position;
}
private isIdxBetweenSelection(idx: number) {
if (this.el.selectionStart === this.el.selectionEnd) {
return false;
}
return (idx >= this.el.selectionStart && idx < this.el.selectionEnd);
}
}

View File

@@ -0,0 +1,4 @@
export default function deepCopy(data: any) {
if (!data) return {};
return JSON.parse(JSON.stringify(data));
}

View File

@@ -0,0 +1,16 @@
export default function isEmpty(value: any) {
switch (value) {
case '':
case 0:
case '0':
case null:
case false:
case 'null':
case 'undefined':
case undefined:
case typeof value === 'undefined':
return true;
default:
return false;
}
}

View File

@@ -0,0 +1,134 @@
import {Pipe, PipeTransform} from '@angular/core';
@Pipe({
name: 'filterBy',
pure: false
})
export class FilterPipe implements PipeTransform {
static isFoundOnWalking(value, key) {
let walker = value;
let found = false;
do {
if (
walker.hasOwnProperty(key) ||
Object.getOwnPropertyDescriptor(walker, key)
) {
found = true;
break;
}
// tslint:disable-next-line:no-conditional-assignment
} while ((walker = Object.getPrototypeOf(walker)));
return found;
}
static isNumber(value) {
return !isNaN(parseInt(value, 10)) && isFinite(value);
}
/**
* Checks function's value if type is function otherwise same value
*/
static getValue(value: any): any {
return typeof value === 'function' ? value() : value;
}
private filterByString(filter) {
if (filter) {
filter = filter.toLowerCase();
}
return value =>
!filter ||
(value ? ('' + value).toLowerCase().indexOf(filter) !== -1 : false);
}
private filterByBoolean(filter) {
return value => Boolean(value) === filter;
}
private filterByObject(filter) {
return value => {
for (const key in filter) {
if (key === '$or') {
if (!this.filterByOr(filter.$or)(FilterPipe.getValue(value))) {
return false;
}
continue;
}
if (!value || !FilterPipe.isFoundOnWalking(value, key)) {
return false;
}
if (!this.isMatching(filter[key], FilterPipe.getValue(value[key]))) {
return false;
}
}
return true;
};
}
private isMatching(filter, val) {
switch (typeof filter) {
case 'boolean':
return this.filterByBoolean(filter)(val);
case 'string':
return this.filterByString(filter)(val);
case 'object':
return this.filterByObject(filter)(val);
}
return this.filterDefault(filter)(val);
}
/**
* Filter value by $or
*/
private filterByOr(filter: any[]): (value: any) => boolean {
return (value: any) => {
const length = filter.length;
const arrayComparison = i => value.indexOf(filter[i]) !== -1;
const otherComparison = i => this.isMatching(filter[i], value);
const comparison = Array.isArray(value)
? arrayComparison
: otherComparison;
for (let i = 0; i < length; i++) {
if (comparison(i)) {
return true;
}
}
return false;
};
}
/**
* Default filterDefault function
*/
private filterDefault(filter: any): (value: any) => boolean {
return (value: any) => filter === undefined || filter === value;
}
transform(array: any[], filter: any): any {
if (!array) {
return array;
}
switch (typeof filter) {
case 'boolean':
return array.filter(this.filterByBoolean(filter));
case 'string':
if (FilterPipe.isNumber(filter)) {
return array.filter(this.filterDefault(filter));
}
return array.filter(this.filterByString(filter));
case 'object':
return array.filter(this.filterByObject(filter));
case 'function':
return array.filter(filter);
}
return array.filter(this.filterDefault(filter));
}
}

View File

@@ -0,0 +1,10 @@
export default function generateParamsValue(jsonValue: any) {
let params = '';
Object.keys(jsonValue).forEach((key) => {
if (jsonValue[key]) {
params += key + '=' + jsonValue[key] + '&';
}
});
params = params.substring(0, params.length - 1);
return params;
}

View File

@@ -0,0 +1,41 @@
import {Directive, ElementRef, EventEmitter, HostListener, Output} from '@angular/core';
@Directive({
selector: '[appNumberOnly]'
})
export class NumberOnlyDirective {
// Emit the parsed number value
@Output() numberValue: EventEmitter<number> = new EventEmitter<number>();
// Allow key codes for special events. Reflect :
// Allow decimal numbers with 2 decimal places and negative values
private regex: RegExp = new RegExp(/^\d*\.?\d{0,2}$/g);
// Backspace, tab, end, home
private specialKeys: Array<string> = ['Backspace', 'Tab', 'End', 'Home', '-', 'ArrowLeft', 'ArrowRight', 'Del', 'Delete'];
constructor(private el: ElementRef) {
}
@HostListener('keydown', ['$event'])
onKeyDown(event: KeyboardEvent) {
if (this.specialKeys.indexOf(event.key) !== -1) {
return;
}
const current: string = this.el.nativeElement.value;
const position = this.el.nativeElement.selectionStart;
const next: string = [current.slice(0, position), event.key === 'Decimal' ? '.' : event.key, current.slice(position)].join('');
if (next && !String(next).match(this.regex)) {
event.preventDefault();
}
}
@HostListener('input', ['$event'])
onInput(event: KeyboardEvent) {
// Get the parsed number value
const parsedValue = Number(this.el.nativeElement.value);
// Emit the parsed value only if it's a number
if (!isNaN(parsedValue)) {
this.numberValue.emit(parsedValue);
}
}
}

View File

@@ -0,0 +1,22 @@
export function orderByArray(data: any, key: string) {
if (!data) return;
if (data.length === 0) return;
return data.sort((a: any, b: any) => a[key].localeCompare(b[key], 'en', {numeric: true}));
}
export function sortByProperty<T>(array: T[], propName: keyof T, order: 'ASC' | 'DESC') {
array.sort((a, b) => {
if (a[propName] < b[propName]) {
return -1;
}
if (a[propName] > b[propName]) {
return 1;
}
return 0;
});
if (order === 'DESC') {
array.reverse();
}
}

View File

@@ -0,0 +1,22 @@
export function generateUUID() {
// Public Domain/MIT
let d = new Date().getTime(); // Timestamp
let d2 =
(typeof performance !== 'undefined' &&
performance.now &&
performance.now() * 1000) ||
0; // Time in microseconds since page-load or 0 if unsupported
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
let r = Math.random() * 16; // random number between 0 and 16
if (d > 0) {
// Use timestamp until depleted
r = (d + r) % 16 | 0;
d = Math.floor(d / 16);
} else {
// Use microseconds since page-load if supported
r = (d2 + r) % 16 | 0;
d2 = Math.floor(d2 / 16);
}
return (c === 'x' ? r : (r & 0x3) | 0x8).toString(16);
});
}