491 lines
12 KiB
TypeScript
491 lines
12 KiB
TypeScript
import {
|
|
AfterContentInit,
|
|
AfterViewInit,
|
|
Component,
|
|
ElementRef,
|
|
HostBinding,
|
|
OnInit,
|
|
QueryList,
|
|
ViewChildren
|
|
} from '@angular/core'
|
|
import { ActivatedRoute, Router } from '@angular/router'
|
|
import { UploadFile } from '@sasjs/adapter'
|
|
import * as XLSX from '@sheet/crypto'
|
|
import { XLMapListItem, globals } from '../_globals'
|
|
import { FileUploader } from '../models/FileUploader.class'
|
|
import {
|
|
EventService,
|
|
LicenceService,
|
|
LoggerService,
|
|
SasService,
|
|
SasStoreService
|
|
} from '../services'
|
|
import { getCellAddress, getFinishingCell } from './utils/xl.utils'
|
|
import { blobToFile, byteArrayToBinaryString } from './utils/file.utils'
|
|
|
|
interface XLMapRule {
|
|
XLMAP_ID: string
|
|
XLMAP_SHEET: string
|
|
XLMAP_RANGE_ID: string
|
|
XLMAP_START: string
|
|
XLMAP_FINISH: string
|
|
}
|
|
|
|
interface XLUploadEntry {
|
|
LOAD_REF: string
|
|
XLMAP_ID: string
|
|
XLMAP_RANGE_ID: string
|
|
ROW_NO: number
|
|
COL_NO: number
|
|
VALUE_TXT: string
|
|
}
|
|
|
|
enum Status {
|
|
NoMapSelected,
|
|
FetchingRules,
|
|
ReadyToUpload,
|
|
ExtractingData,
|
|
ReadyToSubmit,
|
|
SubmittingExtractedData,
|
|
Submitting
|
|
}
|
|
|
|
enum Tabs {
|
|
Rules,
|
|
Data
|
|
}
|
|
|
|
@Component({
|
|
selector: 'app-xlmap',
|
|
templateUrl: './xlmap.component.html',
|
|
styleUrls: ['./xlmap.component.scss']
|
|
})
|
|
export class XLMapComponent implements AfterContentInit, AfterViewInit, OnInit {
|
|
@HostBinding('class.content-container') contentContainerClass = true
|
|
@ViewChildren('fileUploadInput')
|
|
fileUploadInputCompList: QueryList<ElementRef> = new QueryList()
|
|
|
|
StatusEnum = Status
|
|
TabsEnum = Tabs
|
|
|
|
public selectedTab = Tabs.Rules
|
|
public rulesSource = globals.dcLib + '.MPE_XLMAP_RULES'
|
|
|
|
public xlmaps: XLMapListItem[] = []
|
|
public selectedXLMap: XLMapListItem | undefined = undefined
|
|
public searchString = ''
|
|
public xlmapsLoading = true
|
|
public isLoading = false
|
|
public isLoadingDesc = ''
|
|
public status = Status.NoMapSelected
|
|
|
|
public xlmapRulesHeaders = [
|
|
'XLMAP_SHEET',
|
|
'XLMAP_RANGE_ID',
|
|
'XLMAP_START',
|
|
'XLMAP_FINISH'
|
|
]
|
|
public xlmapRulesColumns = [
|
|
{
|
|
data: 'XLMAP_SHEET'
|
|
},
|
|
{
|
|
data: 'XLMAP_RANGE_ID'
|
|
},
|
|
|
|
{
|
|
data: 'XLMAP_START'
|
|
},
|
|
{
|
|
data: 'XLMAP_FINISH'
|
|
}
|
|
]
|
|
public xlmapRules: XLMapRule[] = []
|
|
|
|
public xlUploadHeader = ['XLMAP_RANGE_ID', 'ROW_NO', 'COL_NO', 'VALUE_TXT']
|
|
public xlUploadColumns = [
|
|
{
|
|
data: 'XLMAP_RANGE_ID'
|
|
},
|
|
{
|
|
data: 'ROW_NO'
|
|
},
|
|
{
|
|
data: 'COL_NO'
|
|
},
|
|
{
|
|
data: 'VALUE_TXT'
|
|
}
|
|
]
|
|
public xlData: XLUploadEntry[] = []
|
|
|
|
public showUploadModal = false
|
|
public hasBaseDropZoneOver = false
|
|
public filename = ''
|
|
public submitLimitNotice = false
|
|
|
|
public uploader: FileUploader = new FileUploader()
|
|
|
|
public licenceState = this.licenceService.licenceState
|
|
|
|
public hotTableLicenseKey: string | undefined = undefined
|
|
public hotTableMaxRows =
|
|
this.licenceState.value.viewer_rows_allowed || Infinity
|
|
|
|
constructor(
|
|
private eventService: EventService,
|
|
private licenceService: LicenceService,
|
|
private loggerService: LoggerService,
|
|
private route: ActivatedRoute,
|
|
private router: Router,
|
|
private sasStoreService: SasStoreService,
|
|
private sasService: SasService
|
|
) {}
|
|
|
|
public xlmapOnClick(xlmap: XLMapListItem) {
|
|
if (xlmap.id !== this.selectedXLMap?.id) {
|
|
this.selectedXLMap = xlmap
|
|
this.xlData = []
|
|
this.filename = ''
|
|
this.uploader.queue = []
|
|
if (this.fileUploadInputCompList.first) {
|
|
this.fileUploadInputCompList.first.nativeElement.value = ''
|
|
}
|
|
this.selectedTab = Tabs.Rules
|
|
this.viewXLMapRules()
|
|
this.router.navigateByUrl('/home/files/' + xlmap.id)
|
|
}
|
|
}
|
|
|
|
public xlmapListOnFilter() {
|
|
if (this.searchString.length > 0) {
|
|
const array: XLMapListItem[] = globals.xlmaps
|
|
this.xlmaps = array.filter((item) =>
|
|
item.id.toLowerCase().includes(this.searchString.toLowerCase())
|
|
)
|
|
} else {
|
|
this.xlmaps = globals.xlmaps
|
|
}
|
|
}
|
|
|
|
public isActiveXLMap(id: string) {
|
|
return this.selectedXLMap?.id === id
|
|
}
|
|
|
|
public maxWidthChecker(width: any, col: any) {
|
|
if (width > 200) return 200
|
|
else return width
|
|
}
|
|
|
|
public getCellConfiguration() {
|
|
return { readOnly: true }
|
|
}
|
|
|
|
public rowHeaders() {
|
|
return ' '
|
|
}
|
|
|
|
public onShowUploadModal() {
|
|
this.showUploadModal = true
|
|
}
|
|
|
|
/**
|
|
* Called by FileDropDirective
|
|
* @param e true if file is dragged over the drop zone
|
|
*/
|
|
public fileOverBase(e: boolean): void {
|
|
this.hasBaseDropZoneOver = e
|
|
}
|
|
|
|
public getFileDesc(event: any, dropped = false) {
|
|
const file = dropped ? event[0] : event.target.files[0]
|
|
|
|
if (!file) return
|
|
|
|
const filename = file.name
|
|
this.filename = filename
|
|
|
|
const fileType = filename.slice(
|
|
filename.lastIndexOf('.') + 1,
|
|
filename.lastIndexOf('.') + 4
|
|
)
|
|
|
|
if (fileType.toLowerCase() === 'xls') {
|
|
this.showUploadModal = false
|
|
this.isLoading = true
|
|
this.isLoadingDesc = 'Extracting Data'
|
|
this.status = Status.ExtractingData
|
|
|
|
const reader = new FileReader()
|
|
reader.onload = async (theFile: any) => {
|
|
/* read workbook */
|
|
const bstr = byteArrayToBinaryString(theFile.target.result)
|
|
let wb: XLSX.WorkBook | undefined = undefined
|
|
|
|
const xlsxOptions: XLSX.ParsingOptions = {
|
|
type: 'binary',
|
|
cellDates: false,
|
|
cellFormula: true,
|
|
cellStyles: true,
|
|
cellNF: false,
|
|
cellText: false
|
|
}
|
|
|
|
try {
|
|
wb = XLSX.read(bstr, {
|
|
...xlsxOptions
|
|
})
|
|
} catch (err: any) {
|
|
this.eventService.showAbortModal(
|
|
null,
|
|
err,
|
|
undefined,
|
|
'Error reading file'
|
|
)
|
|
}
|
|
|
|
if (!wb) {
|
|
this.isLoading = false
|
|
this.isLoadingDesc = ''
|
|
this.status = Status.ReadyToUpload
|
|
this.uploader.queue.pop()
|
|
return
|
|
}
|
|
|
|
this.extractData(wb)
|
|
return
|
|
}
|
|
|
|
reader.readAsArrayBuffer(file)
|
|
} else {
|
|
this.isLoading = false
|
|
this.isLoadingDesc = ''
|
|
this.status = Status.ReadyToUpload
|
|
this.showUploadModal = true
|
|
this.uploader.queue.pop()
|
|
|
|
const abortMsg =
|
|
'Invalid file type "<b>' +
|
|
this.filename +
|
|
'</b>". Please upload excel file.'
|
|
this.eventService.showAbortModal(null, abortMsg)
|
|
}
|
|
}
|
|
|
|
public discardExtractedData() {
|
|
this.isLoading = false
|
|
this.isLoadingDesc = ''
|
|
this.status = Status.ReadyToUpload
|
|
this.xlData = []
|
|
this.selectedTab = Tabs.Rules
|
|
this.filename = ''
|
|
this.uploader.queue = []
|
|
if (this.fileUploadInputCompList.first) {
|
|
this.fileUploadInputCompList.first.nativeElement.value = ''
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Submits attached excel file that is in preview mode
|
|
*/
|
|
public submitExcel() {
|
|
if (this.licenceState.value.submit_rows_limit !== Infinity) {
|
|
this.submitLimitNotice = true
|
|
return
|
|
}
|
|
|
|
this.submit()
|
|
}
|
|
|
|
public submit() {
|
|
if (!this.selectedXLMap || !this.xlData.length) return
|
|
|
|
this.status = Status.Submitting
|
|
this.isLoading = true
|
|
this.isLoadingDesc = 'Submitting extracted data'
|
|
|
|
const filesToUpload: UploadFile[] = []
|
|
|
|
for (const file of this.uploader.queue) {
|
|
filesToUpload.push({
|
|
file: file,
|
|
fileName: file.name
|
|
})
|
|
}
|
|
|
|
const csvContent =
|
|
Object.keys(this.xlData[0]).join(',') +
|
|
'\n' +
|
|
this.xlData
|
|
.slice(0, this.licenceState.value.submit_rows_limit)
|
|
.map((row: any) => Object.values(row).join(','))
|
|
.join('\n')
|
|
|
|
const blob = new Blob([csvContent], { type: 'application/csv' })
|
|
const file: File = blobToFile(blob, this.filename + '.csv')
|
|
|
|
filesToUpload.push({
|
|
file: file,
|
|
fileName: file.name
|
|
})
|
|
|
|
const uploadUrl = 'services/editors/loadfile'
|
|
this.sasService
|
|
.uploadFile(uploadUrl, filesToUpload, {
|
|
table: this.selectedXLMap.targetDS
|
|
})
|
|
.then((res: any) => {
|
|
if (res.sasjsAbort) {
|
|
const abortRes = res
|
|
const abortMsg = abortRes.sasjsAbort[0].MSG
|
|
const macMsg = abortRes.sasjsAbort[0].MAC
|
|
|
|
this.eventService.showAbortModal('', abortMsg, {
|
|
SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
|
|
SYSERRORTEXT: abortRes.SYSERRORTEXT,
|
|
MAC: macMsg
|
|
})
|
|
} else if (res.sasparams) {
|
|
const params = res.sasparams[0]
|
|
const tableId = params.DSID
|
|
this.router.navigateByUrl('/stage/' + tableId)
|
|
}
|
|
})
|
|
.catch((err: any) => {
|
|
this.eventService.catchResponseError('file upload', err)
|
|
})
|
|
.finally(() => {
|
|
this.status = Status.ReadyToSubmit
|
|
this.isLoading = false
|
|
this.isLoadingDesc = ''
|
|
})
|
|
}
|
|
|
|
public extractData(wb: XLSX.WorkBook) {
|
|
const extractedData: XLUploadEntry[] = []
|
|
|
|
this.xlmapRules.forEach((rule) => {
|
|
let sheetName = rule.XLMAP_SHEET
|
|
// if sheet name is not an absolute name rather an index string like /1, /2, etc
|
|
// we extract the index and find absolute sheet name for specified index
|
|
if (sheetName.startsWith('/')) {
|
|
const temp = sheetName.split('/')[1]
|
|
const sheetIndex = parseInt(temp) - 1
|
|
sheetName = wb.SheetNames[sheetIndex]
|
|
}
|
|
|
|
const sheet = wb.Sheets[sheetName]
|
|
|
|
const arrayOfObjects = <any[]>XLSX.utils.sheet_to_json(sheet, {
|
|
raw: true,
|
|
header: 'A',
|
|
blankrows: true
|
|
})
|
|
|
|
const start = getCellAddress(rule.XLMAP_START, arrayOfObjects)
|
|
const finish = getFinishingCell(start, rule.XLMAP_FINISH, arrayOfObjects)
|
|
|
|
const a1Range = `${start}:${finish}`
|
|
|
|
const range = XLSX.utils.decode_range(a1Range)
|
|
|
|
const rangedData = <any[]>XLSX.utils.sheet_to_json(sheet, {
|
|
raw: true,
|
|
range: a1Range,
|
|
header: 'A',
|
|
blankrows: true
|
|
})
|
|
|
|
for (let i = 0; i < rangedData.length; i++) {
|
|
const row = rangedData[i]
|
|
|
|
// `range.s.c` is the index of first column in the range
|
|
// `range.e.c` is the index of last column in the range
|
|
// we'll iterate from first column to last column and
|
|
// extract value where defined and push to extracted data array
|
|
for (let j = range.s.c, x = 0; j <= range.e.c; j++, x++) {
|
|
const col = XLSX.utils.encode_col(j)
|
|
|
|
if (col in row) {
|
|
// in excel's R1C1 notation indexing starts from 1 but in JS it starts from 0
|
|
// therefore, we'll have to add 1 to rows and cols
|
|
extractedData.push({
|
|
LOAD_REF: '0',
|
|
XLMAP_ID: rule.XLMAP_ID,
|
|
XLMAP_RANGE_ID: rule.XLMAP_RANGE_ID,
|
|
ROW_NO: i + 1,
|
|
COL_NO: x + 1,
|
|
VALUE_TXT: row[col]
|
|
})
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
this.status = Status.ReadyToSubmit
|
|
this.isLoading = false
|
|
this.isLoadingDesc = ''
|
|
|
|
this.xlData = extractedData
|
|
this.selectedTab = Tabs.Data
|
|
}
|
|
|
|
async viewXLMapRules() {
|
|
if (!this.selectedXLMap) return
|
|
|
|
this.isLoading = true
|
|
this.isLoadingDesc = 'Loading excel rules'
|
|
this.status = Status.FetchingRules
|
|
|
|
await this.sasStoreService
|
|
.getXLMapRules(this.selectedXLMap.id)
|
|
.then((res) => {
|
|
this.xlmapRules = res.xlmaprules
|
|
this.status = Status.ReadyToUpload
|
|
})
|
|
.catch((err) => {
|
|
this.loggerService.error(err)
|
|
})
|
|
|
|
this.isLoading = false
|
|
this.isLoadingDesc = ''
|
|
}
|
|
|
|
private load() {
|
|
this.xlmaps = globals.xlmaps
|
|
this.xlmapsLoading = false
|
|
|
|
const id = this.route.snapshot.params['id']
|
|
|
|
if (id) {
|
|
const xlmapListItem = this.xlmaps.find((item) => item.id === id)
|
|
if (xlmapListItem) {
|
|
this.selectedXLMap = xlmapListItem
|
|
this.viewXLMapRules()
|
|
}
|
|
}
|
|
}
|
|
|
|
ngOnInit() {
|
|
this.licenceService.hot_license_key.subscribe(
|
|
(hot_license_key: string | undefined) => {
|
|
this.hotTableLicenseKey = hot_license_key
|
|
}
|
|
)
|
|
}
|
|
|
|
ngAfterViewInit() {
|
|
return
|
|
}
|
|
|
|
ngAfterContentInit(): void {
|
|
if (globals.editor.startupSet) {
|
|
this.load()
|
|
} else {
|
|
this.eventService.onStartupDataLoaded.subscribe(() => {
|
|
this.load()
|
|
})
|
|
}
|
|
}
|
|
}
|