@noatgnu/cupcake-vanilla 1.3.9 → 1.3.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -11,7 +11,7 @@ import * as i1 from '@angular/forms';
|
|
|
11
11
|
import { FormBuilder, Validators, ReactiveFormsModule, FormsModule } from '@angular/forms';
|
|
12
12
|
import * as i2 from '@ng-bootstrap/ng-bootstrap';
|
|
13
13
|
import { NgbTypeahead, NgbModule, NgbNavModule, NgbActiveModal, NgbModal, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
|
|
14
|
-
import * as
|
|
14
|
+
import * as i6 from '@angular/cdk/drag-drop';
|
|
15
15
|
import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
|
|
16
16
|
import * as i1$1 from '@angular/router';
|
|
17
17
|
import { RouterModule } from '@angular/router';
|
|
@@ -4625,6 +4625,12 @@ class ColumnEditModal {
|
|
|
4625
4625
|
const ontologyTypeValue = selectedOntology?.value;
|
|
4626
4626
|
// Extract custom filters from the selected ontology config
|
|
4627
4627
|
const customOntologyFilters = selectedOntology?.customFilters || {};
|
|
4628
|
+
console.log('[Column Edit] Submitting column:', {
|
|
4629
|
+
ontologyTypeIndex,
|
|
4630
|
+
selectedOntology,
|
|
4631
|
+
ontologyTypeValue,
|
|
4632
|
+
customOntologyFilters
|
|
4633
|
+
});
|
|
4628
4634
|
const columnData = {
|
|
4629
4635
|
name: formValue.name,
|
|
4630
4636
|
type: formValue.type,
|
|
@@ -4639,6 +4645,7 @@ class ColumnEditModal {
|
|
|
4639
4645
|
notAvailable: formValue.notAvailable || false,
|
|
4640
4646
|
staffOnly: formValue.staffOnly || false
|
|
4641
4647
|
};
|
|
4648
|
+
console.log('[Column Edit] Final columnData:', columnData);
|
|
4642
4649
|
this.columnSaved.emit(columnData);
|
|
4643
4650
|
}
|
|
4644
4651
|
else {
|
|
@@ -5009,17 +5016,8 @@ class ColumnEditModal {
|
|
|
5009
5016
|
this.showTemplateSwap = false;
|
|
5010
5017
|
this.showCreateCustomTemplate = false;
|
|
5011
5018
|
}
|
|
5012
|
-
// Toggle advanced mode
|
|
5013
5019
|
toggleAdvancedMode() {
|
|
5014
5020
|
this.showAdvancedMode.set(!this.showAdvancedMode());
|
|
5015
|
-
// Reset ontology type when disabling advanced mode
|
|
5016
|
-
if (!this.showAdvancedMode()) {
|
|
5017
|
-
this.editForm.patchValue({
|
|
5018
|
-
ontologyType: '0',
|
|
5019
|
-
enableTypeahead: false
|
|
5020
|
-
});
|
|
5021
|
-
this.selectedOntologyConfig = null;
|
|
5022
|
-
}
|
|
5023
5021
|
}
|
|
5024
5022
|
// Handle ontology type selection change
|
|
5025
5023
|
onOntologyTypeChange(event) {
|
|
@@ -5067,6 +5065,7 @@ class MetadataTableTemplateEditModal {
|
|
|
5067
5065
|
activeModal;
|
|
5068
5066
|
modalService;
|
|
5069
5067
|
metadataTableTemplateService;
|
|
5068
|
+
metadataColumnService;
|
|
5070
5069
|
toastService;
|
|
5071
5070
|
template = null;
|
|
5072
5071
|
labGroups = [];
|
|
@@ -5105,11 +5104,12 @@ class MetadataTableTemplateEditModal {
|
|
|
5105
5104
|
{ value: ResourceVisibility.GROUP, label: 'Lab Group' },
|
|
5106
5105
|
{ value: ResourceVisibility.PUBLIC, label: 'Public' }
|
|
5107
5106
|
];
|
|
5108
|
-
constructor(fb, activeModal, modalService, metadataTableTemplateService, toastService) {
|
|
5107
|
+
constructor(fb, activeModal, modalService, metadataTableTemplateService, metadataColumnService, toastService) {
|
|
5109
5108
|
this.fb = fb;
|
|
5110
5109
|
this.activeModal = activeModal;
|
|
5111
5110
|
this.modalService = modalService;
|
|
5112
5111
|
this.metadataTableTemplateService = metadataTableTemplateService;
|
|
5112
|
+
this.metadataColumnService = metadataColumnService;
|
|
5113
5113
|
this.toastService = toastService;
|
|
5114
5114
|
this.editForm = this.fb.group({
|
|
5115
5115
|
name: ['', [Validators.required, Validators.minLength(3)]],
|
|
@@ -5345,20 +5345,29 @@ class MetadataTableTemplateEditModal {
|
|
|
5345
5345
|
}
|
|
5346
5346
|
handleColumnSave(columnData, originalColumn, isEdit) {
|
|
5347
5347
|
const currentColumns = this._templateColumns();
|
|
5348
|
-
if (isEdit && originalColumn) {
|
|
5349
|
-
|
|
5350
|
-
|
|
5351
|
-
|
|
5352
|
-
|
|
5353
|
-
|
|
5354
|
-
|
|
5355
|
-
|
|
5356
|
-
|
|
5357
|
-
|
|
5358
|
-
|
|
5348
|
+
if (isEdit && originalColumn && originalColumn.id) {
|
|
5349
|
+
this.isLoading.set(true);
|
|
5350
|
+
this.metadataColumnService.patchMetadataColumn(originalColumn.id, columnData).subscribe({
|
|
5351
|
+
next: (updatedColumn) => {
|
|
5352
|
+
this.isLoading.set(false);
|
|
5353
|
+
const updatedColumns = currentColumns.map(col => col.id === originalColumn.id
|
|
5354
|
+
? updatedColumn
|
|
5355
|
+
: col);
|
|
5356
|
+
if (columnData.name !== originalColumn.name || columnData.type !== originalColumn.type) {
|
|
5357
|
+
this.enforceGrouping(updatedColumns);
|
|
5358
|
+
}
|
|
5359
|
+
this.normalizeColumnPositions(updatedColumns);
|
|
5360
|
+
this._templateColumns.set(updatedColumns);
|
|
5361
|
+
this.toastService.success('Column updated successfully');
|
|
5362
|
+
},
|
|
5363
|
+
error: (error) => {
|
|
5364
|
+
this.isLoading.set(false);
|
|
5365
|
+
console.error('Error updating column:', error);
|
|
5366
|
+
this.toastService.error('Failed to update column');
|
|
5367
|
+
}
|
|
5368
|
+
});
|
|
5359
5369
|
}
|
|
5360
5370
|
else {
|
|
5361
|
-
// Add new column
|
|
5362
5371
|
const newColumn = {
|
|
5363
5372
|
...columnData,
|
|
5364
5373
|
id: this.generateTempId(),
|
|
@@ -5463,13 +5472,13 @@ class MetadataTableTemplateEditModal {
|
|
|
5463
5472
|
columns.length = 0;
|
|
5464
5473
|
columns.push(...regroupedColumns);
|
|
5465
5474
|
}
|
|
5466
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableTemplateService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
|
|
5467
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataTableTemplateEditModal, isStandalone: true, selector: "ccv-metadata-table-template-edit-modal", inputs: { template: "template", labGroups: "labGroups", isEdit: "isEdit" }, outputs: { templateSaved: "templateSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <!-- Basic Information -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-info-circle me-2\"></i>Basic Information\n </h6>\n </div>\n \n <div class=\"col-12 mb-3\">\n <label for=\"templateName\" class=\"form-label\">Template Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"templateName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"Enter template name\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"description\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\" \n id=\"description\"\n formControlName=\"description\"\n rows=\"3\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Configuration -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-gear me-2\"></i>Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"labGroup\" class=\"form-label\">Lab Group</label>\n <select \n class=\"form-select\"\n id=\"labGroup\"\n formControlName=\"labGroupId\">\n <option [ngValue]=\"null\">No Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label for=\"visibility\" class=\"form-label\">Visibility</label>\n <select \n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isDefault\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"isDefault\">\n <i class=\"bi bi-star me-1\"></i>Default Template\n </label>\n <small class=\"form-text text-muted d-block\">Use as default for new projects</small>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Column Management -->\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center border-bottom pb-2\">\n <h6 class=\"text-muted mb-0\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n </h6>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-sm btn-success\" (click)=\"addColumnWithAutoReorder()\"\n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column (Auto-Reorder)\n </button>\n }\n </div>\n </div>\n @if (selectedColumnIds().size > 0) {\n <div class=\"alert alert-info py-2 px-3 mt-2 mb-0\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-check-square me-1\"></i>\n {{ selectedColumnIds().size }} column(s) selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-warning\" (click)=\"bulkToggleStaffOnly(true)\" title=\"Mark selected as Staff Only\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-shield-lock me-1\"></i>Mark Staff Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"bulkToggleStaffOnly(false)\" title=\"Mark selected as User Visible\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-person me-1\"></i>Mark User Visible\n </button>\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"bulkDeleteColumns()\" title=\"Delete selected columns\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-trash me-1\"></i>Delete\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"col-12 mb-3\">\n @if (templateColumns().length > 0) {\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead class=\"table-light\">\n <tr>\n <th style=\"width: 40px;\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"allColumnsSelected()\"\n [indeterminate]=\"someColumnsSelected()\"\n (change)=\"toggleAllColumns()\"\n title=\"Select all columns\">\n </th>\n <th style=\"width: 40px;\"><i class=\"bi bi-arrows-move\"></i></th>\n <th>Position</th>\n <th>Column Name</th>\n <th>Type</th>\n <th>Default Value</th>\n <th>Options</th>\n <th style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"_templateColumns()\">\n @for (column of _templateColumns(); track column.id; let i = $index) {\n <tr cdkDrag [cdkDragData]=\"column\" class=\"column-row\" [attr.data-column-id]=\"column.id\">\n <td class=\"text-center\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"column.id && selectedColumnIds().has(column.id)\"\n (change)=\"column.id && toggleColumnSelection(column.id)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n <td class=\"text-center\">\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ i + 1 }}</span>\n </td>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-info\">{{ column.type }}</span>\n </td>\n <td>\n <span class=\"text-muted\">{{ column.value || '-' }}</span>\n </td>\n <td>\n <div class=\"d-flex gap-1\">\n @if (column.mandatory) {\n <span class=\"badge badge-pill bg-warning\" title=\"Mandatory\">\n <i class=\"bi bi-exclamation-circle\"></i>\n </span>\n }\n @if (column.hidden) {\n <span class=\"badge badge-pill bg-secondary\" title=\"Hidden\">\n <i class=\"bi bi-eye-slash\"></i>\n </span>\n }\n @if (column.readonly) {\n <span class=\"badge badge-pill bg-dark\" title=\"Read Only\">\n <i class=\"bi bi-lock\"></i>\n </span>\n }\n @if (column.staffOnly) {\n <span class=\"badge badge-pill bg-warning text-dark\" title=\"Staff Only\">\n <i class=\"bi bi-shield-lock\"></i>\n </span>\n }\n @if (column.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i5$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
|
|
5475
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableTemplateService }, { token: MetadataColumnService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
|
|
5476
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataTableTemplateEditModal, isStandalone: true, selector: "ccv-metadata-table-template-edit-modal", inputs: { template: "template", labGroups: "labGroups", isEdit: "isEdit" }, outputs: { templateSaved: "templateSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <!-- Basic Information -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-info-circle me-2\"></i>Basic Information\n </h6>\n </div>\n \n <div class=\"col-12 mb-3\">\n <label for=\"templateName\" class=\"form-label\">Template Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"templateName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"Enter template name\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"description\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\" \n id=\"description\"\n formControlName=\"description\"\n rows=\"3\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Configuration -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-gear me-2\"></i>Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"labGroup\" class=\"form-label\">Lab Group</label>\n <select \n class=\"form-select\"\n id=\"labGroup\"\n formControlName=\"labGroupId\">\n <option [ngValue]=\"null\">No Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label for=\"visibility\" class=\"form-label\">Visibility</label>\n <select \n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isDefault\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"isDefault\">\n <i class=\"bi bi-star me-1\"></i>Default Template\n </label>\n <small class=\"form-text text-muted d-block\">Use as default for new projects</small>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Column Management -->\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center border-bottom pb-2\">\n <h6 class=\"text-muted mb-0\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n </h6>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-sm btn-success\" (click)=\"addColumnWithAutoReorder()\"\n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column (Auto-Reorder)\n </button>\n }\n </div>\n </div>\n @if (selectedColumnIds().size > 0) {\n <div class=\"alert alert-info py-2 px-3 mt-2 mb-0\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-check-square me-1\"></i>\n {{ selectedColumnIds().size }} column(s) selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-warning\" (click)=\"bulkToggleStaffOnly(true)\" title=\"Mark selected as Staff Only\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-shield-lock me-1\"></i>Mark Staff Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"bulkToggleStaffOnly(false)\" title=\"Mark selected as User Visible\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-person me-1\"></i>Mark User Visible\n </button>\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"bulkDeleteColumns()\" title=\"Delete selected columns\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-trash me-1\"></i>Delete\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"col-12 mb-3\">\n @if (templateColumns().length > 0) {\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead class=\"table-light\">\n <tr>\n <th style=\"width: 40px;\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"allColumnsSelected()\"\n [indeterminate]=\"someColumnsSelected()\"\n (change)=\"toggleAllColumns()\"\n title=\"Select all columns\">\n </th>\n <th style=\"width: 40px;\"><i class=\"bi bi-arrows-move\"></i></th>\n <th>Position</th>\n <th>Column Name</th>\n <th>Type</th>\n <th>Default Value</th>\n <th>Options</th>\n <th style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"_templateColumns()\">\n @for (column of _templateColumns(); track column.id; let i = $index) {\n <tr cdkDrag [cdkDragData]=\"column\" class=\"column-row\" [attr.data-column-id]=\"column.id\">\n <td class=\"text-center\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"column.id && selectedColumnIds().has(column.id)\"\n (change)=\"column.id && toggleColumnSelection(column.id)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n <td class=\"text-center\">\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ i + 1 }}</span>\n </td>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-info\">{{ column.type }}</span>\n </td>\n <td>\n <span class=\"text-muted\">{{ column.value || '-' }}</span>\n </td>\n <td>\n <div class=\"d-flex gap-1\">\n @if (column.mandatory) {\n <span class=\"badge badge-pill bg-warning\" title=\"Mandatory\">\n <i class=\"bi bi-exclamation-circle\"></i>\n </span>\n }\n @if (column.hidden) {\n <span class=\"badge badge-pill bg-secondary\" title=\"Hidden\">\n <i class=\"bi bi-eye-slash\"></i>\n </span>\n }\n @if (column.readonly) {\n <span class=\"badge badge-pill bg-dark\" title=\"Read Only\">\n <i class=\"bi bi-lock\"></i>\n </span>\n }\n @if (column.staffOnly) {\n <span class=\"badge badge-pill bg-warning text-dark\" title=\"Staff Only\">\n <i class=\"bi bi-shield-lock\"></i>\n </span>\n }\n @if (column.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i6.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i6.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i6.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
|
|
5468
5477
|
}
|
|
5469
5478
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateEditModal, decorators: [{
|
|
5470
5479
|
type: Component,
|
|
5471
5480
|
args: [{ selector: 'ccv-metadata-table-template-edit-modal', imports: [CommonModule, ReactiveFormsModule, NgbModule, DragDropModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row\">\n <!-- Basic Information -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-info-circle me-2\"></i>Basic Information\n </h6>\n </div>\n \n <div class=\"col-12 mb-3\">\n <label for=\"templateName\" class=\"form-label\">Template Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"templateName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"Enter template name\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"description\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\" \n id=\"description\"\n formControlName=\"description\"\n rows=\"3\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Configuration -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-gear me-2\"></i>Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"labGroup\" class=\"form-label\">Lab Group</label>\n <select \n class=\"form-select\"\n id=\"labGroup\"\n formControlName=\"labGroupId\">\n <option [ngValue]=\"null\">No Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <label for=\"visibility\" class=\"form-label\">Visibility</label>\n <select \n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isDefault\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"isDefault\">\n <i class=\"bi bi-star me-1\"></i>Default Template\n </label>\n <small class=\"form-text text-muted d-block\">Use as default for new projects</small>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Column Management -->\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center border-bottom pb-2\">\n <h6 class=\"text-muted mb-0\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n </h6>\n <div class=\"d-flex gap-2\">\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-sm btn-success\" (click)=\"addColumnWithAutoReorder()\"\n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column (Auto-Reorder)\n </button>\n }\n </div>\n </div>\n @if (selectedColumnIds().size > 0) {\n <div class=\"alert alert-info py-2 px-3 mt-2 mb-0\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-check-square me-1\"></i>\n {{ selectedColumnIds().size }} column(s) selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-warning\" (click)=\"bulkToggleStaffOnly(true)\" title=\"Mark selected as Staff Only\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-shield-lock me-1\"></i>Mark Staff Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"bulkToggleStaffOnly(false)\" title=\"Mark selected as User Visible\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-person me-1\"></i>Mark User Visible\n </button>\n <button type=\"button\" class=\"btn btn-danger\" (click)=\"bulkDeleteColumns()\" title=\"Delete selected columns\" [disabled]=\"isLoading() || !isEdit || !template?.id\">\n <i class=\"bi bi-trash me-1\"></i>Delete\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <div class=\"col-12 mb-3\">\n @if (templateColumns().length > 0) {\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead class=\"table-light\">\n <tr>\n <th style=\"width: 40px;\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"allColumnsSelected()\"\n [indeterminate]=\"someColumnsSelected()\"\n (change)=\"toggleAllColumns()\"\n title=\"Select all columns\">\n </th>\n <th style=\"width: 40px;\"><i class=\"bi bi-arrows-move\"></i></th>\n <th>Position</th>\n <th>Column Name</th>\n <th>Type</th>\n <th>Default Value</th>\n <th>Options</th>\n <th style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"_templateColumns()\">\n @for (column of _templateColumns(); track column.id; let i = $index) {\n <tr cdkDrag [cdkDragData]=\"column\" class=\"column-row\" [attr.data-column-id]=\"column.id\">\n <td class=\"text-center\">\n <input type=\"checkbox\" class=\"form-check-input\"\n [checked]=\"column.id && selectedColumnIds().has(column.id)\"\n (change)=\"column.id && toggleColumnSelection(column.id)\"\n (click)=\"$event.stopPropagation()\">\n </td>\n <td class=\"text-center\">\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ i + 1 }}</span>\n </td>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-info\">{{ column.type }}</span>\n </td>\n <td>\n <span class=\"text-muted\">{{ column.value || '-' }}</span>\n </td>\n <td>\n <div class=\"d-flex gap-1\">\n @if (column.mandatory) {\n <span class=\"badge badge-pill bg-warning\" title=\"Mandatory\">\n <i class=\"bi bi-exclamation-circle\"></i>\n </span>\n }\n @if (column.hidden) {\n <span class=\"badge badge-pill bg-secondary\" title=\"Hidden\">\n <i class=\"bi bi-eye-slash\"></i>\n </span>\n }\n @if (column.readonly) {\n <span class=\"badge badge-pill bg-dark\" title=\"Read Only\">\n <i class=\"bi bi-lock\"></i>\n </span>\n }\n @if (column.staffOnly) {\n <span class=\"badge badge-pill bg-warning text-dark\" title=\"Staff Only\">\n <i class=\"bi bi-shield-lock\"></i>\n </span>\n }\n @if (column.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"] }]
|
|
5472
|
-
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableTemplateService }, { type: i3.ToastService }], propDecorators: { template: [{
|
|
5481
|
+
}], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableTemplateService }, { type: MetadataColumnService }, { type: i3.ToastService }], propDecorators: { template: [{
|
|
5473
5482
|
type: Input
|
|
5474
5483
|
}], labGroups: [{
|
|
5475
5484
|
type: Input
|
|
@@ -6387,9 +6396,14 @@ class ColumnTemplateEditModal {
|
|
|
6387
6396
|
if (ontologyType && ontologyType.includes('__')) {
|
|
6388
6397
|
ontologyType = ontologyType.split('__')[0];
|
|
6389
6398
|
}
|
|
6399
|
+
console.log('[Template] Submitting with:', {
|
|
6400
|
+
ontologyType,
|
|
6401
|
+
customOntologyFilters: formValue.customOntologyFilters
|
|
6402
|
+
});
|
|
6390
6403
|
const templateData = {
|
|
6391
6404
|
...formValue,
|
|
6392
6405
|
ontologyType: ontologyType,
|
|
6406
|
+
customOntologyFilters: formValue.customOntologyFilters,
|
|
6393
6407
|
labGroup: formValue.visibility === 'labGroup' ? formValue.labGroup : null,
|
|
6394
6408
|
defaultPosition: Number(formValue.defaultPosition) || 0
|
|
6395
6409
|
};
|
|
@@ -9402,7 +9416,7 @@ class MetadataTableDetails {
|
|
|
9402
9416
|
});
|
|
9403
9417
|
}
|
|
9404
9418
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableDetails, deps: [{ token: i1$1.ActivatedRoute }, { token: i1$1.Router }, { token: MetadataTableService }, { token: AsyncExportService }, { token: ChunkedUploadService }, { token: SamplePoolService }, { token: MetadataColumnService }, { token: i3.ToastService }, { token: AsyncTaskUIService }, { token: i2.NgbModal }], target: i0.ɵɵFactoryTarget.Component });
|
|
9405
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataTableDetails, isStandalone: true, selector: "ccv-metadata-table-details", viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }, { propertyName: "excelInput", first: true, predicate: ["excelInput"], descendants: true }], ngImport: i0, template: "<div class=\"metadata-table-details-container\">\n <!-- Header Section with Breadcrumbs -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <!-- Breadcrumb Navigation -->\n <nav aria-label=\"breadcrumb\" class=\"me-3\">\n <ol class=\"breadcrumb mb-0\">\n <li class=\"breadcrumb-item\">\n <a routerLink=\"/metadata-tables\" class=\"text-decoration-none\">\n <i class=\"bi bi-table me-1\"></i>Metadata Tables\n </a>\n </li>\n <li class=\"breadcrumb-item active\" aria-current=\"page\">\n @if (table()) {\n {{ table()!.name }}\n } @else {\n Table Details\n }\n </li>\n </ol>\n </nav>\n\n <!-- Table Status Badge -->\n @if (table()) {\n <span class=\"badge ms-2\" [class]=\"getTableStatusClass()\">\n {{ getTableStatusBadge() }}\n </span>\n }\n </div>\n\n <!-- Action Buttons -->\n <div class=\"d-flex gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"navigateBack()\"\n title=\"Back to Tables\">\n <i class=\"bi bi-arrow-left me-1\"></i>Back\n </button>\n\n <!-- View Mode Toggle -->\n @if (hasColumns()) {\n <div class=\"btn-group\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn btn-sm\"\n [class]=\"viewMode() === 'list' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"switchToListMode()\"\n title=\"Column List View\">\n <i class=\"bi bi-list-ul me-1\"></i>List\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm\"\n [class]=\"viewMode() === 'table' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"switchToTableMode()\"\n title=\"Table Data View\">\n <i class=\"bi bi-table me-1\"></i>Table\n </button>\n </div>\n }\n <!-- Import Dropdown -->\n @if (table() && table()!.canEdit) {\n <div class=\"btn-group\" ngbDropdown>\n <!-- Hidden file inputs -->\n <input\n #fileInput\n type=\"file\"\n accept=\".txt,.tsv\"\n style=\"display: none;\"\n (change)=\"importSdrf($event)\">\n <input\n #excelInput\n type=\"file\"\n accept=\".xlsx,.xls\"\n style=\"display: none;\"\n (change)=\"importExcel($event)\">\n \n <button\n type=\"button\"\n class=\"btn btn-outline-success btn-sm dropdown-toggle\"\n ngbDropdownToggle\n [disabled]=\"isLoading()\"\n title=\"Import Data\">\n <i class=\"bi bi-upload me-1\"></i>Import\n </button>\n <div class=\"dropdown-menu\" ngbDropdownMenu>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"triggerFileInput(); $event.preventDefault()\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Import SDRF File\n </a>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"triggerExcelInput(); $event.preventDefault()\">\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>Import Excel Template\n </a>\n </div>\n </div>\n }\n\n <!-- Export Dropdown -->\n @if (table() && hasColumns()) {\n <div class=\"btn-group\" ngbDropdown>\n <button\n type=\"button\"\n class=\"btn btn-outline-info btn-sm dropdown-toggle\"\n ngbDropdownToggle\n [disabled]=\"isLoading()\"\n title=\"Export Table\">\n <i class=\"bi bi-download me-1\"></i>Export\n </button>\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"exportToSdrf('sdrf'); $event.preventDefault()\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Export as SDRF\n </a>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"exportToSdrf('excel'); $event.preventDefault()\">\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>Export as Excel\n </a>\n </div>\n </div>\n }\n\n <!-- Edit Button -->\n @if (table() && table()!.canEdit) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary btn-sm\"\n (click)=\"editTable()\"\n [disabled]=\"isLoading()\"\n title=\"Edit Table\">\n <i class=\"bi bi-pencil me-1\"></i>Edit\n </button>\n }\n\n <!-- Reorder Columns Button -->\n @if (table() && table()!.canEdit && hasColumns()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-warning btn-sm\"\n (click)=\"reorderColumns()\"\n [disabled]=\"isLoading()\"\n title=\"Reorder Columns by Schema\">\n <i class=\"bi bi-arrows-move me-1\"></i>Reorder\n </button>\n }\n\n <!-- Create Pool Button -->\n @if (table() && table()!.canEdit && hasColumns()) {\n <button\n type=\"button\"\n class=\"btn btn-success btn-sm\"\n (click)=\"createPool()\"\n [disabled]=\"isLoading()\"\n title=\"Create Sample Pool\">\n <i class=\"bi bi-plus-circle me-1\"></i>Create Pool\n </button>\n }\n\n <!-- More Actions Dropdown -->\n @if (table() && table()!.canEdit) {\n <div class=\"btn-group\" ngbDropdown>\n <button\n type=\"button\"\n class=\"btn btn-outline-danger btn-sm dropdown-toggle\"\n ngbDropdownToggle\n [disabled]=\"isLoading()\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\n <a class=\"dropdown-item text-danger\" href=\"#\" (click)=\"deleteTable(); $event.preventDefault()\">\n <i class=\"bi bi-trash me-2\"></i>Delete Table\n </a>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </nav>\n\n <!-- Loading Spinner -->\n @if (isLoading()) {\n <div class=\"d-flex justify-content-center align-items-center\" style=\"height: 400px;\">\n <div class=\"spinner-border text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </div>\n }\n\n <!-- Table Details Content -->\n @if (table() && !isLoading()) {\n <div class=\"table-details-content p-4\">\n\n <!-- Table Information Card -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-info-circle me-2\"></i>Table Information\n </h5>\n <small class=\"text-muted\">\n Created {{ table()!.createdAt | date:'medium' }}\n </small>\n </div>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-md-8\">\n <h3 class=\"fw-bold mb-3\">{{ table()!.name }}</h3>\n @if (table()!.description) {\n <p class=\"text-muted mb-3\">{{ table()!.description }}</p>\n }\n <div class=\"row\">\n <div class=\"col-md-6\">\n <div class=\"d-flex align-items-center mb-2\">\n <i class=\"bi bi-hash text-muted me-2\"></i>\n <span class=\"fw-semibold me-2\">Sample Count:</span>\n <span class=\"badge bg-primary\">{{ table()!.sampleCount }}</span>\n </div>\n <div class=\"d-flex align-items-center mb-2\">\n <i class=\"bi bi-columns-gap text-muted me-2\"></i>\n <span class=\"fw-semibold me-2\">Columns:</span>\n <span class=\"badge bg-info\">{{ table()!.columns?.length || 0 }}</span>\n </div>\n </div>\n <div class=\"col-md-6\">\n @if (table()!.ownerUsername) {\n <div class=\"d-flex align-items-center mb-2\">\n <i class=\"bi bi-person text-muted me-2\"></i>\n <span class=\"fw-semibold me-2\">Owner:</span>\n <span>{{ table()!.ownerUsername }}</span>\n </div>\n }\n @if (table()!.labGroupName) {\n <div class=\"d-flex align-items-center mb-2\">\n <i class=\"bi bi-people text-muted me-2\"></i>\n <span class=\"fw-semibold me-2\">Lab Group:</span>\n <span>{{ table()!.labGroupName }}</span>\n </div>\n }\n <div class=\"d-flex align-items-center mb-2\">\n <i class=\"bi bi-tag text-muted me-2\"></i>\n <span class=\"fw-semibold me-2\">Version:</span>\n <span class=\"badge bg-light text-dark\">{{ table()!.version || 'v1.0' }}</span>\n </div>\n </div>\n </div>\n </div>\n <div class=\"col-md-4 text-end\">\n <div class=\"d-flex flex-column gap-2\">\n @if (table()!.isLocked) {\n <span class=\"badge bg-warning\">\n <i class=\"bi bi-lock me-1\"></i>Locked\n </span>\n }\n @if (table()!.isPublished) {\n <span class=\"badge bg-success\">\n <i class=\"bi bi-cloud-check me-1\"></i>Published\n </span>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Sample Pool Table -->\n @if (viewMode() === 'table' && hasPools()) {\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-layers me-2\"></i>Sample Pool Table\n <span class=\"badge bg-warning ms-2\">{{ sortedPools().length }} pools</span>\n </h5>\n </div>\n <div class=\"card-body p-0\">\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\" style=\"width: 80px;\" class=\"bg-secondary bg-opacity-10 text-secondary\">Pool</th>\n @for (column of sortedColumns(); track column.id) {\n <th scope=\"col\" [class]=\"getColumnHeaderClass(column)\" [title]=\"column.name\">\n <div class=\"header-cell d-flex justify-content-between align-items-center\">\n <span>{{ getShortColumnName(column) }}</span>\n @if (table()!.canEdit) {\n <div class=\"header-actions\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-primary header-edit-btn me-1\"\n (click)=\"editColumnValue(column)\"\n [title]=\"'Edit default value for ' + column.name\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n </div>\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (poolRow of poolTableRows(); track poolRow._poolId) {\n <tr class=\"pool-row\" [class.editable]=\"table()!.canEdit\">\n <td class=\"text-muted fw-semibold\" \n [class.clickable]=\"table()!.canEdit\"\n (click)=\"table()!.canEdit ? editPool(poolRow._poolData) : null\"\n [title]=\"table()!.canEdit ? 'Click to edit pool: ' + poolRow._poolName : poolRow._poolName\">\n <i class=\"bi bi-layers me-2\"></i>{{ poolRow._poolName }}\n @if (table()!.canEdit) {\n <i class=\"bi bi-pencil-square ms-1 text-primary\" style=\"font-size: 0.8em;\"></i>\n }\n </td>\n @for (column of sortedColumns(); track column.id) {\n <td\n class=\"pool-cell\"\n [class.opacity-50]=\"column.hidden\"\n [attr.data-pool-id]=\"poolRow._poolId\"\n [attr.data-column-id]=\"column.id\"\n (click)=\"table()!.canEdit ? editPoolColumnValue(poolRow._poolData, column) : null\"\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for pool ' + poolRow._poolName : ''\">\n {{ poolRow['col_' + column.id] || '-' }}\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n </div>\n }\n\n <!-- Columns/Table Section -->\n <div class=\"card\">\n <div class=\"card-header\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <h5 class=\"mb-0\">\n @if (viewMode() === 'list') {\n <i class=\"bi bi-list-columns me-2\"></i>Table Columns\n } @else {\n <i class=\"bi bi-table me-2\"></i>Table Data\n }\n </h5>\n \n <!-- Column Filter -->\n @if (hasColumns()) {\n <div class=\"d-flex align-items-center me-3\">\n <div class=\"input-group input-group-sm\" style=\"width: 250px;\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-funnel text-muted\"></i>\n </span>\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"Filter columns...\"\n [value]=\"columnFilter()\"\n (input)=\"onColumnFilterChange($any($event.target).value)\">\n @if (columnFilter()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearColumnFilter()\"\n title=\"Clear filter\">\n <i class=\"bi bi-x\"></i>\n </button>\n }\n </div>\n @if (columnFilter()) {\n <small class=\"text-muted ms-2\">\n {{ filteredColumnCount }}/{{ totalColumnCount }}\n </small>\n }\n </div>\n }\n \n <div class=\"d-flex gap-2 align-items-center\">\n @if (viewMode() === 'list') {\n <span class=\"badge bg-primary\">{{ sortedColumns().length }} columns</span>\n @if (table() && table()!.canEdit) {\n <button\n type=\"button\"\n class=\"btn btn-success btn-sm\"\n (click)=\"openAddColumnModal()\"\n [disabled]=\"isLoading()\"\n title=\"Add new column with automatic reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column\n </button>\n }\n @if (table() && table()!.canEdit && sortedColumns().length === 0) {\n <small class=\"text-muted\">Import SDRF to add columns</small>\n }\n } @else {\n <span class=\"badge bg-info\">{{ tableRows().length }} samples</span>\n @if (hasPools()) {\n <span class=\"badge bg-warning\">{{ sortedPools().length }} pools</span>\n }\n <span class=\"badge bg-primary\">{{ sortedColumns().length }} columns</span>\n @if (table() && table()!.canEdit) {\n <button\n type=\"button\"\n class=\"btn btn-success btn-sm\"\n (click)=\"openAddColumnModal()\"\n [disabled]=\"isLoading()\"\n title=\"Add new column with automatic reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column\n </button>\n }\n }\n </div>\n </div>\n </div>\n\n @if (hasColumns()) {\n <!-- List Mode: Column Definitions -->\n @if (viewMode() === 'list') {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th scope=\"col\" style=\"width: 40px;\">\n @if (table()!.canEdit) {\n <i class=\"bi bi-arrows-move text-muted\" title=\"Drag to reorder\"></i>\n }\n </th>\n <th scope=\"col\" style=\"width: 60px;\" \n class=\"sortable\" \n (click)=\"sortBy('position')\"\n [class.sorted]=\"isSorted('position')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span>#</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('position')\"></i>\n </div>\n </th>\n <th scope=\"col\" \n class=\"sortable\" \n (click)=\"sortBy('name')\"\n [class.sorted]=\"isSorted('name')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span>Column Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\n </div>\n </th>\n <th scope=\"col\" \n class=\"sortable\" \n (click)=\"sortBy('type')\"\n [class.sorted]=\"isSorted('type')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span>Type</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('type')\"></i>\n </div>\n </th>\n <th scope=\"col\" \n class=\"sortable\" \n (click)=\"sortBy('value')\"\n [class.sorted]=\"isSorted('value')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span>Value</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('value')\"></i>\n </div>\n </th>\n <th scope=\"col\">Properties</th>\n <th scope=\"col\" style=\"width: 100px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"dragColumnsForList()\">\n @for (column of dragColumnsForList(); track column.id) {\n <tr cdkDrag [cdkDragData]=\"column\" [cdkDragDisabled]=\"!table()!.canEdit\">\n <td class=\"text-center\">\n @if (table()!.canEdit) {\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n }\n </td>\n <td class=\"text-muted fw-semibold\">\n {{ (column.columnPosition || 0) + 1 }}\n </td>\n <td>\n <div class=\"d-flex align-items-center\">\n <i class=\"me-2\" [class]=\"getColumnTypeIcon(column) + ' ' + getColumnTypeClass(column)\"></i>\n <span class=\"fw-semibold\">{{ column.name }}</span>\n </div>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n <div>\n <!-- Default Value -->\n <div class=\"fw-semibold\">{{ getColumnValueDisplay(column) }}</div>\n\n <!-- Sample-specific Modifiers -->\n @if (hasModifiers(column)) {\n <div class=\"mt-1\">\n @for (modifierDisplay of getModifiersDisplay(column); track $index) {\n <small class=\"badge bg-light text-dark me-1 mb-1 d-inline-block\"\n style=\"font-size: 0.7rem;\"\n [title]=\"modifierDisplay\">\n {{ modifierDisplay }}\n </small>\n }\n </div>\n }\n </div>\n </td>\n <td>\n <div class=\"d-flex gap-1 flex-wrap\">\n @if (column.mandatory) {\n <span class=\"badge bg-danger\" title=\"Required field\">Required</span>\n }\n @if (column.readonly) {\n <span class=\"badge bg-secondary\" title=\"Read-only field\">Read-only</span>\n }\n @if (column.hidden) {\n <span class=\"badge bg-warning\" title=\"Hidden field\">Hidden</span>\n }\n @if (column.autoGenerated) {\n <span class=\"badge bg-info\" title=\"Auto-generated field\">Auto</span>\n }\n @if (column.ontologyType) {\n <span class=\"badge bg-success\" title=\"Ontology: {{ column.ontologyType }}\">\n {{ column.ontologyType }}\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\"\n (click)=\"viewColumnDetails(column)\"\n title=\"View column details\">\n <i class=\"bi bi-eye\"></i>\n </button>\n @if (table()!.canEdit) {\n <button type=\"button\" \n class=\"btn\"\n [class]=\"column.hidden ? 'btn-outline-warning' : 'btn-outline-success'\"\n (click)=\"toggleColumnHidden(column)\"\n [title]=\"column.hidden ? 'Show column' : 'Hide column'\">\n <i class=\"bi\" [class]=\"column.hidden ? 'bi-eye-slash' : 'bi-eye'\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\"\n (click)=\"editColumnValue(column)\"\n title=\"Edit default value\">\n <i class=\"bi bi-pencil-square\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-info\"\n (click)=\"autofillColumnValues(column)\"\n title=\"Auto-fill values\">\n <i class=\"bi bi-magic\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\"\n (click)=\"editColumnSettings(column)\"\n title=\"Edit column settings\">\n <i class=\"bi bi-gear\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-info\"\n (click)=\"openColumnHistory(column)\"\n title=\"View column history\">\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n <!-- Table Data -->\n @if (viewMode() === 'table') {\n <div class=\"card-body p-0\">\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\" style=\"width: 80px;\" class=\"bg-secondary bg-opacity-10 text-secondary\">#</th>\n @for (column of sortedColumns(); track column.id) {\n <th scope=\"col\" [class]=\"getColumnHeaderClass(column)\" [title]=\"column.name\">\n <div class=\"header-cell d-flex justify-content-between align-items-center\">\n <span>{{ getShortColumnName(column) }}</span>\n @if (table()!.canEdit) {\n <div class=\"header-actions\">\n <button\n type=\"button\"\n class=\"btn btn-sm header-edit-btn me-1\"\n [class]=\"column.hidden ? 'btn-outline-warning' : 'btn-outline-success'\"\n (click)=\"toggleColumnHidden(column)\"\n [title]=\"column.hidden ? 'Show column' : 'Hide column'\">\n <i class=\"bi\" [class]=\"column.hidden ? 'bi-eye-slash' : 'bi-eye'\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-primary header-edit-btn me-1\"\n (click)=\"editColumnValue(column)\"\n [title]=\"'Edit default value for ' + column.name\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-info header-edit-btn me-1\"\n (click)=\"autofillColumnValues(column)\"\n [title]=\"'Auto-fill values for ' + column.name\">\n <i class=\"bi bi-magic\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-danger header-edit-btn\"\n (click)=\"removeColumn(column)\"\n [title]=\"'Remove ' + column.name + ' column'\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of paginatedRows(); track row._sampleIndex) {\n <tr class=\"sample-row\" [class.editable]=\"table()!.canEdit\">\n <td class=\"text-muted fw-semibold\">{{ row._sampleIndex }}</td>\n @for (column of sortedColumns(); track column.id) {\n <td\n class=\"sample-cell\"\n [class.opacity-50]=\"column.hidden\"\n [attr.data-sample-index]=\"row._sampleIndex\"\n [attr.data-column-id]=\"column.id\"\n (click)=\"table()!.canEdit ? editSampleColumnValue(column, row._sampleIndex) : null\"\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for sample ' + row._sampleIndex : ''\">\n {{ row['col_' + column.id] || '-' }}\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Table Mode Pagination -->\n @if (viewMode() === 'table' && totalPages() > 1) {\n <div class=\"d-flex justify-content-between align-items-center p-3 border-top\">\n <div class=\"d-flex align-items-center gap-2\">\n <span class=\"text-muted\">Rows per page:</span>\n <select class=\"form-select form-select-sm\" style=\"width: auto;\"\n [value]=\"pageSize()\"\n (change)=\"onPageSizeChange(+($any($event.target).value))\">\n <option value=\"10\">10</option>\n <option value=\"25\">25</option>\n <option value=\"50\">50</option>\n <option value=\"100\">100</option>\n </select>\n </div>\n <ngb-pagination\n [collectionSize]=\"tableRows().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm mb-0\">\n </ngb-pagination>\n <span class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize() + 1 }}-{{ Math.min(currentPage() * pageSize(), tableRows().length) }}\n of {{ tableRows().length }} samples\n </span>\n </div>\n }\n </div>\n }\n } @else {\n <div class=\"card-body text-center py-5\">\n <div class=\"text-muted\">\n <i class=\"bi bi-inbox display-1 mb-3\"></i>\n <h5>No Columns Found</h5>\n <p>This table doesn't have any columns defined yet.</p>\n @if (table()!.canEdit) {\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openAddColumnModal()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.header-cell{position:relative}.header-cell .header-actions{opacity:0;transition:opacity .15s ease}thead th:hover .header-actions{opacity:1}.header-edit-btn{--bs-btn-padding-y: .125rem;--bs-btn-padding-x: .25rem;--bs-btn-font-size: .65rem;border:none!important}.header-edit-btn.btn-outline-primary:hover{background-color:var(--bs-primary);border-color:var(--bs-primary)}.header-edit-btn.btn-outline-danger:hover{background-color:var(--bs-danger);border-color:var(--bs-danger)}.sample-row.editable .sample-cell{cursor:pointer;transition:background-color .1s ease}.sample-row.editable .sample-cell:hover{background-color:#28a74514;position:relative}.sample-row.editable .sample-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:#28a74599;font-size:.75em;pointer-events:none}.pool-row.editable td.clickable{cursor:pointer;transition:background-color .1s ease}.pool-row.editable td.clickable:hover{background-color:#0dcaf01a;color:var(--bs-info)}.pool-row.editable td.clickable:hover .bi-pencil-square{opacity:.8}.pool-row.editable td.clickable .bi-pencil-square{opacity:.4;transition:opacity .1s ease}.pool-row.editable .pool-cell{cursor:pointer;transition:background-color .1s ease}.pool-row.editable .pool-cell:hover{background-color:#ffc10726;position:relative}.pool-row.editable .pool-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:#ffc107cc;font-size:.75em;pointer-events:none}@media (max-width: 768px){.sample-row.editable .sample-cell,.pool-row.editable .pool-cell{transition:none}}.sortable{cursor:pointer;user-select:none;transition:background-color .2s ease}.sortable:hover{background-color:rgba(var(--bs-primary-rgb),.1)}.sortable.sorted{background-color:rgba(var(--bs-primary-rgb),.15)}.sortable.sorted .bi{color:var(--bs-primary)}.sortable .bi{opacity:.5;transition:all .2s ease;font-size:.75em}.sortable:hover .bi{opacity:.8}.drag-handle{cursor:grab;opacity:.6;transition:opacity .2s ease}.drag-handle:hover{opacity:1}.drag-handle:active{cursor:grabbing}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--bs-light);border:1px solid var(--bs-border-color)}.cdk-drag-preview .header-cell{padding:.5rem;font-size:.875rem;font-weight:500}.cdk-drag-placeholder{opacity:.5;background:rgba(var(--bs-primary-rgb),.1)}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drop-list-dragging th:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: NgbModule }, { kind: "directive", type: i2.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i2.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i2.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i5$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
|
|
9419
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataTableDetails, isStandalone: true, selector: "ccv-metadata-table-details", viewQueries: [{ propertyName: "fileInput", first: true, predicate: ["fileInput"], descendants: true }, { propertyName: "excelInput", first: true, predicate: ["excelInput"], descendants: true }], ngImport: i0, template: "<div class=\"metadata-table-details-container\">\n <!-- Header Section with Breadcrumbs -->\n <nav class=\"navbar navbar-expand-lg bg-body-tertiary shadow-sm\">\n <div class=\"container-fluid\">\n <div class=\"d-flex align-items-center justify-content-between w-100\">\n <div class=\"d-flex align-items-center\">\n <!-- Breadcrumb Navigation -->\n <nav aria-label=\"breadcrumb\" class=\"me-3\">\n <ol class=\"breadcrumb mb-0\">\n <li class=\"breadcrumb-item\">\n <a routerLink=\"/metadata-tables\" class=\"text-decoration-none\">\n <i class=\"bi bi-table me-1\"></i>Metadata Tables\n </a>\n </li>\n <li class=\"breadcrumb-item active\" aria-current=\"page\">\n @if (table()) {\n {{ table()!.name }}\n } @else {\n Table Details\n }\n </li>\n </ol>\n </nav>\n\n <!-- Table Status Badge -->\n @if (table()) {\n <span class=\"badge ms-2\" [class]=\"getTableStatusClass()\">\n {{ getTableStatusBadge() }}\n </span>\n }\n </div>\n\n <!-- Action Buttons -->\n <div class=\"d-flex gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"navigateBack()\"\n title=\"Back to Tables\">\n <i class=\"bi bi-arrow-left me-1\"></i>Back\n </button>\n\n <!-- View Mode Toggle -->\n @if (hasColumns()) {\n <div class=\"btn-group\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn btn-sm\"\n [class]=\"viewMode() === 'list' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"switchToListMode()\"\n title=\"Column List View\">\n <i class=\"bi bi-list-ul me-1\"></i>List\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm\"\n [class]=\"viewMode() === 'table' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"switchToTableMode()\"\n title=\"Table Data View\">\n <i class=\"bi bi-table me-1\"></i>Table\n </button>\n </div>\n }\n <!-- Import Dropdown -->\n @if (table() && table()!.canEdit) {\n <div class=\"btn-group\" ngbDropdown>\n <!-- Hidden file inputs -->\n <input\n #fileInput\n type=\"file\"\n accept=\".txt,.tsv\"\n style=\"display: none;\"\n (change)=\"importSdrf($event)\">\n <input\n #excelInput\n type=\"file\"\n accept=\".xlsx,.xls\"\n style=\"display: none;\"\n (change)=\"importExcel($event)\">\n \n <button\n type=\"button\"\n class=\"btn btn-outline-success btn-sm dropdown-toggle\"\n ngbDropdownToggle\n [disabled]=\"isLoading()\"\n title=\"Import Data\">\n <i class=\"bi bi-upload me-1\"></i>Import\n </button>\n <div class=\"dropdown-menu\" ngbDropdownMenu>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"triggerFileInput(); $event.preventDefault()\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Import SDRF File\n </a>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"triggerExcelInput(); $event.preventDefault()\">\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>Import Excel Template\n </a>\n </div>\n </div>\n }\n\n <!-- Export Dropdown -->\n @if (table() && hasColumns()) {\n <div class=\"btn-group\" ngbDropdown>\n <button\n type=\"button\"\n class=\"btn btn-outline-info btn-sm dropdown-toggle\"\n ngbDropdownToggle\n [disabled]=\"isLoading()\"\n title=\"Export Table\">\n <i class=\"bi bi-download me-1\"></i>Export\n </button>\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"exportToSdrf('sdrf'); $event.preventDefault()\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Export as SDRF\n </a>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"exportToSdrf('excel'); $event.preventDefault()\">\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>Export as Excel\n </a>\n </div>\n </div>\n }\n\n <!-- Edit Button -->\n @if (table() && table()!.canEdit) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary btn-sm\"\n (click)=\"editTable()\"\n [disabled]=\"isLoading()\"\n title=\"Edit Table\">\n <i class=\"bi bi-pencil me-1\"></i>Edit\n </button>\n }\n\n <!-- Reorder Columns Button -->\n @if (table() && table()!.canEdit && hasColumns()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-warning btn-sm\"\n (click)=\"reorderColumns()\"\n [disabled]=\"isLoading()\"\n title=\"Reorder Columns by Schema\">\n <i class=\"bi bi-arrows-move me-1\"></i>Reorder\n </button>\n }\n\n <!-- Create Pool Button -->\n @if (table() && table()!.canEdit && hasColumns()) {\n <button\n type=\"button\"\n class=\"btn btn-success btn-sm\"\n (click)=\"createPool()\"\n [disabled]=\"isLoading()\"\n title=\"Create Sample Pool\">\n <i class=\"bi bi-plus-circle me-1\"></i>Create Pool\n </button>\n }\n\n <!-- More Actions Dropdown -->\n @if (table() && table()!.canEdit) {\n <div class=\"btn-group\" ngbDropdown>\n <button\n type=\"button\"\n class=\"btn btn-outline-danger btn-sm dropdown-toggle\"\n ngbDropdownToggle\n [disabled]=\"isLoading()\">\n <i class=\"bi bi-three-dots-vertical\"></i>\n </button>\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\n <a class=\"dropdown-item text-danger\" href=\"#\" (click)=\"deleteTable(); $event.preventDefault()\">\n <i class=\"bi bi-trash me-2\"></i>Delete Table\n </a>\n </div>\n </div>\n }\n </div>\n </div>\n </div>\n </nav>\n\n <!-- Loading Spinner -->\n @if (isLoading()) {\n <div class=\"d-flex justify-content-center align-items-center\" style=\"height: 400px;\">\n <div class=\"spinner-border text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </div>\n }\n\n <!-- Table Details Content -->\n @if (table() && !isLoading()) {\n <div class=\"table-details-content p-4\">\n\n <!-- Table Information Card -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-info-circle me-2\"></i>Table Information\n </h5>\n <small class=\"text-muted\">\n Created {{ table()!.createdAt | date:'medium' }}\n </small>\n </div>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-md-8\">\n <h3 class=\"fw-bold mb-3\">{{ table()!.name }}</h3>\n @if (table()!.description) {\n <p class=\"text-muted mb-3\">{{ table()!.description }}</p>\n }\n <div class=\"row\">\n <div class=\"col-md-6\">\n <div class=\"d-flex align-items-center mb-2\">\n <i class=\"bi bi-hash text-muted me-2\"></i>\n <span class=\"fw-semibold me-2\">Sample Count:</span>\n <span class=\"badge bg-primary\">{{ table()!.sampleCount }}</span>\n </div>\n <div class=\"d-flex align-items-center mb-2\">\n <i class=\"bi bi-columns-gap text-muted me-2\"></i>\n <span class=\"fw-semibold me-2\">Columns:</span>\n <span class=\"badge bg-info\">{{ table()!.columns?.length || 0 }}</span>\n </div>\n </div>\n <div class=\"col-md-6\">\n @if (table()!.ownerUsername) {\n <div class=\"d-flex align-items-center mb-2\">\n <i class=\"bi bi-person text-muted me-2\"></i>\n <span class=\"fw-semibold me-2\">Owner:</span>\n <span>{{ table()!.ownerUsername }}</span>\n </div>\n }\n @if (table()!.labGroupName) {\n <div class=\"d-flex align-items-center mb-2\">\n <i class=\"bi bi-people text-muted me-2\"></i>\n <span class=\"fw-semibold me-2\">Lab Group:</span>\n <span>{{ table()!.labGroupName }}</span>\n </div>\n }\n <div class=\"d-flex align-items-center mb-2\">\n <i class=\"bi bi-tag text-muted me-2\"></i>\n <span class=\"fw-semibold me-2\">Version:</span>\n <span class=\"badge bg-light text-dark\">{{ table()!.version || 'v1.0' }}</span>\n </div>\n </div>\n </div>\n </div>\n <div class=\"col-md-4 text-end\">\n <div class=\"d-flex flex-column gap-2\">\n @if (table()!.isLocked) {\n <span class=\"badge bg-warning\">\n <i class=\"bi bi-lock me-1\"></i>Locked\n </span>\n }\n @if (table()!.isPublished) {\n <span class=\"badge bg-success\">\n <i class=\"bi bi-cloud-check me-1\"></i>Published\n </span>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Sample Pool Table -->\n @if (viewMode() === 'table' && hasPools()) {\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-layers me-2\"></i>Sample Pool Table\n <span class=\"badge bg-warning ms-2\">{{ sortedPools().length }} pools</span>\n </h5>\n </div>\n <div class=\"card-body p-0\">\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\" style=\"width: 80px;\" class=\"bg-secondary bg-opacity-10 text-secondary\">Pool</th>\n @for (column of sortedColumns(); track column.id) {\n <th scope=\"col\" [class]=\"getColumnHeaderClass(column)\" [title]=\"column.name\">\n <div class=\"header-cell d-flex justify-content-between align-items-center\">\n <span>{{ getShortColumnName(column) }}</span>\n @if (table()!.canEdit) {\n <div class=\"header-actions\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-primary header-edit-btn me-1\"\n (click)=\"editColumnValue(column)\"\n [title]=\"'Edit default value for ' + column.name\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n </div>\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (poolRow of poolTableRows(); track poolRow._poolId) {\n <tr class=\"pool-row\" [class.editable]=\"table()!.canEdit\">\n <td class=\"text-muted fw-semibold\" \n [class.clickable]=\"table()!.canEdit\"\n (click)=\"table()!.canEdit ? editPool(poolRow._poolData) : null\"\n [title]=\"table()!.canEdit ? 'Click to edit pool: ' + poolRow._poolName : poolRow._poolName\">\n <i class=\"bi bi-layers me-2\"></i>{{ poolRow._poolName }}\n @if (table()!.canEdit) {\n <i class=\"bi bi-pencil-square ms-1 text-primary\" style=\"font-size: 0.8em;\"></i>\n }\n </td>\n @for (column of sortedColumns(); track column.id) {\n <td\n class=\"pool-cell\"\n [class.opacity-50]=\"column.hidden\"\n [attr.data-pool-id]=\"poolRow._poolId\"\n [attr.data-column-id]=\"column.id\"\n (click)=\"table()!.canEdit ? editPoolColumnValue(poolRow._poolData, column) : null\"\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for pool ' + poolRow._poolName : ''\">\n {{ poolRow['col_' + column.id] || '-' }}\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n </div>\n }\n\n <!-- Columns/Table Section -->\n <div class=\"card\">\n <div class=\"card-header\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <h5 class=\"mb-0\">\n @if (viewMode() === 'list') {\n <i class=\"bi bi-list-columns me-2\"></i>Table Columns\n } @else {\n <i class=\"bi bi-table me-2\"></i>Table Data\n }\n </h5>\n \n <!-- Column Filter -->\n @if (hasColumns()) {\n <div class=\"d-flex align-items-center me-3\">\n <div class=\"input-group input-group-sm\" style=\"width: 250px;\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-funnel text-muted\"></i>\n </span>\n <input\n type=\"text\"\n class=\"form-control\"\n placeholder=\"Filter columns...\"\n [value]=\"columnFilter()\"\n (input)=\"onColumnFilterChange($any($event.target).value)\">\n @if (columnFilter()) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearColumnFilter()\"\n title=\"Clear filter\">\n <i class=\"bi bi-x\"></i>\n </button>\n }\n </div>\n @if (columnFilter()) {\n <small class=\"text-muted ms-2\">\n {{ filteredColumnCount }}/{{ totalColumnCount }}\n </small>\n }\n </div>\n }\n \n <div class=\"d-flex gap-2 align-items-center\">\n @if (viewMode() === 'list') {\n <span class=\"badge bg-primary\">{{ sortedColumns().length }} columns</span>\n @if (table() && table()!.canEdit) {\n <button\n type=\"button\"\n class=\"btn btn-success btn-sm\"\n (click)=\"openAddColumnModal()\"\n [disabled]=\"isLoading()\"\n title=\"Add new column with automatic reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column\n </button>\n }\n @if (table() && table()!.canEdit && sortedColumns().length === 0) {\n <small class=\"text-muted\">Import SDRF to add columns</small>\n }\n } @else {\n <span class=\"badge bg-info\">{{ tableRows().length }} samples</span>\n @if (hasPools()) {\n <span class=\"badge bg-warning\">{{ sortedPools().length }} pools</span>\n }\n <span class=\"badge bg-primary\">{{ sortedColumns().length }} columns</span>\n @if (table() && table()!.canEdit) {\n <button\n type=\"button\"\n class=\"btn btn-success btn-sm\"\n (click)=\"openAddColumnModal()\"\n [disabled]=\"isLoading()\"\n title=\"Add new column with automatic reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Column\n </button>\n }\n }\n </div>\n </div>\n </div>\n\n @if (hasColumns()) {\n <!-- List Mode: Column Definitions -->\n @if (viewMode() === 'list') {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th scope=\"col\" style=\"width: 40px;\">\n @if (table()!.canEdit) {\n <i class=\"bi bi-arrows-move text-muted\" title=\"Drag to reorder\"></i>\n }\n </th>\n <th scope=\"col\" style=\"width: 60px;\" \n class=\"sortable\" \n (click)=\"sortBy('position')\"\n [class.sorted]=\"isSorted('position')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span>#</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('position')\"></i>\n </div>\n </th>\n <th scope=\"col\" \n class=\"sortable\" \n (click)=\"sortBy('name')\"\n [class.sorted]=\"isSorted('name')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span>Column Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\n </div>\n </th>\n <th scope=\"col\" \n class=\"sortable\" \n (click)=\"sortBy('type')\"\n [class.sorted]=\"isSorted('type')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span>Type</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('type')\"></i>\n </div>\n </th>\n <th scope=\"col\" \n class=\"sortable\" \n (click)=\"sortBy('value')\"\n [class.sorted]=\"isSorted('value')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span>Value</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('value')\"></i>\n </div>\n </th>\n <th scope=\"col\">Properties</th>\n <th scope=\"col\" style=\"width: 100px;\">Actions</th>\n </tr>\n </thead>\n <tbody cdkDropList (cdkDropListDropped)=\"onColumnDrop($event)\" [cdkDropListData]=\"dragColumnsForList()\">\n @for (column of dragColumnsForList(); track column.id) {\n <tr cdkDrag [cdkDragData]=\"column\" [cdkDragDisabled]=\"!table()!.canEdit\">\n <td class=\"text-center\">\n @if (table()!.canEdit) {\n <i class=\"bi bi-grip-vertical drag-handle\" cdkDragHandle></i>\n }\n </td>\n <td class=\"text-muted fw-semibold\">\n {{ (column.columnPosition || 0) + 1 }}\n </td>\n <td>\n <div class=\"d-flex align-items-center\">\n <i class=\"me-2\" [class]=\"getColumnTypeIcon(column) + ' ' + getColumnTypeClass(column)\"></i>\n <span class=\"fw-semibold\">{{ column.name }}</span>\n </div>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n <div>\n <!-- Default Value -->\n <div class=\"fw-semibold\">{{ getColumnValueDisplay(column) }}</div>\n\n <!-- Sample-specific Modifiers -->\n @if (hasModifiers(column)) {\n <div class=\"mt-1\">\n @for (modifierDisplay of getModifiersDisplay(column); track $index) {\n <small class=\"badge bg-light text-dark me-1 mb-1 d-inline-block\"\n style=\"font-size: 0.7rem;\"\n [title]=\"modifierDisplay\">\n {{ modifierDisplay }}\n </small>\n }\n </div>\n }\n </div>\n </td>\n <td>\n <div class=\"d-flex gap-1 flex-wrap\">\n @if (column.mandatory) {\n <span class=\"badge bg-danger\" title=\"Required field\">Required</span>\n }\n @if (column.readonly) {\n <span class=\"badge bg-secondary\" title=\"Read-only field\">Read-only</span>\n }\n @if (column.hidden) {\n <span class=\"badge bg-warning\" title=\"Hidden field\">Hidden</span>\n }\n @if (column.autoGenerated) {\n <span class=\"badge bg-info\" title=\"Auto-generated field\">Auto</span>\n }\n @if (column.ontologyType) {\n <span class=\"badge bg-success\" title=\"Ontology: {{ column.ontologyType }}\">\n {{ column.ontologyType }}\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\"\n (click)=\"viewColumnDetails(column)\"\n title=\"View column details\">\n <i class=\"bi bi-eye\"></i>\n </button>\n @if (table()!.canEdit) {\n <button type=\"button\" \n class=\"btn\"\n [class]=\"column.hidden ? 'btn-outline-warning' : 'btn-outline-success'\"\n (click)=\"toggleColumnHidden(column)\"\n [title]=\"column.hidden ? 'Show column' : 'Hide column'\">\n <i class=\"bi\" [class]=\"column.hidden ? 'bi-eye-slash' : 'bi-eye'\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\"\n (click)=\"editColumnValue(column)\"\n title=\"Edit default value\">\n <i class=\"bi bi-pencil-square\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-info\"\n (click)=\"autofillColumnValues(column)\"\n title=\"Auto-fill values\">\n <i class=\"bi bi-magic\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-secondary\"\n (click)=\"editColumnSettings(column)\"\n title=\"Edit column settings\">\n <i class=\"bi bi-gear\"></i>\n </button>\n <button type=\"button\" class=\"btn btn-outline-info\"\n (click)=\"openColumnHistory(column)\"\n title=\"View column history\">\n <i class=\"bi bi-clock-history\"></i>\n </button>\n }\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n }\n\n <!-- Table Data -->\n @if (viewMode() === 'table') {\n <div class=\"card-body p-0\">\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead class=\"table-light\">\n <tr>\n <th scope=\"col\" style=\"width: 80px;\" class=\"bg-secondary bg-opacity-10 text-secondary\">#</th>\n @for (column of sortedColumns(); track column.id) {\n <th scope=\"col\" [class]=\"getColumnHeaderClass(column)\" [title]=\"column.name\">\n <div class=\"header-cell d-flex justify-content-between align-items-center\">\n <span>{{ getShortColumnName(column) }}</span>\n @if (table()!.canEdit) {\n <div class=\"header-actions\">\n <button\n type=\"button\"\n class=\"btn btn-sm header-edit-btn me-1\"\n [class]=\"column.hidden ? 'btn-outline-warning' : 'btn-outline-success'\"\n (click)=\"toggleColumnHidden(column)\"\n [title]=\"column.hidden ? 'Show column' : 'Hide column'\">\n <i class=\"bi\" [class]=\"column.hidden ? 'bi-eye-slash' : 'bi-eye'\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-primary header-edit-btn me-1\"\n (click)=\"editColumnValue(column)\"\n [title]=\"'Edit default value for ' + column.name\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-info header-edit-btn me-1\"\n (click)=\"autofillColumnValues(column)\"\n [title]=\"'Auto-fill values for ' + column.name\">\n <i class=\"bi bi-magic\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-danger header-edit-btn\"\n (click)=\"removeColumn(column)\"\n [title]=\"'Remove ' + column.name + ' column'\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of paginatedRows(); track row._sampleIndex) {\n <tr class=\"sample-row\" [class.editable]=\"table()!.canEdit\">\n <td class=\"text-muted fw-semibold\">{{ row._sampleIndex }}</td>\n @for (column of sortedColumns(); track column.id) {\n <td\n class=\"sample-cell\"\n [class.opacity-50]=\"column.hidden\"\n [attr.data-sample-index]=\"row._sampleIndex\"\n [attr.data-column-id]=\"column.id\"\n (click)=\"table()!.canEdit ? editSampleColumnValue(column, row._sampleIndex) : null\"\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for sample ' + row._sampleIndex : ''\">\n {{ row['col_' + column.id] || '-' }}\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Table Mode Pagination -->\n @if (viewMode() === 'table' && totalPages() > 1) {\n <div class=\"d-flex justify-content-between align-items-center p-3 border-top\">\n <div class=\"d-flex align-items-center gap-2\">\n <span class=\"text-muted\">Rows per page:</span>\n <select class=\"form-select form-select-sm\" style=\"width: auto;\"\n [value]=\"pageSize()\"\n (change)=\"onPageSizeChange(+($any($event.target).value))\">\n <option value=\"10\">10</option>\n <option value=\"25\">25</option>\n <option value=\"50\">50</option>\n <option value=\"100\">100</option>\n </select>\n </div>\n <ngb-pagination\n [collectionSize]=\"tableRows().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm mb-0\">\n </ngb-pagination>\n <span class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize() + 1 }}-{{ Math.min(currentPage() * pageSize(), tableRows().length) }}\n of {{ tableRows().length }} samples\n </span>\n </div>\n }\n </div>\n }\n } @else {\n <div class=\"card-body text-center py-5\">\n <div class=\"text-muted\">\n <i class=\"bi bi-inbox display-1 mb-3\"></i>\n <h5>No Columns Found</h5>\n <p>This table doesn't have any columns defined yet.</p>\n @if (table()!.canEdit) {\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openAddColumnModal()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.header-cell{position:relative}.header-cell .header-actions{opacity:0;transition:opacity .15s ease}thead th:hover .header-actions{opacity:1}.header-edit-btn{--bs-btn-padding-y: .125rem;--bs-btn-padding-x: .25rem;--bs-btn-font-size: .65rem;border:none!important}.header-edit-btn.btn-outline-primary:hover{background-color:var(--bs-primary);border-color:var(--bs-primary)}.header-edit-btn.btn-outline-danger:hover{background-color:var(--bs-danger);border-color:var(--bs-danger)}.sample-row.editable .sample-cell{cursor:pointer;transition:background-color .1s ease}.sample-row.editable .sample-cell:hover{background-color:#28a74514;position:relative}.sample-row.editable .sample-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:#28a74599;font-size:.75em;pointer-events:none}.pool-row.editable td.clickable{cursor:pointer;transition:background-color .1s ease}.pool-row.editable td.clickable:hover{background-color:#0dcaf01a;color:var(--bs-info)}.pool-row.editable td.clickable:hover .bi-pencil-square{opacity:.8}.pool-row.editable td.clickable .bi-pencil-square{opacity:.4;transition:opacity .1s ease}.pool-row.editable .pool-cell{cursor:pointer;transition:background-color .1s ease}.pool-row.editable .pool-cell:hover{background-color:#ffc10726;position:relative}.pool-row.editable .pool-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:#ffc107cc;font-size:.75em;pointer-events:none}@media (max-width: 768px){.sample-row.editable .sample-cell,.pool-row.editable .pool-cell{transition:none}}.sortable{cursor:pointer;user-select:none;transition:background-color .2s ease}.sortable:hover{background-color:rgba(var(--bs-primary-rgb),.1)}.sortable.sorted{background-color:rgba(var(--bs-primary-rgb),.15)}.sortable.sorted .bi{color:var(--bs-primary)}.sortable .bi{opacity:.5;transition:all .2s ease;font-size:.75em}.sortable:hover .bi{opacity:.8}.drag-handle{cursor:grab;opacity:.6;transition:opacity .2s ease}.drag-handle:hover{opacity:1}.drag-handle:active{cursor:grabbing}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--bs-light);border:1px solid var(--bs-border-color)}.cdk-drag-preview .header-cell{padding:.5rem;font-size:.875rem;font-weight:500}.cdk-drag-placeholder{opacity:.5;background:rgba(var(--bs-primary-rgb),.1)}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drop-list-dragging th:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: NgbModule }, { kind: "directive", type: i2.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i2.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i2.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i6.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i6.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i6.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
|
|
9406
9420
|
}
|
|
9407
9421
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableDetails, decorators: [{
|
|
9408
9422
|
type: Component,
|