src/app/xlmap/xlmap.component.ts

Implements

AfterContentInit AfterViewInit OnInit

Metadata

Relationships

Index

Properties
Methods
HostBindings

Constructor

constructor(eventService: EventService, licenceService: LicenceService, loggerService: LoggerService, route: ActivatedRoute, router: Router, sasStoreService: SasStoreService, sasService: SasService)
Parameters :
Name Type Optional
eventService EventService No
licenceService LicenceService No
loggerService LoggerService No
route ActivatedRoute No
router Router No
sasStoreService SasStoreService No
sasService SasService No

HostBindings

class.content-container
Type : boolean
Default value : true

Methods

Public afterGetColHeader
afterGetColHeader(column: number, th: any)
Parameters :
Name Type Optional
column number No
th any No
Returns : void
Private buildHotSettings
buildHotSettings()
Returns : Handsontable.GridSettings
Public discardExtractedData
discardExtractedData()
Returns : void
Public extractData
extractData(wb: XLSX.WorkBook)
Parameters :
Name Type Optional
wb XLSX.WorkBook No
Returns : void
Public fileOverBase
fileOverBase(e: boolean)

Called by FileDropDirective

Parameters :
Name Type Optional Description
e boolean No

true if file is dragged over the drop zone

Returns : void
Public getCellConfiguration
getCellConfiguration()
Returns : { readOnly: boolean; }
Public getFileDesc
getFileDesc(event: any, dropped: unknown)
Parameters :
Name Type Optional Default value
event any No
dropped unknown No false
Returns : void
Public isActiveXLMap
isActiveXLMap(id: string)
Parameters :
Name Type Optional
id string No
Returns : boolean
Private load
load()
Returns : void
Public maxWidthChecker
maxWidthChecker(width: any, col: any)
Parameters :
Name Type Optional
width any No
col any No
Returns : any
ngAfterContentInit
ngAfterContentInit()
Returns : void
ngAfterViewInit
ngAfterViewInit()
Returns : void
ngOnInit
ngOnInit()
Returns : void
Public onShowUploadModal
onShowUploadModal()
Returns : void
Public rowHeaders
rowHeaders()
Returns : string
Private setSelectedTab
setSelectedTab(tab: Tabs)
Parameters :
Name Type Optional
tab Tabs No
Returns : void
Public submit
submit()
Returns : void
Public submitExcel
submitExcel()

Submits attached excel file that is in preview mode

Returns : void
Async viewXLMapRules
viewXLMapRules()
Returns : any
Public xlmapListOnFilter
xlmapListOnFilter()
Returns : void
Public xlmapOnClick
xlmapOnClick(xlmap: XLMapListItem)
Parameters :
Name Type Optional
xlmap XLMapListItem No
Returns : void

Properties

