Files
dc/client/src/app/editor/editor.component.html
T
dc bd3c220af2
Build / Build-and-ng-test (pull_request) Successful in 5m15s
Lighthouse Checks / lighthouse (pull_request) Successful in 20m25s
Build / Build-and-test-development (pull_request) Successful in 33m8s
fix(embed): never build a clause from a value that did not convert, and say so in the banner
Two review findings, both about a filter that looks applied and is not:

- The transform branch of the clause builder interpolated whatever
  convertJsDateToSasDate returned, so an Invalid Date reaching the helper
  would have put a literal NaN in the WHERE clause. A non-finite result is
  now dropped, and unsupportedTransforms reports the parameter, so the
  author sees a banner instead of a broken filter.
- The range count check counted values that survived date parsing, while the
  builder counts resolved bounds. A RANGE sent two values where one did not
  parse therefore built a BETWEEN covering one period with no warning. The
  check now counts what VA sent, the builder refuses a range with more than
  one value rather than picking one, and a value set that is partly
  unparseable gets its own line ('1 of its 2 values was not a date - the
  filter covers only the rest').

The banner also now says what to do: it names the URL parameter to edit and
shows the entry grammar with an example, since the reader is the report
author and has no other way to act on it.
2026-10-28 12:44:05 +00:00

1054 lines
35 KiB
HTML

<main class="content-area d-flex clr-flex-column">
<clr-modal
appFileDrop
(fileOver)="fileOverBase($event)"
[uploader]="uploader"
(fileDrop)="attachFile($event, true)"
[clrModalSize]="'xl'"
[clrModalStaticBackdrop]="false"
[clrModalClosable]="excelUploadState === 'Validating-DQ'"
[(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="card-header"
style="display: flex; flex-direction: column; justify-content: center"
>
<div
ng2FileDrop
*ngIf="!excelFileParsing"
[class.nv-file-over]="hasBaseDropZoneOver"
(fileOver)="fileOverBase($event)"
[uploader]="uploader"
class="well my-drop-zone"
(onFileDrop)="getFileDesc($event, true)"
>
<p class="file-drop-text">Drop File Here !</p>
</div>
</div> -->
<div class="clr-row card-block mt-15 d-flex justify-content-between">
<div class="clr-col-md-auto">
<div class="encoding-block">
<clr-radio-container class="mt-0" clrInline>
<clr-radio-wrapper>
<input
type="radio"
clrRadio
value="UTF-8"
name="encoding"
[(ngModel)]="encoding"
[disabled]="filename != ''"
/>
<label>UTF-8</label>
</clr-radio-wrapper>
<clr-radio-wrapper>
<input
type="radio"
clrRadio
value="WLATIN1"
name="encoding"
[(ngModel)]="encoding"
[disabled]="filename != ''"
/>
<label>WLATIN1</label>
</clr-radio-wrapper>
</clr-radio-container>
</div>
</div>
@if (!excelFileParsing && !uploadLoading) {
<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)="attachFile($event)"
/>
</div>
}
@if (!excelFileParsing && !uploadLoading) {
<div class="clr-col-md-3 editBtn">
<button
[disabled]="true"
class="btnView btn btn-sm btn-success profile-buttons w-100"
(click)="uploadParsedFiles()"
>
Upload
</button>
</div>
}
<div class="clr-col-md-auto">
@if (excelUploadState !== 'Validating') {
<button
type="button"
class="btn btn-primary mr-0"
(click)="showUploadModal = false"
[disabled]="excelFileParsing || uploadLoading"
>
Close
</button>
}
@if (excelUploadState === 'Validating-DQ') {
<button
type="button"
class="btn btn-primary mr-0"
(click)="showUploadModal = false"
>
Skip
</button>
}
</div>
@if (excelFileParsing) {
<div class="excel-parsing clr-col-md-12">
<app-upload-stater #uploadStater></app-upload-stater>
</div>
}
</div>
</div>
</div>
<div class="modal-footer">
<!-- <button type="button" class="btn btn-outline" (click)="showUploadModal = false">Cancel</button> -->
<!-- <button type="button" class="btn btn-primary" (click)="showUploadModal = false">Close</button> -->
</div>
</clr-modal>
@if (currentEditRecordIndex > -1) {
<app-edit-record
[currentRecord]="currentEditRecord"
[recordAction]="recordAction"
[libds]="libds"
[queryFilter]="queryFilter"
[filter]="filter"
[submitLoading]="submitLoading"
[headerPks]="headerPks"
[cellValidation]="cellValidation"
[currentRecordIndex]="currentEditRecordIndex"
[currentRecordLoadings]="currentEditRecordLoadings"
[currentRecordErrors]="currentEditRecordErrors"
[currentRecordValidator]="currentEditRecordValidator"
(onRecordChange)="confirmRecordEdit()"
(onRecordInputFocused)="onRecordInputFocus($event)"
(onRecordEditClose)="closeRecordEdit()"
(onNextRecord)="onNextRecord()"
(onPreviousRecord)="onPreviousRecord()"
></app-edit-record>
}
<div #ht class="clr-flex-1">
@if (!validationDone && submit) {
<div class="progresStatic progress loop">
<progress></progress>
</div>
}
<div
appDragNdrop
(fileDraggedOver)="onShowUploadModal()"
class="card border-0 box-shadow-none-i position-relative h-100 d-flex clr-flex-column background-transparent"
>
<div
class="card-header clr-row buttonBar headerBar clr-flex-md-row clr-justify-content-center clr-justify-content-lg-end"
>
@if (tableTrue && !embed) {
<div class="clr-col-12 clr-col-md-auto clr-col-lg-auto backBtn">
<span
class="btn icon-collapse btn-sm btn-icon btn-dimmed"
[routerLink]="['/home']"
>
<clr-icon
aria-hidden="true"
shape="caret"
dir="left"
size="20"
></clr-icon>
<span class="text">Back to table selection</span>
</span>
<span
(click)="viewboxManager()"
class="btn icon-collapse btn-sm btn-icon btn-dimmed viewbox-open"
>
<clr-icon
aria-hidden="true"
shape="view-cards"
size="20"
></clr-icon>
<span class="text">Viewboxes</span>
</span>
</div>
}
<div
class="clr-col-12 clr-col-md clr-col-lg d-flex flex-column align-items-center"
[class.clr-col-lg-12]="!tableTrue"
>
<h4
*ngVar="{
libName: (libds?.split('.'))![0],
tableName: (libds?.split('.'))![1]
} as libdsParsed"
class="editor-title text-center mt-0"
>
<clr-tooltip>
<clr-icon
clrTooltipTrigger
(click)="datasetInfo = true"
shape="info-circle"
aria-label="View dataset meta info"
class="is-highlight cursor-pointer"
size="24"
></clr-icon>
@if (libdsParsed.tableName.includes('-FC')) {
<clr-icon
aria-hidden="true"
shape="bolt"
class="color-yellow"
></clr-icon>
}
<span clrTooltipTrigger>
{{ libdsParsed.libName }}.<a
class="mr-10 view-table"
[routerLink]="'/view/data/' + libds!"
>{{ libdsParsed.tableName.replace('-FC', '') }}</a
>
</span>
@if (this.dsNote && this.dsNote.length > 0) {
<clr-tooltip-content
clrPosition="bottom-left"
clrSize="lg"
*clrIfOpen
>
{{ this.dsNote }}
</clr-tooltip-content>
}
</clr-tooltip>
@if (truncationNote) {
<clr-tooltip>
<clr-icon
clrTooltipTrigger
shape="warning"
aria-label="The selection was truncated"
class="color-orange truncation-warning"
size="20"
></clr-icon>
<clr-tooltip-content
clrPosition="right"
clrSize="lg"
*clrIfOpen
>
{{ truncationNote }}
</clr-tooltip-content>
</clr-tooltip>
}
@if (dataSource) {
@if (!zeroFilterRows) {
({{ dataSource.length | thousandSeparator: ',' }}
{{ dataSource.length === 1 ? 'row' : 'rows' }}, {{ cols.length
}}{{ cols.length === 1 ? ' col' : ' cols' }})
}
@if (zeroFilterRows) {
(0 rows)
}
}
</h4>
</div>
@if (tableTrue) {
<div class="clr-col-12 clr-col-md-auto clr-col-lg-auto btnCtrl">
@if (hotTable.readOnly && !uploadPreview) {
@if (!isVaEmbed) {
<button
type="button"
class="btnView btn icon-collapse btn-sm btn-icon btn-dimmed"
(click)="openQb()"
>
<clr-icon aria-hidden="true" shape="filter"></clr-icon>
<span class="text">Filter</span>
</button>
}
<button
type="button"
class="btn icon-collapse btn-sm btn-primary"
(click)="editTable()"
>
<clr-icon aria-hidden="true" shape="note"></clr-icon>
<span class="text">Edit</span>
</button>
@if (!columnLevelSecurityFlag) {
<button
(click)="onShowUploadModal()"
type="button"
class="btn icon-collapse btn-sm btn-success mr-0"
>
<clr-icon aria-hidden="true" shape="upload"></clr-icon>
<span class="text">Upload</span>
</button>
}
}
@if (!hotTable.readOnly && !uploadPreview) {
<button
type="button"
class="btn btn-sm btn-icon btn-outline-danger"
(click)="cancelEdit()"
>
<clr-icon aria-hidden="true" shape="times"></clr-icon>
<span>Cancel</span>
</button>
<clr-tooltip>
<button
clrTooltipTrigger
type="button"
class="btn btn-sm btn-success"
[clrLoading]="addingNewRow"
[class.dc-locked-control]="restrictions.restrictAddRow"
(click)="!restrictions.restrictAddRow ? addRow() : ''"
>
<clr-icon aria-hidden="true" shape="plus" size="16"></clr-icon
>Add Row
</button>
<clr-tooltip-content
clrPosition="bottom-left"
clrSize="lg"
*clrIfOpen
>
@if (restrictions.restrictAddRow) {
<span
>To unlock more than
{{ licenceState.value.editor_rows_allowed }}
{{
licenceState.value.editor_rows_allowed === 1
? 'row'
: 'rows'
}}, contact support&#64;datacontroller.io</span
>
}
</clr-tooltip-content>
</clr-tooltip>
<button
type="button"
class="btn btn-sm btn-primary"
(click)="checkSave()"
>
<clr-icon aria-hidden="true" shape="check" size="20"></clr-icon
>Submit
</button>
}
@if (uploadPreview) {
<button
type="button"
class="btn btn-sm btn-icon btn-outline-danger btn-upload-preview"
(click)="discardSourceFile = true"
>
<clr-icon aria-hidden="true" shape="times"></clr-icon>
<span>Discard file</span>
</button>
@if (hotTable.readOnly) {
<button
type="button"
class="btn btn-sm btn-primary btn-upload-preview"
(click)="manualFileEditModal = true"
>
<clr-icon aria-hidden="true" shape="note"></clr-icon>
<span>Edit</span>
</button>
}
<button
type="button"
class="btn btn-sm btn-success preview-submit"
(click)="submitExcel()"
[clrLoading]="uploadLoading"
>
<clr-icon aria-hidden="true" shape="check" size="20"></clr-icon>
Submit
</button>
}
</div>
}
@if (!['', ' '].includes(queryText)) {
<div
#infoBar
class="clr-col-md-12 infoBar"
[class.expanded]="filterExpanded"
>
<span class="infoBar-text"
>FILTER :
<b>{{ queryText }}</b>
</span>
@if (filterOverflows) {
<button
type="button"
class="infoBar-toggle"
[attr.aria-expanded]="filterExpanded"
[attr.aria-label]="
filterExpanded
? 'Collapse the filter clause'
: 'Expand the filter clause'
"
(click)="toggleFilterPanel()"
>
<clr-icon aria-hidden="true" shape="caret down"></clr-icon>
</button>
}
</div>
}
</div>
<div
class="card-block p-0 clr-flex-1 background-transparent"
[ngClass]="{ headerBarFilter: !['', ' '].includes(queryText) }"
>
@if (!tableTrue) {
<div class="card-text">
<div
class="h-70vh d-flex justify-content-center flex-column align-items-center"
>
@if (!getdataError) {
<span class="spinner"> Loading... </span>
<div class="mt-10">
<p cds-text="section">Loading table</p>
</div>
}
@if (getdataError) {
<span>
<clr-icon
aria-hidden="true"
shape="error-standard"
class="error-icon"
></clr-icon>
</span>
<div class="mt-10">
<p cds-text="section">Loading table error</p>
</div>
}
</div>
</div>
}
<div
class="card-text hotEditor h-100 d-flex clr-flex-column background-transparent"
>
<!-- <div *ngIf="generateEditRecordUrlLoading" class="edit-record-spinner">
<span class="spinner">
Loading...
</span>
</div> -->
<!--{{ submit }}-->
<!--<hr>-->
<!--{{ validationDone }}-->
<!-- VA embed: something about a `va_parmap` entry in this report
object's URL needs the report author's attention - it could not be
used, it was applied but AND-ed against another parameter, or it
names a parameter VA never sends. Shown as a banner, not just a
console warning, because
the entries are written by the report author and a typo otherwise
looks like a working filter over the whole table. -->
@if (isVaEmbed && vaMappingWarnings.length) {
<clr-alert
class="va-parmap-warning"
[clrAlertType]="'warning'"
[clrAlertClosable]="false"
>
<clr-alert-item>
<div class="alert-text">
<strong>VA parameter mapping problem</strong>
@for (warning of vaMappingWarnings; track warning) {
<div>{{ warning }}</div>
}
<!-- The reader is the report author, who can only fix this in
the report: name the parameter they edit, and say what a
correct entry looks like, so the message is actionable
without the docs. -->
<div>
Fix: edit the <code>va_parmap</code> entries in the URL of
this Data Controller object in the report. An entry is
<code>parameter-column</code>, optionally
<code>:ADD(anchor,offset)</code> or
<code>:RANGE(period,offset)</code> - for example
<code>va_parmap=SEL_DATE-D_DATE:ADD(QUARTER_START,-1)</code
>.
</div>
</div>
</clr-alert-item>
</clr-alert>
}
<div class="hot-wrapper clr-flex-1">
<hot-table
#hotInstance
id="hotTable"
class="edit-hot"
[class.hidden]="hotTable.hidden"
[data]="hotTable.data"
[settings]="hotTableSettings"
>
</hot-table>
</div>
<div>
@if (
tableTrue && !restrictions.removeAddRecordButton && !isVaEmbed
) {
<clr-tooltip>
<button
clrTooltipTrigger
type="button"
class="btn btn-sm btn-success mt-5-i"
[class.dc-locked-control]="restrictions.restrictAddRow"
(click)="
!restrictions.restrictAddRow ? addRecordButtonClick() : ''
"
>
<clr-icon
aria-hidden="true"
shape="plus"
size="16"
></clr-icon>
Add Record
</button>
<clr-tooltip-content
clrPosition="top-right"
clrSize="lg"
*clrIfOpen
>
@if (restrictions.restrictAddRow) {
<span
>To unlock more than
{{ licenceState.value.editor_rows_allowed }}
{{
licenceState.value.editor_rows_allowed === 1
? 'row'
: 'rows'
}}, contact support&#64;datacontroller.io</span
>
}
</clr-tooltip-content>
</clr-tooltip>
}
<!-- VA data-driven content mode: filter controls only. Edit/Submit
use the normal top toolbar (same as embed=true). VA opens
read-only and filters live with the report; filtering
repopulates the grid, so it is held while editing (change shown
pending) and resumes on return to read-only. -->
@if (isVaEmbed) {
<div class="va-filter-controls w-100 mt-2-i">
<!-- Read-only: auto-apply toggle + manual Apply (confirm mode).
Hidden while editing (filtering is held). -->
@if (hotTable.readOnly) {
<label class="va-toggle">
<input
type="checkbox"
[checked]="vaAutoApply"
(change)="toggleVaAutoApply()"
/>
Auto-apply VA filters
</label>
@if (!vaAutoApply) {
<button
[disabled]="
vaFilterStatus !== 'pending' &&
vaFilterStatus !== 'loading'
"
type="button"
class="btn btn-sm btn-primary"
(click)="applyPendingVaFilter()"
>
Apply filter
</button>
}
}
<!-- Edit mode: filtering is held so edits aren't wiped. -->
@if (!hotTable.readOnly) {
<span class="va-disabled-text">
VA filtering paused while editing
</span>
}
<!-- Persistent live region (shown in both modes) so a filter
change that arrives while editing is announced as pending. -->
<span class="va-filter-status" role="status" aria-live="polite">
@if (vaFilterStatus === 'pending') {
<span class="va-pending-text"> Filter change pending </span>
}
@if (vaFilterStatus === 'loading') {
<span class="va-loading-text"> Loading filter… </span>
}
</span>
</div>
}
@if (
licenceState.value.editor_rows_allowed !== Infinity &&
dataSource &&
dataSource.length > licenceState.value.editor_rows_allowed
) {
<p class="mt-2-i w-100 text-center">
To display more than
{{ licenceState.value.editor_rows_allowed }} rows, contact
<contact-link />
</p>
}
</div>
<!--<hr>
<hot-table hotId="hotInstance" id="hotTable">
</hot-table>-->
@if (submit) {
<div class="modal">
<!-- && validationDone should be in constraint, but validation is not correct on front end so it is comment -->
<!-- TODO: fix validation in line above -->
<div
class="modal-dialog modal-lg"
role="dialog"
aria-hidden="true"
>
<div class="modal-content">
<div class="modal-header">
<button aria-label="Close" class="close" type="button">
<clr-icon aria-hidden="true" shape="close"></clr-icon>
</button>
<h3 class="modal-title">
Submit for Approval ({{ rowsChanged.rowsAdded }} added,
{{ rowsChanged.rowsUpdated }} updated,
{{ rowsChanged.rowsDeleted }} deleted)
</h3>
</div>
<div class="modal-body">
@if (
totalRowsChanged > licenceState.value.submit_rows_limit &&
licenceState.value.submit_rows_limit !== Infinity
) {
<span class="d-block w-100 text-center color-red"
>Only
{{ licenceState.value.submit_rows_limit }}
{{
licenceState.value.submit_rows_limit === 1
? 'row'
: 'rows'
}}
will be submitted. To remove the restriction, contact
support&#64;datacontroller.io</span
>
}
@if (tableTrue) {
<div class="clr-offset-md-2 clr-col-md-8">
<div class="text-area-full-width">
<label for="formFields_8" class="mb-5 d-block"
>Message</label
>
<textarea
clrTextarea
[(ngModel)]="message"
[disabled]="!validationDone"
tabindex="0"
[value]="
!validationDone
? 'Please wait while we validate ' +
cols.length * modifedRowsIndexes.length +
' cells.'
: ''
"
class="submit-reason"
type="text"
id="formFields_8"
></textarea>
</div>
<!-- TODO:approvers list -->
<!-- <div>
<div class="col-md-12" style="max-height: 150px;overflow: auto;">
<ul class="list">
<li *ngFor="let approve of approvers">{{approve}}</li>
</ul>
</div>
</div> -->
</div>
}
</div>
<div class="modal-footer justify-content-center-i">
<button
id="submitBtn"
[disabled]="!validationDone"
type="submit"
class="btn btn-sm btn-success-outline m-0"
tabindex="0"
(click)="saveTable(hotTable.data)"
>
Submit
</button>
<button
id="cancelSubmitBtn"
type="button"
class="btn btn-sm btn-outline"
tabindex="0"
(click)="
cancelSubmit(); submit = false; validationDone = 0
"
>
Cancel
</button>
</div>
</div>
</div>
</div>
}
<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)="uploadParsedFiles(); submitLimitNotice = false"
>
Submit
</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="badEdit">
<h3 class="modal-title">{{ badEditTitle || 'Error' }}</h3>
<div class="modal-body">
<p>{{ badEditCause }}</p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-sm btn-primary"
(click)="badEdit = false"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="pkNull">
<h3 class="modal-title">Error</h3>
<div class="modal-body">
<p>
Null values or incorrect data types cannot be submitted for
Primary Key columns
</p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
(click)="pkNull = false; badEdit = false"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="noPkNull">
<h3 class="modal-title">Error</h3>
<div class="modal-body">
<p>Incorrect data types in non-Primary Key columns</p>
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
(click)="noPkNull = false; badEdit = false"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal class="duplicate-keys-modal" [(clrModalOpen)]="pkDups">
<h3 class="modal-title">Error</h3>
<div class="modal-body">
<p>
Duplicate values cannot be submitted for Primary Key columns
</p>
<p>Duplicates found:</p>
@for (
duplicatePkIndex of duplicatePkIndexes;
track duplicatePkIndex;
let i = $index
) {
<span class="d-block">
On row {{ duplicatePkIndex + 1 }}:
{{
pkFields[duplicatePkIndex].replace('|', '')
| pkSpaceSeparate
}}
</span>
}
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
(click)="pkDups = false"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="tooLong">
<h3 class="modal-title">Error</h3>
<div class="modal-body">
<p>
The following columns have values that exceed length limits:
</p>
@for (cell of exceedCells; track cell) {
<p>{{ cell.col }}({{ cell.len }}), {{ cell.val }}</p>
}
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-primary"
(click)="tooLong = false; cleanExceed()"
>
Ok
</button>
</div>
</clr-modal>
</div>
</div>
<!-- <div class="card-footer" style="display:flex; justify-content: center;">
</div> -->
</div>
</div>
</main>
@if (nullVariables) {
<div class="modal z-index-highest">
<div class="modal-dialog" role="dialog" aria-hidden="true">
<div class="modal-content">
<div class="modal-header">
<button aria-label="Close" class="close" type="button">
<clr-icon aria-hidden="true" shape="close"></clr-icon>
</button>
<h3 class="modal-title">Error</h3>
</div>
<div class="modal-body">
<p>You cannot submit empty clauses</p>
</div>
<div class="modal-footer">
<button
class="btn btn-sm btn-primary"
(click)="nullVariables = false"
type="button"
>
Ok
</button>
</div>
</div>
</div>
</div>
}
<clr-modal
[(clrModalOpen)]="filter"
[clrModalSize]="'xl'"
[clrModalClosable]="false"
class="filter-modal"
>
<h3 class="modal-title center text-center color-darker-gray">
Filter for table:
<span> {{ libds }} </span>
</h3>
<div class="modal-body">
@if (filter) {
<app-query #queryFilter></app-query>
}
</div>
<div class="modal-footer">
<button (click)="resetFilter()" type="button" class="btn btn-sm btn-link">
reset filter
</button>
<button
type="button"
class="btn btn-sm btn-outline"
(click)="filter = false"
>
Cancel
</button>
<button
[clrLoading]="submitLoading"
type="button"
class="btn btn-sm btn-success-outline"
(click)="sendClause()"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal [(clrModalOpen)]="queryErr">
<h3 class="modal-title">Error</h3>
<div class="modal-body">
<p>{{ queryErrMessage }}</p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-outline" (click)="queryErr = false">
Cancel
</button>
<button type="button" class="btn btn-primary" (click)="queryErr = false">
Ok
</button>
</div>
</clr-modal>
<clr-modal
[(clrModalOpen)]="discardSourceFile"
[clrModalSize]="'xl'"
[clrModalClosable]="false"
>
<h3 class="modal-title center text-center color-darker-gray">Warning</h3>
<div class="modal-body">
This action will discard the source file and it's loaded data
<b>{{ filename }}</b> Do you wish to proceed?
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-sm btn-outline"
(click)="discardSourceFile = false"
>
Cancel
</button>
<button
type="button"
class="btn btn-sm btn-success-outline"
(click)="discardSourceFile = false; discardPendingExcel(true)"
>
Ok
</button>
</div>
</clr-modal>
<clr-modal
[(clrModalOpen)]="filePasswordModal"
[clrModalSize]="'md'"
[clrModalClosable]="false"
>
<h3 class="modal-title center text-center color-darker-gray">
Password Protected File
</h3>
<div class="modal-body d-flex clr-justify-content-center">
<p class="m-0">Please enter password:</p>
<input
#filePasswordInput
data-lpignore="true"
autocomplete="off"
id="filePasswordInput"
type="text"
class="clr-input disable-password-manager"
/>
</div>
<div class="modal-footer">
@if (fileUnlockError) {
<p class="m-0 color-red">Sorry that didn't work, try again.</p>
}
<button
type="button"
class="btn btn-sm btn-outline"
(click)="filePasswordModal = false; filePasswordSubject.next(undefined)"
>
Cancel upload
</button>
<button
type="button"
class="btn btn-sm btn-success-outline"
[disabled]="filePasswordInput.value.length < 1"
(click)="
filePasswordModal = false;
filePasswordSubject.next(filePasswordInput.value)
"
>
Unlock
</button>
</div>
</clr-modal>
<clr-modal
[(clrModalOpen)]="manualFileEditModal"
[clrModalSize]="'xl'"
[clrModalClosable]="false"
>
<h3 class="modal-title center text-center color-darker-gray">Warning</h3>
<div class="modal-body">
Manually editing this data will mean the source file
<b>{{ filename }}</b> will be discarded. Do you wish to proceed?
</div>
<div class="modal-footer">
<button
type="button"
class="btn btn-sm btn-outline"
(click)="manualFileEditModal = false"
>
Cancel
</button>
<button
type="button"
class="btn btn-sm btn-success-outline"
(click)="manualFileEditModal = false; previewTableEditConfirm()"
>
Ok
</button>
</div>
</clr-modal>
<app-dataset-info
[(open)]="datasetInfo"
[dsmeta]="dsmeta"
[versions]="versions"
(rowClicked)="datasetInfoModalRowClicked($event)"
>
</app-dataset-info>
<app-viewboxes [(viewboxModal)]="viewboxes"></app-viewboxes>
<app-confirm-modal
[open]="confirmModal.open"
[title]="confirmModal.title"
[message]="confirmModal.message"
(result)="onConfirmModalResult($event)"
></app-confirm-modal>
<app-bulk-validation-modal
[open]="bulkValidation.active"
[done]="bulkValidation.done"
[total]="bulkValidation.total"
(cancel)="cancelBulkValidation({ revert: true })"
></app-bulk-validation-modal>