contentContainerClass
Type : unknown
Default value : true
Decorators :
@HostBinding('class.content-container')
Public filename
Type : string
Default value : ''
fileUploadInputCompList
Type : QueryList<ElementRef>
Default value : new QueryList()
Decorators :
@ViewChildren('fileUploadInput')
Public hasBaseDropZoneOver
Type : unknown
Default value : false
Public hotTableLicenseKey
Type : string | undefined
Default value : undefined
Public hotTableMaxRows
Type : unknown
Default value : this.licenceState.value.viewer_rows_allowed || Infinity
hotTableSettings
Type : unknown
Default value : signal<Handsontable.GridSettings>(this.buildHotSettings())
Public isLoading
Type : unknown
Default value : false
Public isLoadingDesc
Type : string
Default value : ''
Public licenceState
Type : unknown
Default value : this.licenceService.licenceState
Public rulesSource
Type : unknown
Default value : globals.dcLib + '.MPE_XLMAP_RULES'
Public searchString
Type : string
Default value : ''
Public selectedTab
Type : unknown
Default value : Tabs.Rules
Public selectedXLMap
Type : XLMapListItem | undefined
Default value : undefined
Public showUploadModal
Type : unknown
Default value : false
Public status
Type : unknown
Default value : Status.NoMapSelected
StatusEnum
Type : unknown
Default value : Status
Public submitLimitNotice
Type : unknown
Default value : false
TabsEnum
Type : unknown
Default value : Tabs
Public uploader
Type : FileUploader
Default value : new FileUploader()
Public xlData
Type : XLUploadEntry[]
Default value : []
Public xlmapRules
Type : XLMapRule[]
Default value : []
Public xlmapRulesColumns
Type : []
Default value : [ { data: 'XLMAP_SHEET' }, { data: 'XLMAP_RANGE_ID' }, { data: 'XLMAP_START' }, { data: 'XLMAP_FINISH' } ]
Public xlmapRulesHeaders
Type : []
Default value : [ 'XLMAP_SHEET', 'XLMAP_RANGE_ID', 'XLMAP_START', 'XLMAP_FINISH' ]
Public xlmaps
Type : XLMapListItem[]
Default value : []
Public xlmapsLoading
Type : unknown
Default value : true
Public xlUploadColumns
Type : []
Default value : [ { data: 'XLMAP_RANGE_ID' }, { data: 'ROW_NO' }, { data: 'COL_NO' }, { data: 'VALUE_TXT' } ]
Public xlUploadHeader
Type : []
Default value : ['XLMAP_RANGE_ID', 'ROW_NO', 'COL_NO', 'VALUE_TXT']
import {
  AfterContentInit,
  AfterViewInit,
  Component,
  ElementRef,
  HostBinding,
  OnInit,
  QueryList,
  signal,
  ViewChildren,
  ViewEncapsulation
} 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'
import Handsontable from 'handsontable'
import { UploadFileResponse } from '../models/UploadFile'

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'],
  encapsulation: ViewEncapsulation.None,
  standalone: false
})
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

  // Stable settings reference so the HOT v17 wrapper doesn't re-run
  // updateSettings on every change-detection tick (the previous getter returned a
  // new object each call, causing an infinite render loop / freeze). Rebuilt only
  // when the selected tab, or the license, changes (see setSelectedTab + ngOnInit).
  hotTableSettings = signal<Handsontable.GridSettings>(this.buildHotSettings())

  private buildHotSettings(): Handsontable.GridSettings {
    return {
      multiColumnSorting: true,
      viewportRowRenderingOffset: 50,
      colHeaders:
        this.selectedTab === this.TabsEnum.Rules
          ? this.xlmapRulesHeaders
          : this.xlUploadHeader,
      columns:
        this.selectedTab === this.TabsEnum.Rules
          ? this.xlmapRulesColumns
          : this.xlUploadColumns,
      filters: true,
      height: '100%',
      stretchH: 'all',
      afterGetColHeader: this.afterGetColHeader,
      modifyColWidth: this.maxWidthChecker,
      cells: this.getCellConfiguration,
      maxRows: this.hotTableMaxRows,
      manualColumnResize: true,
      rowHeaders: this.rowHeaders,
      rowHeaderWidth: 15,
      rowHeights: 20,
      licenseKey: this.hotTableLicenseKey,
      className: 'htDark',
      theme: 'ht-theme-classic'
    }
  }

  // Single place that changes the active tab so the settings signal stays in sync.
  private setSelectedTab(tab: Tabs) {
    this.selectedTab = tab
    this.hotTableSettings.set(this.buildHotSettings())
  }

  constructor(
    private eventService: EventService,
    private licenceService: LicenceService,
    private loggerService: LoggerService,
    private route: ActivatedRoute,
    private router: Router,
    private sasStoreService: SasStoreService,
    private sasService: SasService
  ) {}

  public afterGetColHeader(column: number, th: any) {
    // Dark mode
    th.classList.add(globals.handsontable.darkTableHeaderClass)
  }

  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.setSelectedTab(Tabs.Rules)
      this.viewXLMapRules()
      this.router.navigateByUrl('/home/excel-maps/' + 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.setSelectedTab(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: UploadFileResponse) => {
        if (res.adapterResponse.sasjsAbort) {
          const abortRes = res.adapterResponse
          const abortMsg = abortRes.sasjsAbort[0].MSG
          const macMsg = abortRes.sasjsAbort[0].MAC

          this.eventService.showAbortModal('', abortMsg, {
            SYSWARNINGTEXT: abortRes.SYSWARNINGTEXT,
            SYSERRORTEXT: abortRes.SYSERRORTEXT,
            MAC: macMsg,
            _PROGRAM: abortRes._PROGRAM
          })
        } else if (res.adapterResponse.sasparams) {
          const params = res.adapterResponse.sasparams[0]
          const tableId = params.DSID
          this.router.navigateByUrl('/stage/' + tableId)
        }
      })
      .catch((err: any) => {
        this.eventService.catchResponseError('file upload', err.response)
      })
      .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.setSelectedTab(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
        this.hotTableSettings.set(this.buildHotSettings())
      }
    )
  }

  ngAfterViewInit() {
    return
  }

  ngAfterContentInit(): void {
    if (globals.editor.startupSet) {
      this.load()
    } else {
      this.eventService.onStartupDataLoaded.subscribe(() => {
        this.load()
      })
    }
  }
}
<app-sidebar>
  @if (xlmapsLoading) {
    <div class="my-10-mx-auto text-center">
      <clr-spinner clrMedium></clr-spinner>
    </div>
  }

  <clr-tree>
    <clr-tree-node class="search-node">
      <div class="tree-search-wrapper">
        <input
          appStealFocus
          clrInput
          #searchXLMapTreeInput
          placeholder="Filter by Id"
          name="input"
          [(ngModel)]="searchString"
          (keyup)="xlmapListOnFilter()"
          autocomplete="off"
        />
        @if (searchXLMapTreeInput.value.length < 1) {
          <clr-icon shape="search"></clr-icon>
        }
        @if (searchXLMapTreeInput.value.length > 0) {
          <clr-icon
            (click)="searchString = ''; xlmapListOnFilter()"
            shape="times"
          ></clr-icon>
        }
      </div>
    </clr-tree-node>

    @for (xlmap of xlmaps; track xlmap) {
      <clr-tree-node>
        <button
          (click)="xlmapOnClick(xlmap)"
          class="clr-treenode-link"
          [class.active]="isActiveXLMap(xlmap.id)"
        >
          <clr-icon shape="file"></clr-icon>
          {{ xlmap.id }}
        </button>
      </clr-tree-node>
    }
  </clr-tree>
</app-sidebar>

<div class="content-area">
  @if (!selectedXLMap) {
    <div class="no-table-selected">
      <img
        src="images/select-table.png"
        class="select-table-icon"
        alt="select table icon"
      />
      @if (xlmaps.length > 0) {
        <p class="text-center color-gray mt-10" cds-text="section">
          Please select a map
        </p>
      }
      @if (xlmaps.length < 1) {
        <p class="text-center color-gray mt-10" cds-text="section">
          No excel map is found
        </p>
      }
    </div>
  }

  @if (isLoading) {
    <div class="loadingSpinner">
      <span class="spinner"> Loading... </span>
      <div>
        <h4>{{ isLoadingDesc }}</h4>
      </div>
    </div>
  }

  @if (!isLoading && selectedXLMap) {
    <div
      appDragNdrop
      (fileDraggedOver)="onShowUploadModal()"
      class="card h-100 d-flex clr-flex-column"
    >
      <clr-tabs>
        <clr-tab>
          <button clrTabLink (click)="selectedTab = TabsEnum.Rules">
            Rules
          </button>
          <clr-tab-content *clrIfActive="selectedTab === TabsEnum.Rules">
          </clr-tab-content>
        </clr-tab>
        <clr-tab>
          <button clrTabLink (click)="selectedTab = TabsEnum.Data">Data</button>
          <clr-tab-content *clrIfActive="selectedTab === TabsEnum.Data">
          </clr-tab-content>
        </clr-tab>
      </clr-tabs>
      <ng-container *ngTemplateOutlet="actionButtons"></ng-container>
      <div class="clr-row m-0 mb-10-i viewerTitle">
        <h3 class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
          {{ selectedXLMap.id }}
        </h3>
        <i class="d-flex clr-col-12 clr-justify-content-center mt-5-i">{{
          selectedXLMap.description
        }}</i>
        <p class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
          Rules Source:
          <a
            cds-text="labelLink"
            class="ml-10"
            [routerLink]="'/view/data/' + rulesSource"
          >
            {{ rulesSource }}
          </a>
        </p>
        <p class="d-flex clr-col-12 clr-justify-content-center mt-5-i">
          Target dataset:
          <a
            cds-text="labelLink"
            class="ml-10"
            [routerLink]="'/view/data/' + selectedXLMap.targetDS"
          >
            {{ selectedXLMap.targetDS }}
          </a>
        </p>
      </div>
      <div class="clr-flex-1">
        <hot-table
          id="hot-table"
          [data]="selectedTab === TabsEnum.Rules ? xlmapRules : xlData"
          [settings]="hotTableSettings()"
        >
        </hot-table>
      </div>
    </div>
  }

  <clr-modal
    appFileDrop
    (fileOver)="fileOverBase($event)"
    (fileDrop)="getFileDesc($event, true)"
    [uploader]="uploader"
    [clrModalSize]="'xl'"
    [clrModalStaticBackdrop]="false"
    [clrModalClosable]="true"
    [(clrModalOpen)]="showUploadModal"
    class="relative"
  >
    <h3 class="modal-title">Upload File</h3>
    <div class="modal-body">
      <div class="drop-area">
        <span>Drop file anywhere to upload!</span>
      </div>

      <div class="clr-col-md-12">
        <div class="clr-row card-block mt-15 d-flex justify-content-between">
          <div class="clr-col-md-3 filterBtn">
            <span class="filterBtn w-100">
              <label
                for="file-upload"
                class="btn btn-sm btn-outline profile-buttons w-100"
              >
                Browse
              </label>
            </span>
            <input
              hidden
              #fileUploadInput
              id="file-upload"
              type="file"
              appFileSelect
              [uploader]="uploader"
              (change)="getFileDesc($event)"
            />
          </div>
        </div>
      </div>
    </div>
  </clr-modal>

  <clr-modal [(clrModalOpen)]="submitLimitNotice">
    <h3 class="modal-title">Notice</h3>
    <div class="modal-body">
      <p class="m-0">
        Due to current licence, only
        {{ licenceState.value.submit_rows_limit }} rows in a file will be
        submitted. To remove the restriction, contact
        support&#64;datacontroller.io
      </p>
    </div>
    <div class="modal-footer">
      <button
        type="button"
        class="btn btn-sm btn-primary"
        (click)="submitLimitNotice = false"
      >
        Cancel
      </button>
      <button
        type="button"
        class="btn btn-sm btn-primary"
        (click)="submit(); submitLimitNotice = false"
      >
        Submit
      </button>
    </div>
  </clr-modal>
</div>

<ng-template #actionButtons>
  <div class="clr-row m-0 clr-justify-content-center">
    @if (status === StatusEnum.ReadyToUpload) {
      <div class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4">
        <button
          type="button"
          class="btn btn-sm btn-success btn-block mr-0"
          (click)="onShowUploadModal()"
        >
          <clr-icon shape="upload"></clr-icon>
          <span>Upload</span>
        </button>
      </div>
    }
    @if (status === StatusEnum.ReadyToSubmit) {
      <div class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4">
        <button
          type="button"
          class="btn btn-sm btn-success btn-block mr-0"
          (click)="submitExcel()"
        >
          <clr-icon shape="upload"></clr-icon>
          <span>Submit</span>
        </button>
      </div>
    }
    @if (status === StatusEnum.ReadyToSubmit) {
      <div class="d-flex clr-justify-content-center clr-col-12 clr-col-lg-4">
        <button
          type="button"
          class="btn btn-sm btn-outline-danger btn-block mr-0"
          (click)="discardExtractedData()"
        >
          <clr-icon shape="times"></clr-icon>
          <span>Discard</span>
        </button>
      </div>
    }
  </div>
</ng-template>

./xlmap.component.scss

Legend
Html element
Component
Html element with directive

results matching ""

    No results matching ""