@noatgnu/cupcake-vanilla 1.3.11 → 1.3.13

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.
@@ -1,9 +1,9 @@
1
1
  import * as i0 from '@angular/core';
2
- import { Injectable, signal, computed, EventEmitter, inject, Output, Input, Component, effect, ViewChild } from '@angular/core';
2
+ import { Injectable, signal, computed, inject, EventEmitter, Output, Input, Component, effect, ViewChild } from '@angular/core';
3
3
  import * as i3 from '@noatgnu/cupcake-core';
4
- import { BaseApiService, WebSocketService, NotificationService as NotificationService$1, LabGroupService, ToastService, AsyncTaskMonitorService, TaskStatus, TaskType, ResourceVisibility } from '@noatgnu/cupcake-core';
4
+ import { BaseApiService, WebSocketService, NotificationService as NotificationService$1, SiteConfigService, LabGroupService, ToastService, AsyncTaskMonitorService, TaskStatus, TaskType, ResourceVisibility } from '@noatgnu/cupcake-core';
5
5
  import { switchMap, map, tap, takeUntil } from 'rxjs/operators';
6
- import { from, Observable, throwError, NEVER, debounceTime, distinctUntilChanged, filter, switchMap as switchMap$1, map as map$1, catchError, of, tap as tap$1, Subject, firstValueFrom, takeUntil as takeUntil$1 } from 'rxjs';
6
+ import { throwError, from, Observable, NEVER, debounceTime, distinctUntilChanged, filter, switchMap as switchMap$1, map as map$1, catchError, of, tap as tap$1, Subject, firstValueFrom, takeUntil as takeUntil$1 } from 'rxjs';
7
7
  import jsSHA from 'jssha';
8
8
  import * as i5 from '@angular/common';
9
9
  import { CommonModule } from '@angular/common';
@@ -652,10 +652,10 @@ class MetadataTableService extends BaseApiService {
652
652
  advancedAutofill(id, request) {
653
653
  return this.post(`${this.apiUrl}/metadata-tables/${id}/advanced_autofill/`, request);
654
654
  }
655
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
656
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableService, providedIn: 'root' });
655
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
656
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableService, providedIn: 'root' });
657
657
  }
658
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableService, decorators: [{
658
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableService, decorators: [{
659
659
  type: Injectable,
660
660
  args: [{
661
661
  providedIn: 'root'
@@ -763,10 +763,10 @@ class MetadataColumnService extends BaseApiService {
763
763
  const httpParams = this.buildHttpParams(params);
764
764
  return this.get(`${this.apiUrl}/metadata-columns/${id}/history/`, { params: httpParams });
765
765
  }
766
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
767
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnService, providedIn: 'root' });
766
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
767
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnService, providedIn: 'root' });
768
768
  }
769
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnService, decorators: [{
769
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnService, decorators: [{
770
770
  type: Injectable,
771
771
  args: [{
772
772
  providedIn: 'root'
@@ -817,10 +817,10 @@ class SamplePoolService extends BaseApiService {
817
817
  getMetadataColumns(id) {
818
818
  return this.get(`${this.apiUrl}/sample-pools/${id}/metadata_columns/`);
819
819
  }
820
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
821
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolService, providedIn: 'root' });
820
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SamplePoolService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
821
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SamplePoolService, providedIn: 'root' });
822
822
  }
823
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolService, decorators: [{
823
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SamplePoolService, decorators: [{
824
824
  type: Injectable,
825
825
  args: [{
826
826
  providedIn: 'root'
@@ -969,10 +969,10 @@ class MetadataTableTemplateService extends BaseApiService {
969
969
  bulkUpdateStaffOnly(id, request) {
970
970
  return this.post(`${this.apiUrl}/metadata-table-templates/${id}/bulk_update_staff_only/`, request);
971
971
  }
972
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
973
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateService, providedIn: 'root' });
972
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
973
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableTemplateService, providedIn: 'root' });
974
974
  }
975
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateService, decorators: [{
975
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableTemplateService, decorators: [{
976
976
  type: Injectable,
977
977
  args: [{
978
978
  providedIn: 'root'
@@ -1017,10 +1017,10 @@ class FavouriteMetadataOptionService extends BaseApiService {
1017
1017
  deleteFavouriteMetadataOption(id) {
1018
1018
  return this.delete(`${this.apiUrl}/favourite-options/${id}/`);
1019
1019
  }
1020
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FavouriteMetadataOptionService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1021
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FavouriteMetadataOptionService, providedIn: 'root' });
1020
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FavouriteMetadataOptionService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1021
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FavouriteMetadataOptionService, providedIn: 'root' });
1022
1022
  }
1023
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FavouriteMetadataOptionService, decorators: [{
1023
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FavouriteMetadataOptionService, decorators: [{
1024
1024
  type: Injectable,
1025
1025
  args: [{
1026
1026
  providedIn: 'root'
@@ -1107,10 +1107,10 @@ class MetadataColumnTemplateService extends BaseApiService {
1107
1107
  });
1108
1108
  return this.get(`${this.apiUrl}/column-templates/ontology_suggestions/`, { params: httpParams });
1109
1109
  }
1110
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1111
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateService, providedIn: 'root' });
1110
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1111
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnTemplateService, providedIn: 'root' });
1112
1112
  }
1113
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateService, decorators: [{
1113
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnTemplateService, decorators: [{
1114
1114
  type: Injectable,
1115
1115
  args: [{
1116
1116
  providedIn: 'root'
@@ -1149,10 +1149,10 @@ class MetadataColumnTemplateShareService extends BaseApiService {
1149
1149
  deleteMetadataColumnTemplateShare(id) {
1150
1150
  return this.delete(`${this.apiUrl}/template-shares/${id}/`);
1151
1151
  }
1152
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateShareService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1153
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateShareService, providedIn: 'root' });
1152
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnTemplateShareService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1153
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnTemplateShareService, providedIn: 'root' });
1154
1154
  }
1155
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateShareService, decorators: [{
1155
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnTemplateShareService, decorators: [{
1156
1156
  type: Injectable,
1157
1157
  args: [{
1158
1158
  providedIn: 'root'
@@ -1205,10 +1205,10 @@ class MetadataManagementService extends BaseApiService {
1205
1205
  exportSdrfFile(request) {
1206
1206
  return this.post(`${this.apiUrl}/metadata-management/export_sdrf_file/`, request, { responseType: 'blob' });
1207
1207
  }
1208
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataManagementService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1209
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataManagementService, providedIn: 'root' });
1208
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataManagementService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1209
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataManagementService, providedIn: 'root' });
1210
1210
  }
1211
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataManagementService, decorators: [{
1211
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataManagementService, decorators: [{
1212
1212
  type: Injectable,
1213
1213
  args: [{
1214
1214
  providedIn: 'root'
@@ -1228,10 +1228,10 @@ class Websocket extends WebSocketService {
1228
1228
  getLabGroupUpdates() {
1229
1229
  return this.filterMessages('lab_group.update');
1230
1230
  }
1231
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: Websocket, deps: [{ token: i3.AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
1232
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: Websocket, providedIn: 'root' });
1231
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: Websocket, deps: [{ token: i3.AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
1232
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: Websocket, providedIn: 'root' });
1233
1233
  }
1234
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: Websocket, decorators: [{
1234
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: Websocket, decorators: [{
1235
1235
  type: Injectable,
1236
1236
  args: [{
1237
1237
  providedIn: 'root'
@@ -1479,10 +1479,10 @@ class NotificationService extends NotificationService$1 {
1479
1479
  createVanillaNotification(type, title, message, level = 'info', autoClose = true) {
1480
1480
  this.createLocalNotification(type, title, message, level, autoClose);
1481
1481
  }
1482
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NotificationService, deps: [{ token: Websocket }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Injectable });
1483
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NotificationService, providedIn: 'root' });
1482
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NotificationService, deps: [{ token: Websocket }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Injectable });
1483
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NotificationService, providedIn: 'root' });
1484
1484
  }
1485
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NotificationService, decorators: [{
1485
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: NotificationService, decorators: [{
1486
1486
  type: Injectable,
1487
1487
  args: [{
1488
1488
  providedIn: 'root'
@@ -1550,10 +1550,10 @@ class OntologySearchService extends BaseApiService {
1550
1550
  limit
1551
1551
  });
1552
1552
  }
1553
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: OntologySearchService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1554
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: OntologySearchService, providedIn: 'root' });
1553
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: OntologySearchService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1554
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: OntologySearchService, providedIn: 'root' });
1555
1555
  }
1556
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: OntologySearchService, decorators: [{
1556
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: OntologySearchService, decorators: [{
1557
1557
  type: Injectable,
1558
1558
  args: [{
1559
1559
  providedIn: 'root'
@@ -1586,10 +1586,10 @@ class SchemaService extends BaseApiService {
1586
1586
  syncBuiltin() {
1587
1587
  return this.post(`${this.apiUrl}/schemas/sync_builtin/`, {});
1588
1588
  }
1589
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SchemaService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1590
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SchemaService, providedIn: 'root' });
1589
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SchemaService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1590
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SchemaService, providedIn: 'root' });
1591
1591
  }
1592
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SchemaService, decorators: [{
1592
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SchemaService, decorators: [{
1593
1593
  type: Injectable,
1594
1594
  args: [{
1595
1595
  providedIn: 'root'
@@ -1680,10 +1680,10 @@ class SDRFDefaultsService extends BaseApiService {
1680
1680
  getQuickValues() {
1681
1681
  return this.get(`${this.apiUrl}/sdrf-defaults/quick_values/`);
1682
1682
  }
1683
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SDRFDefaultsService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1684
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SDRFDefaultsService, providedIn: 'root' });
1683
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SDRFDefaultsService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1684
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SDRFDefaultsService, providedIn: 'root' });
1685
1685
  }
1686
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SDRFDefaultsService, decorators: [{
1686
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SDRFDefaultsService, decorators: [{
1687
1687
  type: Injectable,
1688
1688
  args: [{
1689
1689
  providedIn: 'root'
@@ -1723,10 +1723,10 @@ class AsyncTaskService extends BaseApiService {
1723
1723
  const httpParams = this.buildHttpParams({ token });
1724
1724
  return this.get(`${this.apiUrl}/async-tasks/${id}/download/`, { params: httpParams, responseType: 'blob' });
1725
1725
  }
1726
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1727
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskService, providedIn: 'root' });
1726
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncTaskService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1727
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncTaskService, providedIn: 'root' });
1728
1728
  }
1729
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskService, decorators: [{
1729
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncTaskService, decorators: [{
1730
1730
  type: Injectable,
1731
1731
  args: [{
1732
1732
  providedIn: 'root'
@@ -1746,10 +1746,10 @@ class AsyncExportService extends BaseApiService {
1746
1746
  multipleExcelTemplates(request) {
1747
1747
  return this.post(`${this.apiUrl}/async-export/multiple_excel_templates/`, request);
1748
1748
  }
1749
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncExportService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1750
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncExportService, providedIn: 'root' });
1749
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncExportService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1750
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncExportService, providedIn: 'root' });
1751
1751
  }
1752
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncExportService, decorators: [{
1752
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncExportService, decorators: [{
1753
1753
  type: Injectable,
1754
1754
  args: [{
1755
1755
  providedIn: 'root'
@@ -1757,10 +1757,17 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1757
1757
  }] });
1758
1758
 
1759
1759
  class ChunkedUploadService extends BaseApiService {
1760
+ siteConfigService = inject(SiteConfigService);
1760
1761
  /**
1761
1762
  * Initialize or continue a chunked upload
1762
1763
  */
1763
1764
  uploadChunk(request, uploadId, offset, totalSize) {
1765
+ const fileSize = totalSize || request.file.size;
1766
+ const isChunkedUpload = offset !== undefined && totalSize !== undefined;
1767
+ const validation = this.siteConfigService.validateFileSize(fileSize, isChunkedUpload);
1768
+ if (!validation.valid) {
1769
+ return throwError(() => new Error(validation.message));
1770
+ }
1764
1771
  const formData = new FormData();
1765
1772
  formData.append('file', request.file);
1766
1773
  if (request.filename) {
@@ -1782,7 +1789,6 @@ class ChunkedUploadService extends BaseApiService {
1782
1789
  ? `${this.apiUrl}/chunked-upload/${uploadId}/`
1783
1790
  : `${this.apiUrl}/chunked-upload/`;
1784
1791
  const httpOptions = {};
1785
- const isChunkedUpload = offset !== undefined && totalSize !== undefined;
1786
1792
  if (isChunkedUpload) {
1787
1793
  const chunkEnd = offset + request.file.size - 1;
1788
1794
  httpOptions.headers = {
@@ -1934,10 +1940,10 @@ class ChunkedUploadService extends BaseApiService {
1934
1940
  uploadNextChunk();
1935
1941
  });
1936
1942
  }
1937
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedUploadService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1938
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedUploadService, providedIn: 'root' });
1943
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChunkedUploadService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1944
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChunkedUploadService, providedIn: 'root' });
1939
1945
  }
1940
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedUploadService, decorators: [{
1946
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChunkedUploadService, decorators: [{
1941
1947
  type: Injectable,
1942
1948
  args: [{
1943
1949
  providedIn: 'root'
@@ -1982,10 +1988,10 @@ class AsyncImportService extends BaseApiService {
1982
1988
  result: result
1983
1989
  })));
1984
1990
  }
1985
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncImportService, deps: [{ token: ChunkedUploadService }], target: i0.ɵɵFactoryTarget.Injectable });
1986
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncImportService, providedIn: 'root' });
1991
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncImportService, deps: [{ token: ChunkedUploadService }], target: i0.ɵɵFactoryTarget.Injectable });
1992
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncImportService, providedIn: 'root' });
1987
1993
  }
1988
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncImportService, decorators: [{
1994
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncImportService, decorators: [{
1989
1995
  type: Injectable,
1990
1996
  args: [{
1991
1997
  providedIn: 'root'
@@ -1993,13 +1999,23 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
1993
1999
  }], ctorParameters: () => [{ type: ChunkedUploadService }] });
1994
2000
 
1995
2001
  class AsyncValidationService extends BaseApiService {
2002
+ getAvailableSchemas() {
2003
+ return this.get(`${this.apiUrl}/async-validation/available_schemas/`);
2004
+ }
1996
2005
  metadataTable(request) {
1997
- return this.post(`${this.apiUrl}/async-validation/metadata_table/`, request);
2006
+ const payload = {
2007
+ metadata_table_id: request.metadataTableId,
2008
+ validate_sdrf_format: request.validateSdrfFormat ?? true,
2009
+ include_pools: request.includePools ?? true,
2010
+ schema_names: request.schemaNames ?? ['default'],
2011
+ skip_ontology: request.skipOntology ?? false
2012
+ };
2013
+ return this.post(`${this.apiUrl}/async-validation/metadata_table/`, payload);
1998
2014
  }
1999
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncValidationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2000
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncValidationService, providedIn: 'root' });
2015
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncValidationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2016
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncValidationService, providedIn: 'root' });
2001
2017
  }
2002
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncValidationService, decorators: [{
2018
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncValidationService, decorators: [{
2003
2019
  type: Injectable,
2004
2020
  args: [{
2005
2021
  providedIn: 'root'
@@ -2181,10 +2197,10 @@ class ChunkedImportService extends BaseApiService {
2181
2197
  };
2182
2198
  return this.post(`${this.apiUrl}/async-import/chunked_file/`, payload);
2183
2199
  }
2184
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedImportService, deps: [{ token: ChunkedUploadService }], target: i0.ɵɵFactoryTarget.Injectable });
2185
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedImportService, providedIn: 'root' });
2200
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChunkedImportService, deps: [{ token: ChunkedUploadService }], target: i0.ɵɵFactoryTarget.Injectable });
2201
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChunkedImportService, providedIn: 'root' });
2186
2202
  }
2187
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedImportService, decorators: [{
2203
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ChunkedImportService, decorators: [{
2188
2204
  type: Injectable,
2189
2205
  args: [{
2190
2206
  providedIn: 'root'
@@ -2468,10 +2484,10 @@ class SdrfSyntaxService {
2468
2484
  getSyntheticPeptideValues() {
2469
2485
  return [...this.SYNTHETIC_PEPTIDE_VALUES];
2470
2486
  }
2471
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfSyntaxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2472
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfSyntaxService, providedIn: 'root' });
2487
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfSyntaxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2488
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfSyntaxService, providedIn: 'root' });
2473
2489
  }
2474
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfSyntaxService, decorators: [{
2490
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfSyntaxService, decorators: [{
2475
2491
  type: Injectable,
2476
2492
  args: [{
2477
2493
  providedIn: 'root'
@@ -2569,10 +2585,10 @@ class ElectronService {
2569
2585
  return () => unsubscribe();
2570
2586
  });
2571
2587
  }
2572
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ElectronService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2573
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ElectronService, providedIn: 'root' });
2588
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ElectronService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2589
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ElectronService, providedIn: 'root' });
2574
2590
  }
2575
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ElectronService, decorators: [{
2591
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ElectronService, decorators: [{
2576
2592
  type: Injectable,
2577
2593
  args: [{
2578
2594
  providedIn: 'root'
@@ -2676,10 +2692,10 @@ class SdrfAgeInput {
2676
2692
  }
2677
2693
  }
2678
2694
  }
2679
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfAgeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
2680
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SdrfAgeInput, isStandalone: true, selector: "ccv-sdrf-age-input", inputs: { value: "value" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<form [formGroup]=\"ageForm\" class=\"age-input-form\">\n <!-- Range Toggle -->\n <div class=\"form-check mb-3\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"isRange\" formControlName=\"isRange\">\n <label class=\"form-check-label\" for=\"isRange\">\n Age Range\n </label>\n </div>\n\n <!-- Single Age Input -->\n @if (!isRange) {\n <div class=\"row g-2 mb-2\">\n <div class=\"col-sm-3\">\n <label for=\"years\" class=\"form-label\">Years</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"years\" \n formControlName=\"years\" placeholder=\"0\" min=\"0\" max=\"200\">\n </div>\n <div class=\"col-sm-3\">\n <label for=\"months\" class=\"form-label\">Months</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"months\" \n formControlName=\"months\" placeholder=\"0\" min=\"0\" max=\"11\">\n </div>\n <div class=\"col-sm-3\">\n <label for=\"days\" class=\"form-label\">Days</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"days\" \n formControlName=\"days\" placeholder=\"0\" min=\"0\" max=\"31\">\n </div>\n <div class=\"col-sm-3\">\n <label for=\"weeks\" class=\"form-label\">Weeks <small class=\"text-muted\">(alternative)</small></label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"weeks\" \n formControlName=\"weeks\" placeholder=\"0\" min=\"0\" max=\"520\">\n </div>\n </div>\n <div class=\"form-text\">\n Use weeks for very young subjects (e.g., \"8W\" for 8 weeks). Otherwise use years/months/days.\n </div>\n }\n\n <!-- Age Range Input -->\n @if (isRange) {\n <div class=\"row g-2 mb-2\">\n <div class=\"col-sm-6\">\n <label class=\"form-label fw-semibold\">Start Age</label>\n <div class=\"row g-1\">\n <div class=\"col-6\">\n <label for=\"rangeStartYears\" class=\"form-label small\">Years</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeStartYears\" \n formControlName=\"rangeStartYears\" placeholder=\"0\" min=\"0\" max=\"200\">\n </div>\n <div class=\"col-6\">\n <label for=\"rangeStartMonths\" class=\"form-label small\">Months</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeStartMonths\" \n formControlName=\"rangeStartMonths\" placeholder=\"0\" min=\"0\" max=\"11\">\n </div>\n </div>\n </div>\n <div class=\"col-sm-6\">\n <label class=\"form-label fw-semibold\">End Age</label>\n <div class=\"row g-1\">\n <div class=\"col-6\">\n <label for=\"rangeEndYears\" class=\"form-label small\">Years</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeEndYears\" \n formControlName=\"rangeEndYears\" placeholder=\"0\" min=\"0\" max=\"200\">\n </div>\n <div class=\"col-6\">\n <label for=\"rangeEndMonths\" class=\"form-label small\">Months</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeEndMonths\" \n formControlName=\"rangeEndMonths\" placeholder=\"0\" min=\"0\" max=\"11\">\n </div>\n </div>\n </div>\n </div>\n <div class=\"form-text\">\n Age range format: \"25Y-30Y\" or \"2Y6M-3Y\"\n </div>\n }\n\n <!-- Validation Messages -->\n @if (ageForm.invalid && ageForm.touched) {\n <div class=\"alert alert-warning alert-sm mt-2\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n Please enter valid age values (0-200 years, 0-11 months, 0-31 days)\n </small>\n </div>\n }\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { 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"] }] });
2695
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfAgeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
2696
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SdrfAgeInput, isStandalone: true, selector: "ccv-sdrf-age-input", inputs: { value: "value" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<form [formGroup]=\"ageForm\" class=\"age-input-form\">\n <!-- Range Toggle -->\n <div class=\"form-check mb-3\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"isRange\" formControlName=\"isRange\">\n <label class=\"form-check-label\" for=\"isRange\">\n Age Range\n </label>\n </div>\n\n <!-- Single Age Input -->\n @if (!isRange) {\n <div class=\"row g-2 mb-2\">\n <div class=\"col-sm-3\">\n <label for=\"years\" class=\"form-label\">Years</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"years\" \n formControlName=\"years\" placeholder=\"0\" min=\"0\" max=\"200\">\n </div>\n <div class=\"col-sm-3\">\n <label for=\"months\" class=\"form-label\">Months</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"months\" \n formControlName=\"months\" placeholder=\"0\" min=\"0\" max=\"11\">\n </div>\n <div class=\"col-sm-3\">\n <label for=\"days\" class=\"form-label\">Days</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"days\" \n formControlName=\"days\" placeholder=\"0\" min=\"0\" max=\"31\">\n </div>\n <div class=\"col-sm-3\">\n <label for=\"weeks\" class=\"form-label\">Weeks <small class=\"text-muted\">(alternative)</small></label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"weeks\" \n formControlName=\"weeks\" placeholder=\"0\" min=\"0\" max=\"520\">\n </div>\n </div>\n <div class=\"form-text\">\n Use weeks for very young subjects (e.g., \"8W\" for 8 weeks). Otherwise use years/months/days.\n </div>\n }\n\n <!-- Age Range Input -->\n @if (isRange) {\n <div class=\"row g-2 mb-2\">\n <div class=\"col-sm-6\">\n <label class=\"form-label fw-semibold\">Start Age</label>\n <div class=\"row g-1\">\n <div class=\"col-6\">\n <label for=\"rangeStartYears\" class=\"form-label small\">Years</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeStartYears\" \n formControlName=\"rangeStartYears\" placeholder=\"0\" min=\"0\" max=\"200\">\n </div>\n <div class=\"col-6\">\n <label for=\"rangeStartMonths\" class=\"form-label small\">Months</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeStartMonths\" \n formControlName=\"rangeStartMonths\" placeholder=\"0\" min=\"0\" max=\"11\">\n </div>\n </div>\n </div>\n <div class=\"col-sm-6\">\n <label class=\"form-label fw-semibold\">End Age</label>\n <div class=\"row g-1\">\n <div class=\"col-6\">\n <label for=\"rangeEndYears\" class=\"form-label small\">Years</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeEndYears\" \n formControlName=\"rangeEndYears\" placeholder=\"0\" min=\"0\" max=\"200\">\n </div>\n <div class=\"col-6\">\n <label for=\"rangeEndMonths\" class=\"form-label small\">Months</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeEndMonths\" \n formControlName=\"rangeEndMonths\" placeholder=\"0\" min=\"0\" max=\"11\">\n </div>\n </div>\n </div>\n </div>\n <div class=\"form-text\">\n Age range format: \"25Y-30Y\" or \"2Y6M-3Y\"\n </div>\n }\n\n <!-- Validation Messages -->\n @if (ageForm.invalid && ageForm.touched) {\n <div class=\"alert alert-warning alert-sm mt-2\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n Please enter valid age values (0-200 years, 0-11 months, 0-31 days)\n </small>\n </div>\n }\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { 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"] }] });
2681
2697
  }
2682
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfAgeInput, decorators: [{
2698
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfAgeInput, decorators: [{
2683
2699
  type: Component,
2684
2700
  args: [{ selector: 'ccv-sdrf-age-input', imports: [CommonModule, ReactiveFormsModule], template: "<form [formGroup]=\"ageForm\" class=\"age-input-form\">\n <!-- Range Toggle -->\n <div class=\"form-check mb-3\">\n <input class=\"form-check-input\" type=\"checkbox\" id=\"isRange\" formControlName=\"isRange\">\n <label class=\"form-check-label\" for=\"isRange\">\n Age Range\n </label>\n </div>\n\n <!-- Single Age Input -->\n @if (!isRange) {\n <div class=\"row g-2 mb-2\">\n <div class=\"col-sm-3\">\n <label for=\"years\" class=\"form-label\">Years</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"years\" \n formControlName=\"years\" placeholder=\"0\" min=\"0\" max=\"200\">\n </div>\n <div class=\"col-sm-3\">\n <label for=\"months\" class=\"form-label\">Months</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"months\" \n formControlName=\"months\" placeholder=\"0\" min=\"0\" max=\"11\">\n </div>\n <div class=\"col-sm-3\">\n <label for=\"days\" class=\"form-label\">Days</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"days\" \n formControlName=\"days\" placeholder=\"0\" min=\"0\" max=\"31\">\n </div>\n <div class=\"col-sm-3\">\n <label for=\"weeks\" class=\"form-label\">Weeks <small class=\"text-muted\">(alternative)</small></label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"weeks\" \n formControlName=\"weeks\" placeholder=\"0\" min=\"0\" max=\"520\">\n </div>\n </div>\n <div class=\"form-text\">\n Use weeks for very young subjects (e.g., \"8W\" for 8 weeks). Otherwise use years/months/days.\n </div>\n }\n\n <!-- Age Range Input -->\n @if (isRange) {\n <div class=\"row g-2 mb-2\">\n <div class=\"col-sm-6\">\n <label class=\"form-label fw-semibold\">Start Age</label>\n <div class=\"row g-1\">\n <div class=\"col-6\">\n <label for=\"rangeStartYears\" class=\"form-label small\">Years</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeStartYears\" \n formControlName=\"rangeStartYears\" placeholder=\"0\" min=\"0\" max=\"200\">\n </div>\n <div class=\"col-6\">\n <label for=\"rangeStartMonths\" class=\"form-label small\">Months</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeStartMonths\" \n formControlName=\"rangeStartMonths\" placeholder=\"0\" min=\"0\" max=\"11\">\n </div>\n </div>\n </div>\n <div class=\"col-sm-6\">\n <label class=\"form-label fw-semibold\">End Age</label>\n <div class=\"row g-1\">\n <div class=\"col-6\">\n <label for=\"rangeEndYears\" class=\"form-label small\">Years</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeEndYears\" \n formControlName=\"rangeEndYears\" placeholder=\"0\" min=\"0\" max=\"200\">\n </div>\n <div class=\"col-6\">\n <label for=\"rangeEndMonths\" class=\"form-label small\">Months</label>\n <input type=\"number\" class=\"form-control form-control-sm\" id=\"rangeEndMonths\" \n formControlName=\"rangeEndMonths\" placeholder=\"0\" min=\"0\" max=\"11\">\n </div>\n </div>\n </div>\n </div>\n <div class=\"form-text\">\n Age range format: \"25Y-30Y\" or \"2Y6M-3Y\"\n </div>\n }\n\n <!-- Validation Messages -->\n @if (ageForm.invalid && ageForm.touched) {\n <div class=\"alert alert-warning alert-sm mt-2\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n Please enter valid age values (0-200 years, 0-11 months, 0-31 days)\n </small>\n </div>\n }\n</form>\n" }]
2685
2701
  }], propDecorators: { value: [{
@@ -2970,10 +2986,10 @@ class SdrfModificationInput {
2970
2986
  this.updateAvailableSpecifications(mockSuggestion);
2971
2987
  }
2972
2988
  }
2973
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfModificationInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
2974
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SdrfModificationInput, isStandalone: true, selector: "ccv-sdrf-modification-input", inputs: { value: "value", templateId: "templateId", columnId: "columnId" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<form [formGroup]=\"modificationForm\" class=\"modification-input-form\">\n\n <!-- Search Type Selection for Typeahead -->\n @if (templateId || columnId) {\n <div class=\"mb-3\">\n <small class=\"text-muted d-block mb-1\">Search Mode for Name of Term:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'icontains' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('icontains')\">\n <i class=\"bi bi-search me-1\"></i>Contains\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'istartswith' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('istartswith')\">\n <i class=\"bi bi-arrow-right me-1\"></i>Starts with\n </button>\n </div>\n </div>\n }\n\n <div class=\"row g-3\">\n\n <!-- Name of Term (NT) - Required with Typeahead -->\n <div class=\"col-md-6\">\n <label for=\"NT\" class=\"form-label\">\n Name of Term <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(NT)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"NT\"\n formControlName=\"NT\"\n placeholder=\"e.g., Oxidation, Acetyl, Phospho\"\n [ngbTypeahead]=\"searchModificationTerms\"\n [resultFormatter]=\"formatOntologySuggestion\"\n [inputFormatter]=\"formatOntologySuggestion\"\n (selectItem)=\"onOntologySuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [class.is-invalid]=\"modificationForm.get('NT')?.invalid && modificationForm.get('NT')?.touched\">\n <div class=\"form-text\">\n @if (templateId || columnId) {\n Typeahead search: {{ searchType() === 'icontains' ? 'Contains' : 'Starts with' }} mode\n } @else {\n Common modifications: Oxidation, Carbamidomethyl, Acetyl\n }\n </div>\n <div class=\"invalid-feedback\">\n Name of Term is required\n </div>\n </div>\n\n <!-- Unimod Specification Selection -->\n @if (showSpecifications() && availableSpecifications().length > 0) {\n <div class=\"col-md-12\">\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Unimod Specifications\n <small class=\"text-muted\">(Select to auto-fill fields)</small>\n </label>\n \n @if (hiddenSpecificationCount() > 0) {\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"showHiddenSpecs\"\n [checked]=\"showHiddenSpecifications()\"\n (change)=\"toggleHiddenSpecifications()\">\n <label class=\"form-check-label small text-muted\" for=\"showHiddenSpecs\">\n Show {{ hiddenSpecificationCount() }} hidden spec{{ hiddenSpecificationCount() === 1 ? '' : 's' }}\n </label>\n </div>\n }\n </div>\n \n <div class=\"border rounded p-2 bg-body-secondary\">\n <div class=\"mb-2\">\n <small class=\"text-info fw-semibold\">\n <i class=\"bi bi-info-circle me-1\"></i>\n {{ showHiddenSpecifications() ? 'All' : 'Visible' }} specifications for {{ selectedUnimodData()?.name }}\n @if (!showHiddenSpecifications() && hiddenSpecificationCount() > 0) {\n <span class=\"text-muted\">({{ hiddenSpecificationCount() }} hidden)</span>\n }\n </small>\n </div>\n <div class=\"row g-2\">\n @for (spec of availableSpecifications(); track spec.specNumber) {\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"radio\"\n [id]=\"'spec_' + spec.specNumber\"\n [value]=\"spec.specNumber\"\n [checked]=\"selectedSpecification() === spec.specNumber\"\n (change)=\"onSpecificationSelected(spec.specNumber)\">\n <label\n class=\"form-check-label small\"\n [for]=\"'spec_' + spec.specNumber\">\n <div class=\"fw-semibold\">\n Spec {{ spec.specNumber }}\n @if (spec['hidden'] === '1') {\n <span class=\"badge bg-warning text-dark ms-1\" title=\"Hidden specification\">Hidden</span>\n }\n </div>\n <div class=\"text-muted\">{{ getSpecificationDisplay(spec) }}</div>\n </label>\n </div>\n </div>\n }\n </div>\n @if (selectedSpecification()) {\n <div class=\"mt-2 p-2 bg-body border rounded\">\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>\n Fields auto-filled from specification {{ selectedSpecification() }}\n </small>\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- Target Amino Acid (TA) - Required -->\n <div class=\"col-md-6\">\n <label for=\"TA\" class=\"form-label\">\n Target Amino Acid <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(TA)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"TA\"\n formControlName=\"TA\"\n placeholder=\"e.g., M, S,T,Y, C\"\n (input)=\"onTargetAminoAcidChange($event)\"\n [class.is-invalid]=\"modificationForm.get('TA')?.invalid && modificationForm.get('TA')?.touched\">\n <div class=\"form-text\">Single letter amino acid codes, comma-separated</div>\n <div class=\"invalid-feedback\">\n Target amino acid is required\n </div>\n </div>\n\n <!-- Accession (AC) -->\n <div class=\"col-md-6\">\n <label for=\"AC\" class=\"form-label\">\n Accession <small class=\"text-muted\">(AC)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"AC\"\n formControlName=\"AC\"\n placeholder=\"e.g., UNIMOD:35, PSI-MOD:00719\">\n <div class=\"form-text\">UNIMOD or PSI-MOD accession</div>\n </div>\n\n <!-- Modification Type (MT) -->\n <div class=\"col-md-6\">\n <label for=\"MT\" class=\"form-label\">\n Modification Type <small class=\"text-muted\">(MT)</small>\n </label>\n <select class=\"form-select\" id=\"MT\" formControlName=\"MT\">\n <option value=\"\">Select type...</option>\n @for (type of modificationTypes; track type) {\n <option [value]=\"type\">{{ type }}</option>\n }\n </select>\n <div class=\"form-text\">Fixed, Variable, or Annotated</div>\n </div>\n\n <!-- Position (PP) -->\n <div class=\"col-md-6\">\n <label for=\"PP\" class=\"form-label\">\n Position <small class=\"text-muted\">(PP)</small>\n </label>\n <select class=\"form-select\" id=\"PP\" formControlName=\"PP\">\n <option value=\"\">Select position...</option>\n @for (position of positions; track position) {\n <option [value]=\"position\">{{ position }}</option>\n }\n </select>\n <div class=\"form-text\">Where the modification occurs</div>\n </div>\n\n <!-- Chemical Formula (CF) -->\n <div class=\"col-md-6\">\n <label for=\"CF\" class=\"form-label\">\n Chemical Formula <small class=\"text-muted\">(CF)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"CF\"\n formControlName=\"CF\"\n placeholder=\"e.g., H(2)C(2)O\">\n <div class=\"form-text\">Follow UNIMOD format: H(2)C(2)O</div>\n </div>\n\n <!-- Monoisotopic Mass (MM) -->\n <div class=\"col-md-6\">\n <label for=\"MM\" class=\"form-label\">\n Monoisotopic Mass <small class=\"text-muted\">(MM)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"MM\"\n formControlName=\"MM\"\n placeholder=\"e.g., 42.010565\"\n [class.is-invalid]=\"modificationForm.get('MM')?.invalid && modificationForm.get('MM')?.touched\">\n <div class=\"form-text\">Mass shift in Da (5+ decimal places)</div>\n <div class=\"invalid-feedback\">\n Please enter a valid mass (numbers and decimal point only)\n </div>\n </div>\n\n <!-- Target Site (TS) -->\n <div class=\"col-md-6\">\n <label for=\"TS\" class=\"form-label\">\n Target Site <small class=\"text-muted\">(TS)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"TS\"\n formControlName=\"TS\"\n placeholder=\"e.g., N[^P][ST]\">\n <div class=\"form-text\">Regular expression for complex site rules</div>\n </div>\n\n </div>\n\n <!-- Preview -->\n @if (modificationForm.valid && (modificationForm.get('NT')?.value || modificationForm.get('TA')?.value)) {\n <div class=\"mt-3 p-2 bg-body-secondary border rounded\">\n <small class=\"text-muted fw-semibold\">Preview:</small>\n <div class=\"font-monospace small\">{{ value || 'Building...' }}</div>\n </div>\n }\n\n <!-- Validation Summary -->\n @if (modificationForm.invalid && modificationForm.touched) {\n <div class=\"alert alert-warning mt-3\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n <strong>Required fields:</strong> Name of Term (NT) and Target Amino Acid (TA) must be filled.\n </small>\n </div>\n }\n</form>\n", styles: [""], 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.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: "directive", type: NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "ngmodule", type: NgbModule }] });
2989
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfModificationInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
2990
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SdrfModificationInput, isStandalone: true, selector: "ccv-sdrf-modification-input", inputs: { value: "value", templateId: "templateId", columnId: "columnId" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<form [formGroup]=\"modificationForm\" class=\"modification-input-form\">\n\n <!-- Search Type Selection for Typeahead -->\n @if (templateId || columnId) {\n <div class=\"mb-3\">\n <small class=\"text-muted d-block mb-1\">Search Mode for Name of Term:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'icontains' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('icontains')\">\n <i class=\"bi bi-search me-1\"></i>Contains\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'istartswith' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('istartswith')\">\n <i class=\"bi bi-arrow-right me-1\"></i>Starts with\n </button>\n </div>\n </div>\n }\n\n <div class=\"row g-3\">\n\n <!-- Name of Term (NT) - Required with Typeahead -->\n <div class=\"col-md-6\">\n <label for=\"NT\" class=\"form-label\">\n Name of Term <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(NT)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"NT\"\n formControlName=\"NT\"\n placeholder=\"e.g., Oxidation, Acetyl, Phospho\"\n [ngbTypeahead]=\"searchModificationTerms\"\n [resultFormatter]=\"formatOntologySuggestion\"\n [inputFormatter]=\"formatOntologySuggestion\"\n (selectItem)=\"onOntologySuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [class.is-invalid]=\"modificationForm.get('NT')?.invalid && modificationForm.get('NT')?.touched\">\n <div class=\"form-text\">\n @if (templateId || columnId) {\n Typeahead search: {{ searchType() === 'icontains' ? 'Contains' : 'Starts with' }} mode\n } @else {\n Common modifications: Oxidation, Carbamidomethyl, Acetyl\n }\n </div>\n <div class=\"invalid-feedback\">\n Name of Term is required\n </div>\n </div>\n\n <!-- Unimod Specification Selection -->\n @if (showSpecifications() && availableSpecifications().length > 0) {\n <div class=\"col-md-12\">\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Unimod Specifications\n <small class=\"text-muted\">(Select to auto-fill fields)</small>\n </label>\n \n @if (hiddenSpecificationCount() > 0) {\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"showHiddenSpecs\"\n [checked]=\"showHiddenSpecifications()\"\n (change)=\"toggleHiddenSpecifications()\">\n <label class=\"form-check-label small text-muted\" for=\"showHiddenSpecs\">\n Show {{ hiddenSpecificationCount() }} hidden spec{{ hiddenSpecificationCount() === 1 ? '' : 's' }}\n </label>\n </div>\n }\n </div>\n \n <div class=\"border rounded p-2 bg-body-secondary\">\n <div class=\"mb-2\">\n <small class=\"text-info fw-semibold\">\n <i class=\"bi bi-info-circle me-1\"></i>\n {{ showHiddenSpecifications() ? 'All' : 'Visible' }} specifications for {{ selectedUnimodData()?.name }}\n @if (!showHiddenSpecifications() && hiddenSpecificationCount() > 0) {\n <span class=\"text-muted\">({{ hiddenSpecificationCount() }} hidden)</span>\n }\n </small>\n </div>\n <div class=\"row g-2\">\n @for (spec of availableSpecifications(); track spec.specNumber) {\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"radio\"\n [id]=\"'spec_' + spec.specNumber\"\n [value]=\"spec.specNumber\"\n [checked]=\"selectedSpecification() === spec.specNumber\"\n (change)=\"onSpecificationSelected(spec.specNumber)\">\n <label\n class=\"form-check-label small\"\n [for]=\"'spec_' + spec.specNumber\">\n <div class=\"fw-semibold\">\n Spec {{ spec.specNumber }}\n @if (spec['hidden'] === '1') {\n <span class=\"badge bg-warning text-dark ms-1\" title=\"Hidden specification\">Hidden</span>\n }\n </div>\n <div class=\"text-muted\">{{ getSpecificationDisplay(spec) }}</div>\n </label>\n </div>\n </div>\n }\n </div>\n @if (selectedSpecification()) {\n <div class=\"mt-2 p-2 bg-body border rounded\">\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>\n Fields auto-filled from specification {{ selectedSpecification() }}\n </small>\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- Target Amino Acid (TA) - Required -->\n <div class=\"col-md-6\">\n <label for=\"TA\" class=\"form-label\">\n Target Amino Acid <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(TA)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"TA\"\n formControlName=\"TA\"\n placeholder=\"e.g., M, S,T,Y, C\"\n (input)=\"onTargetAminoAcidChange($event)\"\n [class.is-invalid]=\"modificationForm.get('TA')?.invalid && modificationForm.get('TA')?.touched\">\n <div class=\"form-text\">Single letter amino acid codes, comma-separated</div>\n <div class=\"invalid-feedback\">\n Target amino acid is required\n </div>\n </div>\n\n <!-- Accession (AC) -->\n <div class=\"col-md-6\">\n <label for=\"AC\" class=\"form-label\">\n Accession <small class=\"text-muted\">(AC)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"AC\"\n formControlName=\"AC\"\n placeholder=\"e.g., UNIMOD:35, PSI-MOD:00719\">\n <div class=\"form-text\">UNIMOD or PSI-MOD accession</div>\n </div>\n\n <!-- Modification Type (MT) -->\n <div class=\"col-md-6\">\n <label for=\"MT\" class=\"form-label\">\n Modification Type <small class=\"text-muted\">(MT)</small>\n </label>\n <select class=\"form-select\" id=\"MT\" formControlName=\"MT\">\n <option value=\"\">Select type...</option>\n @for (type of modificationTypes; track type) {\n <option [value]=\"type\">{{ type }}</option>\n }\n </select>\n <div class=\"form-text\">Fixed, Variable, or Annotated</div>\n </div>\n\n <!-- Position (PP) -->\n <div class=\"col-md-6\">\n <label for=\"PP\" class=\"form-label\">\n Position <small class=\"text-muted\">(PP)</small>\n </label>\n <select class=\"form-select\" id=\"PP\" formControlName=\"PP\">\n <option value=\"\">Select position...</option>\n @for (position of positions; track position) {\n <option [value]=\"position\">{{ position }}</option>\n }\n </select>\n <div class=\"form-text\">Where the modification occurs</div>\n </div>\n\n <!-- Chemical Formula (CF) -->\n <div class=\"col-md-6\">\n <label for=\"CF\" class=\"form-label\">\n Chemical Formula <small class=\"text-muted\">(CF)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"CF\"\n formControlName=\"CF\"\n placeholder=\"e.g., H(2)C(2)O\">\n <div class=\"form-text\">Follow UNIMOD format: H(2)C(2)O</div>\n </div>\n\n <!-- Monoisotopic Mass (MM) -->\n <div class=\"col-md-6\">\n <label for=\"MM\" class=\"form-label\">\n Monoisotopic Mass <small class=\"text-muted\">(MM)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"MM\"\n formControlName=\"MM\"\n placeholder=\"e.g., 42.010565\"\n [class.is-invalid]=\"modificationForm.get('MM')?.invalid && modificationForm.get('MM')?.touched\">\n <div class=\"form-text\">Mass shift in Da (5+ decimal places)</div>\n <div class=\"invalid-feedback\">\n Please enter a valid mass (numbers and decimal point only)\n </div>\n </div>\n\n <!-- Target Site (TS) -->\n <div class=\"col-md-6\">\n <label for=\"TS\" class=\"form-label\">\n Target Site <small class=\"text-muted\">(TS)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"TS\"\n formControlName=\"TS\"\n placeholder=\"e.g., N[^P][ST]\">\n <div class=\"form-text\">Regular expression for complex site rules</div>\n </div>\n\n </div>\n\n <!-- Preview -->\n @if (modificationForm.valid && (modificationForm.get('NT')?.value || modificationForm.get('TA')?.value)) {\n <div class=\"mt-3 p-2 bg-body-secondary border rounded\">\n <small class=\"text-muted fw-semibold\">Preview:</small>\n <div class=\"font-monospace small\">{{ value || 'Building...' }}</div>\n </div>\n }\n\n <!-- Validation Summary -->\n @if (modificationForm.invalid && modificationForm.touched) {\n <div class=\"alert alert-warning mt-3\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n <strong>Required fields:</strong> Name of Term (NT) and Target Amino Acid (TA) must be filled.\n </small>\n </div>\n }\n</form>\n", styles: [""], 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.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: "directive", type: NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "ngmodule", type: NgbModule }] });
2975
2991
  }
2976
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfModificationInput, decorators: [{
2992
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfModificationInput, decorators: [{
2977
2993
  type: Component,
2978
2994
  args: [{ selector: 'ccv-sdrf-modification-input', imports: [CommonModule, ReactiveFormsModule, NgbTypeahead, NgbModule], template: "<form [formGroup]=\"modificationForm\" class=\"modification-input-form\">\n\n <!-- Search Type Selection for Typeahead -->\n @if (templateId || columnId) {\n <div class=\"mb-3\">\n <small class=\"text-muted d-block mb-1\">Search Mode for Name of Term:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'icontains' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('icontains')\">\n <i class=\"bi bi-search me-1\"></i>Contains\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'istartswith' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('istartswith')\">\n <i class=\"bi bi-arrow-right me-1\"></i>Starts with\n </button>\n </div>\n </div>\n }\n\n <div class=\"row g-3\">\n\n <!-- Name of Term (NT) - Required with Typeahead -->\n <div class=\"col-md-6\">\n <label for=\"NT\" class=\"form-label\">\n Name of Term <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(NT)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"NT\"\n formControlName=\"NT\"\n placeholder=\"e.g., Oxidation, Acetyl, Phospho\"\n [ngbTypeahead]=\"searchModificationTerms\"\n [resultFormatter]=\"formatOntologySuggestion\"\n [inputFormatter]=\"formatOntologySuggestion\"\n (selectItem)=\"onOntologySuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [class.is-invalid]=\"modificationForm.get('NT')?.invalid && modificationForm.get('NT')?.touched\">\n <div class=\"form-text\">\n @if (templateId || columnId) {\n Typeahead search: {{ searchType() === 'icontains' ? 'Contains' : 'Starts with' }} mode\n } @else {\n Common modifications: Oxidation, Carbamidomethyl, Acetyl\n }\n </div>\n <div class=\"invalid-feedback\">\n Name of Term is required\n </div>\n </div>\n\n <!-- Unimod Specification Selection -->\n @if (showSpecifications() && availableSpecifications().length > 0) {\n <div class=\"col-md-12\">\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Unimod Specifications\n <small class=\"text-muted\">(Select to auto-fill fields)</small>\n </label>\n \n @if (hiddenSpecificationCount() > 0) {\n <div class=\"form-check form-switch\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"showHiddenSpecs\"\n [checked]=\"showHiddenSpecifications()\"\n (change)=\"toggleHiddenSpecifications()\">\n <label class=\"form-check-label small text-muted\" for=\"showHiddenSpecs\">\n Show {{ hiddenSpecificationCount() }} hidden spec{{ hiddenSpecificationCount() === 1 ? '' : 's' }}\n </label>\n </div>\n }\n </div>\n \n <div class=\"border rounded p-2 bg-body-secondary\">\n <div class=\"mb-2\">\n <small class=\"text-info fw-semibold\">\n <i class=\"bi bi-info-circle me-1\"></i>\n {{ showHiddenSpecifications() ? 'All' : 'Visible' }} specifications for {{ selectedUnimodData()?.name }}\n @if (!showHiddenSpecifications() && hiddenSpecificationCount() > 0) {\n <span class=\"text-muted\">({{ hiddenSpecificationCount() }} hidden)</span>\n }\n </small>\n </div>\n <div class=\"row g-2\">\n @for (spec of availableSpecifications(); track spec.specNumber) {\n <div class=\"col-md-6\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"radio\"\n [id]=\"'spec_' + spec.specNumber\"\n [value]=\"spec.specNumber\"\n [checked]=\"selectedSpecification() === spec.specNumber\"\n (change)=\"onSpecificationSelected(spec.specNumber)\">\n <label\n class=\"form-check-label small\"\n [for]=\"'spec_' + spec.specNumber\">\n <div class=\"fw-semibold\">\n Spec {{ spec.specNumber }}\n @if (spec['hidden'] === '1') {\n <span class=\"badge bg-warning text-dark ms-1\" title=\"Hidden specification\">Hidden</span>\n }\n </div>\n <div class=\"text-muted\">{{ getSpecificationDisplay(spec) }}</div>\n </label>\n </div>\n </div>\n }\n </div>\n @if (selectedSpecification()) {\n <div class=\"mt-2 p-2 bg-body border rounded\">\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>\n Fields auto-filled from specification {{ selectedSpecification() }}\n </small>\n </div>\n }\n </div>\n </div>\n </div>\n }\n\n <!-- Target Amino Acid (TA) - Required -->\n <div class=\"col-md-6\">\n <label for=\"TA\" class=\"form-label\">\n Target Amino Acid <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(TA)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"TA\"\n formControlName=\"TA\"\n placeholder=\"e.g., M, S,T,Y, C\"\n (input)=\"onTargetAminoAcidChange($event)\"\n [class.is-invalid]=\"modificationForm.get('TA')?.invalid && modificationForm.get('TA')?.touched\">\n <div class=\"form-text\">Single letter amino acid codes, comma-separated</div>\n <div class=\"invalid-feedback\">\n Target amino acid is required\n </div>\n </div>\n\n <!-- Accession (AC) -->\n <div class=\"col-md-6\">\n <label for=\"AC\" class=\"form-label\">\n Accession <small class=\"text-muted\">(AC)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"AC\"\n formControlName=\"AC\"\n placeholder=\"e.g., UNIMOD:35, PSI-MOD:00719\">\n <div class=\"form-text\">UNIMOD or PSI-MOD accession</div>\n </div>\n\n <!-- Modification Type (MT) -->\n <div class=\"col-md-6\">\n <label for=\"MT\" class=\"form-label\">\n Modification Type <small class=\"text-muted\">(MT)</small>\n </label>\n <select class=\"form-select\" id=\"MT\" formControlName=\"MT\">\n <option value=\"\">Select type...</option>\n @for (type of modificationTypes; track type) {\n <option [value]=\"type\">{{ type }}</option>\n }\n </select>\n <div class=\"form-text\">Fixed, Variable, or Annotated</div>\n </div>\n\n <!-- Position (PP) -->\n <div class=\"col-md-6\">\n <label for=\"PP\" class=\"form-label\">\n Position <small class=\"text-muted\">(PP)</small>\n </label>\n <select class=\"form-select\" id=\"PP\" formControlName=\"PP\">\n <option value=\"\">Select position...</option>\n @for (position of positions; track position) {\n <option [value]=\"position\">{{ position }}</option>\n }\n </select>\n <div class=\"form-text\">Where the modification occurs</div>\n </div>\n\n <!-- Chemical Formula (CF) -->\n <div class=\"col-md-6\">\n <label for=\"CF\" class=\"form-label\">\n Chemical Formula <small class=\"text-muted\">(CF)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"CF\"\n formControlName=\"CF\"\n placeholder=\"e.g., H(2)C(2)O\">\n <div class=\"form-text\">Follow UNIMOD format: H(2)C(2)O</div>\n </div>\n\n <!-- Monoisotopic Mass (MM) -->\n <div class=\"col-md-6\">\n <label for=\"MM\" class=\"form-label\">\n Monoisotopic Mass <small class=\"text-muted\">(MM)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"MM\"\n formControlName=\"MM\"\n placeholder=\"e.g., 42.010565\"\n [class.is-invalid]=\"modificationForm.get('MM')?.invalid && modificationForm.get('MM')?.touched\">\n <div class=\"form-text\">Mass shift in Da (5+ decimal places)</div>\n <div class=\"invalid-feedback\">\n Please enter a valid mass (numbers and decimal point only)\n </div>\n </div>\n\n <!-- Target Site (TS) -->\n <div class=\"col-md-6\">\n <label for=\"TS\" class=\"form-label\">\n Target Site <small class=\"text-muted\">(TS)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"TS\"\n formControlName=\"TS\"\n placeholder=\"e.g., N[^P][ST]\">\n <div class=\"form-text\">Regular expression for complex site rules</div>\n </div>\n\n </div>\n\n <!-- Preview -->\n @if (modificationForm.valid && (modificationForm.get('NT')?.value || modificationForm.get('TA')?.value)) {\n <div class=\"mt-3 p-2 bg-body-secondary border rounded\">\n <small class=\"text-muted fw-semibold\">Preview:</small>\n <div class=\"font-monospace small\">{{ value || 'Building...' }}</div>\n </div>\n }\n\n <!-- Validation Summary -->\n @if (modificationForm.invalid && modificationForm.touched) {\n <div class=\"alert alert-warning mt-3\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n <strong>Required fields:</strong> Name of Term (NT) and Target Amino Acid (TA) must be filled.\n </small>\n </div>\n }\n</form>\n" }]
2979
2995
  }], propDecorators: { value: [{
@@ -3066,10 +3082,10 @@ class SdrfSpikedCompoundInput {
3066
3082
  get showPeptideFields() {
3067
3083
  return this.spikedForm.get('CT')?.value === 'peptide';
3068
3084
  }
3069
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfSpikedCompoundInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3070
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SdrfSpikedCompoundInput, isStandalone: true, selector: "ccv-sdrf-spiked-compound-input", inputs: { value: "value" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<form [formGroup]=\"spikedForm\" class=\"spiked-compound-input-form\">\n <div class=\"row g-3\">\n \n <!-- Compound Type (CT) - Required -->\n <div class=\"col-md-6\">\n <label for=\"CT\" class=\"form-label\">\n Compound Type <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(CT)</small>\n </label>\n <select class=\"form-select\" id=\"CT\" formControlName=\"CT\"\n [class.is-invalid]=\"spikedForm.get('CT')?.invalid && spikedForm.get('CT')?.touched\">\n <option value=\"\">Select compound type...</option>\n @for (type of compoundTypes; track type) {\n <option [value]=\"type\">{{ type }}</option>\n }\n </select>\n <div class=\"form-text\">Type of spiked compound</div>\n <div class=\"invalid-feedback\">\n Compound type is required\n </div>\n </div>\n\n <!-- Quantity (QY) - Required -->\n <div class=\"col-md-6\">\n <label for=\"QY\" class=\"form-label\">\n Quantity <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(QY)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"QY\"\n formControlName=\"QY\"\n placeholder=\"e.g., 10 fmol, 50 ng\"\n [class.is-invalid]=\"spikedForm.get('QY')?.invalid && spikedForm.get('QY')?.touched\">\n <div class=\"form-text\">Quantity with units (molar or mass)</div>\n <div class=\"invalid-feedback\">\n Quantity is required\n </div>\n </div>\n\n <!-- Species (SP) -->\n <div class=\"col-md-6\">\n <label for=\"SP\" class=\"form-label\">\n Species <small class=\"text-muted\">(SP)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"SP\"\n formControlName=\"SP\"\n placeholder=\"e.g., homo sapiens\">\n <div class=\"form-text\">Species of origin</div>\n </div>\n\n <!-- Compound Name (CN) -->\n <div class=\"col-md-6\">\n <label for=\"CN\" class=\"form-label\">\n Compound Name <small class=\"text-muted\">(CN)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"CN\"\n formControlName=\"CN\"\n placeholder=\"e.g., Myoglobin\">\n <div class=\"form-text\">Name of the compound</div>\n </div>\n\n <!-- Protein-specific fields -->\n @if (showProteinFields) {\n <div class=\"col-md-12\">\n <label for=\"AC\" class=\"form-label\">\n Uniprot Accession <small class=\"text-muted\">(AC)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"AC\"\n formControlName=\"AC\"\n placeholder=\"e.g., P68871\">\n <div class=\"form-text\">Uniprot accession for protein compounds</div>\n </div>\n }\n\n <!-- Peptide-specific fields -->\n @if (showPeptideFields) {\n <div class=\"col-md-12\">\n <label for=\"PS\" class=\"form-label\">\n Peptide Sequence <small class=\"text-muted\">(PS)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"PS\"\n formControlName=\"PS\"\n placeholder=\"e.g., PEPTIDESEQ\">\n <div class=\"form-text\">Peptide sequence for peptide compounds</div>\n </div>\n }\n\n <!-- Compound Vendor (CV) -->\n <div class=\"col-md-6\">\n <label for=\"CV\" class=\"form-label\">\n Compound Vendor <small class=\"text-muted\">(CV)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"CV\"\n formControlName=\"CV\"\n placeholder=\"e.g., Sigma-Aldrich\">\n <div class=\"form-text\">Vendor or supplier name</div>\n </div>\n\n <!-- Compound Formula (CF) -->\n <div class=\"col-md-6\">\n <label for=\"CF\" class=\"form-label\">\n Compound Formula <small class=\"text-muted\">(CF)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"CF\"\n formControlName=\"CF\"\n placeholder=\"e.g., C8H14N4O2\">\n <div class=\"form-text\">Chemical formula</div>\n </div>\n\n <!-- Compound Specification URI (CS) -->\n <div class=\"col-md-12\">\n <label for=\"CS\" class=\"form-label\">\n Compound Specification URI <small class=\"text-muted\">(CS)</small>\n </label>\n <input \n type=\"url\" \n class=\"form-control\" \n id=\"CS\"\n formControlName=\"CS\"\n placeholder=\"https://example.com/compound-spec\">\n <div class=\"form-text\">URI to compound specification or documentation</div>\n </div>\n\n </div>\n\n <!-- Preview -->\n @if (spikedForm.valid && (spikedForm.get('CT')?.value && spikedForm.get('QY')?.value)) {\n <div class=\"mt-3 p-2 bg-body-secondary border rounded\">\n <small class=\"text-muted fw-semibold\">Preview:</small>\n <div class=\"font-monospace small\">{{ value || 'Building...' }}</div>\n </div>\n }\n\n <!-- Validation Summary -->\n @if (spikedForm.invalid && spikedForm.touched) {\n <div class=\"alert alert-warning mt-3\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n <strong>Required fields:</strong> Compound Type (CT) and Quantity (QY) must be filled.\n </small>\n </div>\n }\n</form>\n", styles: [""], 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.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"] }] });
3085
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfSpikedCompoundInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3086
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SdrfSpikedCompoundInput, isStandalone: true, selector: "ccv-sdrf-spiked-compound-input", inputs: { value: "value" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<form [formGroup]=\"spikedForm\" class=\"spiked-compound-input-form\">\n <div class=\"row g-3\">\n \n <!-- Compound Type (CT) - Required -->\n <div class=\"col-md-6\">\n <label for=\"CT\" class=\"form-label\">\n Compound Type <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(CT)</small>\n </label>\n <select class=\"form-select\" id=\"CT\" formControlName=\"CT\"\n [class.is-invalid]=\"spikedForm.get('CT')?.invalid && spikedForm.get('CT')?.touched\">\n <option value=\"\">Select compound type...</option>\n @for (type of compoundTypes; track type) {\n <option [value]=\"type\">{{ type }}</option>\n }\n </select>\n <div class=\"form-text\">Type of spiked compound</div>\n <div class=\"invalid-feedback\">\n Compound type is required\n </div>\n </div>\n\n <!-- Quantity (QY) - Required -->\n <div class=\"col-md-6\">\n <label for=\"QY\" class=\"form-label\">\n Quantity <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(QY)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"QY\"\n formControlName=\"QY\"\n placeholder=\"e.g., 10 fmol, 50 ng\"\n [class.is-invalid]=\"spikedForm.get('QY')?.invalid && spikedForm.get('QY')?.touched\">\n <div class=\"form-text\">Quantity with units (molar or mass)</div>\n <div class=\"invalid-feedback\">\n Quantity is required\n </div>\n </div>\n\n <!-- Species (SP) -->\n <div class=\"col-md-6\">\n <label for=\"SP\" class=\"form-label\">\n Species <small class=\"text-muted\">(SP)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"SP\"\n formControlName=\"SP\"\n placeholder=\"e.g., homo sapiens\">\n <div class=\"form-text\">Species of origin</div>\n </div>\n\n <!-- Compound Name (CN) -->\n <div class=\"col-md-6\">\n <label for=\"CN\" class=\"form-label\">\n Compound Name <small class=\"text-muted\">(CN)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"CN\"\n formControlName=\"CN\"\n placeholder=\"e.g., Myoglobin\">\n <div class=\"form-text\">Name of the compound</div>\n </div>\n\n <!-- Protein-specific fields -->\n @if (showProteinFields) {\n <div class=\"col-md-12\">\n <label for=\"AC\" class=\"form-label\">\n Uniprot Accession <small class=\"text-muted\">(AC)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"AC\"\n formControlName=\"AC\"\n placeholder=\"e.g., P68871\">\n <div class=\"form-text\">Uniprot accession for protein compounds</div>\n </div>\n }\n\n <!-- Peptide-specific fields -->\n @if (showPeptideFields) {\n <div class=\"col-md-12\">\n <label for=\"PS\" class=\"form-label\">\n Peptide Sequence <small class=\"text-muted\">(PS)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"PS\"\n formControlName=\"PS\"\n placeholder=\"e.g., PEPTIDESEQ\">\n <div class=\"form-text\">Peptide sequence for peptide compounds</div>\n </div>\n }\n\n <!-- Compound Vendor (CV) -->\n <div class=\"col-md-6\">\n <label for=\"CV\" class=\"form-label\">\n Compound Vendor <small class=\"text-muted\">(CV)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"CV\"\n formControlName=\"CV\"\n placeholder=\"e.g., Sigma-Aldrich\">\n <div class=\"form-text\">Vendor or supplier name</div>\n </div>\n\n <!-- Compound Formula (CF) -->\n <div class=\"col-md-6\">\n <label for=\"CF\" class=\"form-label\">\n Compound Formula <small class=\"text-muted\">(CF)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"CF\"\n formControlName=\"CF\"\n placeholder=\"e.g., C8H14N4O2\">\n <div class=\"form-text\">Chemical formula</div>\n </div>\n\n <!-- Compound Specification URI (CS) -->\n <div class=\"col-md-12\">\n <label for=\"CS\" class=\"form-label\">\n Compound Specification URI <small class=\"text-muted\">(CS)</small>\n </label>\n <input \n type=\"url\" \n class=\"form-control\" \n id=\"CS\"\n formControlName=\"CS\"\n placeholder=\"https://example.com/compound-spec\">\n <div class=\"form-text\">URI to compound specification or documentation</div>\n </div>\n\n </div>\n\n <!-- Preview -->\n @if (spikedForm.valid && (spikedForm.get('CT')?.value && spikedForm.get('QY')?.value)) {\n <div class=\"mt-3 p-2 bg-body-secondary border rounded\">\n <small class=\"text-muted fw-semibold\">Preview:</small>\n <div class=\"font-monospace small\">{{ value || 'Building...' }}</div>\n </div>\n }\n\n <!-- Validation Summary -->\n @if (spikedForm.invalid && spikedForm.touched) {\n <div class=\"alert alert-warning mt-3\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n <strong>Required fields:</strong> Compound Type (CT) and Quantity (QY) must be filled.\n </small>\n </div>\n }\n</form>\n", styles: [""], 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.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"] }] });
3071
3087
  }
3072
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfSpikedCompoundInput, decorators: [{
3088
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfSpikedCompoundInput, decorators: [{
3073
3089
  type: Component,
3074
3090
  args: [{ selector: 'ccv-sdrf-spiked-compound-input', imports: [CommonModule, ReactiveFormsModule], template: "<form [formGroup]=\"spikedForm\" class=\"spiked-compound-input-form\">\n <div class=\"row g-3\">\n \n <!-- Compound Type (CT) - Required -->\n <div class=\"col-md-6\">\n <label for=\"CT\" class=\"form-label\">\n Compound Type <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(CT)</small>\n </label>\n <select class=\"form-select\" id=\"CT\" formControlName=\"CT\"\n [class.is-invalid]=\"spikedForm.get('CT')?.invalid && spikedForm.get('CT')?.touched\">\n <option value=\"\">Select compound type...</option>\n @for (type of compoundTypes; track type) {\n <option [value]=\"type\">{{ type }}</option>\n }\n </select>\n <div class=\"form-text\">Type of spiked compound</div>\n <div class=\"invalid-feedback\">\n Compound type is required\n </div>\n </div>\n\n <!-- Quantity (QY) - Required -->\n <div class=\"col-md-6\">\n <label for=\"QY\" class=\"form-label\">\n Quantity <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(QY)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"QY\"\n formControlName=\"QY\"\n placeholder=\"e.g., 10 fmol, 50 ng\"\n [class.is-invalid]=\"spikedForm.get('QY')?.invalid && spikedForm.get('QY')?.touched\">\n <div class=\"form-text\">Quantity with units (molar or mass)</div>\n <div class=\"invalid-feedback\">\n Quantity is required\n </div>\n </div>\n\n <!-- Species (SP) -->\n <div class=\"col-md-6\">\n <label for=\"SP\" class=\"form-label\">\n Species <small class=\"text-muted\">(SP)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"SP\"\n formControlName=\"SP\"\n placeholder=\"e.g., homo sapiens\">\n <div class=\"form-text\">Species of origin</div>\n </div>\n\n <!-- Compound Name (CN) -->\n <div class=\"col-md-6\">\n <label for=\"CN\" class=\"form-label\">\n Compound Name <small class=\"text-muted\">(CN)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"CN\"\n formControlName=\"CN\"\n placeholder=\"e.g., Myoglobin\">\n <div class=\"form-text\">Name of the compound</div>\n </div>\n\n <!-- Protein-specific fields -->\n @if (showProteinFields) {\n <div class=\"col-md-12\">\n <label for=\"AC\" class=\"form-label\">\n Uniprot Accession <small class=\"text-muted\">(AC)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"AC\"\n formControlName=\"AC\"\n placeholder=\"e.g., P68871\">\n <div class=\"form-text\">Uniprot accession for protein compounds</div>\n </div>\n }\n\n <!-- Peptide-specific fields -->\n @if (showPeptideFields) {\n <div class=\"col-md-12\">\n <label for=\"PS\" class=\"form-label\">\n Peptide Sequence <small class=\"text-muted\">(PS)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"PS\"\n formControlName=\"PS\"\n placeholder=\"e.g., PEPTIDESEQ\">\n <div class=\"form-text\">Peptide sequence for peptide compounds</div>\n </div>\n }\n\n <!-- Compound Vendor (CV) -->\n <div class=\"col-md-6\">\n <label for=\"CV\" class=\"form-label\">\n Compound Vendor <small class=\"text-muted\">(CV)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"CV\"\n formControlName=\"CV\"\n placeholder=\"e.g., Sigma-Aldrich\">\n <div class=\"form-text\">Vendor or supplier name</div>\n </div>\n\n <!-- Compound Formula (CF) -->\n <div class=\"col-md-6\">\n <label for=\"CF\" class=\"form-label\">\n Compound Formula <small class=\"text-muted\">(CF)</small>\n </label>\n <input \n type=\"text\" \n class=\"form-control\" \n id=\"CF\"\n formControlName=\"CF\"\n placeholder=\"e.g., C8H14N4O2\">\n <div class=\"form-text\">Chemical formula</div>\n </div>\n\n <!-- Compound Specification URI (CS) -->\n <div class=\"col-md-12\">\n <label for=\"CS\" class=\"form-label\">\n Compound Specification URI <small class=\"text-muted\">(CS)</small>\n </label>\n <input \n type=\"url\" \n class=\"form-control\" \n id=\"CS\"\n formControlName=\"CS\"\n placeholder=\"https://example.com/compound-spec\">\n <div class=\"form-text\">URI to compound specification or documentation</div>\n </div>\n\n </div>\n\n <!-- Preview -->\n @if (spikedForm.valid && (spikedForm.get('CT')?.value && spikedForm.get('QY')?.value)) {\n <div class=\"mt-3 p-2 bg-body-secondary border rounded\">\n <small class=\"text-muted fw-semibold\">Preview:</small>\n <div class=\"font-monospace small\">{{ value || 'Building...' }}</div>\n </div>\n }\n\n <!-- Validation Summary -->\n @if (spikedForm.invalid && spikedForm.touched) {\n <div class=\"alert alert-warning mt-3\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n <strong>Required fields:</strong> Compound Type (CT) and Quantity (QY) must be filled.\n </small>\n </div>\n }\n</form>\n" }]
3075
3091
  }], propDecorators: { value: [{
@@ -3202,10 +3218,10 @@ class SdrfCleavageInput {
3202
3218
  onSearchTypeChange(type) {
3203
3219
  this.searchType.set(type);
3204
3220
  }
3205
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfCleavageInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3206
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SdrfCleavageInput, isStandalone: true, selector: "ccv-sdrf-cleavage-input", inputs: { value: "value", templateId: "templateId", columnId: "columnId" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<form [formGroup]=\"cleavageForm\" class=\"cleavage-input-form\">\n\n <!-- Search Type Selection for Typeahead -->\n @if (templateId || columnId) {\n <div class=\"mb-3\">\n <small class=\"text-muted d-block mb-1\">Search Mode for Name of Term:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'icontains' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('icontains')\">\n <i class=\"bi bi-search me-1\"></i>Contains\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'istartswith' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('istartswith')\">\n <i class=\"bi bi-arrow-right me-1\"></i>Starts with\n </button>\n </div>\n </div>\n }\n\n <div class=\"row g-3\">\n\n <!-- Name of Term (NT) - Required -->\n <div class=\"col-md-6\">\n <label for=\"NT\" class=\"form-label\">\n Name of Term <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(NT)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"NT\"\n formControlName=\"NT\"\n placeholder=\"e.g., Trypsin, Chymotrypsin\"\n [ngbTypeahead]=\"searchCleavageTerms\"\n [resultFormatter]=\"formatOntologySuggestion\"\n [inputFormatter]=\"formatOntologySuggestion\"\n (selectItem)=\"onOntologySuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [class.is-invalid]=\"cleavageForm.get('NT')?.invalid && cleavageForm.get('NT')?.touched\">\n <div class=\"form-text\">\n @if (templateId || columnId) {\n Typeahead search: {{ searchType() === 'icontains' ? 'Contains' : 'Starts with' }} mode\n } @else {\n Cleavage agent name (uses ontology typeahead when available)\n }\n </div>\n <div class=\"invalid-feedback\">\n Name of Term is required\n </div>\n </div>\n\n <!-- Accession (AC) -->\n <div class=\"col-md-6\">\n <label for=\"AC\" class=\"form-label\">\n Accession <small class=\"text-muted\">(AC)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"AC\"\n formControlName=\"AC\"\n placeholder=\"e.g., PSI-MS:1001251\">\n <div class=\"form-text\">PSI-MS or other ontology accession</div>\n </div>\n\n <!-- Cleavage Site (CS) -->\n <div class=\"col-md-12\">\n <label for=\"CS\" class=\"form-label\">\n Cleavage Site <small class=\"text-muted\">(CS)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"CS\"\n formControlName=\"CS\"\n placeholder=\"e.g., (?<=[KR])(?![P])\">\n <div class=\"form-text\">Regular expression pattern for cleavage site specificity</div>\n </div>\n\n </div>\n\n <!-- Preview -->\n @if (cleavageForm.valid && cleavageForm.get('NT')?.value) {\n <div class=\"mt-3 p-2 bg-body-secondary border rounded\">\n <small class=\"text-muted fw-semibold\">Preview:</small>\n <div class=\"font-monospace small\">{{ value || 'Building...' }}</div>\n </div>\n }\n\n <!-- Validation Summary -->\n @if (cleavageForm.invalid && cleavageForm.touched) {\n <div class=\"alert alert-warning mt-3\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n <strong>Required field:</strong> Name of Term (NT) must be filled.\n </small>\n </div>\n }\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.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: "directive", type: NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "ngmodule", type: NgbModule }] });
3221
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfCleavageInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3222
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SdrfCleavageInput, isStandalone: true, selector: "ccv-sdrf-cleavage-input", inputs: { value: "value", templateId: "templateId", columnId: "columnId" }, outputs: { valueChange: "valueChange" }, ngImport: i0, template: "<form [formGroup]=\"cleavageForm\" class=\"cleavage-input-form\">\n\n <!-- Search Type Selection for Typeahead -->\n @if (templateId || columnId) {\n <div class=\"mb-3\">\n <small class=\"text-muted d-block mb-1\">Search Mode for Name of Term:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'icontains' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('icontains')\">\n <i class=\"bi bi-search me-1\"></i>Contains\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'istartswith' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('istartswith')\">\n <i class=\"bi bi-arrow-right me-1\"></i>Starts with\n </button>\n </div>\n </div>\n }\n\n <div class=\"row g-3\">\n\n <!-- Name of Term (NT) - Required -->\n <div class=\"col-md-6\">\n <label for=\"NT\" class=\"form-label\">\n Name of Term <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(NT)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"NT\"\n formControlName=\"NT\"\n placeholder=\"e.g., Trypsin, Chymotrypsin\"\n [ngbTypeahead]=\"searchCleavageTerms\"\n [resultFormatter]=\"formatOntologySuggestion\"\n [inputFormatter]=\"formatOntologySuggestion\"\n (selectItem)=\"onOntologySuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [class.is-invalid]=\"cleavageForm.get('NT')?.invalid && cleavageForm.get('NT')?.touched\">\n <div class=\"form-text\">\n @if (templateId || columnId) {\n Typeahead search: {{ searchType() === 'icontains' ? 'Contains' : 'Starts with' }} mode\n } @else {\n Cleavage agent name (uses ontology typeahead when available)\n }\n </div>\n <div class=\"invalid-feedback\">\n Name of Term is required\n </div>\n </div>\n\n <!-- Accession (AC) -->\n <div class=\"col-md-6\">\n <label for=\"AC\" class=\"form-label\">\n Accession <small class=\"text-muted\">(AC)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"AC\"\n formControlName=\"AC\"\n placeholder=\"e.g., PSI-MS:1001251\">\n <div class=\"form-text\">PSI-MS or other ontology accession</div>\n </div>\n\n <!-- Cleavage Site (CS) -->\n <div class=\"col-md-12\">\n <label for=\"CS\" class=\"form-label\">\n Cleavage Site <small class=\"text-muted\">(CS)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"CS\"\n formControlName=\"CS\"\n placeholder=\"e.g., (?<=[KR])(?![P])\">\n <div class=\"form-text\">Regular expression pattern for cleavage site specificity</div>\n </div>\n\n </div>\n\n <!-- Preview -->\n @if (cleavageForm.valid && cleavageForm.get('NT')?.value) {\n <div class=\"mt-3 p-2 bg-body-secondary border rounded\">\n <small class=\"text-muted fw-semibold\">Preview:</small>\n <div class=\"font-monospace small\">{{ value || 'Building...' }}</div>\n </div>\n }\n\n <!-- Validation Summary -->\n @if (cleavageForm.invalid && cleavageForm.touched) {\n <div class=\"alert alert-warning mt-3\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n <strong>Required field:</strong> Name of Term (NT) must be filled.\n </small>\n </div>\n }\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.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: "directive", type: NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "ngmodule", type: NgbModule }] });
3207
3223
  }
3208
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfCleavageInput, decorators: [{
3224
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SdrfCleavageInput, decorators: [{
3209
3225
  type: Component,
3210
3226
  args: [{ selector: 'ccv-sdrf-cleavage-input', imports: [CommonModule, ReactiveFormsModule, NgbTypeahead, NgbModule], template: "<form [formGroup]=\"cleavageForm\" class=\"cleavage-input-form\">\n\n <!-- Search Type Selection for Typeahead -->\n @if (templateId || columnId) {\n <div class=\"mb-3\">\n <small class=\"text-muted d-block mb-1\">Search Mode for Name of Term:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'icontains' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('icontains')\">\n <i class=\"bi bi-search me-1\"></i>Contains\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'istartswith' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('istartswith')\">\n <i class=\"bi bi-arrow-right me-1\"></i>Starts with\n </button>\n </div>\n </div>\n }\n\n <div class=\"row g-3\">\n\n <!-- Name of Term (NT) - Required -->\n <div class=\"col-md-6\">\n <label for=\"NT\" class=\"form-label\">\n Name of Term <span class=\"text-danger\">*</span>\n <small class=\"text-muted\">(NT)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"NT\"\n formControlName=\"NT\"\n placeholder=\"e.g., Trypsin, Chymotrypsin\"\n [ngbTypeahead]=\"searchCleavageTerms\"\n [resultFormatter]=\"formatOntologySuggestion\"\n [inputFormatter]=\"formatOntologySuggestion\"\n (selectItem)=\"onOntologySuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [class.is-invalid]=\"cleavageForm.get('NT')?.invalid && cleavageForm.get('NT')?.touched\">\n <div class=\"form-text\">\n @if (templateId || columnId) {\n Typeahead search: {{ searchType() === 'icontains' ? 'Contains' : 'Starts with' }} mode\n } @else {\n Cleavage agent name (uses ontology typeahead when available)\n }\n </div>\n <div class=\"invalid-feedback\">\n Name of Term is required\n </div>\n </div>\n\n <!-- Accession (AC) -->\n <div class=\"col-md-6\">\n <label for=\"AC\" class=\"form-label\">\n Accession <small class=\"text-muted\">(AC)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"AC\"\n formControlName=\"AC\"\n placeholder=\"e.g., PSI-MS:1001251\">\n <div class=\"form-text\">PSI-MS or other ontology accession</div>\n </div>\n\n <!-- Cleavage Site (CS) -->\n <div class=\"col-md-12\">\n <label for=\"CS\" class=\"form-label\">\n Cleavage Site <small class=\"text-muted\">(CS)</small>\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"CS\"\n formControlName=\"CS\"\n placeholder=\"e.g., (?<=[KR])(?![P])\">\n <div class=\"form-text\">Regular expression pattern for cleavage site specificity</div>\n </div>\n\n </div>\n\n <!-- Preview -->\n @if (cleavageForm.valid && cleavageForm.get('NT')?.value) {\n <div class=\"mt-3 p-2 bg-body-secondary border rounded\">\n <small class=\"text-muted fw-semibold\">Preview:</small>\n <div class=\"font-monospace small\">{{ value || 'Building...' }}</div>\n </div>\n }\n\n <!-- Validation Summary -->\n @if (cleavageForm.invalid && cleavageForm.touched) {\n <div class=\"alert alert-warning mt-3\">\n <small>\n <i class=\"bi bi-exclamation-triangle me-1\"></i>\n <strong>Required field:</strong> Name of Term (NT) must be filled.\n </small>\n </div>\n }\n</form>\n" }]
3211
3227
  }], propDecorators: { value: [{
@@ -3686,10 +3702,10 @@ class MetadataValueEditModal {
3686
3702
  }
3687
3703
  }
3688
3704
  }
3689
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataValueEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i3.AuthService }, { token: FavouriteMetadataOptionService }, { token: MetadataColumnService }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }, { token: OntologySearchService }], target: i0.ɵɵFactoryTarget.Component });
3690
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataValueEditModal, isStandalone: true, selector: "ccv-metadata-value-edit-modal", inputs: { config: "config" }, outputs: { valueSaved: "valueSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-pencil-square 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 <div class=\"row\">\n <!-- Main Content -->\n <div [class]=\"hasMultiSampleEdit ? 'col-md-8' : 'col-12'\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n\n <!-- Column Information -->\n <div class=\"mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Column Name:</small>\n <div class=\"fw-semibold\">{{ config.columnName }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Column Type:</small>\n <div><span class=\"badge bg-light text-dark\">{{ config.columnType }}</span></div>\n </div>\n </div>\n @if (hasOntologyType) {\n <div class=\"mt-2\">\n <small class=\"text-muted\">Ontology Type:</small>\n <div><span class=\"badge bg-info\">{{ ontologyTypeLabel }}</span></div>\n </div>\n }\n </div>\n\n <hr class=\"mb-4\">\n\n <!-- Favorite Options Section -->\n @if (hasFavorites && !isLoadingFavorites()) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-heart-fill me-2 text-danger\"></i>Recommended Values\n </h6>\n\n <!-- Favorites Navigation Tabs -->\n <ul ngbNav #favoritesNav=\"ngbNav\" [(activeId)]=\"activeFavoritesTab\" class=\"nav-tabs\">\n @if (userFavorites().length > 0) {\n <li [ngbNavItem]=\"'user'\">\n <button ngbNavLink>\n <i class=\"bi bi-person-fill me-1\"></i>Your Favorites\n <span class=\"badge bg-primary ms-1\">{{ userFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, userFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of userFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Your personal favorite values for this column\n </div>\n </div>\n </ng-template>\n </li>\n }\n\n @if (labGroupFavorites().length > 0) {\n <li [ngbNavItem]=\"'lab_group'\">\n <button ngbNavLink>\n <i class=\"bi bi-people-fill me-1\"></i>Lab Group\n <span class=\"badge bg-info ms-1\">{{ labGroupFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, labGroupFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of labGroupFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Favorite values shared by your lab group\n </div>\n </div>\n </ng-template>\n </li>\n }\n\n @if (globalFavorites().length > 0) {\n <li [ngbNavItem]=\"'global'\">\n <button ngbNavLink>\n <i class=\"bi bi-globe me-1\"></i>Global\n <span class=\"badge bg-success ms-1\">{{ globalFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, globalFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of globalFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Globally recommended values for this column\n </div>\n </div>\n </ng-template>\n </li>\n }\n </ul>\n\n <div [ngbNavOutlet]=\"favoritesNav\" class=\"mt-3\"></div>\n\n <hr class=\"my-3\">\n </div>\n }\n\n @if (isLoadingFavorites()) {\n <div class=\"text-center mb-4\">\n <div class=\"spinner-border spinner-border-sm text-primary me-2\"></div>\n <small class=\"text-muted\">Loading favorite options...</small>\n </div>\n <hr class=\"my-3\">\n }\n\n <!-- Value Input Section -->\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label for=\"metadataValue\" class=\"form-label mb-0\">\n Metadata Value\n @if (hasOntologyType) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Autocomplete Available\n </span>\n }\n @if (specialSyntaxType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n </label>\n\n @if (specialSyntaxType()) {\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"specialInputToggle\"\n [checked]=\"showSpecialInput()\"\n (change)=\"toggleSpecialInput()\">\n <label class=\"form-check-label small text-muted\" for=\"specialInputToggle\">\n Enhanced Editor\n </label>\n </div>\n }\n </div>\n\n <!-- Quick-select buttons for standard values -->\n @if (config.allowNotApplicable || config.allowNotAvailable) {\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Quick Select:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (config.allowNotApplicable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (config.allowNotAvailable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType()) {\n <div class=\"border rounded p-3 mb-3 bg-body-secondary\">\n <div class=\"mb-2\">\n <small class=\"text-muted fw-semibold\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </small>\n </div>\n\n @switch (specialSyntaxType()) {\n @case ('age') {\n <ccv-sdrf-age-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-age-input>\n }\n @case ('modification') {\n <ccv-sdrf-modification-input\n [value]=\"specialInputValue()\"\n [columnId]=\"config.columnId ?? null\"\n [templateId]=\"config.templateId ?? null\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-modification-input>\n }\n @case ('cleavage') {\n <ccv-sdrf-cleavage-input\n [value]=\"specialInputValue()\"\n [columnId]=\"config.columnId ?? null\"\n [templateId]=\"config.templateId ?? null\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-cleavage-input>\n }\n @case ('spiked_compound') {\n <ccv-sdrf-spiked-compound-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-spiked-compound-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the enhanced editor for structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n @if (hasOntologyType && !showSpecialInput()) {\n <!-- Search type selection for ontology -->\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Search Mode:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'icontains' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('icontains')\">\n <i class=\"bi bi-search me-1\"></i>Contains\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'istartswith' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('istartswith')\">\n <i class=\"bi bi-arrow-right me-1\"></i>Starts with\n </button>\n </div>\n </div>\n\n <!-- Typeahead input for ontology suggestions -->\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.border-success]=\"selectedFavorite() !== null\"\n id=\"metadataValue\"\n formControlName=\"value\"\n placeholder=\"Type to search ontology suggestions...\"\n [ngbTypeahead]=\"searchOntology\"\n [resultFormatter]=\"formatSuggestion\"\n [inputFormatter]=\"inputFormatter\"\n (selectItem)=\"onSuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n @if (isLoadingSuggestions()) {\n <span class=\"input-group-text\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </span>\n } @else {\n <span class=\"input-group-text\">\n <i class=\"bi bi-search text-muted\"></i>\n </span>\n }\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearValue()\"\n title=\"Clear value\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Search {{ searchType() === 'icontains' ? 'contains' : 'starts with' }} for {{ ontologyTypeLabel }} ontology\n </div>\n } @else if (!showSpecialInput()) {\n <!-- Regular input when no ontology type or special input is disabled -->\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.border-success]=\"selectedFavorite() !== null\"\n id=\"metadataValue\"\n formControlName=\"value\"\n placeholder=\"Enter metadata value\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearValue()\"\n title=\"Clear value\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n <div class=\"form-text text-muted\">\n @if (specialSyntaxType()) {\n Manual text entry mode. Use the Enhanced Editor toggle for structured {{ specialSyntaxType() }} input.\n } @else {\n Enter the value manually (no ontology suggestions available for this column type)\n }\n </div>\n }\n </div>\n\n <!-- Character count and value info -->\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Characters: {{ editForm.get('value')?.value?.length || 0 }}/500\n </small>\n @if (getSelectedFavoriteInfo()) {\n <small class=\"text-info\">\n <i class=\"bi bi-info-circle me-1\"></i>{{ getSelectedFavoriteInfo() }}\n </small>\n }\n </div>\n </div>\n\n </form>\n </div>\n\n <!-- Sample Selection Panel -->\n @if (hasMultiSampleEdit) {\n <div class=\"col-md-4\">\n <div class=\"border-start ps-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h6 class=\"mb-0 text-muted\">\n <i class=\"bi bi-list-check me-1\"></i>Apply to Samples\n </h6>\n <span class=\"badge bg-primary\">\n {{ selectedSampleCount }}/{{ totalSampleCount }}\n </span>\n </div>\n\n <!-- Select All/Clear All Controls -->\n <div class=\"d-flex gap-1 mb-3\">\n <button \n type=\"button\" \n class=\"btn btn-outline-primary btn-sm flex-fill\"\n (click)=\"selectAllSamples()\"\n [disabled]=\"selectedSampleCount === totalSampleCount\">\n <i class=\"bi bi-check-all me-1\"></i>All\n </button>\n <button \n type=\"button\" \n class=\"btn btn-outline-secondary btn-sm flex-fill\"\n (click)=\"clearSampleSelection()\"\n [disabled]=\"selectedSampleCount === 0\">\n <i class=\"bi bi-x me-1\"></i>Clear\n </button>\n </div>\n\n <!-- Sample List -->\n <div class=\"sample-list\" style=\"max-height: 400px; overflow-y: auto;\">\n @for (sample of sampleData; track sample.index) {\n <div class=\"form-check d-flex align-items-center mb-2 p-2 rounded border-bottom\">\n <input \n class=\"form-check-input me-2\" \n type=\"checkbox\" \n [id]=\"'sample-' + sample.index\"\n [checked]=\"isSampleSelected(sample.index)\"\n (change)=\"toggleSampleSelection(sample.index)\">\n <label \n [for]=\"'sample-' + sample.index\" \n class=\"form-check-label flex-grow-1 cursor-pointer\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div>\n <div class=\"fw-semibold\">Sample {{ sample.index }}</div>\n @if (sample.sourceName) {\n <small class=\"text-muted\">{{ sample.sourceName }}</small>\n }\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted\">Current:</small>\n <div class=\"small fw-mono\" [title]=\"sample.value || ''\">\n {{ (sample.value && sample.value.length > 15) ? (sample.value | slice:0:15) + '...' : (sample.value || '-') }}\n </div>\n </div>\n </div>\n </label>\n </div>\n }\n </div>\n\n @if (selectedSampleCount > 0) {\n <div class=\"alert alert-info mt-3 p-2\">\n <small>\n <i class=\"bi bi-info-circle me-1\"></i>\n The value will be applied to {{ selectedSampleCount }} selected sample{{ selectedSampleCount !== 1 ? 's' : '' }}.\n </small>\n </div>\n }\n </div>\n </div>\n }\n </div>\n</div>\n\n<div class=\"modal-footer\">\n <div class=\"me-auto\">\n @if (editForm.get('value')?.value && !isLoading() && !isFromFavoriteManagement) {\n <div class=\"btn-group dropup\" ngbDropdown>\n <button\n type=\"button\"\n class=\"btn btn-outline-warning btn-sm dropdown-toggle\"\n ngbDropdownToggle>\n <i class=\"bi bi-heart me-1\"></i>Add to Favorites\n </button>\n <div class=\"dropdown-menu\" ngbDropdownMenu>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('user')\">\n <i class=\"bi bi-person me-2\"></i>Personal Favorite\n </button>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('lab_group')\">\n <i class=\"bi bi-people me-2\"></i>Lab Group Favorite\n </button>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('global')\">\n <i class=\"bi bi-globe me-2\"></i>Global Recommendation\n </button>\n </div>\n </div>\n }\n </div>\n\n <div class=\"d-flex gap-2\">\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 Save Value\n </button>\n </div>\n</div>\n", styles: [".sample-list .form-check{border:1px solid transparent;transition:all .2s ease}.sample-list .form-check:hover{background-color:rgba(var(--bs-primary-rgb),.05);border-color:rgba(var(--bs-primary-rgb),.2)}.sample-list .form-check .form-check-input:checked~.form-check-label .fw-semibold{color:var(--bs-primary)}.cursor-pointer{cursor:pointer}.fw-mono{font-family:var(--bs-font-monospace)}\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.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: "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: "directive", type: i2.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i2.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i2.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i2.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i2.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i2.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i2.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "directive", type: i2.NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "ngmodule", type: NgbNavModule }, { kind: "component", type: SdrfAgeInput, selector: "ccv-sdrf-age-input", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: SdrfModificationInput, selector: "ccv-sdrf-modification-input", inputs: ["value", "templateId", "columnId"], outputs: ["valueChange"] }, { kind: "component", type: SdrfCleavageInput, selector: "ccv-sdrf-cleavage-input", inputs: ["value", "templateId", "columnId"], outputs: ["valueChange"] }, { kind: "component", type: SdrfSpikedCompoundInput, selector: "ccv-sdrf-spiked-compound-input", inputs: ["value"], outputs: ["valueChange"] }, { kind: "pipe", type: i5.SlicePipe, name: "slice" }] });
3705
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataValueEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i3.AuthService }, { token: FavouriteMetadataOptionService }, { token: MetadataColumnService }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }, { token: OntologySearchService }], target: i0.ɵɵFactoryTarget.Component });
3706
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: MetadataValueEditModal, isStandalone: true, selector: "ccv-metadata-value-edit-modal", inputs: { config: "config" }, outputs: { valueSaved: "valueSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-pencil-square 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 <div class=\"row\">\n <!-- Main Content -->\n <div [class]=\"hasMultiSampleEdit ? 'col-md-8' : 'col-12'\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"onSubmit()\">\n\n <!-- Column Information -->\n <div class=\"mb-3\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Column Name:</small>\n <div class=\"fw-semibold\">{{ config.columnName }}</div>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">Column Type:</small>\n <div><span class=\"badge bg-light text-dark\">{{ config.columnType }}</span></div>\n </div>\n </div>\n @if (hasOntologyType) {\n <div class=\"mt-2\">\n <small class=\"text-muted\">Ontology Type:</small>\n <div><span class=\"badge bg-info\">{{ ontologyTypeLabel }}</span></div>\n </div>\n }\n </div>\n\n <hr class=\"mb-4\">\n\n <!-- Favorite Options Section -->\n @if (hasFavorites && !isLoadingFavorites()) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-heart-fill me-2 text-danger\"></i>Recommended Values\n </h6>\n\n <!-- Favorites Navigation Tabs -->\n <ul ngbNav #favoritesNav=\"ngbNav\" [(activeId)]=\"activeFavoritesTab\" class=\"nav-tabs\">\n @if (userFavorites().length > 0) {\n <li [ngbNavItem]=\"'user'\">\n <button ngbNavLink>\n <i class=\"bi bi-person-fill me-1\"></i>Your Favorites\n <span class=\"badge bg-primary ms-1\">{{ userFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, userFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of userFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Your personal favorite values for this column\n </div>\n </div>\n </ng-template>\n </li>\n }\n\n @if (labGroupFavorites().length > 0) {\n <li [ngbNavItem]=\"'lab_group'\">\n <button ngbNavLink>\n <i class=\"bi bi-people-fill me-1\"></i>Lab Group\n <span class=\"badge bg-info ms-1\">{{ labGroupFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, labGroupFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of labGroupFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Favorite values shared by your lab group\n </div>\n </div>\n </ng-template>\n </li>\n }\n\n @if (globalFavorites().length > 0) {\n <li [ngbNavItem]=\"'global'\">\n <button ngbNavLink>\n <i class=\"bi bi-globe me-1\"></i>Global\n <span class=\"badge bg-success ms-1\">{{ globalFavorites().length }}</span>\n </button>\n <ng-template ngbNavContent>\n <div class=\"mt-3\">\n <select \n class=\"form-select\" \n size=\"5\" \n (change)=\"onFavoriteSelected($event, globalFavorites())\">\n <option value=\"\">Select a favorite value...</option>\n @for (favorite of globalFavorites(); track favorite.id) {\n <option [value]=\"favorite.id\">\n {{ getFavoriteDisplayValue(favorite) }}\n @if (hasCustomDisplayValue(favorite)) { (Custom Display) }\n </option>\n }\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Globally recommended values for this column\n </div>\n </div>\n </ng-template>\n </li>\n }\n </ul>\n\n <div [ngbNavOutlet]=\"favoritesNav\" class=\"mt-3\"></div>\n\n <hr class=\"my-3\">\n </div>\n }\n\n @if (isLoadingFavorites()) {\n <div class=\"text-center mb-4\">\n <div class=\"spinner-border spinner-border-sm text-primary me-2\"></div>\n <small class=\"text-muted\">Loading favorite options...</small>\n </div>\n <hr class=\"my-3\">\n }\n\n <!-- Value Input Section -->\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label for=\"metadataValue\" class=\"form-label mb-0\">\n Metadata Value\n @if (hasOntologyType) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Autocomplete Available\n </span>\n }\n @if (specialSyntaxType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n </label>\n\n @if (specialSyntaxType()) {\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"specialInputToggle\"\n [checked]=\"showSpecialInput()\"\n (change)=\"toggleSpecialInput()\">\n <label class=\"form-check-label small text-muted\" for=\"specialInputToggle\">\n Enhanced Editor\n </label>\n </div>\n }\n </div>\n\n <!-- Quick-select buttons for standard values -->\n @if (config.allowNotApplicable || config.allowNotAvailable) {\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Quick Select:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (config.allowNotApplicable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (config.allowNotAvailable) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n\n <!-- Special SDRF Syntax Input Components -->\n @if (showSpecialInput() && specialSyntaxType()) {\n <div class=\"border rounded p-3 mb-3 bg-body-secondary\">\n <div class=\"mb-2\">\n <small class=\"text-muted fw-semibold\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </small>\n </div>\n\n @switch (specialSyntaxType()) {\n @case ('age') {\n <ccv-sdrf-age-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-age-input>\n }\n @case ('modification') {\n <ccv-sdrf-modification-input\n [value]=\"specialInputValue()\"\n [columnId]=\"config.columnId ?? null\"\n [templateId]=\"config.templateId ?? null\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-modification-input>\n }\n @case ('cleavage') {\n <ccv-sdrf-cleavage-input\n [value]=\"specialInputValue()\"\n [columnId]=\"config.columnId ?? null\"\n [templateId]=\"config.templateId ?? null\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-cleavage-input>\n }\n @case ('spiked_compound') {\n <ccv-sdrf-spiked-compound-input\n [value]=\"specialInputValue()\"\n (valueChange)=\"onSpecialInputChange($event)\">\n </ccv-sdrf-spiked-compound-input>\n }\n }\n\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use the enhanced editor for structured input, or toggle off for manual text entry.\n </div>\n </div>\n }\n\n @if (hasOntologyType && !showSpecialInput()) {\n <!-- Search type selection for ontology -->\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Search Mode:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'icontains' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('icontains')\">\n <i class=\"bi bi-search me-1\"></i>Contains\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"searchType() === 'istartswith' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSearchTypeChange('istartswith')\">\n <i class=\"bi bi-arrow-right me-1\"></i>Starts with\n </button>\n </div>\n </div>\n\n <!-- Typeahead input for ontology suggestions -->\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.border-success]=\"selectedFavorite() !== null\"\n id=\"metadataValue\"\n formControlName=\"value\"\n placeholder=\"Type to search ontology suggestions...\"\n [ngbTypeahead]=\"searchOntology\"\n [resultFormatter]=\"formatSuggestion\"\n [inputFormatter]=\"inputFormatter\"\n (selectItem)=\"onSuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n @if (isLoadingSuggestions()) {\n <span class=\"input-group-text\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </span>\n } @else {\n <span class=\"input-group-text\">\n <i class=\"bi bi-search text-muted\"></i>\n </span>\n }\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearValue()\"\n title=\"Clear value\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Search {{ searchType() === 'icontains' ? 'contains' : 'starts with' }} for {{ ontologyTypeLabel }} ontology\n </div>\n } @else if (!showSpecialInput()) {\n <!-- Regular input when no ontology type or special input is disabled -->\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.border-success]=\"selectedFavorite() !== null\"\n id=\"metadataValue\"\n formControlName=\"value\"\n placeholder=\"Enter metadata value\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearValue()\"\n title=\"Clear value\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n <div class=\"form-text text-muted\">\n @if (specialSyntaxType()) {\n Manual text entry mode. Use the Enhanced Editor toggle for structured {{ specialSyntaxType() }} input.\n } @else {\n Enter the value manually (no ontology suggestions available for this column type)\n }\n </div>\n }\n </div>\n\n <!-- Character count and value info -->\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Characters: {{ editForm.get('value')?.value?.length || 0 }}/500\n </small>\n @if (getSelectedFavoriteInfo()) {\n <small class=\"text-info\">\n <i class=\"bi bi-info-circle me-1\"></i>{{ getSelectedFavoriteInfo() }}\n </small>\n }\n </div>\n </div>\n\n </form>\n </div>\n\n <!-- Sample Selection Panel -->\n @if (hasMultiSampleEdit) {\n <div class=\"col-md-4\">\n <div class=\"border-start ps-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h6 class=\"mb-0 text-muted\">\n <i class=\"bi bi-list-check me-1\"></i>Apply to Samples\n </h6>\n <span class=\"badge bg-primary\">\n {{ selectedSampleCount }}/{{ totalSampleCount }}\n </span>\n </div>\n\n <!-- Select All/Clear All Controls -->\n <div class=\"d-flex gap-1 mb-3\">\n <button \n type=\"button\" \n class=\"btn btn-outline-primary btn-sm flex-fill\"\n (click)=\"selectAllSamples()\"\n [disabled]=\"selectedSampleCount === totalSampleCount\">\n <i class=\"bi bi-check-all me-1\"></i>All\n </button>\n <button \n type=\"button\" \n class=\"btn btn-outline-secondary btn-sm flex-fill\"\n (click)=\"clearSampleSelection()\"\n [disabled]=\"selectedSampleCount === 0\">\n <i class=\"bi bi-x me-1\"></i>Clear\n </button>\n </div>\n\n <!-- Sample List -->\n <div class=\"sample-list\" style=\"max-height: 400px; overflow-y: auto;\">\n @for (sample of sampleData; track sample.index) {\n <div class=\"form-check d-flex align-items-center mb-2 p-2 rounded border-bottom\">\n <input \n class=\"form-check-input me-2\" \n type=\"checkbox\" \n [id]=\"'sample-' + sample.index\"\n [checked]=\"isSampleSelected(sample.index)\"\n (change)=\"toggleSampleSelection(sample.index)\">\n <label \n [for]=\"'sample-' + sample.index\" \n class=\"form-check-label flex-grow-1 cursor-pointer\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <div>\n <div class=\"fw-semibold\">Sample {{ sample.index }}</div>\n @if (sample.sourceName) {\n <small class=\"text-muted\">{{ sample.sourceName }}</small>\n }\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted\">Current:</small>\n <div class=\"small fw-mono\" [title]=\"sample.value || ''\">\n {{ (sample.value && sample.value.length > 15) ? (sample.value | slice:0:15) + '...' : (sample.value || '-') }}\n </div>\n </div>\n </div>\n </label>\n </div>\n }\n </div>\n\n @if (selectedSampleCount > 0) {\n <div class=\"alert alert-info mt-3 p-2\">\n <small>\n <i class=\"bi bi-info-circle me-1\"></i>\n The value will be applied to {{ selectedSampleCount }} selected sample{{ selectedSampleCount !== 1 ? 's' : '' }}.\n </small>\n </div>\n }\n </div>\n </div>\n }\n </div>\n</div>\n\n<div class=\"modal-footer\">\n <div class=\"me-auto\">\n @if (editForm.get('value')?.value && !isLoading() && !isFromFavoriteManagement) {\n <div class=\"btn-group dropup\" ngbDropdown>\n <button\n type=\"button\"\n class=\"btn btn-outline-warning btn-sm dropdown-toggle\"\n ngbDropdownToggle>\n <i class=\"bi bi-heart me-1\"></i>Add to Favorites\n </button>\n <div class=\"dropdown-menu\" ngbDropdownMenu>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('user')\">\n <i class=\"bi bi-person me-2\"></i>Personal Favorite\n </button>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('lab_group')\">\n <i class=\"bi bi-people me-2\"></i>Lab Group Favorite\n </button>\n <button\n type=\"button\"\n class=\"dropdown-item\"\n (click)=\"addToFavorites('global')\">\n <i class=\"bi bi-globe me-2\"></i>Global Recommendation\n </button>\n </div>\n </div>\n }\n </div>\n\n <div class=\"d-flex gap-2\">\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 Save Value\n </button>\n </div>\n</div>\n", styles: [".sample-list .form-check{border:1px solid transparent;transition:all .2s ease}.sample-list .form-check:hover{background-color:rgba(var(--bs-primary-rgb),.05);border-color:rgba(var(--bs-primary-rgb),.2)}.sample-list .form-check .form-check-input:checked~.form-check-label .fw-semibold{color:var(--bs-primary)}.cursor-pointer{cursor:pointer}.fw-mono{font-family:var(--bs-font-monospace)}\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.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: "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: "directive", type: i2.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i2.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i2.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i2.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i2.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i2.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i2.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "directive", type: i2.NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "ngmodule", type: NgbNavModule }, { kind: "component", type: SdrfAgeInput, selector: "ccv-sdrf-age-input", inputs: ["value"], outputs: ["valueChange"] }, { kind: "component", type: SdrfModificationInput, selector: "ccv-sdrf-modification-input", inputs: ["value", "templateId", "columnId"], outputs: ["valueChange"] }, { kind: "component", type: SdrfCleavageInput, selector: "ccv-sdrf-cleavage-input", inputs: ["value", "templateId", "columnId"], outputs: ["valueChange"] }, { kind: "component", type: SdrfSpikedCompoundInput, selector: "ccv-sdrf-spiked-compound-input", inputs: ["value"], outputs: ["valueChange"] }, { kind: "pipe", type: i5.SlicePipe, name: "slice" }] });
3691
3707
  }
3692
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataValueEditModal, decorators: [{
3708
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataValueEditModal, decorators: [{
3693
3709
  type: Component,
3694
3710
  args: [{ selector: 'ccv-metadata-value-edit-modal', standalone: true, imports: [
3695
3711
  CommonModule,
@@ -3779,10 +3795,10 @@ class ExcelExportModalComponent {
3779
3795
  cancel() {
3780
3796
  this.activeModal.dismiss();
3781
3797
  }
3782
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ExcelExportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3783
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: ExcelExportModalComponent, isStandalone: true, selector: "ccv-excel-export-modal", ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">Excel Export Options</h4>\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"cancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <!-- Lab Groups Section -->\n <div class=\"mb-4\">\n <h6 class=\"fw-semibold mb-3\">\n <i class=\"bi bi-people me-2\"></i>Lab Group Recommendations\n </h6>\n \n <div class=\"form-check mb-2\">\n <input \n class=\"form-check-input\" \n type=\"radio\" \n name=\"includeLabGroups\"\n id=\"labGroups_none\"\n [checked]=\"includeLabGroups() === 'none'\"\n (change)=\"onIncludeLabGroupsChange('none')\">\n <label class=\"form-check-label\" for=\"labGroups_none\">\n <strong>None</strong> - Only user-specific and global recommendations\n </label>\n </div>\n\n <div class=\"form-check mb-2\">\n <input \n class=\"form-check-input\" \n type=\"radio\" \n name=\"includeLabGroups\"\n id=\"labGroups_selected\"\n [checked]=\"includeLabGroups() === 'selected'\"\n (change)=\"onIncludeLabGroupsChange('selected')\">\n <label class=\"form-check-label\" for=\"labGroups_selected\">\n <strong>Selected groups</strong> - Choose specific lab groups\n </label>\n </div>\n\n <div class=\"form-check mb-3\">\n <input \n class=\"form-check-input\" \n type=\"radio\" \n name=\"includeLabGroups\"\n id=\"labGroups_all\"\n [checked]=\"includeLabGroups() === 'all'\"\n (change)=\"onIncludeLabGroupsChange('all')\">\n <label class=\"form-check-label\" for=\"labGroups_all\">\n <strong>All groups</strong> - Include all available lab group recommendations\n </label>\n </div>\n\n <!-- Lab Group Selection -->\n @if (showLabGroupSelection()) {\n <div class=\"border rounded p-3 bg-light\">\n @if (isLoadingLabGroups()) {\n <div class=\"text-center\">\n <div class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></div>\n Loading lab groups...\n </div>\n } @else if (availableLabGroups().length === 0) {\n <div class=\"text-muted text-center\">\n <i class=\"bi bi-info-circle me-2\"></i>\n No lab groups available\n </div>\n } @else {\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h6 class=\"mb-0\">Select Lab Groups:</h6>\n <div class=\"btn-group btn-group-sm\">\n <button \n type=\"button\" \n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"selectAllLabGroups()\"\n [disabled]=\"selectedLabGroupIds().length === availableLabGroups().length\">\n Select All\n </button>\n <button \n type=\"button\" \n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"deselectAllLabGroups()\"\n [disabled]=\"selectedLabGroupIds().length === 0\">\n Clear All\n </button>\n </div>\n </div>\n\n <div class=\"row\">\n @for (labGroup of availableLabGroups(); track labGroup.id) {\n <div class=\"col-md-6 mb-2\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n [id]=\"'labGroup_' + labGroup.id\"\n [checked]=\"isLabGroupSelected(labGroup.id)\"\n (change)=\"onLabGroupToggle(labGroup.id, $event.target.checked)\">\n <label class=\"form-check-label\" [for]=\"'labGroup_' + labGroup.id\">\n {{ labGroup.name }}\n @if (labGroup.description) {\n <small class=\"text-muted d-block\">{{ labGroup.description }}</small>\n }\n </label>\n </div>\n </div>\n }\n </div>\n\n @if (selectedLabGroupIds().length > 0) {\n <div class=\"mt-2\">\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ selectedLabGroupIds().length }} group{{ selectedLabGroupIds().length > 1 ? 's' : '' }} selected\n </small>\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Pools Section -->\n <div class=\"mb-4\">\n <h6 class=\"fw-semibold mb-3\">\n <i class=\"bi bi-collection me-2\"></i>Sample Pools\n </h6>\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"includePools\"\n [checked]=\"includePools()\"\n (change)=\"includePools.set($event.target.checked)\">\n <label class=\"form-check-label\" for=\"includePools\">\n Include sample pools in export\n </label>\n <small class=\"form-text text-muted d-block mt-1\">\n Creates additional worksheets with pool information and mappings\n </small>\n </div>\n </div>\n\n <!-- Summary -->\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-info-circle me-2\"></i>Export Summary\n </h6>\n <ul class=\"mb-0\">\n <li>\n <strong>Lab Groups:</strong>\n @switch (includeLabGroups()) {\n @case ('none') { None - only user and global recommendations }\n @case ('selected') { \n @if (selectedLabGroupIds().length > 0) {\n {{ selectedLabGroupIds().length }} selected group{{ selectedLabGroupIds().length > 1 ? 's' : '' }}\n } @else {\n No groups selected\n }\n }\n @case ('all') { All available lab groups }\n }\n </li>\n <li><strong>Sample Pools:</strong> {{ includePools() ? 'Included' : 'Excluded' }}</li>\n </ul>\n </div>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"cancel()\">\n Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\"\n [disabled]=\"!canConfirm()\"\n (click)=\"confirm()\">\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>\n Export Excel\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
3798
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ExcelExportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3799
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ExcelExportModalComponent, isStandalone: true, selector: "ccv-excel-export-modal", ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">Excel Export Options</h4>\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"cancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <!-- Lab Groups Section -->\n <div class=\"mb-4\">\n <h6 class=\"fw-semibold mb-3\">\n <i class=\"bi bi-people me-2\"></i>Lab Group Recommendations\n </h6>\n \n <div class=\"form-check mb-2\">\n <input \n class=\"form-check-input\" \n type=\"radio\" \n name=\"includeLabGroups\"\n id=\"labGroups_none\"\n [checked]=\"includeLabGroups() === 'none'\"\n (change)=\"onIncludeLabGroupsChange('none')\">\n <label class=\"form-check-label\" for=\"labGroups_none\">\n <strong>None</strong> - Only user-specific and global recommendations\n </label>\n </div>\n\n <div class=\"form-check mb-2\">\n <input \n class=\"form-check-input\" \n type=\"radio\" \n name=\"includeLabGroups\"\n id=\"labGroups_selected\"\n [checked]=\"includeLabGroups() === 'selected'\"\n (change)=\"onIncludeLabGroupsChange('selected')\">\n <label class=\"form-check-label\" for=\"labGroups_selected\">\n <strong>Selected groups</strong> - Choose specific lab groups\n </label>\n </div>\n\n <div class=\"form-check mb-3\">\n <input \n class=\"form-check-input\" \n type=\"radio\" \n name=\"includeLabGroups\"\n id=\"labGroups_all\"\n [checked]=\"includeLabGroups() === 'all'\"\n (change)=\"onIncludeLabGroupsChange('all')\">\n <label class=\"form-check-label\" for=\"labGroups_all\">\n <strong>All groups</strong> - Include all available lab group recommendations\n </label>\n </div>\n\n <!-- Lab Group Selection -->\n @if (showLabGroupSelection()) {\n <div class=\"border rounded p-3 bg-light\">\n @if (isLoadingLabGroups()) {\n <div class=\"text-center\">\n <div class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></div>\n Loading lab groups...\n </div>\n } @else if (availableLabGroups().length === 0) {\n <div class=\"text-muted text-center\">\n <i class=\"bi bi-info-circle me-2\"></i>\n No lab groups available\n </div>\n } @else {\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h6 class=\"mb-0\">Select Lab Groups:</h6>\n <div class=\"btn-group btn-group-sm\">\n <button \n type=\"button\" \n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"selectAllLabGroups()\"\n [disabled]=\"selectedLabGroupIds().length === availableLabGroups().length\">\n Select All\n </button>\n <button \n type=\"button\" \n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"deselectAllLabGroups()\"\n [disabled]=\"selectedLabGroupIds().length === 0\">\n Clear All\n </button>\n </div>\n </div>\n\n <div class=\"row\">\n @for (labGroup of availableLabGroups(); track labGroup.id) {\n <div class=\"col-md-6 mb-2\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n [id]=\"'labGroup_' + labGroup.id\"\n [checked]=\"isLabGroupSelected(labGroup.id)\"\n (change)=\"onLabGroupToggle(labGroup.id, $event.target.checked)\">\n <label class=\"form-check-label\" [for]=\"'labGroup_' + labGroup.id\">\n {{ labGroup.name }}\n @if (labGroup.description) {\n <small class=\"text-muted d-block\">{{ labGroup.description }}</small>\n }\n </label>\n </div>\n </div>\n }\n </div>\n\n @if (selectedLabGroupIds().length > 0) {\n <div class=\"mt-2\">\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ selectedLabGroupIds().length }} group{{ selectedLabGroupIds().length > 1 ? 's' : '' }} selected\n </small>\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Pools Section -->\n <div class=\"mb-4\">\n <h6 class=\"fw-semibold mb-3\">\n <i class=\"bi bi-collection me-2\"></i>Sample Pools\n </h6>\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"includePools\"\n [checked]=\"includePools()\"\n (change)=\"includePools.set($event.target.checked)\">\n <label class=\"form-check-label\" for=\"includePools\">\n Include sample pools in export\n </label>\n <small class=\"form-text text-muted d-block mt-1\">\n Creates additional worksheets with pool information and mappings\n </small>\n </div>\n </div>\n\n <!-- Summary -->\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-info-circle me-2\"></i>Export Summary\n </h6>\n <ul class=\"mb-0\">\n <li>\n <strong>Lab Groups:</strong>\n @switch (includeLabGroups()) {\n @case ('none') { None - only user and global recommendations }\n @case ('selected') { \n @if (selectedLabGroupIds().length > 0) {\n {{ selectedLabGroupIds().length }} selected group{{ selectedLabGroupIds().length > 1 ? 's' : '' }}\n } @else {\n No groups selected\n }\n }\n @case ('all') { All available lab groups }\n }\n </li>\n <li><strong>Sample Pools:</strong> {{ includePools() ? 'Included' : 'Excluded' }}</li>\n </ul>\n </div>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"cancel()\">\n Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\"\n [disabled]=\"!canConfirm()\"\n (click)=\"confirm()\">\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>\n Export Excel\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }] });
3784
3800
  }
3785
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ExcelExportModalComponent, decorators: [{
3801
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ExcelExportModalComponent, decorators: [{
3786
3802
  type: Component,
3787
3803
  args: [{ selector: 'ccv-excel-export-modal', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">Excel Export Options</h4>\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"cancel()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <!-- Lab Groups Section -->\n <div class=\"mb-4\">\n <h6 class=\"fw-semibold mb-3\">\n <i class=\"bi bi-people me-2\"></i>Lab Group Recommendations\n </h6>\n \n <div class=\"form-check mb-2\">\n <input \n class=\"form-check-input\" \n type=\"radio\" \n name=\"includeLabGroups\"\n id=\"labGroups_none\"\n [checked]=\"includeLabGroups() === 'none'\"\n (change)=\"onIncludeLabGroupsChange('none')\">\n <label class=\"form-check-label\" for=\"labGroups_none\">\n <strong>None</strong> - Only user-specific and global recommendations\n </label>\n </div>\n\n <div class=\"form-check mb-2\">\n <input \n class=\"form-check-input\" \n type=\"radio\" \n name=\"includeLabGroups\"\n id=\"labGroups_selected\"\n [checked]=\"includeLabGroups() === 'selected'\"\n (change)=\"onIncludeLabGroupsChange('selected')\">\n <label class=\"form-check-label\" for=\"labGroups_selected\">\n <strong>Selected groups</strong> - Choose specific lab groups\n </label>\n </div>\n\n <div class=\"form-check mb-3\">\n <input \n class=\"form-check-input\" \n type=\"radio\" \n name=\"includeLabGroups\"\n id=\"labGroups_all\"\n [checked]=\"includeLabGroups() === 'all'\"\n (change)=\"onIncludeLabGroupsChange('all')\">\n <label class=\"form-check-label\" for=\"labGroups_all\">\n <strong>All groups</strong> - Include all available lab group recommendations\n </label>\n </div>\n\n <!-- Lab Group Selection -->\n @if (showLabGroupSelection()) {\n <div class=\"border rounded p-3 bg-light\">\n @if (isLoadingLabGroups()) {\n <div class=\"text-center\">\n <div class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></div>\n Loading lab groups...\n </div>\n } @else if (availableLabGroups().length === 0) {\n <div class=\"text-muted text-center\">\n <i class=\"bi bi-info-circle me-2\"></i>\n No lab groups available\n </div>\n } @else {\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h6 class=\"mb-0\">Select Lab Groups:</h6>\n <div class=\"btn-group btn-group-sm\">\n <button \n type=\"button\" \n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"selectAllLabGroups()\"\n [disabled]=\"selectedLabGroupIds().length === availableLabGroups().length\">\n Select All\n </button>\n <button \n type=\"button\" \n class=\"btn btn-outline-secondary btn-sm\"\n (click)=\"deselectAllLabGroups()\"\n [disabled]=\"selectedLabGroupIds().length === 0\">\n Clear All\n </button>\n </div>\n </div>\n\n <div class=\"row\">\n @for (labGroup of availableLabGroups(); track labGroup.id) {\n <div class=\"col-md-6 mb-2\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n [id]=\"'labGroup_' + labGroup.id\"\n [checked]=\"isLabGroupSelected(labGroup.id)\"\n (change)=\"onLabGroupToggle(labGroup.id, $event.target.checked)\">\n <label class=\"form-check-label\" [for]=\"'labGroup_' + labGroup.id\">\n {{ labGroup.name }}\n @if (labGroup.description) {\n <small class=\"text-muted d-block\">{{ labGroup.description }}</small>\n }\n </label>\n </div>\n </div>\n }\n </div>\n\n @if (selectedLabGroupIds().length > 0) {\n <div class=\"mt-2\">\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ selectedLabGroupIds().length }} group{{ selectedLabGroupIds().length > 1 ? 's' : '' }} selected\n </small>\n </div>\n }\n }\n </div>\n }\n </div>\n\n <!-- Pools Section -->\n <div class=\"mb-4\">\n <h6 class=\"fw-semibold mb-3\">\n <i class=\"bi bi-collection me-2\"></i>Sample Pools\n </h6>\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"includePools\"\n [checked]=\"includePools()\"\n (change)=\"includePools.set($event.target.checked)\">\n <label class=\"form-check-label\" for=\"includePools\">\n Include sample pools in export\n </label>\n <small class=\"form-text text-muted d-block mt-1\">\n Creates additional worksheets with pool information and mappings\n </small>\n </div>\n </div>\n\n <!-- Summary -->\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-info-circle me-2\"></i>Export Summary\n </h6>\n <ul class=\"mb-0\">\n <li>\n <strong>Lab Groups:</strong>\n @switch (includeLabGroups()) {\n @case ('none') { None - only user and global recommendations }\n @case ('selected') { \n @if (selectedLabGroupIds().length > 0) {\n {{ selectedLabGroupIds().length }} selected group{{ selectedLabGroupIds().length > 1 ? 's' : '' }}\n } @else {\n No groups selected\n }\n }\n @case ('all') { All available lab groups }\n }\n </li>\n <li><strong>Sample Pools:</strong> {{ includePools() ? 'Included' : 'Excluded' }}</li>\n </ul>\n </div>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"cancel()\">\n Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\"\n [disabled]=\"!canConfirm()\"\n (click)=\"confirm()\">\n <i class=\"bi bi-file-earmark-spreadsheet me-2\"></i>\n Export Excel\n </button>\n</div>\n" }]
3788
3804
  }] });
@@ -3799,12 +3815,12 @@ class ValidationResultsModal {
3799
3815
  return timestamp;
3800
3816
  }
3801
3817
  }
3802
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ValidationResultsModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
3803
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: ValidationResultsModal, isStandalone: true, selector: "ccv-validation-results-modal", inputs: { results: "results" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n @if (results?.success) {\n <i class=\"fa fa-check-circle me-2 text-success\"></i>\n Validation Passed\n } @else {\n <i class=\"fa fa-exclamation-triangle me-2 text-danger\"></i>\n Validation Failed\n }\n </h4>\n <button \n type=\"button\" \n class=\"btn-close\" \n aria-label=\"Close\"\n (click)=\"activeModal.dismiss()\">\n </button>\n</div>\n\n<div class=\"modal-body\">\n @if (results) {\n <div class=\"mb-3\">\n <h6 class=\"mb-2\">\n <i class=\"fa fa-table me-2 text-primary\"></i>\n {{ results.metadata_table_name }}\n </h6>\n <small class=\"text-muted\">\n <i class=\"fa fa-clock me-1\"></i>\n Validated: {{ formatTimestamp(results.validation_timestamp) }}\n </small>\n </div>\n\n @if (results.success) {\n <!-- Success message -->\n <div class=\"alert alert-success\">\n <i class=\"fa fa-check-circle me-2\"></i>\n <strong>Validation Successful!</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table complies with SDRF format standards and contains no validation errors.\n </p>\n </div>\n } @else {\n <!-- Validation failed - show errors and warnings -->\n <div class=\"alert alert-danger mb-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n <strong>Validation Failed</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table contains validation issues that need to be resolved.\n </p>\n </div>\n\n <!-- Summary -->\n <div class=\"row mb-3\">\n <div class=\"col-md-6\">\n <div class=\"card border-danger\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-danger\">{{ results.errors.length }}</h5>\n <p class=\"card-text\">Error{{ results.errors.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"card border-warning\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-warning\">{{ results.warnings.length }}</h5>\n <p class=\"card-text\">Warning{{ results.warnings.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Errors -->\n @if (results.errors.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-danger\">\n <i class=\"fa fa-times-circle me-2\"></i>\n Errors ({{ results.errors.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (error of results.errors; track $index) {\n <div class=\"alert alert-danger py-2 px-3\">\n <i class=\"fa fa-times-circle me-2\"></i>\n {{ error }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Warnings -->\n @if (results.warnings.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-warning\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n Warnings ({{ results.warnings.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (warning of results.warnings; track $index) {\n <div class=\"alert alert-warning py-2 px-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n {{ warning }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Help text -->\n <div class=\"alert alert-info\">\n <i class=\"fa fa-info-circle me-2\"></i>\n <strong>Need Help?</strong>\n <ul class=\"mb-0 mt-2\">\n <li>Errors must be fixed before your metadata can be exported as valid SDRF</li>\n <li>Warnings indicate potential issues but don't prevent SDRF export</li>\n <li>Check the <a href=\"/help/sdrf-validation\" target=\"_blank\">SDRF validation guide</a> for detailed explanations</li>\n </ul>\n </div>\n }\n }\n</div>\n\n<div class=\"modal-footer\">\n <button \n type=\"button\" \n class=\"btn btn-secondary\"\n (click)=\"activeModal.dismiss()\">\n Close\n </button>\n</div>\n", styles: [".validation-issues{max-height:300px;overflow-y:auto}.validation-issues .alert{font-size:.875rem;line-height:1.4;word-break:break-word}.card{height:100%}.modal-body{max-height:70vh;overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }] });
3818
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ValidationResultsModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
3819
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ValidationResultsModal, isStandalone: true, selector: "ccv-validation-results-modal", inputs: { results: "results" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n @if (results?.success) {\n <i class=\"fa fa-check-circle me-2 text-success\"></i>\n Validation Passed\n } @else {\n <i class=\"fa fa-exclamation-triangle me-2 text-danger\"></i>\n Validation Failed\n }\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"activeModal.dismiss()\">\n </button>\n</div>\n\n<div class=\"modal-body\">\n @if (results) {\n <div class=\"mb-3\">\n <h6 class=\"mb-2\">\n <i class=\"fa fa-table me-2 text-primary\"></i>\n {{ results.metadata_table_name }}\n </h6>\n <small class=\"text-muted\">\n <i class=\"fa fa-clock me-1\"></i>\n Validated: {{ formatTimestamp(results.validation_timestamp) }}\n </small>\n </div>\n\n @if (results.success) {\n <!-- Success message -->\n <div class=\"alert alert-success\">\n <i class=\"fa fa-check-circle me-2\"></i>\n <strong>Validation Successful!</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table complies with SDRF format standards and contains no validation errors.\n </p>\n </div>\n } @else {\n <!-- Validation failed - show errors and warnings -->\n <div class=\"alert alert-danger mb-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n <strong>Validation Failed</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table contains validation issues that need to be resolved.\n </p>\n </div>\n\n <!-- Summary -->\n <div class=\"row mb-3\">\n <div class=\"col-md-6\">\n <div class=\"card border-danger\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-danger\">{{ results.errors.length }}</h5>\n <p class=\"card-text\">Error{{ results.errors.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"card border-warning\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-warning\">{{ results.warnings.length }}</h5>\n <p class=\"card-text\">Warning{{ results.warnings.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Errors -->\n @if (results.errors.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-danger\">\n <i class=\"fa fa-times-circle me-2\"></i>\n Errors ({{ results.errors.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (error of results.errors; track $index) {\n <div class=\"alert alert-danger py-2 px-3\">\n <i class=\"fa fa-times-circle me-2\"></i>\n {{ error }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Warnings -->\n @if (results.warnings.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-warning\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n Warnings ({{ results.warnings.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (warning of results.warnings; track $index) {\n <div class=\"alert alert-warning py-2 px-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n {{ warning }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Help text -->\n <div class=\"alert alert-info\">\n <i class=\"fa fa-info-circle me-2\"></i>\n <strong>Need Help?</strong>\n <ul class=\"mb-0 mt-2\">\n <li>Errors must be fixed before your metadata can be exported as valid SDRF</li>\n <li>Warnings indicate potential issues but don't prevent SDRF export or validation</li>\n <li>Check the <a href=\"https://sdrf.quantms.org\" target=\"_blank\">SDRF Specification</a> for detailed explanations of column and their requirements</li>\n </ul>\n </div>\n }\n }\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"activeModal.dismiss()\">\n Close\n </button>\n</div>\n", styles: [".validation-issues{max-height:300px;overflow-y:auto}.validation-issues .alert{font-size:.875rem;line-height:1.4;word-break:break-word}.card{height:100%}.modal-body{max-height:70vh;overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }] });
3804
3820
  }
3805
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ValidationResultsModal, decorators: [{
3821
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ValidationResultsModal, decorators: [{
3806
3822
  type: Component,
3807
- args: [{ selector: 'ccv-validation-results-modal', imports: [CommonModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n @if (results?.success) {\n <i class=\"fa fa-check-circle me-2 text-success\"></i>\n Validation Passed\n } @else {\n <i class=\"fa fa-exclamation-triangle me-2 text-danger\"></i>\n Validation Failed\n }\n </h4>\n <button \n type=\"button\" \n class=\"btn-close\" \n aria-label=\"Close\"\n (click)=\"activeModal.dismiss()\">\n </button>\n</div>\n\n<div class=\"modal-body\">\n @if (results) {\n <div class=\"mb-3\">\n <h6 class=\"mb-2\">\n <i class=\"fa fa-table me-2 text-primary\"></i>\n {{ results.metadata_table_name }}\n </h6>\n <small class=\"text-muted\">\n <i class=\"fa fa-clock me-1\"></i>\n Validated: {{ formatTimestamp(results.validation_timestamp) }}\n </small>\n </div>\n\n @if (results.success) {\n <!-- Success message -->\n <div class=\"alert alert-success\">\n <i class=\"fa fa-check-circle me-2\"></i>\n <strong>Validation Successful!</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table complies with SDRF format standards and contains no validation errors.\n </p>\n </div>\n } @else {\n <!-- Validation failed - show errors and warnings -->\n <div class=\"alert alert-danger mb-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n <strong>Validation Failed</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table contains validation issues that need to be resolved.\n </p>\n </div>\n\n <!-- Summary -->\n <div class=\"row mb-3\">\n <div class=\"col-md-6\">\n <div class=\"card border-danger\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-danger\">{{ results.errors.length }}</h5>\n <p class=\"card-text\">Error{{ results.errors.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"card border-warning\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-warning\">{{ results.warnings.length }}</h5>\n <p class=\"card-text\">Warning{{ results.warnings.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Errors -->\n @if (results.errors.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-danger\">\n <i class=\"fa fa-times-circle me-2\"></i>\n Errors ({{ results.errors.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (error of results.errors; track $index) {\n <div class=\"alert alert-danger py-2 px-3\">\n <i class=\"fa fa-times-circle me-2\"></i>\n {{ error }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Warnings -->\n @if (results.warnings.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-warning\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n Warnings ({{ results.warnings.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (warning of results.warnings; track $index) {\n <div class=\"alert alert-warning py-2 px-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n {{ warning }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Help text -->\n <div class=\"alert alert-info\">\n <i class=\"fa fa-info-circle me-2\"></i>\n <strong>Need Help?</strong>\n <ul class=\"mb-0 mt-2\">\n <li>Errors must be fixed before your metadata can be exported as valid SDRF</li>\n <li>Warnings indicate potential issues but don't prevent SDRF export</li>\n <li>Check the <a href=\"/help/sdrf-validation\" target=\"_blank\">SDRF validation guide</a> for detailed explanations</li>\n </ul>\n </div>\n }\n }\n</div>\n\n<div class=\"modal-footer\">\n <button \n type=\"button\" \n class=\"btn btn-secondary\"\n (click)=\"activeModal.dismiss()\">\n Close\n </button>\n</div>\n", styles: [".validation-issues{max-height:300px;overflow-y:auto}.validation-issues .alert{font-size:.875rem;line-height:1.4;word-break:break-word}.card{height:100%}.modal-body{max-height:70vh;overflow-y:auto}\n"] }]
3823
+ args: [{ selector: 'ccv-validation-results-modal', imports: [CommonModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n @if (results?.success) {\n <i class=\"fa fa-check-circle me-2 text-success\"></i>\n Validation Passed\n } @else {\n <i class=\"fa fa-exclamation-triangle me-2 text-danger\"></i>\n Validation Failed\n }\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"activeModal.dismiss()\">\n </button>\n</div>\n\n<div class=\"modal-body\">\n @if (results) {\n <div class=\"mb-3\">\n <h6 class=\"mb-2\">\n <i class=\"fa fa-table me-2 text-primary\"></i>\n {{ results.metadata_table_name }}\n </h6>\n <small class=\"text-muted\">\n <i class=\"fa fa-clock me-1\"></i>\n Validated: {{ formatTimestamp(results.validation_timestamp) }}\n </small>\n </div>\n\n @if (results.success) {\n <!-- Success message -->\n <div class=\"alert alert-success\">\n <i class=\"fa fa-check-circle me-2\"></i>\n <strong>Validation Successful!</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table complies with SDRF format standards and contains no validation errors.\n </p>\n </div>\n } @else {\n <!-- Validation failed - show errors and warnings -->\n <div class=\"alert alert-danger mb-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n <strong>Validation Failed</strong>\n <p class=\"mb-0 mt-2\">\n Your metadata table contains validation issues that need to be resolved.\n </p>\n </div>\n\n <!-- Summary -->\n <div class=\"row mb-3\">\n <div class=\"col-md-6\">\n <div class=\"card border-danger\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-danger\">{{ results.errors.length }}</h5>\n <p class=\"card-text\">Error{{ results.errors.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"card border-warning\">\n <div class=\"card-body text-center\">\n <h5 class=\"card-title text-warning\">{{ results.warnings.length }}</h5>\n <p class=\"card-text\">Warning{{ results.warnings.length !== 1 ? 's' : '' }}</p>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Errors -->\n @if (results.errors.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-danger\">\n <i class=\"fa fa-times-circle me-2\"></i>\n Errors ({{ results.errors.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (error of results.errors; track $index) {\n <div class=\"alert alert-danger py-2 px-3\">\n <i class=\"fa fa-times-circle me-2\"></i>\n {{ error }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Warnings -->\n @if (results.warnings.length > 0) {\n <div class=\"mb-3\">\n <h6 class=\"text-warning\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n Warnings ({{ results.warnings.length }})\n </h6>\n <div class=\"validation-issues\">\n @for (warning of results.warnings; track $index) {\n <div class=\"alert alert-warning py-2 px-3\">\n <i class=\"fa fa-exclamation-triangle me-2\"></i>\n {{ warning }}\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Help text -->\n <div class=\"alert alert-info\">\n <i class=\"fa fa-info-circle me-2\"></i>\n <strong>Need Help?</strong>\n <ul class=\"mb-0 mt-2\">\n <li>Errors must be fixed before your metadata can be exported as valid SDRF</li>\n <li>Warnings indicate potential issues but don't prevent SDRF export or validation</li>\n <li>Check the <a href=\"https://sdrf.quantms.org\" target=\"_blank\">SDRF Specification</a> for detailed explanations of column and their requirements</li>\n </ul>\n </div>\n }\n }\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"activeModal.dismiss()\">\n Close\n </button>\n</div>\n", styles: [".validation-issues{max-height:300px;overflow-y:auto}.validation-issues .alert{font-size:.875rem;line-height:1.4;word-break:break-word}.card{height:100%}.modal-body{max-height:70vh;overflow-y:auto}\n"] }]
3808
3824
  }], propDecorators: { results: [{
3809
3825
  type: Input
3810
3826
  }] } });
@@ -4189,10 +4205,10 @@ class AsyncTaskUIService {
4189
4205
  break;
4190
4206
  }
4191
4207
  }
4192
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskUIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4193
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskUIService, providedIn: 'root' });
4208
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncTaskUIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4209
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncTaskUIService, providedIn: 'root' });
4194
4210
  }
4195
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskUIService, decorators: [{
4211
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AsyncTaskUIService, decorators: [{
4196
4212
  type: Injectable,
4197
4213
  args: [{
4198
4214
  providedIn: 'root'
@@ -4203,27 +4219,84 @@ class MetadataValidationModal {
4203
4219
  config;
4204
4220
  fb = inject(FormBuilder);
4205
4221
  asyncTaskService = inject(AsyncTaskUIService);
4222
+ asyncValidationService = inject(AsyncValidationService);
4206
4223
  toastService = inject(ToastService);
4207
4224
  activeModal = inject(NgbActiveModal);
4208
4225
  isValidating = signal(false, ...(ngDevMode ? [{ debugName: "isValidating" }] : []));
4226
+ isLoadingSchemas = signal(false, ...(ngDevMode ? [{ debugName: "isLoadingSchemas" }] : []));
4209
4227
  validationError = signal(null, ...(ngDevMode ? [{ debugName: "validationError" }] : []));
4228
+ availableSchemas = signal([], ...(ngDevMode ? [{ debugName: "availableSchemas" }] : []));
4229
+ selectedSchemas = signal(new Set(['default']), ...(ngDevMode ? [{ debugName: "selectedSchemas" }] : []));
4210
4230
  validationForm = this.fb.group({
4211
- validate_sdrf_format: [true]
4231
+ validate_sdrf_format: [true],
4232
+ skip_ontology: [false],
4233
+ include_pools: [true]
4212
4234
  });
4235
+ ngOnInit() {
4236
+ this.loadAvailableSchemas();
4237
+ }
4238
+ loadAvailableSchemas() {
4239
+ this.isLoadingSchemas.set(true);
4240
+ this.asyncValidationService.getAvailableSchemas().subscribe({
4241
+ next: (schemas) => {
4242
+ this.availableSchemas.set(schemas);
4243
+ this.isLoadingSchemas.set(false);
4244
+ },
4245
+ error: (error) => {
4246
+ console.error('Failed to load schemas:', error);
4247
+ this.isLoadingSchemas.set(false);
4248
+ this.availableSchemas.set([
4249
+ { name: 'default', displayName: 'Default', description: 'Standard proteomics schema', columnCount: 0 },
4250
+ { name: 'minimum', displayName: 'Minimum', description: 'Minimum required columns', columnCount: 0 }
4251
+ ]);
4252
+ }
4253
+ });
4254
+ }
4255
+ toggleSchema(schemaName) {
4256
+ const current = new Set(this.selectedSchemas());
4257
+ if (current.has(schemaName)) {
4258
+ current.delete(schemaName);
4259
+ }
4260
+ else {
4261
+ current.add(schemaName);
4262
+ }
4263
+ this.selectedSchemas.set(current);
4264
+ }
4265
+ isSchemaSelected(schemaName) {
4266
+ return this.selectedSchemas().has(schemaName);
4267
+ }
4268
+ selectAllSchemas() {
4269
+ const allNames = new Set(this.availableSchemas().map(s => s.name));
4270
+ this.selectedSchemas.set(allNames);
4271
+ }
4272
+ clearAllSchemas() {
4273
+ this.selectedSchemas.set(new Set());
4274
+ }
4213
4275
  onSubmit() {
4214
4276
  if (this.validationForm.invalid || !this.config) {
4215
4277
  return;
4216
4278
  }
4279
+ const selectedSchemasList = Array.from(this.selectedSchemas());
4280
+ if (selectedSchemasList.length === 0) {
4281
+ this.validationError.set('Please select at least one schema to validate against.');
4282
+ return;
4283
+ }
4217
4284
  this.isValidating.set(true);
4218
4285
  this.validationError.set(null);
4219
4286
  const formValue = this.validationForm.value;
4220
4287
  const request = {
4221
4288
  metadataTableId: this.config.metadataTableId,
4222
- validateSdrfFormat: formValue.validate_sdrf_format ?? true
4289
+ validateSdrfFormat: formValue.validate_sdrf_format ?? true,
4290
+ includePools: formValue.include_pools ?? true,
4291
+ schemaNames: selectedSchemasList,
4292
+ skipOntology: formValue.skip_ontology ?? false
4223
4293
  };
4224
4294
  this.asyncTaskService.validateMetadataTable(request).subscribe({
4225
4295
  next: (response) => {
4226
- this.toastService.success(`Validation started successfully for "${this.config?.metadataTableName}"`);
4296
+ const schemaText = selectedSchemasList.length === 1
4297
+ ? selectedSchemasList[0]
4298
+ : `${selectedSchemasList.length} schemas`;
4299
+ this.toastService.success(`Validation started for "${this.config?.metadataTableName}" against ${schemaText}`);
4227
4300
  this.activeModal.close({
4228
4301
  success: true,
4229
4302
  task_id: response.taskId,
@@ -4238,12 +4311,12 @@ class MetadataValidationModal {
4238
4311
  }
4239
4312
  });
4240
4313
  }
4241
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataValidationModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
4242
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataValidationModal, isStandalone: true, selector: "ccv-metadata-validation-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-check-circle me-2 text-success\"></i>\r\n Validate Metadata Table\r\n </h4>\r\n <button \r\n type=\"button\" \r\n class=\"btn-close\" \r\n aria-label=\"Close\"\r\n (click)=\"activeModal.dismiss()\"\r\n [disabled]=\"isValidating()\">\r\n </button>\r\n</div>\r\n\r\n<form [formGroup]=\"validationForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"modal-body\">\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n <strong>{{ config?.metadataTableName }}</strong> will be validated against SDRF format standards using SDRFDataFrame.\r\n </div>\r\n\r\n <div class=\"row\">\r\n <div class=\"col-md-6\">\r\n <h6 class=\"mb-3\">Validation Options</h6>\r\n \r\n <div class=\"form-check mb-3\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"validate_sdrf_format\"\r\n formControlName=\"validate_sdrf_format\">\r\n <label class=\"form-check-label\" for=\"validate_sdrf_format\">\r\n <i class=\"bi bi-file-earmark-text me-1 text-warning\"></i>\r\n SDRF Format Validation\r\n </label>\r\n <small class=\"form-text text-muted d-block\">\r\n Validate against MAGE-TAB SDRF standard format using SDRFDataFrame\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <h6 class=\"mb-3\">What gets checked?</h6>\r\n \r\n <div class=\"validation-info\">\r\n <div class=\"mb-2\">\r\n <i class=\"bi bi-check-circle text-success me-2\"></i>\r\n Required SDRF columns presence\r\n </div>\r\n <div class=\"mb-2\">\r\n <i class=\"bi bi-check-circle text-success me-2\"></i>\r\n SDRF format compliance\r\n </div>\r\n <div class=\"mb-2\">\r\n <i class=\"bi bi-check-circle text-success me-2\"></i>\r\n Column header validation\r\n </div>\r\n <div class=\"mb-2\">\r\n <i class=\"bi bi-check-circle text-success me-2\"></i>\r\n Data structure integrity\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (validationError()) {\r\n <div class=\"alert alert-danger mt-3\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n {{ validationError() }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"modal-footer\">\r\n <button \r\n type=\"button\" \r\n class=\"btn btn-secondary\"\r\n (click)=\"activeModal.dismiss()\"\r\n [disabled]=\"isValidating()\">\r\n Cancel\r\n </button>\r\n \r\n <button \r\n type=\"submit\" \r\n class=\"btn btn-success\"\r\n [disabled]=\"validationForm.invalid || isValidating()\">\r\n @if (isValidating()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\r\n Starting Validation...\r\n } @else {\r\n <i class=\"bi bi-play me-2\"></i>\r\n Start Validation\r\n }\r\n </button>\r\n </div>\r\n</form>", styles: [".validation-info{border-radius:.375rem;padding:1rem;border:1px solid var(--bs-border-color)}.form-check-label{font-weight:500}.modal-title{font-weight:600}.alert{border-radius:.5rem}\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.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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 }] });
4314
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataValidationModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
4315
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: MetadataValidationModal, isStandalone: true, selector: "ccv-metadata-validation-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-check-circle me-2 text-success\"></i>\r\n Validate Metadata Table\r\n </h4>\r\n <button\r\n type=\"button\"\r\n class=\"btn-close\"\r\n aria-label=\"Close\"\r\n (click)=\"activeModal.dismiss()\"\r\n [disabled]=\"isValidating()\">\r\n </button>\r\n</div>\r\n\r\n<form [formGroup]=\"validationForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"modal-body\">\r\n <div class=\"alert alert-info py-2\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n <strong>{{ config?.metadataTableName }}</strong> will be validated against SDRF format standards.\r\n </div>\r\n\r\n <div class=\"row\">\r\n <div class=\"col-12 mb-3\">\r\n <h6 class=\"mb-2\">\r\n <i class=\"bi bi-diagram-3 me-1\"></i>\r\n Validation Schemas\r\n <span class=\"badge bg-primary ms-2\">{{ selectedSchemas().size }} selected</span>\r\n </h6>\r\n\r\n <div class=\"d-flex gap-2 mb-2\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"selectAllSchemas()\"\r\n [disabled]=\"isLoadingSchemas()\">\r\n Select All\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"clearAllSchemas()\"\r\n [disabled]=\"isLoadingSchemas()\">\r\n Clear All\r\n </button>\r\n </div>\r\n\r\n @if (isLoadingSchemas()) {\r\n <div class=\"text-center py-3\">\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n Loading schemas...\r\n </div>\r\n } @else {\r\n <div class=\"schema-list border rounded p-2\" style=\"max-height: 200px; overflow-y: auto;\">\r\n @for (schema of availableSchemas(); track schema.name) {\r\n <div\r\n class=\"schema-item d-flex align-items-center p-2 rounded cursor-pointer\"\r\n [class.bg-primary-subtle]=\"isSchemaSelected(schema.name)\"\r\n [class.border-primary]=\"isSchemaSelected(schema.name)\"\r\n (click)=\"toggleSchema(schema.name)\">\r\n <input\r\n type=\"checkbox\"\r\n class=\"form-check-input me-2\"\r\n [checked]=\"isSchemaSelected(schema.name)\"\r\n (click)=\"$event.stopPropagation()\">\r\n <div class=\"flex-grow-1\">\r\n <div class=\"fw-medium\">{{ schema.displayName }}</div>\r\n @if (schema.description) {\r\n <small class=\"text-muted\">{{ schema.description }}</small>\r\n }\r\n </div>\r\n @if (schema.columnCount > 0) {\r\n <span class=\"badge bg-secondary ms-2\">{{ schema.columnCount }} cols</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <h6 class=\"mb-2\">Validation Options</h6>\r\n\r\n <div class=\"form-check mb-2\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"validate_sdrf_format\"\r\n formControlName=\"validate_sdrf_format\">\r\n <label class=\"form-check-label\" for=\"validate_sdrf_format\">\r\n SDRF Format Validation\r\n </label>\r\n </div>\r\n\r\n <div class=\"form-check mb-2\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"include_pools\"\r\n formControlName=\"include_pools\">\r\n <label class=\"form-check-label\" for=\"include_pools\">\r\n Include Sample Pools\r\n </label>\r\n </div>\r\n\r\n <div class=\"form-check mb-2\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"skip_ontology\"\r\n formControlName=\"skip_ontology\">\r\n <label class=\"form-check-label\" for=\"skip_ontology\">\r\n Skip Ontology Validation\r\n </label>\r\n <small class=\"form-text text-muted d-block\">Faster validation without OLS lookups</small>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <h6 class=\"mb-2\">What gets checked?</h6>\r\n <div class=\"validation-info small\">\r\n <div class=\"mb-1\">\r\n <i class=\"bi bi-check-circle text-success me-1\"></i>\r\n Required columns presence\r\n </div>\r\n <div class=\"mb-1\">\r\n <i class=\"bi bi-check-circle text-success me-1\"></i>\r\n SDRF format compliance\r\n </div>\r\n <div class=\"mb-1\">\r\n <i class=\"bi bi-check-circle text-success me-1\"></i>\r\n Column header validation\r\n </div>\r\n <div class=\"mb-1\">\r\n <i class=\"bi bi-check-circle text-success me-1\"></i>\r\n Data structure integrity\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (validationError()) {\r\n <div class=\"alert alert-danger mt-3 py-2\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n {{ validationError() }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"modal-footer\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-secondary btn-sm\"\r\n (click)=\"activeModal.dismiss()\"\r\n [disabled]=\"isValidating()\">\r\n Cancel\r\n </button>\r\n\r\n <button\r\n type=\"submit\"\r\n class=\"btn btn-success btn-sm\"\r\n [disabled]=\"validationForm.invalid || isValidating() || selectedSchemas().size === 0\">\r\n @if (isValidating()) {\r\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\"></span>\r\n Starting...\r\n } @else {\r\n <i class=\"bi bi-play me-1\"></i>\r\n Validate ({{ selectedSchemas().size }} schema{{ selectedSchemas().size !== 1 ? 's' : '' }})\r\n }\r\n </button>\r\n </div>\r\n</form>\r\n", styles: [".validation-info{border-radius:.375rem;padding:.75rem;border:1px solid var(--bs-border-color)}.form-check-label{font-weight:500}.modal-title{font-weight:600}.alert{border-radius:.5rem}.schema-list{background-color:var(--bs-body-bg)}.schema-item{cursor:pointer;transition:background-color .15s ease;border:1px solid transparent;margin-bottom:.25rem}.schema-item:last-child{margin-bottom:0}.schema-item:hover{background-color:var(--bs-tertiary-bg)}.schema-item.bg-primary-subtle{border-color:var(--bs-primary)}.cursor-pointer{cursor:pointer}\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.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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 }] });
4243
4316
  }
4244
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataValidationModal, decorators: [{
4317
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataValidationModal, decorators: [{
4245
4318
  type: Component,
4246
- args: [{ selector: 'ccv-metadata-validation-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-check-circle me-2 text-success\"></i>\r\n Validate Metadata Table\r\n </h4>\r\n <button \r\n type=\"button\" \r\n class=\"btn-close\" \r\n aria-label=\"Close\"\r\n (click)=\"activeModal.dismiss()\"\r\n [disabled]=\"isValidating()\">\r\n </button>\r\n</div>\r\n\r\n<form [formGroup]=\"validationForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"modal-body\">\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n <strong>{{ config?.metadataTableName }}</strong> will be validated against SDRF format standards using SDRFDataFrame.\r\n </div>\r\n\r\n <div class=\"row\">\r\n <div class=\"col-md-6\">\r\n <h6 class=\"mb-3\">Validation Options</h6>\r\n \r\n <div class=\"form-check mb-3\">\r\n <input \r\n class=\"form-check-input\" \r\n type=\"checkbox\" \r\n id=\"validate_sdrf_format\"\r\n formControlName=\"validate_sdrf_format\">\r\n <label class=\"form-check-label\" for=\"validate_sdrf_format\">\r\n <i class=\"bi bi-file-earmark-text me-1 text-warning\"></i>\r\n SDRF Format Validation\r\n </label>\r\n <small class=\"form-text text-muted d-block\">\r\n Validate against MAGE-TAB SDRF standard format using SDRFDataFrame\r\n </small>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <h6 class=\"mb-3\">What gets checked?</h6>\r\n \r\n <div class=\"validation-info\">\r\n <div class=\"mb-2\">\r\n <i class=\"bi bi-check-circle text-success me-2\"></i>\r\n Required SDRF columns presence\r\n </div>\r\n <div class=\"mb-2\">\r\n <i class=\"bi bi-check-circle text-success me-2\"></i>\r\n SDRF format compliance\r\n </div>\r\n <div class=\"mb-2\">\r\n <i class=\"bi bi-check-circle text-success me-2\"></i>\r\n Column header validation\r\n </div>\r\n <div class=\"mb-2\">\r\n <i class=\"bi bi-check-circle text-success me-2\"></i>\r\n Data structure integrity\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (validationError()) {\r\n <div class=\"alert alert-danger mt-3\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n {{ validationError() }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"modal-footer\">\r\n <button \r\n type=\"button\" \r\n class=\"btn btn-secondary\"\r\n (click)=\"activeModal.dismiss()\"\r\n [disabled]=\"isValidating()\">\r\n Cancel\r\n </button>\r\n \r\n <button \r\n type=\"submit\" \r\n class=\"btn btn-success\"\r\n [disabled]=\"validationForm.invalid || isValidating()\">\r\n @if (isValidating()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\r\n Starting Validation...\r\n } @else {\r\n <i class=\"bi bi-play me-2\"></i>\r\n Start Validation\r\n }\r\n </button>\r\n </div>\r\n</form>", styles: [".validation-info{border-radius:.375rem;padding:1rem;border:1px solid var(--bs-border-color)}.form-check-label{font-weight:500}.modal-title{font-weight:600}.alert{border-radius:.5rem}\n"] }]
4319
+ args: [{ selector: 'ccv-metadata-validation-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-check-circle me-2 text-success\"></i>\r\n Validate Metadata Table\r\n </h4>\r\n <button\r\n type=\"button\"\r\n class=\"btn-close\"\r\n aria-label=\"Close\"\r\n (click)=\"activeModal.dismiss()\"\r\n [disabled]=\"isValidating()\">\r\n </button>\r\n</div>\r\n\r\n<form [formGroup]=\"validationForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"modal-body\">\r\n <div class=\"alert alert-info py-2\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n <strong>{{ config?.metadataTableName }}</strong> will be validated against SDRF format standards.\r\n </div>\r\n\r\n <div class=\"row\">\r\n <div class=\"col-12 mb-3\">\r\n <h6 class=\"mb-2\">\r\n <i class=\"bi bi-diagram-3 me-1\"></i>\r\n Validation Schemas\r\n <span class=\"badge bg-primary ms-2\">{{ selectedSchemas().size }} selected</span>\r\n </h6>\r\n\r\n <div class=\"d-flex gap-2 mb-2\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"selectAllSchemas()\"\r\n [disabled]=\"isLoadingSchemas()\">\r\n Select All\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"clearAllSchemas()\"\r\n [disabled]=\"isLoadingSchemas()\">\r\n Clear All\r\n </button>\r\n </div>\r\n\r\n @if (isLoadingSchemas()) {\r\n <div class=\"text-center py-3\">\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n Loading schemas...\r\n </div>\r\n } @else {\r\n <div class=\"schema-list border rounded p-2\" style=\"max-height: 200px; overflow-y: auto;\">\r\n @for (schema of availableSchemas(); track schema.name) {\r\n <div\r\n class=\"schema-item d-flex align-items-center p-2 rounded cursor-pointer\"\r\n [class.bg-primary-subtle]=\"isSchemaSelected(schema.name)\"\r\n [class.border-primary]=\"isSchemaSelected(schema.name)\"\r\n (click)=\"toggleSchema(schema.name)\">\r\n <input\r\n type=\"checkbox\"\r\n class=\"form-check-input me-2\"\r\n [checked]=\"isSchemaSelected(schema.name)\"\r\n (click)=\"$event.stopPropagation()\">\r\n <div class=\"flex-grow-1\">\r\n <div class=\"fw-medium\">{{ schema.displayName }}</div>\r\n @if (schema.description) {\r\n <small class=\"text-muted\">{{ schema.description }}</small>\r\n }\r\n </div>\r\n @if (schema.columnCount > 0) {\r\n <span class=\"badge bg-secondary ms-2\">{{ schema.columnCount }} cols</span>\r\n }\r\n </div>\r\n }\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <h6 class=\"mb-2\">Validation Options</h6>\r\n\r\n <div class=\"form-check mb-2\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"validate_sdrf_format\"\r\n formControlName=\"validate_sdrf_format\">\r\n <label class=\"form-check-label\" for=\"validate_sdrf_format\">\r\n SDRF Format Validation\r\n </label>\r\n </div>\r\n\r\n <div class=\"form-check mb-2\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"include_pools\"\r\n formControlName=\"include_pools\">\r\n <label class=\"form-check-label\" for=\"include_pools\">\r\n Include Sample Pools\r\n </label>\r\n </div>\r\n\r\n <div class=\"form-check mb-2\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"skip_ontology\"\r\n formControlName=\"skip_ontology\">\r\n <label class=\"form-check-label\" for=\"skip_ontology\">\r\n Skip Ontology Validation\r\n </label>\r\n <small class=\"form-text text-muted d-block\">Faster validation without OLS lookups</small>\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-md-6\">\r\n <h6 class=\"mb-2\">What gets checked?</h6>\r\n <div class=\"validation-info small\">\r\n <div class=\"mb-1\">\r\n <i class=\"bi bi-check-circle text-success me-1\"></i>\r\n Required columns presence\r\n </div>\r\n <div class=\"mb-1\">\r\n <i class=\"bi bi-check-circle text-success me-1\"></i>\r\n SDRF format compliance\r\n </div>\r\n <div class=\"mb-1\">\r\n <i class=\"bi bi-check-circle text-success me-1\"></i>\r\n Column header validation\r\n </div>\r\n <div class=\"mb-1\">\r\n <i class=\"bi bi-check-circle text-success me-1\"></i>\r\n Data structure integrity\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n @if (validationError()) {\r\n <div class=\"alert alert-danger mt-3 py-2\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n {{ validationError() }}\r\n </div>\r\n }\r\n </div>\r\n\r\n <div class=\"modal-footer\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-secondary btn-sm\"\r\n (click)=\"activeModal.dismiss()\"\r\n [disabled]=\"isValidating()\">\r\n Cancel\r\n </button>\r\n\r\n <button\r\n type=\"submit\"\r\n class=\"btn btn-success btn-sm\"\r\n [disabled]=\"validationForm.invalid || isValidating() || selectedSchemas().size === 0\">\r\n @if (isValidating()) {\r\n <span class=\"spinner-border spinner-border-sm me-1\" role=\"status\"></span>\r\n Starting...\r\n } @else {\r\n <i class=\"bi bi-play me-1\"></i>\r\n Validate ({{ selectedSchemas().size }} schema{{ selectedSchemas().size !== 1 ? 's' : '' }})\r\n }\r\n </button>\r\n </div>\r\n</form>\r\n", styles: [".validation-info{border-radius:.375rem;padding:.75rem;border:1px solid var(--bs-border-color)}.form-check-label{font-weight:500}.modal-title{font-weight:600}.alert{border-radius:.5rem}.schema-list{background-color:var(--bs-body-bg)}.schema-item{cursor:pointer;transition:background-color .15s ease;border:1px solid transparent;margin-bottom:.25rem}.schema-item:last-child{margin-bottom:0}.schema-item:hover{background-color:var(--bs-tertiary-bg)}.schema-item.bg-primary-subtle{border-color:var(--bs-primary)}.cursor-pointer{cursor:pointer}\n"] }]
4247
4320
  }], propDecorators: { config: [{
4248
4321
  type: Input
4249
4322
  }] } });
@@ -4459,10 +4532,10 @@ Do you want to continue?`;
4459
4532
  };
4460
4533
  return displayNames[fieldName] || fieldName;
4461
4534
  }
4462
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableService }, { token: i3.LabGroupService }], target: i0.ɵɵFactoryTarget.Component });
4463
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataTableEditModal, isStandalone: true, selector: "ccv-metadata-table-edit-modal", inputs: { table: "table", isCreateMode: "isCreateMode" }, outputs: { tableSaved: "tableSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-table 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\n <!-- Table Name -->\n <div class=\"mb-3\">\n <label for=\"tableName\" class=\"form-label\">\n Table Name *\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"tableName\"\n formControlName=\"name\"\n [class.is-invalid]=\"isFieldInvalid('name')\"\n placeholder=\"Enter table name\"\n maxlength=\"255\">\n @if (isFieldInvalid('name')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n }\n </div>\n\n <!-- Description -->\n <div class=\"mb-3\">\n <label for=\"tableDescription\" class=\"form-label\">\n Description\n </label>\n <textarea\n class=\"form-control\"\n id=\"tableDescription\"\n formControlName=\"description\"\n rows=\"3\"\n placeholder=\"Optional description of the metadata table\"></textarea>\n <div class=\"form-text\">\n Provide a brief description of what this metadata table contains or its purpose.\n </div>\n </div>\n\n <!-- Sample Count and Version Row -->\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <label for=\"sampleCount\" class=\"form-label\">\n Sample Count *\n </label>\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"sampleCount\"\n formControlName=\"sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('sampleCount')\"\n placeholder=\"Number of samples\"\n min=\"1\"\n max=\"10000\">\n @if (isFieldInvalid('sampleCount')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('sampleCount') }}\n </div>\n }\n <div class=\"form-text\">\n Number of samples in this metadata table (1-10,000).\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"version\" class=\"form-label\">\n Version\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"version\"\n formControlName=\"version\"\n [class.is-invalid]=\"isFieldInvalid('version')\"\n placeholder=\"e.g., 1.0, v2.1\"\n maxlength=\"50\">\n @if (isFieldInvalid('version')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('version') }}\n </div>\n }\n <div class=\"form-text\">\n Version identifier for this table.\n </div>\n </div>\n </div>\n\n <!-- Lab Group Assignment -->\n <div class=\"mb-3\">\n <label for=\"labGroup\" class=\"form-label\">\n Lab Group Assignment\n </label>\n @if (isLoadingLabGroups()) {\n <div class=\"d-flex align-items-center\">\n <div class=\"spinner-border spinner-border-sm me-2\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <span class=\"text-muted\">Loading lab groups...</span>\n </div>\n } @else {\n <select\n class=\"form-select\"\n id=\"labGroup\"\n formControlName=\"labGroup\">\n <option [value]=\"null\">No lab group (Personal)</option>\n @for (group of availableLabGroups(); track group.id) {\n <option [value]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n }\n <div class=\"form-text\">\n Assign this table to a lab group for shared access, or leave unassigned for personal use.\n </div>\n </div>\n\n <!-- Table Status Info (Read-only) -->\n @if (table) {\n <div class=\"mb-3\">\n <label class=\"form-label\">Current Status</label>\n <div class=\"d-flex 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 } @else {\n <span class=\"badge bg-success\">\n <i class=\"bi bi-unlock me-1\"></i>Unlocked\n </span>\n }\n @if (table.isPublished) {\n <span class=\"badge bg-info\">\n <i class=\"bi bi-check-circle me-1\"></i>Published\n </span>\n } @else {\n <span class=\"badge bg-secondary\">\n <i class=\"bi bi-clock me-1\"></i>Draft\n </span>\n }\n </div>\n <div class=\"form-text\">\n Lock status and publication state can be managed through advanced options.\n </div>\n </div>\n }\n\n <!-- Character Count -->\n <div class=\"mb-2\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Name: {{ editForm.get('name')?.value?.length || 0 }}/255 characters\n </small>\n @if (hasChanges) {\n <small class=\"text-info\">\n <i class=\"bi bi-info-circle me-1\"></i>Changes detected\n </small>\n }\n </div>\n </div>\n\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() || (!isCreateMode && !hasChanges)\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n @if (isCreateMode) {\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Table\n } @else {\n <i class=\"bi bi-check-circle me-1\"></i>\n Save Changes\n }\n </button>\n</div>\n", styles: [""], 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][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.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { 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 }] });
4535
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableService }, { token: i3.LabGroupService }], target: i0.ɵɵFactoryTarget.Component });
4536
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: MetadataTableEditModal, isStandalone: true, selector: "ccv-metadata-table-edit-modal", inputs: { table: "table", isCreateMode: "isCreateMode" }, outputs: { tableSaved: "tableSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-table 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\n <!-- Table Name -->\n <div class=\"mb-3\">\n <label for=\"tableName\" class=\"form-label\">\n Table Name *\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"tableName\"\n formControlName=\"name\"\n [class.is-invalid]=\"isFieldInvalid('name')\"\n placeholder=\"Enter table name\"\n maxlength=\"255\">\n @if (isFieldInvalid('name')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n }\n </div>\n\n <!-- Description -->\n <div class=\"mb-3\">\n <label for=\"tableDescription\" class=\"form-label\">\n Description\n </label>\n <textarea\n class=\"form-control\"\n id=\"tableDescription\"\n formControlName=\"description\"\n rows=\"3\"\n placeholder=\"Optional description of the metadata table\"></textarea>\n <div class=\"form-text\">\n Provide a brief description of what this metadata table contains or its purpose.\n </div>\n </div>\n\n <!-- Sample Count and Version Row -->\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <label for=\"sampleCount\" class=\"form-label\">\n Sample Count *\n </label>\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"sampleCount\"\n formControlName=\"sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('sampleCount')\"\n placeholder=\"Number of samples\"\n min=\"1\"\n max=\"10000\">\n @if (isFieldInvalid('sampleCount')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('sampleCount') }}\n </div>\n }\n <div class=\"form-text\">\n Number of samples in this metadata table (1-10,000).\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"version\" class=\"form-label\">\n Version\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"version\"\n formControlName=\"version\"\n [class.is-invalid]=\"isFieldInvalid('version')\"\n placeholder=\"e.g., 1.0, v2.1\"\n maxlength=\"50\">\n @if (isFieldInvalid('version')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('version') }}\n </div>\n }\n <div class=\"form-text\">\n Version identifier for this table.\n </div>\n </div>\n </div>\n\n <!-- Lab Group Assignment -->\n <div class=\"mb-3\">\n <label for=\"labGroup\" class=\"form-label\">\n Lab Group Assignment\n </label>\n @if (isLoadingLabGroups()) {\n <div class=\"d-flex align-items-center\">\n <div class=\"spinner-border spinner-border-sm me-2\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <span class=\"text-muted\">Loading lab groups...</span>\n </div>\n } @else {\n <select\n class=\"form-select\"\n id=\"labGroup\"\n formControlName=\"labGroup\">\n <option [value]=\"null\">No lab group (Personal)</option>\n @for (group of availableLabGroups(); track group.id) {\n <option [value]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n }\n <div class=\"form-text\">\n Assign this table to a lab group for shared access, or leave unassigned for personal use.\n </div>\n </div>\n\n <!-- Table Status Info (Read-only) -->\n @if (table) {\n <div class=\"mb-3\">\n <label class=\"form-label\">Current Status</label>\n <div class=\"d-flex 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 } @else {\n <span class=\"badge bg-success\">\n <i class=\"bi bi-unlock me-1\"></i>Unlocked\n </span>\n }\n @if (table.isPublished) {\n <span class=\"badge bg-info\">\n <i class=\"bi bi-check-circle me-1\"></i>Published\n </span>\n } @else {\n <span class=\"badge bg-secondary\">\n <i class=\"bi bi-clock me-1\"></i>Draft\n </span>\n }\n </div>\n <div class=\"form-text\">\n Lock status and publication state can be managed through advanced options.\n </div>\n </div>\n }\n\n <!-- Character Count -->\n <div class=\"mb-2\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Name: {{ editForm.get('name')?.value?.length || 0 }}/255 characters\n </small>\n @if (hasChanges) {\n <small class=\"text-info\">\n <i class=\"bi bi-info-circle me-1\"></i>Changes detected\n </small>\n }\n </div>\n </div>\n\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() || (!isCreateMode && !hasChanges)\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n @if (isCreateMode) {\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Table\n } @else {\n <i class=\"bi bi-check-circle me-1\"></i>\n Save Changes\n }\n </button>\n</div>\n", styles: [""], 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][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.MaxLengthValidator, selector: "[maxlength][formControlName],[maxlength][formControl],[maxlength][ngModel]", inputs: ["maxlength"] }, { kind: "directive", type: i1.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { 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 }] });
4464
4537
  }
4465
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableEditModal, decorators: [{
4538
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableEditModal, decorators: [{
4466
4539
  type: Component,
4467
4540
  args: [{ selector: 'ccv-metadata-table-edit-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-table 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\n <!-- Table Name -->\n <div class=\"mb-3\">\n <label for=\"tableName\" class=\"form-label\">\n Table Name *\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"tableName\"\n formControlName=\"name\"\n [class.is-invalid]=\"isFieldInvalid('name')\"\n placeholder=\"Enter table name\"\n maxlength=\"255\">\n @if (isFieldInvalid('name')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('name') }}\n </div>\n }\n </div>\n\n <!-- Description -->\n <div class=\"mb-3\">\n <label for=\"tableDescription\" class=\"form-label\">\n Description\n </label>\n <textarea\n class=\"form-control\"\n id=\"tableDescription\"\n formControlName=\"description\"\n rows=\"3\"\n placeholder=\"Optional description of the metadata table\"></textarea>\n <div class=\"form-text\">\n Provide a brief description of what this metadata table contains or its purpose.\n </div>\n </div>\n\n <!-- Sample Count and Version Row -->\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <label for=\"sampleCount\" class=\"form-label\">\n Sample Count *\n </label>\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"sampleCount\"\n formControlName=\"sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('sampleCount')\"\n placeholder=\"Number of samples\"\n min=\"1\"\n max=\"10000\">\n @if (isFieldInvalid('sampleCount')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('sampleCount') }}\n </div>\n }\n <div class=\"form-text\">\n Number of samples in this metadata table (1-10,000).\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"version\" class=\"form-label\">\n Version\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"version\"\n formControlName=\"version\"\n [class.is-invalid]=\"isFieldInvalid('version')\"\n placeholder=\"e.g., 1.0, v2.1\"\n maxlength=\"50\">\n @if (isFieldInvalid('version')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('version') }}\n </div>\n }\n <div class=\"form-text\">\n Version identifier for this table.\n </div>\n </div>\n </div>\n\n <!-- Lab Group Assignment -->\n <div class=\"mb-3\">\n <label for=\"labGroup\" class=\"form-label\">\n Lab Group Assignment\n </label>\n @if (isLoadingLabGroups()) {\n <div class=\"d-flex align-items-center\">\n <div class=\"spinner-border spinner-border-sm me-2\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n <span class=\"text-muted\">Loading lab groups...</span>\n </div>\n } @else {\n <select\n class=\"form-select\"\n id=\"labGroup\"\n formControlName=\"labGroup\">\n <option [value]=\"null\">No lab group (Personal)</option>\n @for (group of availableLabGroups(); track group.id) {\n <option [value]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n }\n <div class=\"form-text\">\n Assign this table to a lab group for shared access, or leave unassigned for personal use.\n </div>\n </div>\n\n <!-- Table Status Info (Read-only) -->\n @if (table) {\n <div class=\"mb-3\">\n <label class=\"form-label\">Current Status</label>\n <div class=\"d-flex 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 } @else {\n <span class=\"badge bg-success\">\n <i class=\"bi bi-unlock me-1\"></i>Unlocked\n </span>\n }\n @if (table.isPublished) {\n <span class=\"badge bg-info\">\n <i class=\"bi bi-check-circle me-1\"></i>Published\n </span>\n } @else {\n <span class=\"badge bg-secondary\">\n <i class=\"bi bi-clock me-1\"></i>Draft\n </span>\n }\n </div>\n <div class=\"form-text\">\n Lock status and publication state can be managed through advanced options.\n </div>\n </div>\n }\n\n <!-- Character Count -->\n <div class=\"mb-2\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Name: {{ editForm.get('name')?.value?.length || 0 }}/255 characters\n </small>\n @if (hasChanges) {\n <small class=\"text-info\">\n <i class=\"bi bi-info-circle me-1\"></i>Changes detected\n </small>\n }\n </div>\n </div>\n\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() || (!isCreateMode && !hasChanges)\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n @if (isCreateMode) {\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Table\n } @else {\n <i class=\"bi bi-check-circle me-1\"></i>\n Save Changes\n }\n </button>\n</div>\n" }]
4468
4541
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableService }, { type: i3.LabGroupService }], propDecorators: { table: [{
@@ -5044,10 +5117,10 @@ class ColumnEditModal {
5044
5117
  }
5045
5118
  return columnType;
5046
5119
  }
5047
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ColumnEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataColumnTemplateService }, { token: SdrfSyntaxService }], target: i0.ɵɵFactoryTarget.Component });
5048
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: ColumnEditModal, isStandalone: true, selector: "ccv-column-edit-modal", inputs: { column: "column", templateId: "templateId", isEdit: "isEdit" }, outputs: { columnSaved: "columnSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-columns-gap 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 <!-- Selection Required Notice for New Columns -->\n @if (!isEdit) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-warning\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>Column Selection Required\n </h6>\n <p class=\"mb-0\">\n You must select from official SDRF columns or existing templates to ensure data compliance and consistency.\n </p>\n </div>\n </div>\n }\n\n <!-- Official SDRF Columns -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-file-earmark-check me-2\"></i>Official SDRF Columns\n <small class=\"text-muted ms-2\">\n @if (!isEdit) {\n (Step 1: Select from standardized SDRF-Proteomics column names)\n } @else {\n (Recommended - select from standardized SDRF-Proteomics column names)\n }\n </small>\n </h6>\n </div>\n\n @if (!isEdit) {\n <div class=\"col-12 mb-3\">\n @if (!showOfficialColumns) {\n <button\n type=\"button\"\n class=\"btn btn-primary mb-3\"\n (click)=\"toggleOfficialColumns()\">\n <i class=\"bi bi-list-ul me-2\"></i>\n Select from Official SDRF Columns\n </button>\n } @else {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary mb-3\"\n (click)=\"toggleOfficialColumns()\">\n <i class=\"bi bi-x me-2\"></i>\n Hide Official Columns\n </button>\n }\n </div>\n } @else {\n <div class=\"col-12 mb-3\">\n <button\n type=\"button\"\n class=\"btn btn-outline-info mb-3\"\n (click)=\"toggleOfficialColumns()\">\n <i class=\"bi bi-list-ul me-2\"></i>\n {{ showOfficialColumns ? 'Hide' : 'Show' }} Official Column Names\n </button>\n </div>\n }\n\n @if (showOfficialColumns) {\n <div class=\"col-12 mb-3\">\n <div class=\"border rounded p-3\">\n <p class=\"mb-3 text-muted\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Select from official SDRF-Proteomics column names. Only columns without existing templates are shown.\n </p>\n\n @if (availableOfficialColumns.length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n All official SDRF columns already have templates created.\n </div>\n } @else {\n <div class=\"row\">\n @for (column of availableOfficialColumns; track column.name) {\n <div class=\"col-md-6 mb-2\">\n <div class=\"card h-100 cursor-pointer border-info\"\n (click)=\"selectOfficialColumn(column)\"\n [class.border-primary]=\"selectedOfficialColumn?.name === column.name\">\n <div class=\"card-body py-2\">\n <h6 class=\"card-title mb-1 font-monospace\">\n {{ column.name }}\n </h6>\n <p class=\"card-text small mb-1\">\n <span class=\"badge\"\n [class]=\"column.type === 'characteristics' ? 'bg-primary' :\n column.type === 'comment' ? 'bg-success' :\n column.type === 'factor_value' ? 'bg-warning' : 'bg-secondary'\">\n {{ column.type }}\n </span>\n </p>\n <p class=\"card-text small mb-0\">{{ column.description }}</p>\n </div>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Template Swapping for Edit Mode -->\n @if (isEdit && showTemplateSwap) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-arrow-left-right me-2\"></i>Swap to Different Template\n </h6>\n <p class=\"mb-3\">\n Choose a different template to change the column name and settings. Your current value will be preserved.\n </p>\n\n <div class=\"mb-3\">\n <input\n type=\"text\"\n class=\"form-control mb-2\"\n placeholder=\"Search templates...\"\n [(ngModel)]=\"templateFilter\"\n [ngModelOptions]=\"{standalone: true}\"\n (input)=\"onTemplateFilterChange()\">\n\n <select\n class=\"form-select mb-3\"\n size=\"4\"\n [disabled]=\"isLoadingTemplates\">\n @if (isLoadingTemplates) {\n <option value=\"\" disabled>Loading templates...</option>\n } @else {\n @for (template of templates; track template.id) {\n <option\n [value]=\"template.id\"\n (click)=\"swapToTemplate(template)\"\n style=\"cursor: pointer;\">\n {{ template.columnName }} - {{ template.columnType }}@if (template.ontologyType) { ({{ getOntologyTypeLabel(template.ontologyType) }})}\n </option>\n }\n }\n </select>\n </div>\n\n <div class=\"d-flex justify-content-between\">\n <button\n type=\"button\"\n class=\"btn btn-success\"\n (click)=\"showCreateCustomTemplateOption()\">\n <i class=\"bi bi-plus-circle me-1\"></i>Create Custom Template\n </button>\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"cancelTemplateSwap()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Custom Template Creation Option -->\n @if (isEdit && showCreateCustomTemplate) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-warning\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Custom Template\n </h6>\n <p class=\"mb-3\">\n This will create a new custom template with your desired column name and settings.\n </p>\n\n <div class=\"d-flex justify-content-between\">\n <button\n type=\"button\"\n class=\"btn btn-warning\"\n (click)=\"createCustomTemplate()\">\n <i class=\"bi bi-gear me-1\"></i>Create & Apply Template\n </button>\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"cancelTemplateSwap()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Template Selection -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Template Selection\n <small class=\"text-muted ms-2\">\n @if (!isEdit) {\n (Step 2: Or select from existing templates)\n } @else {\n (Optional - select a template to auto-fill column settings)\n }\n </small>\n </h6>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"input-group mb-3\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input \n type=\"text\" \n class=\"form-control\" \n placeholder=\"Search column templates...\"\n [(ngModel)]=\"templateFilter\"\n [ngModelOptions]=\"{standalone: true}\"\n (input)=\"onTemplateFilterChange()\">\n @if (templateFilter) {\n <button class=\"btn btn-outline-secondary\" type=\"button\" (click)=\"clearTemplateFilter()\">\n <i class=\"bi bi-x\"></i>\n </button>\n }\n </div>\n\n <select \n class=\"form-select mb-3\"\n size=\"5\"\n [(ngModel)]=\"selectedTemplateId\"\n [ngModelOptions]=\"{standalone: true}\"\n (change)=\"onTemplateSelectionChange()\"\n [disabled]=\"isLoadingTemplates\">\n <option value=\"\">No template selected</option>\n @if (isLoadingTemplates) {\n <option value=\"\" disabled>Loading templates...</option>\n } @else {\n @for (template of templates; track template.id) {\n <option [value]=\"template.id\">\n {{ template.columnName }} - {{ template.columnType }}@if (template.ontologyType) { ({{ getOntologyTypeLabel(template.ontologyType) }})}\n </option>\n }\n }\n </select>\n\n @if (totalTemplatePages > 1) {\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Showing {{ (currentTemplatePage - 1) * templatePageSize + 1 }}-{{ Math.min(currentTemplatePage * templatePageSize, totalTemplates) }}\n of {{ totalTemplates }}\n </small>\n <ngb-pagination\n [collectionSize]=\"totalTemplates\"\n [page]=\"currentTemplatePage\"\n [pageSize]=\"templatePageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onTemplatePageChange($event)\"\n class=\"pagination-sm mb-0\">\n </ngb-pagination>\n </div>\n }\n\n @if (selectedTemplate) {\n <div class=\"alert alert-info mt-3\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Template \"{{ selectedTemplate.name }}\" selected. Settings will be applied automatically.\n <button type=\"button\" class=\"btn btn-sm btn-outline-info float-end\" (click)=\"clearTemplate()\">\n Clear Selection\n </button>\n </div>\n }\n </div>\n\n <!-- Manual Entry -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-pencil me-2\"></i>Column Details\n @if (selectedTemplate) {\n <small class=\"text-muted ms-2\">(Auto-filled from template - you can modify as needed)</small>\n }\n </h6>\n </div>\n \n <div class=\"col-12 mb-3\">\n <label for=\"columnName\" class=\"form-label\">\n Column Name *\n <span class=\"text-muted ms-2\">\n <i class=\"bi bi-info-circle\"></i>\n Column names are managed through templates\n </span>\n </label>\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"columnName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n readonly\n placeholder=\"Select from official columns or templates below\">\n @if (isEdit) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"showTemplateSwapOptions()\"\n title=\"Change to different template\">\n <i class=\"bi bi-arrow-left-right\"></i> Swap Template\n </button>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n @if (isEdit) {\n To change the column name, swap to a different template or create a new custom template.\n } @else {\n Select from official SDRF columns or existing templates below to ensure compliance.\n }\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnType\" class=\"form-label\">\n Column Type *\n <span class=\"text-muted ms-2\">\n <i class=\"bi bi-info-circle\"></i>\n Managed through templates\n </span>\n </label>\n <select\n class=\"form-select\"\n id=\"columnType\"\n formControlName=\"type\"\n [class.is-invalid]=\"editForm.get('type')?.invalid && editForm.get('type')?.touched\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n </div>\n\n <!-- Advanced Mode Toggle -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"advancedMode\"\n [checked]=\"showAdvancedMode()\"\n (change)=\"toggleAdvancedMode()\">\n <label class=\"form-check-label\" for=\"advancedMode\">\n <i class=\"bi bi-gear me-1\"></i>Advanced Mode\n </label>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Show advanced options for ontology configuration and column settings\n </div>\n </div>\n </div>\n\n @if (showAdvancedMode()) {\n <div class=\"col-md-6 mb-3\">\n <label for=\"ontologyType\" class=\"form-label\">\n Ontology Type\n <span class=\"badge bg-warning ms-2\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>Advanced\n </span>\n </label>\n <select\n class=\"form-select\"\n id=\"ontologyType\"\n formControlName=\"ontologyType\"\n (change)=\"onOntologyTypeChange($event)\">\n @for (ontology of ontologyTypes; track $index) {\n <option [value]=\"$index\">{{ ontology.label }}</option>\n }\n </select>\n\n @if (hasOntologyType) {\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Custom ontology filters will be applied based on the selected type\n </div>\n }\n </div>\n\n @if (hasOntologyType) {\n <div class=\"col-12 mb-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"enableTypeahead\"\n formControlName=\"enableTypeahead\">\n <label class=\"form-check-label\" for=\"enableTypeahead\">\n <i class=\"bi bi-search me-1\"></i>Enable Typeahead Autocomplete\n </label>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Enable autocomplete suggestions from {{ selectedOntologyConfig?.label || 'selected' }} ontology when editing values\n </div>\n </div>\n </div>\n }\n\n <!-- Column Options - Advanced Mode Only -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-sliders me-2\"></i>Column Options\n <span class=\"badge bg-warning ms-2\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>Advanced\n </span>\n </h6>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"mandatory\"\n formControlName=\"mandatory\">\n <label class=\"form-check-label\" for=\"mandatory\">\n <i class=\"bi bi-exclamation-circle me-1\"></i>Mandatory\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"hidden\"\n formControlName=\"hidden\">\n <label class=\"form-check-label\" for=\"hidden\">\n <i class=\"bi bi-eye-slash me-1\"></i>Hidden\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"readonly\"\n formControlName=\"readonly\">\n <label class=\"form-check-label\" for=\"readonly\">\n <i class=\"bi bi-lock me-1\"></i>Read Only\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notApplicable\"\n formControlName=\"notApplicable\">\n <label class=\"form-check-label\" for=\"notApplicable\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notAvailable\"\n formControlName=\"notAvailable\">\n <label class=\"form-check-label\" for=\"notAvailable\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"staffOnly\"\n formControlName=\"staffOnly\">\n <label class=\"form-check-label\" for=\"staffOnly\">\n <i class=\"bi bi-shield-lock me-1\"></i>Staff Only\n </label>\n </div>\n </div>\n </div>\n <div class=\"row mt-2\">\n <div class=\"col-12\">\n <div class=\"alert alert-info py-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n <small>\n <strong>Staff Only:</strong> When enabled, this column will only be visible and editable by staff members.\n </small>\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <label for=\"defaultValue\" class=\"form-label\">\n Default Value\n @if (hasTypeaheadEnabled) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Typeahead Enabled\n </span>\n }\n </label>\n\n @if (editForm.get('notApplicable')?.value || editForm.get('notAvailable')?.value) {\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Quick Select:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (editForm.get('notApplicable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (editForm.get('notAvailable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n\n <div class=\"input-group\">\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"value\"\n placeholder=\"Enter default value\"\n readonly>\n <button \n type=\"button\" \n class=\"btn btn-outline-primary\"\n (click)=\"openValueEditModal()\"\n title=\"Open advanced value editor\">\n <i class=\"bi bi-pencil me-1\"></i>Edit\n </button>\n </div>\n \n <div class=\"form-text\">\n @if (hasTypeaheadEnabled) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to use autocomplete suggestions from {{ selectedOntologyConfig?.label || 'selected' }} ontology\n } @else if (hasOntologyType) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set default value. Enable typeahead in Advanced Mode for autocomplete suggestions.\n } @else if (showAdvancedMode()) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set the default value (select an ontology type above for smart suggestions)\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set the default value (enable Advanced Mode for ontology autocomplete)\n }\n </div>\n </div>\n\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' : 'Add' }} Column\n </button>\n</div>\n", styles: [".template-selection-container{border:1px solid var(--bs-border-color);border-radius:.375rem;padding:.5rem}.template-item{cursor:pointer;transition:all .2s ease}.template-item:hover{background-color:rgba(var(--bs-primary-rgb),.1);border-color:var(--bs-primary)!important}.template-item.selected{background-color:rgba(var(--bs-primary-rgb),.15);border-color:var(--bs-primary)!important;box-shadow:0 0 0 .2rem rgba(var(--bs-primary-rgb),.25)}.cursor-pointer{cursor:pointer}.card.cursor-pointer{transition:all .2s ease}.card.cursor-pointer:hover{transform:translateY(-2px);box-shadow:0 4px 8px #0000001a}.card.cursor-pointer.border-info{border-width:2px}.modal-lg{max-width:900px}\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: FormsModule }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }] });
5120
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ColumnEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataColumnTemplateService }, { token: SdrfSyntaxService }], target: i0.ɵɵFactoryTarget.Component });
5121
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ColumnEditModal, isStandalone: true, selector: "ccv-column-edit-modal", inputs: { column: "column", templateId: "templateId", isEdit: "isEdit" }, outputs: { columnSaved: "columnSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-columns-gap 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 <!-- Selection Required Notice for New Columns -->\n @if (!isEdit) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-warning\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>Column Selection Required\n </h6>\n <p class=\"mb-0\">\n You must select from official SDRF columns or existing templates to ensure data compliance and consistency.\n </p>\n </div>\n </div>\n }\n\n <!-- Official SDRF Columns -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-file-earmark-check me-2\"></i>Official SDRF Columns\n <small class=\"text-muted ms-2\">\n @if (!isEdit) {\n (Step 1: Select from standardized SDRF-Proteomics column names)\n } @else {\n (Recommended - select from standardized SDRF-Proteomics column names)\n }\n </small>\n </h6>\n </div>\n\n @if (!isEdit) {\n <div class=\"col-12 mb-3\">\n @if (!showOfficialColumns) {\n <button\n type=\"button\"\n class=\"btn btn-primary mb-3\"\n (click)=\"toggleOfficialColumns()\">\n <i class=\"bi bi-list-ul me-2\"></i>\n Select from Official SDRF Columns\n </button>\n } @else {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary mb-3\"\n (click)=\"toggleOfficialColumns()\">\n <i class=\"bi bi-x me-2\"></i>\n Hide Official Columns\n </button>\n }\n </div>\n } @else {\n <div class=\"col-12 mb-3\">\n <button\n type=\"button\"\n class=\"btn btn-outline-info mb-3\"\n (click)=\"toggleOfficialColumns()\">\n <i class=\"bi bi-list-ul me-2\"></i>\n {{ showOfficialColumns ? 'Hide' : 'Show' }} Official Column Names\n </button>\n </div>\n }\n\n @if (showOfficialColumns) {\n <div class=\"col-12 mb-3\">\n <div class=\"border rounded p-3\">\n <p class=\"mb-3 text-muted\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Select from official SDRF-Proteomics column names. Only columns without existing templates are shown.\n </p>\n\n @if (availableOfficialColumns.length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n All official SDRF columns already have templates created.\n </div>\n } @else {\n <div class=\"row\">\n @for (column of availableOfficialColumns; track column.name) {\n <div class=\"col-md-6 mb-2\">\n <div class=\"card h-100 cursor-pointer border-info\"\n (click)=\"selectOfficialColumn(column)\"\n [class.border-primary]=\"selectedOfficialColumn?.name === column.name\">\n <div class=\"card-body py-2\">\n <h6 class=\"card-title mb-1 font-monospace\">\n {{ column.name }}\n </h6>\n <p class=\"card-text small mb-1\">\n <span class=\"badge\"\n [class]=\"column.type === 'characteristics' ? 'bg-primary' :\n column.type === 'comment' ? 'bg-success' :\n column.type === 'factor_value' ? 'bg-warning' : 'bg-secondary'\">\n {{ column.type }}\n </span>\n </p>\n <p class=\"card-text small mb-0\">{{ column.description }}</p>\n </div>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Template Swapping for Edit Mode -->\n @if (isEdit && showTemplateSwap) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-arrow-left-right me-2\"></i>Swap to Different Template\n </h6>\n <p class=\"mb-3\">\n Choose a different template to change the column name and settings. Your current value will be preserved.\n </p>\n\n <div class=\"mb-3\">\n <input\n type=\"text\"\n class=\"form-control mb-2\"\n placeholder=\"Search templates...\"\n [(ngModel)]=\"templateFilter\"\n [ngModelOptions]=\"{standalone: true}\"\n (input)=\"onTemplateFilterChange()\">\n\n <select\n class=\"form-select mb-3\"\n size=\"4\"\n [disabled]=\"isLoadingTemplates\">\n @if (isLoadingTemplates) {\n <option value=\"\" disabled>Loading templates...</option>\n } @else {\n @for (template of templates; track template.id) {\n <option\n [value]=\"template.id\"\n (click)=\"swapToTemplate(template)\"\n style=\"cursor: pointer;\">\n {{ template.columnName }} - {{ template.columnType }}@if (template.ontologyType) { ({{ getOntologyTypeLabel(template.ontologyType) }})}\n </option>\n }\n }\n </select>\n </div>\n\n <div class=\"d-flex justify-content-between\">\n <button\n type=\"button\"\n class=\"btn btn-success\"\n (click)=\"showCreateCustomTemplateOption()\">\n <i class=\"bi bi-plus-circle me-1\"></i>Create Custom Template\n </button>\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"cancelTemplateSwap()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Custom Template Creation Option -->\n @if (isEdit && showCreateCustomTemplate) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-warning\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Custom Template\n </h6>\n <p class=\"mb-3\">\n This will create a new custom template with your desired column name and settings.\n </p>\n\n <div class=\"d-flex justify-content-between\">\n <button\n type=\"button\"\n class=\"btn btn-warning\"\n (click)=\"createCustomTemplate()\">\n <i class=\"bi bi-gear me-1\"></i>Create & Apply Template\n </button>\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"cancelTemplateSwap()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Template Selection -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Template Selection\n <small class=\"text-muted ms-2\">\n @if (!isEdit) {\n (Step 2: Or select from existing templates)\n } @else {\n (Optional - select a template to auto-fill column settings)\n }\n </small>\n </h6>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"input-group mb-3\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input \n type=\"text\" \n class=\"form-control\" \n placeholder=\"Search column templates...\"\n [(ngModel)]=\"templateFilter\"\n [ngModelOptions]=\"{standalone: true}\"\n (input)=\"onTemplateFilterChange()\">\n @if (templateFilter) {\n <button class=\"btn btn-outline-secondary\" type=\"button\" (click)=\"clearTemplateFilter()\">\n <i class=\"bi bi-x\"></i>\n </button>\n }\n </div>\n\n <select \n class=\"form-select mb-3\"\n size=\"5\"\n [(ngModel)]=\"selectedTemplateId\"\n [ngModelOptions]=\"{standalone: true}\"\n (change)=\"onTemplateSelectionChange()\"\n [disabled]=\"isLoadingTemplates\">\n <option value=\"\">No template selected</option>\n @if (isLoadingTemplates) {\n <option value=\"\" disabled>Loading templates...</option>\n } @else {\n @for (template of templates; track template.id) {\n <option [value]=\"template.id\">\n {{ template.columnName }} - {{ template.columnType }}@if (template.ontologyType) { ({{ getOntologyTypeLabel(template.ontologyType) }})}\n </option>\n }\n }\n </select>\n\n @if (totalTemplatePages > 1) {\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Showing {{ (currentTemplatePage - 1) * templatePageSize + 1 }}-{{ Math.min(currentTemplatePage * templatePageSize, totalTemplates) }}\n of {{ totalTemplates }}\n </small>\n <ngb-pagination\n [collectionSize]=\"totalTemplates\"\n [page]=\"currentTemplatePage\"\n [pageSize]=\"templatePageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onTemplatePageChange($event)\"\n class=\"pagination-sm mb-0\">\n </ngb-pagination>\n </div>\n }\n\n @if (selectedTemplate) {\n <div class=\"alert alert-info mt-3\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Template \"{{ selectedTemplate.name }}\" selected. Settings will be applied automatically.\n <button type=\"button\" class=\"btn btn-sm btn-outline-info float-end\" (click)=\"clearTemplate()\">\n Clear Selection\n </button>\n </div>\n }\n </div>\n\n <!-- Manual Entry -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-pencil me-2\"></i>Column Details\n @if (selectedTemplate) {\n <small class=\"text-muted ms-2\">(Auto-filled from template - you can modify as needed)</small>\n }\n </h6>\n </div>\n \n <div class=\"col-12 mb-3\">\n <label for=\"columnName\" class=\"form-label\">\n Column Name *\n <span class=\"text-muted ms-2\">\n <i class=\"bi bi-info-circle\"></i>\n Column names are managed through templates\n </span>\n </label>\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"columnName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n readonly\n placeholder=\"Select from official columns or templates below\">\n @if (isEdit) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"showTemplateSwapOptions()\"\n title=\"Change to different template\">\n <i class=\"bi bi-arrow-left-right\"></i> Swap Template\n </button>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n @if (isEdit) {\n To change the column name, swap to a different template or create a new custom template.\n } @else {\n Select from official SDRF columns or existing templates below to ensure compliance.\n }\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnType\" class=\"form-label\">\n Column Type *\n <span class=\"text-muted ms-2\">\n <i class=\"bi bi-info-circle\"></i>\n Managed through templates\n </span>\n </label>\n <select\n class=\"form-select\"\n id=\"columnType\"\n formControlName=\"type\"\n [class.is-invalid]=\"editForm.get('type')?.invalid && editForm.get('type')?.touched\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n </div>\n\n <!-- Advanced Mode Toggle -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"advancedMode\"\n [checked]=\"showAdvancedMode()\"\n (change)=\"toggleAdvancedMode()\">\n <label class=\"form-check-label\" for=\"advancedMode\">\n <i class=\"bi bi-gear me-1\"></i>Advanced Mode\n </label>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Show advanced options for ontology configuration and column settings\n </div>\n </div>\n </div>\n\n @if (showAdvancedMode()) {\n <div class=\"col-md-6 mb-3\">\n <label for=\"ontologyType\" class=\"form-label\">\n Ontology Type\n <span class=\"badge bg-warning ms-2\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>Advanced\n </span>\n </label>\n <select\n class=\"form-select\"\n id=\"ontologyType\"\n formControlName=\"ontologyType\"\n (change)=\"onOntologyTypeChange($event)\">\n @for (ontology of ontologyTypes; track $index) {\n <option [value]=\"$index\">{{ ontology.label }}</option>\n }\n </select>\n\n @if (hasOntologyType) {\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Custom ontology filters will be applied based on the selected type\n </div>\n }\n </div>\n\n @if (hasOntologyType) {\n <div class=\"col-12 mb-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"enableTypeahead\"\n formControlName=\"enableTypeahead\">\n <label class=\"form-check-label\" for=\"enableTypeahead\">\n <i class=\"bi bi-search me-1\"></i>Enable Typeahead Autocomplete\n </label>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Enable autocomplete suggestions from {{ selectedOntologyConfig?.label || 'selected' }} ontology when editing values\n </div>\n </div>\n </div>\n }\n\n <!-- Column Options - Advanced Mode Only -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-sliders me-2\"></i>Column Options\n <span class=\"badge bg-warning ms-2\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>Advanced\n </span>\n </h6>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"mandatory\"\n formControlName=\"mandatory\">\n <label class=\"form-check-label\" for=\"mandatory\">\n <i class=\"bi bi-exclamation-circle me-1\"></i>Mandatory\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"hidden\"\n formControlName=\"hidden\">\n <label class=\"form-check-label\" for=\"hidden\">\n <i class=\"bi bi-eye-slash me-1\"></i>Hidden\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"readonly\"\n formControlName=\"readonly\">\n <label class=\"form-check-label\" for=\"readonly\">\n <i class=\"bi bi-lock me-1\"></i>Read Only\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notApplicable\"\n formControlName=\"notApplicable\">\n <label class=\"form-check-label\" for=\"notApplicable\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notAvailable\"\n formControlName=\"notAvailable\">\n <label class=\"form-check-label\" for=\"notAvailable\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"staffOnly\"\n formControlName=\"staffOnly\">\n <label class=\"form-check-label\" for=\"staffOnly\">\n <i class=\"bi bi-shield-lock me-1\"></i>Staff Only\n </label>\n </div>\n </div>\n </div>\n <div class=\"row mt-2\">\n <div class=\"col-12\">\n <div class=\"alert alert-info py-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n <small>\n <strong>Staff Only:</strong> When enabled, this column will only be visible and editable by staff members.\n </small>\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <label for=\"defaultValue\" class=\"form-label\">\n Default Value\n @if (hasTypeaheadEnabled) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Typeahead Enabled\n </span>\n }\n </label>\n\n @if (editForm.get('notApplicable')?.value || editForm.get('notAvailable')?.value) {\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Quick Select:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (editForm.get('notApplicable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (editForm.get('notAvailable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n\n <div class=\"input-group\">\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"value\"\n placeholder=\"Enter default value\"\n readonly>\n <button \n type=\"button\" \n class=\"btn btn-outline-primary\"\n (click)=\"openValueEditModal()\"\n title=\"Open advanced value editor\">\n <i class=\"bi bi-pencil me-1\"></i>Edit\n </button>\n </div>\n \n <div class=\"form-text\">\n @if (hasTypeaheadEnabled) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to use autocomplete suggestions from {{ selectedOntologyConfig?.label || 'selected' }} ontology\n } @else if (hasOntologyType) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set default value. Enable typeahead in Advanced Mode for autocomplete suggestions.\n } @else if (showAdvancedMode()) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set the default value (select an ontology type above for smart suggestions)\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set the default value (enable Advanced Mode for ontology autocomplete)\n }\n </div>\n </div>\n\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' : 'Add' }} Column\n </button>\n</div>\n", styles: [".template-selection-container{border:1px solid var(--bs-border-color);border-radius:.375rem;padding:.5rem}.template-item{cursor:pointer;transition:all .2s ease}.template-item:hover{background-color:rgba(var(--bs-primary-rgb),.1);border-color:var(--bs-primary)!important}.template-item.selected{background-color:rgba(var(--bs-primary-rgb),.15);border-color:var(--bs-primary)!important;box-shadow:0 0 0 .2rem rgba(var(--bs-primary-rgb),.25)}.cursor-pointer{cursor:pointer}.card.cursor-pointer{transition:all .2s ease}.card.cursor-pointer:hover{transform:translateY(-2px);box-shadow:0 4px 8px #0000001a}.card.cursor-pointer.border-info{border-width:2px}.modal-lg{max-width:900px}\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: FormsModule }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }] });
5049
5122
  }
5050
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ColumnEditModal, decorators: [{
5123
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ColumnEditModal, decorators: [{
5051
5124
  type: Component,
5052
5125
  args: [{ selector: 'ccv-column-edit-modal', imports: [CommonModule, ReactiveFormsModule, FormsModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-columns-gap 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 <!-- Selection Required Notice for New Columns -->\n @if (!isEdit) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-warning\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>Column Selection Required\n </h6>\n <p class=\"mb-0\">\n You must select from official SDRF columns or existing templates to ensure data compliance and consistency.\n </p>\n </div>\n </div>\n }\n\n <!-- Official SDRF Columns -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-file-earmark-check me-2\"></i>Official SDRF Columns\n <small class=\"text-muted ms-2\">\n @if (!isEdit) {\n (Step 1: Select from standardized SDRF-Proteomics column names)\n } @else {\n (Recommended - select from standardized SDRF-Proteomics column names)\n }\n </small>\n </h6>\n </div>\n\n @if (!isEdit) {\n <div class=\"col-12 mb-3\">\n @if (!showOfficialColumns) {\n <button\n type=\"button\"\n class=\"btn btn-primary mb-3\"\n (click)=\"toggleOfficialColumns()\">\n <i class=\"bi bi-list-ul me-2\"></i>\n Select from Official SDRF Columns\n </button>\n } @else {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary mb-3\"\n (click)=\"toggleOfficialColumns()\">\n <i class=\"bi bi-x me-2\"></i>\n Hide Official Columns\n </button>\n }\n </div>\n } @else {\n <div class=\"col-12 mb-3\">\n <button\n type=\"button\"\n class=\"btn btn-outline-info mb-3\"\n (click)=\"toggleOfficialColumns()\">\n <i class=\"bi bi-list-ul me-2\"></i>\n {{ showOfficialColumns ? 'Hide' : 'Show' }} Official Column Names\n </button>\n </div>\n }\n\n @if (showOfficialColumns) {\n <div class=\"col-12 mb-3\">\n <div class=\"border rounded p-3\">\n <p class=\"mb-3 text-muted\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Select from official SDRF-Proteomics column names. Only columns without existing templates are shown.\n </p>\n\n @if (availableOfficialColumns.length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n All official SDRF columns already have templates created.\n </div>\n } @else {\n <div class=\"row\">\n @for (column of availableOfficialColumns; track column.name) {\n <div class=\"col-md-6 mb-2\">\n <div class=\"card h-100 cursor-pointer border-info\"\n (click)=\"selectOfficialColumn(column)\"\n [class.border-primary]=\"selectedOfficialColumn?.name === column.name\">\n <div class=\"card-body py-2\">\n <h6 class=\"card-title mb-1 font-monospace\">\n {{ column.name }}\n </h6>\n <p class=\"card-text small mb-1\">\n <span class=\"badge\"\n [class]=\"column.type === 'characteristics' ? 'bg-primary' :\n column.type === 'comment' ? 'bg-success' :\n column.type === 'factor_value' ? 'bg-warning' : 'bg-secondary'\">\n {{ column.type }}\n </span>\n </p>\n <p class=\"card-text small mb-0\">{{ column.description }}</p>\n </div>\n </div>\n </div>\n }\n </div>\n }\n </div>\n </div>\n }\n\n <!-- Template Swapping for Edit Mode -->\n @if (isEdit && showTemplateSwap) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-arrow-left-right me-2\"></i>Swap to Different Template\n </h6>\n <p class=\"mb-3\">\n Choose a different template to change the column name and settings. Your current value will be preserved.\n </p>\n\n <div class=\"mb-3\">\n <input\n type=\"text\"\n class=\"form-control mb-2\"\n placeholder=\"Search templates...\"\n [(ngModel)]=\"templateFilter\"\n [ngModelOptions]=\"{standalone: true}\"\n (input)=\"onTemplateFilterChange()\">\n\n <select\n class=\"form-select mb-3\"\n size=\"4\"\n [disabled]=\"isLoadingTemplates\">\n @if (isLoadingTemplates) {\n <option value=\"\" disabled>Loading templates...</option>\n } @else {\n @for (template of templates; track template.id) {\n <option\n [value]=\"template.id\"\n (click)=\"swapToTemplate(template)\"\n style=\"cursor: pointer;\">\n {{ template.columnName }} - {{ template.columnType }}@if (template.ontologyType) { ({{ getOntologyTypeLabel(template.ontologyType) }})}\n </option>\n }\n }\n </select>\n </div>\n\n <div class=\"d-flex justify-content-between\">\n <button\n type=\"button\"\n class=\"btn btn-success\"\n (click)=\"showCreateCustomTemplateOption()\">\n <i class=\"bi bi-plus-circle me-1\"></i>Create Custom Template\n </button>\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"cancelTemplateSwap()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Custom Template Creation Option -->\n @if (isEdit && showCreateCustomTemplate) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-warning\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Custom Template\n </h6>\n <p class=\"mb-3\">\n This will create a new custom template with your desired column name and settings.\n </p>\n\n <div class=\"d-flex justify-content-between\">\n <button\n type=\"button\"\n class=\"btn btn-warning\"\n (click)=\"createCustomTemplate()\">\n <i class=\"bi bi-gear me-1\"></i>Create & Apply Template\n </button>\n <button\n type=\"button\"\n class=\"btn btn-secondary\"\n (click)=\"cancelTemplateSwap()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n </div>\n </div>\n </div>\n }\n\n <!-- Template Selection -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Template Selection\n <small class=\"text-muted ms-2\">\n @if (!isEdit) {\n (Step 2: Or select from existing templates)\n } @else {\n (Optional - select a template to auto-fill column settings)\n }\n </small>\n </h6>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"input-group mb-3\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input \n type=\"text\" \n class=\"form-control\" \n placeholder=\"Search column templates...\"\n [(ngModel)]=\"templateFilter\"\n [ngModelOptions]=\"{standalone: true}\"\n (input)=\"onTemplateFilterChange()\">\n @if (templateFilter) {\n <button class=\"btn btn-outline-secondary\" type=\"button\" (click)=\"clearTemplateFilter()\">\n <i class=\"bi bi-x\"></i>\n </button>\n }\n </div>\n\n <select \n class=\"form-select mb-3\"\n size=\"5\"\n [(ngModel)]=\"selectedTemplateId\"\n [ngModelOptions]=\"{standalone: true}\"\n (change)=\"onTemplateSelectionChange()\"\n [disabled]=\"isLoadingTemplates\">\n <option value=\"\">No template selected</option>\n @if (isLoadingTemplates) {\n <option value=\"\" disabled>Loading templates...</option>\n } @else {\n @for (template of templates; track template.id) {\n <option [value]=\"template.id\">\n {{ template.columnName }} - {{ template.columnType }}@if (template.ontologyType) { ({{ getOntologyTypeLabel(template.ontologyType) }})}\n </option>\n }\n }\n </select>\n\n @if (totalTemplatePages > 1) {\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Showing {{ (currentTemplatePage - 1) * templatePageSize + 1 }}-{{ Math.min(currentTemplatePage * templatePageSize, totalTemplates) }}\n of {{ totalTemplates }}\n </small>\n <ngb-pagination\n [collectionSize]=\"totalTemplates\"\n [page]=\"currentTemplatePage\"\n [pageSize]=\"templatePageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onTemplatePageChange($event)\"\n class=\"pagination-sm mb-0\">\n </ngb-pagination>\n </div>\n }\n\n @if (selectedTemplate) {\n <div class=\"alert alert-info mt-3\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Template \"{{ selectedTemplate.name }}\" selected. Settings will be applied automatically.\n <button type=\"button\" class=\"btn btn-sm btn-outline-info float-end\" (click)=\"clearTemplate()\">\n Clear Selection\n </button>\n </div>\n }\n </div>\n\n <!-- Manual Entry -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-pencil me-2\"></i>Column Details\n @if (selectedTemplate) {\n <small class=\"text-muted ms-2\">(Auto-filled from template - you can modify as needed)</small>\n }\n </h6>\n </div>\n \n <div class=\"col-12 mb-3\">\n <label for=\"columnName\" class=\"form-label\">\n Column Name *\n <span class=\"text-muted ms-2\">\n <i class=\"bi bi-info-circle\"></i>\n Column names are managed through templates\n </span>\n </label>\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"columnName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n readonly\n placeholder=\"Select from official columns or templates below\">\n @if (isEdit) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"showTemplateSwapOptions()\"\n title=\"Change to different template\">\n <i class=\"bi bi-arrow-left-right\"></i> Swap Template\n </button>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n @if (isEdit) {\n To change the column name, swap to a different template or create a new custom template.\n } @else {\n Select from official SDRF columns or existing templates below to ensure compliance.\n }\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnType\" class=\"form-label\">\n Column Type *\n <span class=\"text-muted ms-2\">\n <i class=\"bi bi-info-circle\"></i>\n Managed through templates\n </span>\n </label>\n <select\n class=\"form-select\"\n id=\"columnType\"\n formControlName=\"type\"\n [class.is-invalid]=\"editForm.get('type')?.invalid && editForm.get('type')?.touched\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n </div>\n\n <!-- Advanced Mode Toggle -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"advancedMode\"\n [checked]=\"showAdvancedMode()\"\n (change)=\"toggleAdvancedMode()\">\n <label class=\"form-check-label\" for=\"advancedMode\">\n <i class=\"bi bi-gear me-1\"></i>Advanced Mode\n </label>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Show advanced options for ontology configuration and column settings\n </div>\n </div>\n </div>\n\n @if (showAdvancedMode()) {\n <div class=\"col-md-6 mb-3\">\n <label for=\"ontologyType\" class=\"form-label\">\n Ontology Type\n <span class=\"badge bg-warning ms-2\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>Advanced\n </span>\n </label>\n <select\n class=\"form-select\"\n id=\"ontologyType\"\n formControlName=\"ontologyType\"\n (change)=\"onOntologyTypeChange($event)\">\n @for (ontology of ontologyTypes; track $index) {\n <option [value]=\"$index\">{{ ontology.label }}</option>\n }\n </select>\n\n @if (hasOntologyType) {\n <div class=\"form-text mt-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Custom ontology filters will be applied based on the selected type\n </div>\n }\n </div>\n\n @if (hasOntologyType) {\n <div class=\"col-12 mb-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"enableTypeahead\"\n formControlName=\"enableTypeahead\">\n <label class=\"form-check-label\" for=\"enableTypeahead\">\n <i class=\"bi bi-search me-1\"></i>Enable Typeahead Autocomplete\n </label>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Enable autocomplete suggestions from {{ selectedOntologyConfig?.label || 'selected' }} ontology when editing values\n </div>\n </div>\n </div>\n }\n\n <!-- Column Options - Advanced Mode Only -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-sliders me-2\"></i>Column Options\n <span class=\"badge bg-warning ms-2\">\n <i class=\"bi bi-exclamation-triangle me-1\"></i>Advanced\n </span>\n </h6>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"mandatory\"\n formControlName=\"mandatory\">\n <label class=\"form-check-label\" for=\"mandatory\">\n <i class=\"bi bi-exclamation-circle me-1\"></i>Mandatory\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"hidden\"\n formControlName=\"hidden\">\n <label class=\"form-check-label\" for=\"hidden\">\n <i class=\"bi bi-eye-slash me-1\"></i>Hidden\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"readonly\"\n formControlName=\"readonly\">\n <label class=\"form-check-label\" for=\"readonly\">\n <i class=\"bi bi-lock me-1\"></i>Read Only\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notApplicable\"\n formControlName=\"notApplicable\">\n <label class=\"form-check-label\" for=\"notApplicable\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notAvailable\"\n formControlName=\"notAvailable\">\n <label class=\"form-check-label\" for=\"notAvailable\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"staffOnly\"\n formControlName=\"staffOnly\">\n <label class=\"form-check-label\" for=\"staffOnly\">\n <i class=\"bi bi-shield-lock me-1\"></i>Staff Only\n </label>\n </div>\n </div>\n </div>\n <div class=\"row mt-2\">\n <div class=\"col-12\">\n <div class=\"alert alert-info py-2\">\n <i class=\"bi bi-info-circle me-1\"></i>\n <small>\n <strong>Staff Only:</strong> When enabled, this column will only be visible and editable by staff members.\n </small>\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <label for=\"defaultValue\" class=\"form-label\">\n Default Value\n @if (hasTypeaheadEnabled) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Typeahead Enabled\n </span>\n }\n </label>\n\n @if (editForm.get('notApplicable')?.value || editForm.get('notAvailable')?.value) {\n <div class=\"mb-2\">\n <small class=\"text-muted d-block mb-1\">Quick Select:</small>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (editForm.get('notApplicable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (editForm.get('notAvailable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n\n <div class=\"input-group\">\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"value\"\n placeholder=\"Enter default value\"\n readonly>\n <button \n type=\"button\" \n class=\"btn btn-outline-primary\"\n (click)=\"openValueEditModal()\"\n title=\"Open advanced value editor\">\n <i class=\"bi bi-pencil me-1\"></i>Edit\n </button>\n </div>\n \n <div class=\"form-text\">\n @if (hasTypeaheadEnabled) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to use autocomplete suggestions from {{ selectedOntologyConfig?.label || 'selected' }} ontology\n } @else if (hasOntologyType) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set default value. Enable typeahead in Advanced Mode for autocomplete suggestions.\n } @else if (showAdvancedMode()) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set the default value (select an ontology type above for smart suggestions)\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set the default value (enable Advanced Mode for ontology autocomplete)\n }\n </div>\n </div>\n\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' : 'Add' }} Column\n </button>\n</div>\n", styles: [".template-selection-container{border:1px solid var(--bs-border-color);border-radius:.375rem;padding:.5rem}.template-item{cursor:pointer;transition:all .2s ease}.template-item:hover{background-color:rgba(var(--bs-primary-rgb),.1);border-color:var(--bs-primary)!important}.template-item.selected{background-color:rgba(var(--bs-primary-rgb),.15);border-color:var(--bs-primary)!important;box-shadow:0 0 0 .2rem rgba(var(--bs-primary-rgb),.25)}.cursor-pointer{cursor:pointer}.card.cursor-pointer{transition:all .2s ease}.card.cursor-pointer:hover{transform:translateY(-2px);box-shadow:0 4px 8px #0000001a}.card.cursor-pointer.border-info{border-width:2px}.modal-lg{max-width:900px}\n"] }]
5053
5126
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataColumnTemplateService }, { type: SdrfSyntaxService }], propDecorators: { column: [{
@@ -5076,6 +5149,7 @@ class MetadataTableTemplateEditModal {
5076
5149
  // Column management
5077
5150
  _templateColumns = signal([], ...(ngDevMode ? [{ debugName: "_templateColumns" }] : []));
5078
5151
  selectedColumnIds = signal(new Set(), ...(ngDevMode ? [{ debugName: "selectedColumnIds" }] : []));
5152
+ recentlyModifiedColumnId = signal(null, ...(ngDevMode ? [{ debugName: "recentlyModifiedColumnId" }] : []));
5079
5153
  templateColumns = computed(() => {
5080
5154
  // Sort columns by position, then by name+type for grouping same columns together
5081
5155
  return this._templateColumns().sort((a, b) => {
@@ -5359,6 +5433,10 @@ class MetadataTableTemplateEditModal {
5359
5433
  this.normalizeColumnPositions(updatedColumns);
5360
5434
  this._templateColumns.set(updatedColumns);
5361
5435
  this.toastService.success('Column updated successfully');
5436
+ this.recentlyModifiedColumnId.set(updatedColumn.id);
5437
+ setTimeout(() => {
5438
+ this.recentlyModifiedColumnId.set(null);
5439
+ }, 3000);
5362
5440
  },
5363
5441
  error: (error) => {
5364
5442
  this.isLoading.set(false);
@@ -5472,12 +5550,12 @@ class MetadataTableTemplateEditModal {
5472
5550
  columns.length = 0;
5473
5551
  columns.push(...regroupedColumns);
5474
5552
  }
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"] }] });
5553
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", 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 });
5554
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", 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\"\n class=\"column-row\"\n [class.recently-modified]=\"column.id && recentlyModifiedColumnId() === column.id\"\n [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}.column-row.recently-modified{animation:highlight-row 3s ease-in-out}@keyframes highlight-row{0%{background-color:rgba(var(--bs-success-rgb),.3)}50%{background-color:rgba(var(--bs-success-rgb),.15)}to{background-color:transparent}}.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"] }] });
5477
5555
  }
5478
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateEditModal, decorators: [{
5556
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableTemplateEditModal, decorators: [{
5479
5557
  type: Component,
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"] }]
5558
+ 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\"\n class=\"column-row\"\n [class.recently-modified]=\"column.id && recentlyModifiedColumnId() === column.id\"\n [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}.column-row.recently-modified{animation:highlight-row 3s ease-in-out}@keyframes highlight-row{0%{background-color:rgba(var(--bs-success-rgb),.3)}50%{background-color:rgba(var(--bs-success-rgb),.15)}to{background-color:transparent}}.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"] }]
5481
5559
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableTemplateService }, { type: MetadataColumnService }, { type: i3.ToastService }], propDecorators: { template: [{
5482
5560
  type: Input
5483
5561
  }], labGroups: [{
@@ -5590,10 +5668,10 @@ class TemplateSchemaSelectionModal {
5590
5668
  onCancel() {
5591
5669
  this.activeModal.dismiss();
5592
5670
  }
5593
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TemplateSchemaSelectionModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
5594
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: TemplateSchemaSelectionModal, isStandalone: true, selector: "ccv-template-schema-selection-modal", inputs: { availableSchemas: "availableSchemas", labGroups: "labGroups" }, outputs: { templateCreated: "templateCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-plus me-2\"></i>Create Template from Schemas\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"onCancel()\">\n </button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"templateForm\" class=\"needs-validation\" novalidate>\n <!-- Template Basic Information -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-info-circle me-1\"></i>Template Information\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.is-invalid]=\"templateForm.get('name')?.invalid && templateForm.get('name')?.touched\"\n id=\"templateName\"\n formControlName=\"name\"\n placeholder=\"Enter template name\">\n <label for=\"templateName\">Template Name *</label>\n @if (templateForm.get('name')?.invalid && templateForm.get('name')?.touched) {\n <div class=\"invalid-feedback\">\n Template name is required.\n </div>\n }\n </div>\n </div>\n <div class=\"col-12 mb-3\">\n <div class=\"form-floating\">\n <textarea\n class=\"form-control\"\n id=\"templateDescription\"\n formControlName=\"description\"\n style=\"height: 80px\"\n placeholder=\"Enter template description\">\n </textarea>\n <label for=\"templateDescription\">Description</label>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Schema Selection -->\n <div class=\"card mb-4\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-check2-square me-1\"></i>Select Schemas\n </h6>\n <small class=\"text-muted\">{{ selectedSchemasCount() }} selected</small>\n </div>\n <div class=\"card-body\">\n @if (availableSchemas.length === 0) {\n <div class=\"text-center text-muted py-3\">\n <i class=\"bi bi-exclamation-triangle fs-4 d-block mb-2\"></i>\n No schemas available\n </div>\n } @else {\n <div class=\"row\">\n @for (schema of availableSchemas; track schema.id) {\n <div class=\"col-md-6 mb-2\">\n <div class=\"card schema-card\"\n [class.selected]=\"isSchemaSelected(schema.id)\"\n (click)=\"toggleSchema(schema.id)\">\n <div class=\"card-body p-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n [id]=\"'schema-' + schema.id\"\n [checked]=\"isSchemaSelected(schema.id)\"\n (change)=\"onSchemaCheckChange($event, schema.id)\">\n <label class=\"form-check-label w-100\" [for]=\"'schema-' + schema.id\">\n <div class=\"d-flex justify-content-between align-items-start\">\n <div class=\"flex-grow-1\">\n <strong class=\"schema-title\">{{ schema.displayName }}</strong>\n @if (schema.description) {\n <p class=\"schema-description text-muted small mb-1\">\n {{ schema.description }}\n </p>\n }\n <div class=\"schema-metadata\">\n @if (schema.tags && schema.tags.length > 0) {\n @for (tag of schema.tags; track tag) {\n <span class=\"badge bg-secondary me-1\">{{ tag }}</span>\n }\n }\n @if (schema.usage_count > 0) {\n <small class=\"text-muted\">\n <i class=\"bi bi-graph-up me-1\"></i>{{ schema.usage_count }} uses\n </small>\n }\n </div>\n </div>\n </div>\n </label>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n\n @if (selectedSchemasCount() === 0) {\n <div class=\"alert alert-warning mt-3\" role=\"alert\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n Please select at least one schema to create a template.\n </div>\n }\n }\n </div>\n </div>\n\n <!-- Template Settings -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-gear me-1\"></i>Template Settings\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <div class=\"form-floating\">\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 <label for=\"labGroup\">Lab Group</label>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch mb-3\">\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 </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Preview Section -->\n @if (selectedSchemasCount() > 0) {\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-eye me-1\"></i>Selected Schemas Preview\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"d-flex flex-wrap gap-2\">\n @for (schemaId of getSelectedSchemas(); track schemaId) {\n <span class=\"badge bg-primary fs-6 py-2 px-3\">\n {{ getSchemaDisplayName(schemaId) }}\n <button\n type=\"button\"\n class=\"btn-close btn-close-white ms-2\"\n style=\"font-size: 0.6em;\"\n (click)=\"removeSchema(schemaId)\"\n [attr.aria-label]=\"'Remove ' + getSchemaDisplayName(schemaId)\">\n </button>\n </span>\n }\n </div>\n <p class=\"text-muted small mt-2 mb-0\">\n This template will combine columns from {{ selectedSchemasCount() }} schema{{ selectedSchemasCount() === 1 ? '' : 's' }}.\n </p>\n </div>\n </div>\n }\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (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 [disabled]=\"!canCreateTemplate()\"\n (click)=\"onCreateTemplate()\">\n <i class=\"bi bi-plus-circle me-1\"></i>Create Template\n </button>\n</div>\n", styles: [".schema-card{cursor:pointer;transition:all .2s ease;border:2px solid transparent}.schema-card:hover{border-color:var(--bs-primary);box-shadow:0 2px 8px #0000001a}.schema-card.selected{border-color:var(--bs-primary);background-color:var(--bs-primary-bg-subtle)}.schema-title{font-size:.95em;color:var(--bs-body-color)}.schema-description{font-size:.8em;line-height:1.3}.schema-metadata{font-size:.75em}.schema-metadata .badge{font-size:.65em}.form-check-input{pointer-events:none}.modal-body{max-height:80vh;overflow-y:auto}.badge.fs-6{font-size:.875rem!important}\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 }] });
5671
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TemplateSchemaSelectionModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
5672
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TemplateSchemaSelectionModal, isStandalone: true, selector: "ccv-template-schema-selection-modal", inputs: { availableSchemas: "availableSchemas", labGroups: "labGroups" }, outputs: { templateCreated: "templateCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-plus me-2\"></i>Create Template from Schemas\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"onCancel()\">\n </button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"templateForm\" class=\"needs-validation\" novalidate>\n <!-- Template Basic Information -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-info-circle me-1\"></i>Template Information\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.is-invalid]=\"templateForm.get('name')?.invalid && templateForm.get('name')?.touched\"\n id=\"templateName\"\n formControlName=\"name\"\n placeholder=\"Enter template name\">\n <label for=\"templateName\">Template Name *</label>\n @if (templateForm.get('name')?.invalid && templateForm.get('name')?.touched) {\n <div class=\"invalid-feedback\">\n Template name is required.\n </div>\n }\n </div>\n </div>\n <div class=\"col-12 mb-3\">\n <div class=\"form-floating\">\n <textarea\n class=\"form-control\"\n id=\"templateDescription\"\n formControlName=\"description\"\n style=\"height: 80px\"\n placeholder=\"Enter template description\">\n </textarea>\n <label for=\"templateDescription\">Description</label>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Schema Selection -->\n <div class=\"card mb-4\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-check2-square me-1\"></i>Select Schemas\n </h6>\n <small class=\"text-muted\">{{ selectedSchemasCount() }} selected</small>\n </div>\n <div class=\"card-body\">\n @if (availableSchemas.length === 0) {\n <div class=\"text-center text-muted py-3\">\n <i class=\"bi bi-exclamation-triangle fs-4 d-block mb-2\"></i>\n No schemas available\n </div>\n } @else {\n <div class=\"row\">\n @for (schema of availableSchemas; track schema.id) {\n <div class=\"col-md-6 mb-2\">\n <div class=\"card schema-card\"\n [class.selected]=\"isSchemaSelected(schema.id)\"\n (click)=\"toggleSchema(schema.id)\">\n <div class=\"card-body p-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n [id]=\"'schema-' + schema.id\"\n [checked]=\"isSchemaSelected(schema.id)\"\n (change)=\"onSchemaCheckChange($event, schema.id)\">\n <label class=\"form-check-label w-100\" [for]=\"'schema-' + schema.id\">\n <div class=\"d-flex justify-content-between align-items-start\">\n <div class=\"flex-grow-1\">\n <strong class=\"schema-title\">{{ schema.displayName }}</strong>\n @if (schema.description) {\n <p class=\"schema-description text-muted small mb-1\">\n {{ schema.description }}\n </p>\n }\n <div class=\"schema-metadata\">\n @if (schema.tags && schema.tags.length > 0) {\n @for (tag of schema.tags; track tag) {\n <span class=\"badge bg-secondary me-1\">{{ tag }}</span>\n }\n }\n @if (schema.usage_count > 0) {\n <small class=\"text-muted\">\n <i class=\"bi bi-graph-up me-1\"></i>{{ schema.usage_count }} uses\n </small>\n }\n </div>\n </div>\n </div>\n </label>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n\n @if (selectedSchemasCount() === 0) {\n <div class=\"alert alert-warning mt-3\" role=\"alert\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n Please select at least one schema to create a template.\n </div>\n }\n }\n </div>\n </div>\n\n <!-- Template Settings -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-gear me-1\"></i>Template Settings\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <div class=\"form-floating\">\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 <label for=\"labGroup\">Lab Group</label>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch mb-3\">\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 </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Preview Section -->\n @if (selectedSchemasCount() > 0) {\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-eye me-1\"></i>Selected Schemas Preview\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"d-flex flex-wrap gap-2\">\n @for (schemaId of getSelectedSchemas(); track schemaId) {\n <span class=\"badge bg-primary fs-6 py-2 px-3\">\n {{ getSchemaDisplayName(schemaId) }}\n <button\n type=\"button\"\n class=\"btn-close btn-close-white ms-2\"\n style=\"font-size: 0.6em;\"\n (click)=\"removeSchema(schemaId)\"\n [attr.aria-label]=\"'Remove ' + getSchemaDisplayName(schemaId)\">\n </button>\n </span>\n }\n </div>\n <p class=\"text-muted small mt-2 mb-0\">\n This template will combine columns from {{ selectedSchemasCount() }} schema{{ selectedSchemasCount() === 1 ? '' : 's' }}.\n </p>\n </div>\n </div>\n }\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (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 [disabled]=\"!canCreateTemplate()\"\n (click)=\"onCreateTemplate()\">\n <i class=\"bi bi-plus-circle me-1\"></i>Create Template\n </button>\n</div>\n", styles: [".schema-card{cursor:pointer;transition:all .2s ease;border:2px solid transparent}.schema-card:hover{border-color:var(--bs-primary);box-shadow:0 2px 8px #0000001a}.schema-card.selected{border-color:var(--bs-primary);background-color:var(--bs-primary-bg-subtle)}.schema-title{font-size:.95em;color:var(--bs-body-color)}.schema-description{font-size:.8em;line-height:1.3}.schema-metadata{font-size:.75em}.schema-metadata .badge{font-size:.65em}.form-check-input{pointer-events:none}.modal-body{max-height:80vh;overflow-y:auto}.badge.fs-6{font-size:.875rem!important}\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 }] });
5595
5673
  }
5596
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TemplateSchemaSelectionModal, decorators: [{
5674
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TemplateSchemaSelectionModal, decorators: [{
5597
5675
  type: Component,
5598
5676
  args: [{ selector: 'ccv-template-schema-selection-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-file-earmark-plus me-2\"></i>Create Template from Schemas\n </h4>\n <button\n type=\"button\"\n class=\"btn-close\"\n aria-label=\"Close\"\n (click)=\"onCancel()\">\n </button>\n</div>\n\n<div class=\"modal-body\">\n <form [formGroup]=\"templateForm\" class=\"needs-validation\" novalidate>\n <!-- Template Basic Information -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-info-circle me-1\"></i>Template Information\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n [class.is-invalid]=\"templateForm.get('name')?.invalid && templateForm.get('name')?.touched\"\n id=\"templateName\"\n formControlName=\"name\"\n placeholder=\"Enter template name\">\n <label for=\"templateName\">Template Name *</label>\n @if (templateForm.get('name')?.invalid && templateForm.get('name')?.touched) {\n <div class=\"invalid-feedback\">\n Template name is required.\n </div>\n }\n </div>\n </div>\n <div class=\"col-12 mb-3\">\n <div class=\"form-floating\">\n <textarea\n class=\"form-control\"\n id=\"templateDescription\"\n formControlName=\"description\"\n style=\"height: 80px\"\n placeholder=\"Enter template description\">\n </textarea>\n <label for=\"templateDescription\">Description</label>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Schema Selection -->\n <div class=\"card mb-4\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-check2-square me-1\"></i>Select Schemas\n </h6>\n <small class=\"text-muted\">{{ selectedSchemasCount() }} selected</small>\n </div>\n <div class=\"card-body\">\n @if (availableSchemas.length === 0) {\n <div class=\"text-center text-muted py-3\">\n <i class=\"bi bi-exclamation-triangle fs-4 d-block mb-2\"></i>\n No schemas available\n </div>\n } @else {\n <div class=\"row\">\n @for (schema of availableSchemas; track schema.id) {\n <div class=\"col-md-6 mb-2\">\n <div class=\"card schema-card\"\n [class.selected]=\"isSchemaSelected(schema.id)\"\n (click)=\"toggleSchema(schema.id)\">\n <div class=\"card-body p-3\">\n <div class=\"form-check\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n [id]=\"'schema-' + schema.id\"\n [checked]=\"isSchemaSelected(schema.id)\"\n (change)=\"onSchemaCheckChange($event, schema.id)\">\n <label class=\"form-check-label w-100\" [for]=\"'schema-' + schema.id\">\n <div class=\"d-flex justify-content-between align-items-start\">\n <div class=\"flex-grow-1\">\n <strong class=\"schema-title\">{{ schema.displayName }}</strong>\n @if (schema.description) {\n <p class=\"schema-description text-muted small mb-1\">\n {{ schema.description }}\n </p>\n }\n <div class=\"schema-metadata\">\n @if (schema.tags && schema.tags.length > 0) {\n @for (tag of schema.tags; track tag) {\n <span class=\"badge bg-secondary me-1\">{{ tag }}</span>\n }\n }\n @if (schema.usage_count > 0) {\n <small class=\"text-muted\">\n <i class=\"bi bi-graph-up me-1\"></i>{{ schema.usage_count }} uses\n </small>\n }\n </div>\n </div>\n </div>\n </label>\n </div>\n </div>\n </div>\n </div>\n }\n </div>\n\n @if (selectedSchemasCount() === 0) {\n <div class=\"alert alert-warning mt-3\" role=\"alert\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n Please select at least one schema to create a template.\n </div>\n }\n }\n </div>\n </div>\n\n <!-- Template Settings -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-gear me-1\"></i>Template Settings\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <div class=\"form-floating\">\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 <label for=\"labGroup\">Lab Group</label>\n </div>\n </div>\n <div class=\"col-md-6\">\n <div class=\"form-check form-switch mb-3\">\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 </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Preview Section -->\n @if (selectedSchemasCount() > 0) {\n <div class=\"card\">\n <div class=\"card-header\">\n <h6 class=\"mb-0\">\n <i class=\"bi bi-eye me-1\"></i>Selected Schemas Preview\n </h6>\n </div>\n <div class=\"card-body\">\n <div class=\"d-flex flex-wrap gap-2\">\n @for (schemaId of getSelectedSchemas(); track schemaId) {\n <span class=\"badge bg-primary fs-6 py-2 px-3\">\n {{ getSchemaDisplayName(schemaId) }}\n <button\n type=\"button\"\n class=\"btn-close btn-close-white ms-2\"\n style=\"font-size: 0.6em;\"\n (click)=\"removeSchema(schemaId)\"\n [attr.aria-label]=\"'Remove ' + getSchemaDisplayName(schemaId)\">\n </button>\n </span>\n }\n </div>\n <p class=\"text-muted small mt-2 mb-0\">\n This template will combine columns from {{ selectedSchemasCount() }} schema{{ selectedSchemasCount() === 1 ? '' : 's' }}.\n </p>\n </div>\n </div>\n }\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (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 [disabled]=\"!canCreateTemplate()\"\n (click)=\"onCreateTemplate()\">\n <i class=\"bi bi-plus-circle me-1\"></i>Create Template\n </button>\n</div>\n", styles: [".schema-card{cursor:pointer;transition:all .2s ease;border:2px solid transparent}.schema-card:hover{border-color:var(--bs-primary);box-shadow:0 2px 8px #0000001a}.schema-card.selected{border-color:var(--bs-primary);background-color:var(--bs-primary-bg-subtle)}.schema-title{font-size:.95em;color:var(--bs-body-color)}.schema-description{font-size:.8em;line-height:1.3}.schema-metadata{font-size:.75em}.schema-metadata .badge{font-size:.65em}.form-check-input{pointer-events:none}.modal-body{max-height:80vh;overflow-y:auto}.badge.fs-6{font-size:.875rem!important}\n"] }]
5599
5677
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { availableSchemas: [{
@@ -5699,10 +5777,10 @@ class SchemaSelectionModal {
5699
5777
  };
5700
5778
  return categoryMap[category.toLowerCase()] || 'bg-secondary';
5701
5779
  }
5702
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SchemaSelectionModal, deps: [{ token: i2.NgbActiveModal }, { token: SchemaService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
5703
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SchemaSelectionModal, isStandalone: true, selector: "ccv-schema-selection-modal", ngImport: i0, template: "<!-- Schema Selection Modal -->\r\n<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-arrows-move me-2\"></i>{{ title }}\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"onCancel()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n <!-- Description -->\r\n <div class=\"alert alert-info mb-4\">\r\n <div class=\"d-flex align-items-start\">\r\n <i class=\"bi bi-info-circle-fill me-2 mt-1\"></i>\r\n <div>\r\n <small>{{ description }}</small>\r\n <br>\r\n <small class=\"text-muted\">\r\n Leave empty to use all available schemas or select specific ones for custom ordering.\r\n </small>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Search and Actions -->\r\n <div class=\"row mb-3\">\r\n <div class=\"col-md-8\">\r\n <div class=\"input-group\">\r\n <span class=\"input-group-text\">\r\n <i class=\"bi bi-search\"></i>\r\n </span>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n placeholder=\"Search schemas...\"\r\n [(ngModel)]=\"searchTerm\"\r\n [ngModelOptions]=\"{updateOn: 'change'}\"\r\n >\r\n </div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <div class=\"btn-group w-100\" role=\"group\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-primary btn-sm\"\r\n (click)=\"selectAll()\"\r\n [disabled]=\"filteredSchemas.length === 0\"\r\n title=\"Select All Visible\">\r\n <i class=\"bi bi-check-all\"></i> All\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"clearSelection()\"\r\n [disabled]=\"selectedSchemaIds().size === 0\"\r\n title=\"Clear Selection\">\r\n <i class=\"bi bi-x-circle\"></i> Clear\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Selection Summary -->\r\n @if (selectedSchemaIds().size > 0) {\r\n <div class=\"alert alert-success mb-3\">\r\n <i class=\"bi bi-check-circle me-2\"></i>\r\n <strong>{{ selectedSchemaIds().size }}</strong> schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }} selected\r\n </div>\r\n }\r\n\r\n <!-- Loading State -->\r\n @if (isLoading()) {\r\n <div class=\"text-center py-4\">\r\n <div class=\"spinner-border text-primary\" role=\"status\">\r\n <span class=\"visually-hidden\">Loading schemas...</span>\r\n </div>\r\n <p class=\"text-muted mt-2\">Loading available schemas...</p>\r\n </div>\r\n } @else {\r\n <!-- Schema List -->\r\n <div class=\"schema-list\" style=\"max-height: 400px; overflow-y: auto;\">\r\n @if (filteredSchemas.length === 0) {\r\n <div class=\"text-center py-4 text-muted\">\r\n <i class=\"bi bi-search fs-1 mb-3 opacity-50\"></i>\r\n <h6>No schemas found</h6>\r\n <p class=\"small mb-0\">\r\n @if (searchTerm()) {\r\n Try adjusting your search terms\r\n } @else {\r\n No schemas are currently available\r\n }\r\n </p>\r\n </div>\r\n } @else {\r\n @for (schema of filteredSchemas; track schema.id) {\r\n <div\r\n class=\"schema-item border rounded p-3 mb-2\"\r\n [class.selected]=\"isSchemaSelected(schema.id)\"\r\n (click)=\"toggleSchemaSelection(schema.id)\"\r\n role=\"button\">\r\n\r\n <div class=\"d-flex align-items-start\">\r\n <!-- Checkbox -->\r\n <div class=\"form-check me-3\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [checked]=\"isSchemaSelected(schema.id)\"\r\n [id]=\"'schema-' + schema.id\"\r\n (click)=\"$event.stopPropagation()\">\r\n <label class=\"form-check-label visually-hidden\" [for]=\"'schema-' + schema.id\">\r\n Select {{ schema.name }}\r\n </label>\r\n </div>\r\n\r\n <!-- Schema Info -->\r\n <div class=\"flex-grow-1\">\r\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\r\n <h6 class=\"mb-0 text-truncate me-2\">\r\n {{ schema.name }}\r\n @if (schema.version) {\r\n <small class=\"text-muted\">v{{ schema.version }}</small>\r\n }\r\n </h6>\r\n\r\n <div class=\"flex-shrink-0\">\r\n @if (schema.category) {\r\n <span class=\"badge\" [class]=\"getBadgeClass(schema.category)\">\r\n {{ schema.category }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (schema.description) {\r\n <p class=\"text-muted small mb-1\">{{ schema.description }}</p>\r\n }\r\n\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n @if (schema.columnsCount !== undefined) {\r\n <small class=\"text-muted\">\r\n <i class=\"bi bi-columns me-1\"></i>{{ schema.columnsCount }} columns\r\n </small>\r\n }\r\n\r\n @if (schema.url) {\r\n <a\r\n [href]=\"schema.url\"\r\n target=\"_blank\"\r\n class=\"btn btn-sm btn-outline-secondary\"\r\n title=\"View Schema Documentation\"\r\n (click)=\"$event.stopPropagation()\">\r\n <i class=\"bi bi-box-arrow-up-right\"></i>\r\n </a>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"onConfirm()\"\r\n [disabled]=\"isLoading()\">\r\n <i class=\"bi bi-arrows-move me-1\"></i>\r\n @if (selectedSchemaIds().size > 0) {\r\n Reorder with {{ selectedSchemaIds().size }} Schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }}\r\n } @else {\r\n Reorder with All Schemas\r\n }\r\n </button>\r\n</div>", styles: [".schema-list{scrollbar-width:thin;scrollbar-color:rgba(var(--bs-secondary-rgb),.5) transparent}.schema-list .schema-item{cursor:pointer;transition:all .2s ease}.schema-list .schema-item:hover{background-color:rgba(var(--bs-primary-rgb),.05);border-color:var(--bs-primary)!important}.schema-list .schema-item.selected{background-color:rgba(var(--bs-success-rgb),.1);border-color:var(--bs-success)!important}.schema-list .schema-item.selected .form-check-input{background-color:var(--bs-success);border-color:var(--bs-success)}.schema-list .schema-item .form-check-input{cursor:pointer}.schema-list::-webkit-scrollbar{width:4px}.schema-list::-webkit-scrollbar-track{background:transparent}.schema-list::-webkit-scrollbar-thumb{background-color:rgba(var(--bs-secondary-rgb),.5);border-radius:2px}.schema-list::-webkit-scrollbar-thumb:hover{background-color:rgba(var(--bs-secondary-rgb),.7)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NgbModule }] });
5780
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SchemaSelectionModal, deps: [{ token: i2.NgbActiveModal }, { token: SchemaService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
5781
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SchemaSelectionModal, isStandalone: true, selector: "ccv-schema-selection-modal", ngImport: i0, template: "<!-- Schema Selection Modal -->\r\n<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-arrows-move me-2\"></i>{{ title }}\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"onCancel()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n <!-- Description -->\r\n <div class=\"alert alert-info mb-4\">\r\n <div class=\"d-flex align-items-start\">\r\n <i class=\"bi bi-info-circle-fill me-2 mt-1\"></i>\r\n <div>\r\n <small>{{ description }}</small>\r\n <br>\r\n <small class=\"text-muted\">\r\n Leave empty to use all available schemas or select specific ones for custom ordering.\r\n </small>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Search and Actions -->\r\n <div class=\"row mb-3\">\r\n <div class=\"col-md-8\">\r\n <div class=\"input-group\">\r\n <span class=\"input-group-text\">\r\n <i class=\"bi bi-search\"></i>\r\n </span>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n placeholder=\"Search schemas...\"\r\n [(ngModel)]=\"searchTerm\"\r\n [ngModelOptions]=\"{updateOn: 'change'}\"\r\n >\r\n </div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <div class=\"btn-group w-100\" role=\"group\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-primary btn-sm\"\r\n (click)=\"selectAll()\"\r\n [disabled]=\"filteredSchemas.length === 0\"\r\n title=\"Select All Visible\">\r\n <i class=\"bi bi-check-all\"></i> All\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"clearSelection()\"\r\n [disabled]=\"selectedSchemaIds().size === 0\"\r\n title=\"Clear Selection\">\r\n <i class=\"bi bi-x-circle\"></i> Clear\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Selection Summary -->\r\n @if (selectedSchemaIds().size > 0) {\r\n <div class=\"alert alert-success mb-3\">\r\n <i class=\"bi bi-check-circle me-2\"></i>\r\n <strong>{{ selectedSchemaIds().size }}</strong> schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }} selected\r\n </div>\r\n }\r\n\r\n <!-- Loading State -->\r\n @if (isLoading()) {\r\n <div class=\"text-center py-4\">\r\n <div class=\"spinner-border text-primary\" role=\"status\">\r\n <span class=\"visually-hidden\">Loading schemas...</span>\r\n </div>\r\n <p class=\"text-muted mt-2\">Loading available schemas...</p>\r\n </div>\r\n } @else {\r\n <!-- Schema List -->\r\n <div class=\"schema-list\" style=\"max-height: 400px; overflow-y: auto;\">\r\n @if (filteredSchemas.length === 0) {\r\n <div class=\"text-center py-4 text-muted\">\r\n <i class=\"bi bi-search fs-1 mb-3 opacity-50\"></i>\r\n <h6>No schemas found</h6>\r\n <p class=\"small mb-0\">\r\n @if (searchTerm()) {\r\n Try adjusting your search terms\r\n } @else {\r\n No schemas are currently available\r\n }\r\n </p>\r\n </div>\r\n } @else {\r\n @for (schema of filteredSchemas; track schema.id) {\r\n <div\r\n class=\"schema-item border rounded p-3 mb-2\"\r\n [class.selected]=\"isSchemaSelected(schema.id)\"\r\n (click)=\"toggleSchemaSelection(schema.id)\"\r\n role=\"button\">\r\n\r\n <div class=\"d-flex align-items-start\">\r\n <!-- Checkbox -->\r\n <div class=\"form-check me-3\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [checked]=\"isSchemaSelected(schema.id)\"\r\n [id]=\"'schema-' + schema.id\"\r\n (click)=\"$event.stopPropagation()\">\r\n <label class=\"form-check-label visually-hidden\" [for]=\"'schema-' + schema.id\">\r\n Select {{ schema.name }}\r\n </label>\r\n </div>\r\n\r\n <!-- Schema Info -->\r\n <div class=\"flex-grow-1\">\r\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\r\n <h6 class=\"mb-0 text-truncate me-2\">\r\n {{ schema.name }}\r\n @if (schema.version) {\r\n <small class=\"text-muted\">v{{ schema.version }}</small>\r\n }\r\n </h6>\r\n\r\n <div class=\"flex-shrink-0\">\r\n @if (schema.category) {\r\n <span class=\"badge\" [class]=\"getBadgeClass(schema.category)\">\r\n {{ schema.category }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (schema.description) {\r\n <p class=\"text-muted small mb-1\">{{ schema.description }}</p>\r\n }\r\n\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n @if (schema.columnsCount !== undefined) {\r\n <small class=\"text-muted\">\r\n <i class=\"bi bi-columns me-1\"></i>{{ schema.columnsCount }} columns\r\n </small>\r\n }\r\n\r\n @if (schema.url) {\r\n <a\r\n [href]=\"schema.url\"\r\n target=\"_blank\"\r\n class=\"btn btn-sm btn-outline-secondary\"\r\n title=\"View Schema Documentation\"\r\n (click)=\"$event.stopPropagation()\">\r\n <i class=\"bi bi-box-arrow-up-right\"></i>\r\n </a>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"onConfirm()\"\r\n [disabled]=\"isLoading()\">\r\n <i class=\"bi bi-arrows-move me-1\"></i>\r\n @if (selectedSchemaIds().size > 0) {\r\n Reorder with {{ selectedSchemaIds().size }} Schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }}\r\n } @else {\r\n Reorder with All Schemas\r\n }\r\n </button>\r\n</div>", styles: [".schema-list{scrollbar-width:thin;scrollbar-color:rgba(var(--bs-secondary-rgb),.5) transparent}.schema-list .schema-item{cursor:pointer;transition:all .2s ease}.schema-list .schema-item:hover{background-color:rgba(var(--bs-primary-rgb),.05);border-color:var(--bs-primary)!important}.schema-list .schema-item.selected{background-color:rgba(var(--bs-success-rgb),.1);border-color:var(--bs-success)!important}.schema-list .schema-item.selected .form-check-input{background-color:var(--bs-success);border-color:var(--bs-success)}.schema-list .schema-item .form-check-input{cursor:pointer}.schema-list::-webkit-scrollbar{width:4px}.schema-list::-webkit-scrollbar-track{background:transparent}.schema-list::-webkit-scrollbar-thumb{background-color:rgba(var(--bs-secondary-rgb),.5);border-radius:2px}.schema-list::-webkit-scrollbar-thumb:hover{background-color:rgba(var(--bs-secondary-rgb),.7)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { 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.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NgbModule }] });
5704
5782
  }
5705
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SchemaSelectionModal, decorators: [{
5783
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SchemaSelectionModal, decorators: [{
5706
5784
  type: Component,
5707
5785
  args: [{ selector: 'ccv-schema-selection-modal', standalone: true, imports: [CommonModule, FormsModule, NgbModule], template: "<!-- Schema Selection Modal -->\r\n<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-arrows-move me-2\"></i>{{ title }}\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" aria-label=\"Close\" (click)=\"onCancel()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n <!-- Description -->\r\n <div class=\"alert alert-info mb-4\">\r\n <div class=\"d-flex align-items-start\">\r\n <i class=\"bi bi-info-circle-fill me-2 mt-1\"></i>\r\n <div>\r\n <small>{{ description }}</small>\r\n <br>\r\n <small class=\"text-muted\">\r\n Leave empty to use all available schemas or select specific ones for custom ordering.\r\n </small>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Search and Actions -->\r\n <div class=\"row mb-3\">\r\n <div class=\"col-md-8\">\r\n <div class=\"input-group\">\r\n <span class=\"input-group-text\">\r\n <i class=\"bi bi-search\"></i>\r\n </span>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n placeholder=\"Search schemas...\"\r\n [(ngModel)]=\"searchTerm\"\r\n [ngModelOptions]=\"{updateOn: 'change'}\"\r\n >\r\n </div>\r\n </div>\r\n <div class=\"col-md-4\">\r\n <div class=\"btn-group w-100\" role=\"group\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-primary btn-sm\"\r\n (click)=\"selectAll()\"\r\n [disabled]=\"filteredSchemas.length === 0\"\r\n title=\"Select All Visible\">\r\n <i class=\"bi bi-check-all\"></i> All\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-secondary btn-sm\"\r\n (click)=\"clearSelection()\"\r\n [disabled]=\"selectedSchemaIds().size === 0\"\r\n title=\"Clear Selection\">\r\n <i class=\"bi bi-x-circle\"></i> Clear\r\n </button>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n <!-- Selection Summary -->\r\n @if (selectedSchemaIds().size > 0) {\r\n <div class=\"alert alert-success mb-3\">\r\n <i class=\"bi bi-check-circle me-2\"></i>\r\n <strong>{{ selectedSchemaIds().size }}</strong> schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }} selected\r\n </div>\r\n }\r\n\r\n <!-- Loading State -->\r\n @if (isLoading()) {\r\n <div class=\"text-center py-4\">\r\n <div class=\"spinner-border text-primary\" role=\"status\">\r\n <span class=\"visually-hidden\">Loading schemas...</span>\r\n </div>\r\n <p class=\"text-muted mt-2\">Loading available schemas...</p>\r\n </div>\r\n } @else {\r\n <!-- Schema List -->\r\n <div class=\"schema-list\" style=\"max-height: 400px; overflow-y: auto;\">\r\n @if (filteredSchemas.length === 0) {\r\n <div class=\"text-center py-4 text-muted\">\r\n <i class=\"bi bi-search fs-1 mb-3 opacity-50\"></i>\r\n <h6>No schemas found</h6>\r\n <p class=\"small mb-0\">\r\n @if (searchTerm()) {\r\n Try adjusting your search terms\r\n } @else {\r\n No schemas are currently available\r\n }\r\n </p>\r\n </div>\r\n } @else {\r\n @for (schema of filteredSchemas; track schema.id) {\r\n <div\r\n class=\"schema-item border rounded p-3 mb-2\"\r\n [class.selected]=\"isSchemaSelected(schema.id)\"\r\n (click)=\"toggleSchemaSelection(schema.id)\"\r\n role=\"button\">\r\n\r\n <div class=\"d-flex align-items-start\">\r\n <!-- Checkbox -->\r\n <div class=\"form-check me-3\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n [checked]=\"isSchemaSelected(schema.id)\"\r\n [id]=\"'schema-' + schema.id\"\r\n (click)=\"$event.stopPropagation()\">\r\n <label class=\"form-check-label visually-hidden\" [for]=\"'schema-' + schema.id\">\r\n Select {{ schema.name }}\r\n </label>\r\n </div>\r\n\r\n <!-- Schema Info -->\r\n <div class=\"flex-grow-1\">\r\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\r\n <h6 class=\"mb-0 text-truncate me-2\">\r\n {{ schema.name }}\r\n @if (schema.version) {\r\n <small class=\"text-muted\">v{{ schema.version }}</small>\r\n }\r\n </h6>\r\n\r\n <div class=\"flex-shrink-0\">\r\n @if (schema.category) {\r\n <span class=\"badge\" [class]=\"getBadgeClass(schema.category)\">\r\n {{ schema.category }}\r\n </span>\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (schema.description) {\r\n <p class=\"text-muted small mb-1\">{{ schema.description }}</p>\r\n }\r\n\r\n <div class=\"d-flex align-items-center justify-content-between\">\r\n @if (schema.columnsCount !== undefined) {\r\n <small class=\"text-muted\">\r\n <i class=\"bi bi-columns me-1\"></i>{{ schema.columnsCount }} columns\r\n </small>\r\n }\r\n\r\n @if (schema.url) {\r\n <a\r\n [href]=\"schema.url\"\r\n target=\"_blank\"\r\n class=\"btn btn-sm btn-outline-secondary\"\r\n title=\"View Schema Documentation\"\r\n (click)=\"$event.stopPropagation()\">\r\n <i class=\"bi bi-box-arrow-up-right\"></i>\r\n </a>\r\n }\r\n </div>\r\n </div>\r\n </div>\r\n </div>\r\n }\r\n }\r\n </div>\r\n }\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onCancel()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"onConfirm()\"\r\n [disabled]=\"isLoading()\">\r\n <i class=\"bi bi-arrows-move me-1\"></i>\r\n @if (selectedSchemaIds().size > 0) {\r\n Reorder with {{ selectedSchemaIds().size }} Schema{{ selectedSchemaIds().size !== 1 ? 's' : '' }}\r\n } @else {\r\n Reorder with All Schemas\r\n }\r\n </button>\r\n</div>", styles: [".schema-list{scrollbar-width:thin;scrollbar-color:rgba(var(--bs-secondary-rgb),.5) transparent}.schema-list .schema-item{cursor:pointer;transition:all .2s ease}.schema-list .schema-item:hover{background-color:rgba(var(--bs-primary-rgb),.05);border-color:var(--bs-primary)!important}.schema-list .schema-item.selected{background-color:rgba(var(--bs-success-rgb),.1);border-color:var(--bs-success)!important}.schema-list .schema-item.selected .form-check-input{background-color:var(--bs-success);border-color:var(--bs-success)}.schema-list .schema-item .form-check-input{cursor:pointer}.schema-list::-webkit-scrollbar{width:4px}.schema-list::-webkit-scrollbar-track{background:transparent}.schema-list::-webkit-scrollbar-thumb{background-color:rgba(var(--bs-secondary-rgb),.5);border-radius:2px}.schema-list::-webkit-scrollbar-thumb:hover{background-color:rgba(var(--bs-secondary-rgb),.7)}\n"] }]
5708
5786
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }, { type: SchemaService }, { type: i3.ToastService }] });
@@ -5787,10 +5865,10 @@ class TableCreationModalComponent {
5787
5865
  };
5788
5866
  return labels[fieldName] || fieldName;
5789
5867
  }
5790
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TableCreationModalComponent, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
5791
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: TableCreationModalComponent, isStandalone: true, selector: "ccv-table-creation-modal", inputs: { template: "template", labGroups: "labGroups", selectedLabGroupId: "selectedLabGroupId" }, outputs: { tableCreated: "tableCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-table me-2\"></i>Create Table from Template\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\" aria-label=\"Close\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (template) {\n <div class=\"alert alert-info d-flex align-items-center mb-4\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <div>\n <strong>Template:</strong> {{ template.name }}\n @if (template.description) {\n <br><small>{{ template.description }}</small>\n }\n </div>\n </div>\n\n @if (errorMessage) {\n <div class=\"alert alert-danger d-flex align-items-center\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n {{ errorMessage }}\n </div>\n }\n\n <form [formGroup]=\"tableForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row g-3\">\n <!-- Table Name -->\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"tableName\"\n formControlName=\"name\"\n placeholder=\"Table name\"\n [class.is-invalid]=\"getFieldError('name')\">\n <label for=\"tableName\">Table Name *</label>\n </div>\n @if (getFieldError('name')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldError('name') }}\n </div>\n }\n </div>\n\n <!-- Sample Count -->\n <div class=\"col-md-6\">\n <div class=\"form-floating\">\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"sampleCount\"\n formControlName=\"sampleCount\"\n placeholder=\"Sample count\"\n min=\"1\"\n max=\"10000\"\n [class.is-invalid]=\"getFieldError('sampleCount')\">\n <label for=\"sampleCount\">Number of Samples *</label>\n </div>\n @if (getFieldError('sampleCount')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldError('sampleCount') }}\n </div>\n }\n <div class=\"form-text\">\n Number of sample rows to create in the table (1-10,000)\n </div>\n </div>\n\n <!-- Lab Group -->\n <div class=\"col-md-6\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\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 <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n <div class=\"form-text\">\n Optional: Assign table to a specific lab group\n </div>\n </div>\n\n <!-- Description -->\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <textarea\n class=\"form-control\"\n id=\"tableDescription\"\n formControlName=\"description\"\n placeholder=\"Description\"\n style=\"height: 100px\"></textarea>\n <label for=\"tableDescription\">Description (optional)</label>\n </div>\n <div class=\"form-text\">\n Optional description for the metadata table\n </div>\n </div>\n </div>\n </form>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\" [disabled]=\"isCreating\">\n Cancel\n </button>\n <button\n type=\"submit\"\n class=\"btn btn-success\"\n (click)=\"onSubmit()\"\n [disabled]=\"tableForm.invalid || isCreating\">\n @if (isCreating) {\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\n }\n <i class=\"bi bi-table me-1\"></i>Create Table\n </button>\n</div>\n", styles: [".form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px)}.form-text{font-size:.8rem;margin-top:.25rem}.alert{border-radius:.5rem}.modal-header{border-bottom:1px solid var(--bs-border-color)}.modal-footer{border-top:1px solid var(--bs-border-color)}.spinner-border-sm{width:1rem;height:1rem}.invalid-feedback{font-size:.875rem}\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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { 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"] }] });
5868
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TableCreationModalComponent, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
5869
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: TableCreationModalComponent, isStandalone: true, selector: "ccv-table-creation-modal", inputs: { template: "template", labGroups: "labGroups", selectedLabGroupId: "selectedLabGroupId" }, outputs: { tableCreated: "tableCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-table me-2\"></i>Create Table from Template\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\" aria-label=\"Close\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (template) {\n <div class=\"alert alert-info d-flex align-items-center mb-4\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <div>\n <strong>Template:</strong> {{ template.name }}\n @if (template.description) {\n <br><small>{{ template.description }}</small>\n }\n </div>\n </div>\n\n @if (errorMessage) {\n <div class=\"alert alert-danger d-flex align-items-center\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n {{ errorMessage }}\n </div>\n }\n\n <form [formGroup]=\"tableForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row g-3\">\n <!-- Table Name -->\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"tableName\"\n formControlName=\"name\"\n placeholder=\"Table name\"\n [class.is-invalid]=\"getFieldError('name')\">\n <label for=\"tableName\">Table Name *</label>\n </div>\n @if (getFieldError('name')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldError('name') }}\n </div>\n }\n </div>\n\n <!-- Sample Count -->\n <div class=\"col-md-6\">\n <div class=\"form-floating\">\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"sampleCount\"\n formControlName=\"sampleCount\"\n placeholder=\"Sample count\"\n min=\"1\"\n max=\"10000\"\n [class.is-invalid]=\"getFieldError('sampleCount')\">\n <label for=\"sampleCount\">Number of Samples *</label>\n </div>\n @if (getFieldError('sampleCount')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldError('sampleCount') }}\n </div>\n }\n <div class=\"form-text\">\n Number of sample rows to create in the table (1-10,000)\n </div>\n </div>\n\n <!-- Lab Group -->\n <div class=\"col-md-6\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\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 <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n <div class=\"form-text\">\n Optional: Assign table to a specific lab group\n </div>\n </div>\n\n <!-- Description -->\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <textarea\n class=\"form-control\"\n id=\"tableDescription\"\n formControlName=\"description\"\n placeholder=\"Description\"\n style=\"height: 100px\"></textarea>\n <label for=\"tableDescription\">Description (optional)</label>\n </div>\n <div class=\"form-text\">\n Optional description for the metadata table\n </div>\n </div>\n </div>\n </form>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\" [disabled]=\"isCreating\">\n Cancel\n </button>\n <button\n type=\"submit\"\n class=\"btn btn-success\"\n (click)=\"onSubmit()\"\n [disabled]=\"tableForm.invalid || isCreating\">\n @if (isCreating) {\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\n }\n <i class=\"bi bi-table me-1\"></i>Create Table\n </button>\n</div>\n", styles: [".form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px)}.form-text{font-size:.8rem;margin-top:.25rem}.alert{border-radius:.5rem}.modal-header{border-bottom:1px solid var(--bs-border-color)}.modal-footer{border-top:1px solid var(--bs-border-color)}.spinner-border-sm{width:1rem;height:1rem}.invalid-feedback{font-size:.875rem}\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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { 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"] }] });
5792
5870
  }
5793
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TableCreationModalComponent, decorators: [{
5871
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: TableCreationModalComponent, decorators: [{
5794
5872
  type: Component,
5795
5873
  args: [{ selector: 'ccv-table-creation-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: "<div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-table me-2\"></i>Create Table from Template\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\" aria-label=\"Close\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (template) {\n <div class=\"alert alert-info d-flex align-items-center mb-4\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <div>\n <strong>Template:</strong> {{ template.name }}\n @if (template.description) {\n <br><small>{{ template.description }}</small>\n }\n </div>\n </div>\n\n @if (errorMessage) {\n <div class=\"alert alert-danger d-flex align-items-center\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n {{ errorMessage }}\n </div>\n }\n\n <form [formGroup]=\"tableForm\" (ngSubmit)=\"onSubmit()\">\n <div class=\"row g-3\">\n <!-- Table Name -->\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"tableName\"\n formControlName=\"name\"\n placeholder=\"Table name\"\n [class.is-invalid]=\"getFieldError('name')\">\n <label for=\"tableName\">Table Name *</label>\n </div>\n @if (getFieldError('name')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldError('name') }}\n </div>\n }\n </div>\n\n <!-- Sample Count -->\n <div class=\"col-md-6\">\n <div class=\"form-floating\">\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"sampleCount\"\n formControlName=\"sampleCount\"\n placeholder=\"Sample count\"\n min=\"1\"\n max=\"10000\"\n [class.is-invalid]=\"getFieldError('sampleCount')\">\n <label for=\"sampleCount\">Number of Samples *</label>\n </div>\n @if (getFieldError('sampleCount')) {\n <div class=\"invalid-feedback d-block\">\n {{ getFieldError('sampleCount') }}\n </div>\n }\n <div class=\"form-text\">\n Number of sample rows to create in the table (1-10,000)\n </div>\n </div>\n\n <!-- Lab Group -->\n <div class=\"col-md-6\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\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 <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n <div class=\"form-text\">\n Optional: Assign table to a specific lab group\n </div>\n </div>\n\n <!-- Description -->\n <div class=\"col-12\">\n <div class=\"form-floating\">\n <textarea\n class=\"form-control\"\n id=\"tableDescription\"\n formControlName=\"description\"\n placeholder=\"Description\"\n style=\"height: 100px\"></textarea>\n <label for=\"tableDescription\">Description (optional)</label>\n </div>\n <div class=\"form-text\">\n Optional description for the metadata table\n </div>\n </div>\n </div>\n </form>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\" [disabled]=\"isCreating\">\n Cancel\n </button>\n <button\n type=\"submit\"\n class=\"btn btn-success\"\n (click)=\"onSubmit()\"\n [disabled]=\"tableForm.invalid || isCreating\">\n @if (isCreating) {\n <span class=\"spinner-border spinner-border-sm me-2\" role=\"status\"></span>\n }\n <i class=\"bi bi-table me-1\"></i>Create Table\n </button>\n</div>\n", styles: [".form-floating>.form-control,.form-floating>.form-select{height:calc(3.5rem + 2px)}.form-text{font-size:.8rem;margin-top:.25rem}.alert{border-radius:.5rem}.modal-header{border-bottom:1px solid var(--bs-border-color)}.modal-footer{border-top:1px solid var(--bs-border-color)}.spinner-border-sm{width:1rem;height:1rem}.invalid-feedback{font-size:.875rem}\n"] }]
5796
5874
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { template: [{
@@ -6214,10 +6292,10 @@ class MetadataTableTemplates {
6214
6292
  }
6215
6293
  });
6216
6294
  }
6217
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplates, deps: [{ token: i1.FormBuilder }, { token: MetadataTableTemplateService }, { token: i3.LabGroupService }, { token: i2.NgbModal }, { token: i3.ToastService }, { token: AsyncTaskUIService }], target: i0.ɵɵFactoryTarget.Component });
6218
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataTableTemplates, isStandalone: true, selector: "ccv-metadata-table-templates", ngImport: i0, template: "<div class=\"metadata-table-templates-container\">\n <!-- Header Section -->\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 <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Table Templates\n </h4>\n @if (selectedLabGroup()) {\n <span class=\"badge bg-primary ms-3\">{{ selectedLabGroup()!.name }}</span>\n }\n </div>\n <div class=\"d-flex gap-2\">\n <div class=\"btn-group\" ngbDropdown>\n <button type=\"button\" class=\"btn btn-primary btn-sm dropdown-toggle\" ngbDropdownToggle>\n <i class=\"bi bi-plus me-1\"></i>New Template\n </button>\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"createTemplateWithCustomSchemas(); $event.preventDefault()\"\n title=\"Select one or multiple schemas to create template\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>\n <strong>Create from Schemas</strong>\n </a>\n <div class=\"dropdown-divider\"></div>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"createTemplate(); $event.preventDefault()\"\n title=\"Create from scratch\">\n <i class=\"bi bi-wrench me-2\"></i>\n <strong>Manual Configuration</strong>\n </a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"templates-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-3\">\n <div class=\"form-floating\">\n <input\n type=\"search\"\n class=\"form-control\"\n id=\"templateSearch\"\n formControlName=\"search\"\n placeholder=\"Search templates...\">\n <label for=\"templateSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Templates\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\n formControlName=\"labGroupId\"\n (change)=\"onLabGroupChange($event)\">\n <option [ngValue]=\"null\">All Lab Groups</option>\n @if (hasLabGroups()) {\n @for (group of labGroupsData().results; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n }\n </select>\n <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n\n <!-- Lab Groups Pagination -->\n @if (showLabGroupsPagination()) {\n <div class=\"d-flex justify-content-center mt-2\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"labGroupsCurrentPage()\"\n [pageSize]=\"labGroupsPageSize()\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"false\"\n [rotate]=\"true\"\n (pageChange)=\"onLabGroupsPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch mt-3\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"defaultTemplates\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"defaultTemplates\">\n <i class=\"bi bi-star me-1\"></i>Default Templates Only\n </label>\n </div>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Templates Table -->\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Table Templates\n <span class=\"badge bg-primary ms-2\">{{ templatesData().count }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th><i class=\"bi bi-tag me-1\"></i>Template Name</th>\n <th><i class=\"bi bi-person me-1\"></i>Creator</th>\n <th><i class=\"bi bi-folder me-1\"></i>Lab Group</th>\n <th><i class=\"bi bi-list me-1\"></i>Columns</th>\n <th><i class=\"bi bi-eye me-1\"></i>Visibility</th>\n <th><i class=\"bi bi-star me-1\"></i>Default</th>\n <th><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (template of templatesData().results; track template.id) {\n <tr>\n <td>\n <strong>{{ template.name }}</strong>\n @if (template.description) {\n <br><small class=\"text-muted\">{{ template.description }}</small>\n }\n </td>\n <td>{{ template.ownerUsername || 'System' }}</td>\n <td>\n @if (template.labGroup) {\n <span class=\"badge bg-info\">{{ getLabGroupName(template.labGroup) }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ template.columnCount || 0 }}</span>\n </td>\n <td>\n @switch (template.visibility) {\n @case ('public') {\n <span class=\"badge bg-success\">Public</span>\n }\n @case ('group') {\n <span class=\"badge bg-info\">Lab Group</span>\n }\n @case ('private') {\n <span class=\"badge bg-warning\">Private</span>\n }\n @default {\n <span class=\"badge bg-secondary\">{{ template.visibility }}</span>\n }\n }\n </td>\n <td>\n @if (template.isDefault) {\n <i class=\"bi bi-star-fill text-warning\"></i>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-success\"\n (click)=\"createTableFromTemplate(template)\"\n title=\"Create metadata table from this template\">\n <i class=\"bi bi-table\"></i>\n </button>\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template)\"\n title=\"Edit template\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n class=\"btn btn-outline-info\"\n (click)=\"duplicateTemplate(template)\"\n title=\"Duplicate template\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button\n class=\"btn btn-outline-warning\"\n (click)=\"reorderTemplateColumns(template)\"\n title=\"Reorder columns by schema\"\n [disabled]=\"!template.columnCount || template.columnCount === 0\">\n <i class=\"bi bi-arrows-move\"></i>\n </button>\n <button\n class=\"btn btn-outline-danger\"\n (click)=\"deleteTemplate(template)\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showTemplatesPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"templatesData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-file-earmark-text fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Table Templates Found</h5>\n <p class=\"text-muted\">\n @if (searchForm.get('search')?.value || searchForm.get('labGroupId')?.value) {\n Try adjusting your search criteria or create a new template.\n } @else {\n Get started by creating your first table template.\n }\n </p>\n <button class=\"btn btn-primary\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Template\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [""], 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: "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"] }] });
6295
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableTemplates, deps: [{ token: i1.FormBuilder }, { token: MetadataTableTemplateService }, { token: i3.LabGroupService }, { token: i2.NgbModal }, { token: i3.ToastService }, { token: AsyncTaskUIService }], target: i0.ɵɵFactoryTarget.Component });
6296
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: MetadataTableTemplates, isStandalone: true, selector: "ccv-metadata-table-templates", ngImport: i0, template: "<div class=\"metadata-table-templates-container\">\n <!-- Header Section -->\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 <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Table Templates\n </h4>\n @if (selectedLabGroup()) {\n <span class=\"badge bg-primary ms-3\">{{ selectedLabGroup()!.name }}</span>\n }\n </div>\n <div class=\"d-flex gap-2\">\n <div class=\"btn-group\" ngbDropdown>\n <button type=\"button\" class=\"btn btn-primary btn-sm dropdown-toggle\" ngbDropdownToggle>\n <i class=\"bi bi-plus me-1\"></i>New Template\n </button>\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"createTemplateWithCustomSchemas(); $event.preventDefault()\"\n title=\"Select one or multiple schemas to create template\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>\n <strong>Create from Schemas</strong>\n </a>\n <div class=\"dropdown-divider\"></div>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"createTemplate(); $event.preventDefault()\"\n title=\"Create from scratch\">\n <i class=\"bi bi-wrench me-2\"></i>\n <strong>Manual Configuration</strong>\n </a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"templates-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-3\">\n <div class=\"form-floating\">\n <input\n type=\"search\"\n class=\"form-control\"\n id=\"templateSearch\"\n formControlName=\"search\"\n placeholder=\"Search templates...\">\n <label for=\"templateSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Templates\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\n formControlName=\"labGroupId\"\n (change)=\"onLabGroupChange($event)\">\n <option [ngValue]=\"null\">All Lab Groups</option>\n @if (hasLabGroups()) {\n @for (group of labGroupsData().results; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n }\n </select>\n <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n\n <!-- Lab Groups Pagination -->\n @if (showLabGroupsPagination()) {\n <div class=\"d-flex justify-content-center mt-2\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"labGroupsCurrentPage()\"\n [pageSize]=\"labGroupsPageSize()\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"false\"\n [rotate]=\"true\"\n (pageChange)=\"onLabGroupsPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch mt-3\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"defaultTemplates\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"defaultTemplates\">\n <i class=\"bi bi-star me-1\"></i>Default Templates Only\n </label>\n </div>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Templates Table -->\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Table Templates\n <span class=\"badge bg-primary ms-2\">{{ templatesData().count }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th><i class=\"bi bi-tag me-1\"></i>Template Name</th>\n <th><i class=\"bi bi-person me-1\"></i>Creator</th>\n <th><i class=\"bi bi-folder me-1\"></i>Lab Group</th>\n <th><i class=\"bi bi-list me-1\"></i>Columns</th>\n <th><i class=\"bi bi-eye me-1\"></i>Visibility</th>\n <th><i class=\"bi bi-star me-1\"></i>Default</th>\n <th><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (template of templatesData().results; track template.id) {\n <tr>\n <td>\n <strong>{{ template.name }}</strong>\n @if (template.description) {\n <br><small class=\"text-muted\">{{ template.description }}</small>\n }\n </td>\n <td>{{ template.ownerUsername || 'System' }}</td>\n <td>\n @if (template.labGroup) {\n <span class=\"badge bg-info\">{{ getLabGroupName(template.labGroup) }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ template.columnCount || 0 }}</span>\n </td>\n <td>\n @switch (template.visibility) {\n @case ('public') {\n <span class=\"badge bg-success\">Public</span>\n }\n @case ('group') {\n <span class=\"badge bg-info\">Lab Group</span>\n }\n @case ('private') {\n <span class=\"badge bg-warning\">Private</span>\n }\n @default {\n <span class=\"badge bg-secondary\">{{ template.visibility }}</span>\n }\n }\n </td>\n <td>\n @if (template.isDefault) {\n <i class=\"bi bi-star-fill text-warning\"></i>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-success\"\n (click)=\"createTableFromTemplate(template)\"\n title=\"Create metadata table from this template\">\n <i class=\"bi bi-table\"></i>\n </button>\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template)\"\n title=\"Edit template\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n class=\"btn btn-outline-info\"\n (click)=\"duplicateTemplate(template)\"\n title=\"Duplicate template\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button\n class=\"btn btn-outline-warning\"\n (click)=\"reorderTemplateColumns(template)\"\n title=\"Reorder columns by schema\"\n [disabled]=\"!template.columnCount || template.columnCount === 0\">\n <i class=\"bi bi-arrows-move\"></i>\n </button>\n <button\n class=\"btn btn-outline-danger\"\n (click)=\"deleteTemplate(template)\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showTemplatesPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"templatesData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-file-earmark-text fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Table Templates Found</h5>\n <p class=\"text-muted\">\n @if (searchForm.get('search')?.value || searchForm.get('labGroupId')?.value) {\n Try adjusting your search criteria or create a new template.\n } @else {\n Get started by creating your first table template.\n }\n </p>\n <button class=\"btn btn-primary\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Template\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [""], 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: "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"] }] });
6219
6297
  }
6220
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplates, decorators: [{
6298
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableTemplates, decorators: [{
6221
6299
  type: Component,
6222
6300
  args: [{ selector: 'ccv-metadata-table-templates', imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"metadata-table-templates-container\">\n <!-- Header Section -->\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 <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Table Templates\n </h4>\n @if (selectedLabGroup()) {\n <span class=\"badge bg-primary ms-3\">{{ selectedLabGroup()!.name }}</span>\n }\n </div>\n <div class=\"d-flex gap-2\">\n <div class=\"btn-group\" ngbDropdown>\n <button type=\"button\" class=\"btn btn-primary btn-sm dropdown-toggle\" ngbDropdownToggle>\n <i class=\"bi bi-plus me-1\"></i>New Template\n </button>\n <div class=\"dropdown-menu dropdown-menu-end\" ngbDropdownMenu>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"createTemplateWithCustomSchemas(); $event.preventDefault()\"\n title=\"Select one or multiple schemas to create template\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>\n <strong>Create from Schemas</strong>\n </a>\n <div class=\"dropdown-divider\"></div>\n <a class=\"dropdown-item\" href=\"#\" (click)=\"createTemplate(); $event.preventDefault()\"\n title=\"Create from scratch\">\n <i class=\"bi bi-wrench me-2\"></i>\n <strong>Manual Configuration</strong>\n </a>\n </div>\n </div>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"templates-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-3\">\n <div class=\"form-floating\">\n <input\n type=\"search\"\n class=\"form-control\"\n id=\"templateSearch\"\n formControlName=\"search\"\n placeholder=\"Search templates...\">\n <label for=\"templateSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Templates\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\n formControlName=\"labGroupId\"\n (change)=\"onLabGroupChange($event)\">\n <option [ngValue]=\"null\">All Lab Groups</option>\n @if (hasLabGroups()) {\n @for (group of labGroupsData().results; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n }\n </select>\n <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n\n <!-- Lab Groups Pagination -->\n @if (showLabGroupsPagination()) {\n <div class=\"d-flex justify-content-center mt-2\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"labGroupsCurrentPage()\"\n [pageSize]=\"labGroupsPageSize()\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"false\"\n [rotate]=\"true\"\n (pageChange)=\"onLabGroupsPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch mt-3\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"defaultTemplates\"\n formControlName=\"isDefault\">\n <label class=\"form-check-label\" for=\"defaultTemplates\">\n <i class=\"bi bi-star me-1\"></i>Default Templates Only\n </label>\n </div>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Templates Table -->\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-file-earmark-text me-2\"></i>Table Templates\n <span class=\"badge bg-primary ms-2\">{{ templatesData().count }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th><i class=\"bi bi-tag me-1\"></i>Template Name</th>\n <th><i class=\"bi bi-person me-1\"></i>Creator</th>\n <th><i class=\"bi bi-folder me-1\"></i>Lab Group</th>\n <th><i class=\"bi bi-list me-1\"></i>Columns</th>\n <th><i class=\"bi bi-eye me-1\"></i>Visibility</th>\n <th><i class=\"bi bi-star me-1\"></i>Default</th>\n <th><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (template of templatesData().results; track template.id) {\n <tr>\n <td>\n <strong>{{ template.name }}</strong>\n @if (template.description) {\n <br><small class=\"text-muted\">{{ template.description }}</small>\n }\n </td>\n <td>{{ template.ownerUsername || 'System' }}</td>\n <td>\n @if (template.labGroup) {\n <span class=\"badge bg-info\">{{ getLabGroupName(template.labGroup) }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <span class=\"badge bg-secondary\">{{ template.columnCount || 0 }}</span>\n </td>\n <td>\n @switch (template.visibility) {\n @case ('public') {\n <span class=\"badge bg-success\">Public</span>\n }\n @case ('group') {\n <span class=\"badge bg-info\">Lab Group</span>\n }\n @case ('private') {\n <span class=\"badge bg-warning\">Private</span>\n }\n @default {\n <span class=\"badge bg-secondary\">{{ template.visibility }}</span>\n }\n }\n </td>\n <td>\n @if (template.isDefault) {\n <i class=\"bi bi-star-fill text-warning\"></i>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-success\"\n (click)=\"createTableFromTemplate(template)\"\n title=\"Create metadata table from this template\">\n <i class=\"bi bi-table\"></i>\n </button>\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template)\"\n title=\"Edit template\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n class=\"btn btn-outline-info\"\n (click)=\"duplicateTemplate(template)\"\n title=\"Duplicate template\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button\n class=\"btn btn-outline-warning\"\n (click)=\"reorderTemplateColumns(template)\"\n title=\"Reorder columns by schema\"\n [disabled]=\"!template.columnCount || template.columnCount === 0\">\n <i class=\"bi bi-arrows-move\"></i>\n </button>\n <button\n class=\"btn btn-outline-danger\"\n (click)=\"deleteTemplate(template)\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showTemplatesPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"templatesData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-file-earmark-text fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Table Templates Found</h5>\n <p class=\"text-muted\">\n @if (searchForm.get('search')?.value || searchForm.get('labGroupId')?.value) {\n Try adjusting your search criteria or create a new template.\n } @else {\n Get started by creating your first table template.\n }\n </p>\n <button class=\"btn btn-primary\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Template\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n" }]
6223
6301
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: MetadataTableTemplateService }, { type: i3.LabGroupService }, { type: i2.NgbModal }, { type: i3.ToastService }, { type: AsyncTaskUIService }] });
@@ -6534,10 +6612,10 @@ class ColumnTemplateEditModal {
6534
6612
  this.editForm.get('defaultValue')?.markAsTouched();
6535
6613
  }
6536
6614
  };
6537
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ColumnTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: MetadataColumnTemplateService }, { token: OntologySearchService }], target: i0.ɵɵFactoryTarget.Component });
6538
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: ColumnTemplateEditModal, isStandalone: true, selector: "ccv-column-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-clipboard-list 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-md-6 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-md-6 mb-3\">\n <label for=\"columnName\" class=\"form-label\">Column Name *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"columnName\"\n formControlName=\"columnName\"\n [class.is-invalid]=\"editForm.get('columnName')?.invalid && editForm.get('columnName')?.touched\"\n placeholder=\"columnName\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnName') }}\n </div>\n <small class=\"form-text text-muted\">Column name based on column type and template name. Example: characteristics[biological replicate]</small>\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=\"2\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Column 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>Column Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnType\" class=\"form-label\">Column Type *</label>\n <select\n class=\"form-select\"\n id=\"columnType\"\n formControlName=\"columnType\"\n [class.is-invalid]=\"editForm.get('columnType')?.invalid && editForm.get('columnType')?.touched\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnType') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultPosition\" class=\"form-label\">Default Position</label>\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"defaultPosition\"\n formControlName=\"defaultPosition\"\n [class.is-invalid]=\"editForm.get('defaultPosition')?.invalid && editForm.get('defaultPosition')?.touched\"\n min=\"0\"\n placeholder=\"0\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('defaultPosition') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultValue\" class=\"form-label\">\n Default Value\n @if (hasOntologyType) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Autocomplete Available\n </span>\n }\n </label>\n @if (editForm.get('notApplicable')?.value || editForm.get('notAvailable')?.value) {\n <div class=\"mb-2\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (editForm.get('notApplicable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (editForm.get('notAvailable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n @if (hasOntologyType) {\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Type to search ontology suggestions...\"\n [ngbTypeahead]=\"searchDefaultValue\"\n [resultFormatter]=\"formatSuggestion\"\n [inputFormatter]=\"inputFormatter\"\n (selectItem)=\"onSuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n @if (isLoadingSuggestions()) {\n <span class=\"input-group-text\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </span>\n } @else {\n <span class=\"input-group-text\">\n <i class=\"bi bi-search text-muted\"></i>\n </span>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Type to search for ontology suggestions\n </div>\n } @else {\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Optional default value\">\n <div class=\"form-text text-muted\">\n Select an ontology type to enable autocomplete\n </div>\n }\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"ontologyType\" class=\"form-label\">Ontology Type</label>\n <select\n class=\"form-select\"\n id=\"ontologyType\"\n formControlName=\"ontologyType\"\n (change)=\"onOntologyTypeChange()\">\n @for (ontology of ontologyTypes; track $index) {\n <option [value]=\"getOptionValue(ontology, $index)\">{{ ontology.label }}</option>\n }\n </select>\n </div>\n\n @if (editForm.get('ontologyType')?.value && editForm.get('customOntologyFilters')?.value && Object.keys(editForm.get('customOntologyFilters')?.value || {}).length > 0) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-filter me-2\"></i>Custom Ontology Filters Applied\n </h6>\n <small class=\"mb-0\">\n The following custom filters will be automatically applied for this ontology type:\n <code class=\"ms-1\">{{ editForm.get('customOntologyFilters')?.value | json }}</code>\n </small>\n </div>\n </div>\n }\n\n <!-- Visibility and Access -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-eye me-2\"></i>Visibility and Access\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"visibility\" class=\"form-label\">Visibility *</label>\n <select\n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\"\n (change)=\"onVisibilityChange()\"\n [class.is-invalid]=\"editForm.get('visibility')?.invalid && editForm.get('visibility')?.touched\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('visibility') }}\n </div>\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=\"labGroup\"\n [class.is-invalid]=\"editForm.get('labGroup')?.invalid && editForm.get('labGroup')?.touched\">\n <option [ngValue]=\"null\">Select Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('labGroup') }}\n </div>\n </div>\n\n <!-- Additional Options -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-sliders me-2\"></i>Additional Options\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"category\" class=\"form-label\">Category</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"category\"\n formControlName=\"category\"\n placeholder=\"Template category\">\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"tags\" class=\"form-label\">Tags</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"tags\"\n formControlName=\"tags\"\n placeholder=\"Comma-separated tags\">\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"enableTypeahead\"\n formControlName=\"enableTypeahead\">\n <label class=\"form-check-label\" for=\"enableTypeahead\">\n Enable Typeahead\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notApplicable\"\n formControlName=\"notApplicable\">\n <label class=\"form-check-label\" for=\"notApplicable\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notAvailable\"\n formControlName=\"notAvailable\">\n <label class=\"form-check-label\" for=\"notAvailable\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </label>\n </div>\n </div>\n </div>\n </div>\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"excelValidation\"\n formControlName=\"excelValidation\">\n <label class=\"form-check-label\" for=\"excelValidation\">\n Excel Validation\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"isActive\"\n formControlName=\"isActive\">\n <label class=\"form-check-label\" for=\"isActive\">\n Active\n </label>\n </div>\n </div>\n </div>\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: [""], 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { 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: "directive", type: i2.NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "pipe", type: i5.JsonPipe, name: "json" }] });
6615
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ColumnTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: MetadataColumnTemplateService }, { token: OntologySearchService }], target: i0.ɵɵFactoryTarget.Component });
6616
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ColumnTemplateEditModal, isStandalone: true, selector: "ccv-column-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-clipboard-list 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-md-6 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-md-6 mb-3\">\n <label for=\"columnName\" class=\"form-label\">Column Name *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"columnName\"\n formControlName=\"columnName\"\n [class.is-invalid]=\"editForm.get('columnName')?.invalid && editForm.get('columnName')?.touched\"\n placeholder=\"columnName\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnName') }}\n </div>\n <small class=\"form-text text-muted\">Column name based on column type and template name. Example: characteristics[biological replicate]</small>\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=\"2\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Column 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>Column Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnType\" class=\"form-label\">Column Type *</label>\n <select\n class=\"form-select\"\n id=\"columnType\"\n formControlName=\"columnType\"\n [class.is-invalid]=\"editForm.get('columnType')?.invalid && editForm.get('columnType')?.touched\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnType') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultPosition\" class=\"form-label\">Default Position</label>\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"defaultPosition\"\n formControlName=\"defaultPosition\"\n [class.is-invalid]=\"editForm.get('defaultPosition')?.invalid && editForm.get('defaultPosition')?.touched\"\n min=\"0\"\n placeholder=\"0\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('defaultPosition') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultValue\" class=\"form-label\">\n Default Value\n @if (hasOntologyType) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Autocomplete Available\n </span>\n }\n </label>\n @if (editForm.get('notApplicable')?.value || editForm.get('notAvailable')?.value) {\n <div class=\"mb-2\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (editForm.get('notApplicable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (editForm.get('notAvailable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n @if (hasOntologyType) {\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Type to search ontology suggestions...\"\n [ngbTypeahead]=\"searchDefaultValue\"\n [resultFormatter]=\"formatSuggestion\"\n [inputFormatter]=\"inputFormatter\"\n (selectItem)=\"onSuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n @if (isLoadingSuggestions()) {\n <span class=\"input-group-text\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </span>\n } @else {\n <span class=\"input-group-text\">\n <i class=\"bi bi-search text-muted\"></i>\n </span>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Type to search for ontology suggestions\n </div>\n } @else {\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Optional default value\">\n <div class=\"form-text text-muted\">\n Select an ontology type to enable autocomplete\n </div>\n }\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"ontologyType\" class=\"form-label\">Ontology Type</label>\n <select\n class=\"form-select\"\n id=\"ontologyType\"\n formControlName=\"ontologyType\"\n (change)=\"onOntologyTypeChange()\">\n @for (ontology of ontologyTypes; track $index) {\n <option [value]=\"getOptionValue(ontology, $index)\">{{ ontology.label }}</option>\n }\n </select>\n </div>\n\n @if (editForm.get('ontologyType')?.value && editForm.get('customOntologyFilters')?.value && Object.keys(editForm.get('customOntologyFilters')?.value || {}).length > 0) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-filter me-2\"></i>Custom Ontology Filters Applied\n </h6>\n <small class=\"mb-0\">\n The following custom filters will be automatically applied for this ontology type:\n <code class=\"ms-1\">{{ editForm.get('customOntologyFilters')?.value | json }}</code>\n </small>\n </div>\n </div>\n }\n\n <!-- Visibility and Access -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-eye me-2\"></i>Visibility and Access\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"visibility\" class=\"form-label\">Visibility *</label>\n <select\n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\"\n (change)=\"onVisibilityChange()\"\n [class.is-invalid]=\"editForm.get('visibility')?.invalid && editForm.get('visibility')?.touched\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('visibility') }}\n </div>\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=\"labGroup\"\n [class.is-invalid]=\"editForm.get('labGroup')?.invalid && editForm.get('labGroup')?.touched\">\n <option [ngValue]=\"null\">Select Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('labGroup') }}\n </div>\n </div>\n\n <!-- Additional Options -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-sliders me-2\"></i>Additional Options\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"category\" class=\"form-label\">Category</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"category\"\n formControlName=\"category\"\n placeholder=\"Template category\">\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"tags\" class=\"form-label\">Tags</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"tags\"\n formControlName=\"tags\"\n placeholder=\"Comma-separated tags\">\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"enableTypeahead\"\n formControlName=\"enableTypeahead\">\n <label class=\"form-check-label\" for=\"enableTypeahead\">\n Enable Typeahead\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notApplicable\"\n formControlName=\"notApplicable\">\n <label class=\"form-check-label\" for=\"notApplicable\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notAvailable\"\n formControlName=\"notAvailable\">\n <label class=\"form-check-label\" for=\"notAvailable\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </label>\n </div>\n </div>\n </div>\n </div>\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"excelValidation\"\n formControlName=\"excelValidation\">\n <label class=\"form-check-label\" for=\"excelValidation\">\n Excel Validation\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"isActive\"\n formControlName=\"isActive\">\n <label class=\"form-check-label\" for=\"isActive\">\n Active\n </label>\n </div>\n </div>\n </div>\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: [""], 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { 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: "directive", type: i2.NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "pipe", type: i5.JsonPipe, name: "json" }] });
6539
6617
  }
6540
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ColumnTemplateEditModal, decorators: [{
6618
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ColumnTemplateEditModal, decorators: [{
6541
6619
  type: Component,
6542
6620
  args: [{ selector: 'ccv-column-template-edit-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-clipboard-list 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-md-6 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-md-6 mb-3\">\n <label for=\"columnName\" class=\"form-label\">Column Name *</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"columnName\"\n formControlName=\"columnName\"\n [class.is-invalid]=\"editForm.get('columnName')?.invalid && editForm.get('columnName')?.touched\"\n placeholder=\"columnName\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnName') }}\n </div>\n <small class=\"form-text text-muted\">Column name based on column type and template name. Example: characteristics[biological replicate]</small>\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=\"2\"\n placeholder=\"Optional description\"></textarea>\n </div>\n\n <!-- Column 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>Column Configuration\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"columnType\" class=\"form-label\">Column Type *</label>\n <select\n class=\"form-select\"\n id=\"columnType\"\n formControlName=\"columnType\"\n [class.is-invalid]=\"editForm.get('columnType')?.invalid && editForm.get('columnType')?.touched\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('columnType') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultPosition\" class=\"form-label\">Default Position</label>\n <input\n type=\"number\"\n class=\"form-control\"\n id=\"defaultPosition\"\n formControlName=\"defaultPosition\"\n [class.is-invalid]=\"editForm.get('defaultPosition')?.invalid && editForm.get('defaultPosition')?.touched\"\n min=\"0\"\n placeholder=\"0\">\n <div class=\"invalid-feedback\">\n {{ getFieldError('defaultPosition') }}\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"defaultValue\" class=\"form-label\">\n Default Value\n @if (hasOntologyType) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Autocomplete Available\n </span>\n }\n </label>\n @if (editForm.get('notApplicable')?.value || editForm.get('notAvailable')?.value) {\n <div class=\"mb-2\">\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n @if (editForm.get('notApplicable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not applicable')\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </button>\n }\n @if (editForm.get('notAvailable')?.value) {\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"setDefaultValue('not available')\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </button>\n }\n </div>\n </div>\n }\n @if (hasOntologyType) {\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Type to search ontology suggestions...\"\n [ngbTypeahead]=\"searchDefaultValue\"\n [resultFormatter]=\"formatSuggestion\"\n [inputFormatter]=\"inputFormatter\"\n (selectItem)=\"onSuggestionSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n @if (isLoadingSuggestions()) {\n <span class=\"input-group-text\">\n <div class=\"spinner-border spinner-border-sm\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n </span>\n } @else {\n <span class=\"input-group-text\">\n <i class=\"bi bi-search text-muted\"></i>\n </span>\n }\n </div>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>Type to search for ontology suggestions\n </div>\n } @else {\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Optional default value\">\n <div class=\"form-text text-muted\">\n Select an ontology type to enable autocomplete\n </div>\n }\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"ontologyType\" class=\"form-label\">Ontology Type</label>\n <select\n class=\"form-select\"\n id=\"ontologyType\"\n formControlName=\"ontologyType\"\n (change)=\"onOntologyTypeChange()\">\n @for (ontology of ontologyTypes; track $index) {\n <option [value]=\"getOptionValue(ontology, $index)\">{{ ontology.label }}</option>\n }\n </select>\n </div>\n\n @if (editForm.get('ontologyType')?.value && editForm.get('customOntologyFilters')?.value && Object.keys(editForm.get('customOntologyFilters')?.value || {}).length > 0) {\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <h6 class=\"alert-heading\">\n <i class=\"bi bi-filter me-2\"></i>Custom Ontology Filters Applied\n </h6>\n <small class=\"mb-0\">\n The following custom filters will be automatically applied for this ontology type:\n <code class=\"ms-1\">{{ editForm.get('customOntologyFilters')?.value | json }}</code>\n </small>\n </div>\n </div>\n }\n\n <!-- Visibility and Access -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-eye me-2\"></i>Visibility and Access\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"visibility\" class=\"form-label\">Visibility *</label>\n <select\n class=\"form-select\"\n id=\"visibility\"\n formControlName=\"visibility\"\n (change)=\"onVisibilityChange()\"\n [class.is-invalid]=\"editForm.get('visibility')?.invalid && editForm.get('visibility')?.touched\">\n @for (option of visibilityOptions; track option.value) {\n <option [value]=\"option.value\">{{ option.label }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('visibility') }}\n </div>\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=\"labGroup\"\n [class.is-invalid]=\"editForm.get('labGroup')?.invalid && editForm.get('labGroup')?.touched\">\n <option [ngValue]=\"null\">Select Lab Group</option>\n @for (group of labGroups; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n </select>\n <div class=\"invalid-feedback\">\n {{ getFieldError('labGroup') }}\n </div>\n </div>\n\n <!-- Additional Options -->\n <div class=\"col-12 mb-3\">\n <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-sliders me-2\"></i>Additional Options\n </h6>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"category\" class=\"form-label\">Category</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"category\"\n formControlName=\"category\"\n placeholder=\"Template category\">\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"tags\" class=\"form-label\">Tags</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"tags\"\n formControlName=\"tags\"\n placeholder=\"Comma-separated tags\">\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"enableTypeahead\"\n formControlName=\"enableTypeahead\">\n <label class=\"form-check-label\" for=\"enableTypeahead\">\n Enable Typeahead\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notApplicable\"\n formControlName=\"notApplicable\">\n <label class=\"form-check-label\" for=\"notApplicable\">\n <i class=\"bi bi-dash-circle me-1\"></i>Not Applicable\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"notAvailable\"\n formControlName=\"notAvailable\">\n <label class=\"form-check-label\" for=\"notAvailable\">\n <i class=\"bi bi-question-circle me-1\"></i>Not Available\n </label>\n </div>\n </div>\n </div>\n </div>\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"excelValidation\"\n formControlName=\"excelValidation\">\n <label class=\"form-check-label\" for=\"excelValidation\">\n Excel Validation\n </label>\n </div>\n </div>\n <div class=\"col-md-3\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"isActive\"\n formControlName=\"isActive\">\n <label class=\"form-check-label\" for=\"isActive\">\n Active\n </label>\n </div>\n </div>\n </div>\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" }]
6543
6621
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: MetadataColumnTemplateService }, { type: OntologySearchService }], propDecorators: { template: [{
@@ -6554,6 +6632,7 @@ class ColumnTemplates {
6554
6632
  fb;
6555
6633
  metadataColumnTemplateService;
6556
6634
  labGroupService;
6635
+ schemaService;
6557
6636
  modalService;
6558
6637
  searchForm;
6559
6638
  // Signals for reactive state management
@@ -6561,6 +6640,7 @@ class ColumnTemplates {
6561
6640
  search: '',
6562
6641
  labGroupId: null,
6563
6642
  visibility: null,
6643
+ schemaId: null,
6564
6644
  limit: 10,
6565
6645
  offset: 0
6566
6646
  }, ...(ngDevMode ? [{ debugName: "searchParams" }] : []));
@@ -6585,9 +6665,14 @@ class ColumnTemplates {
6585
6665
  // Direct signal-based data management
6586
6666
  templatesData = signal({ count: 0, results: [] }, ...(ngDevMode ? [{ debugName: "templatesData" }] : []));
6587
6667
  labGroupsData = signal({ count: 0, results: [] }, ...(ngDevMode ? [{ debugName: "labGroupsData" }] : []));
6668
+ schemasData = signal({ count: 0, results: [] }, ...(ngDevMode ? [{ debugName: "schemasData" }] : []));
6669
+ selectedSchema = signal(null, ...(ngDevMode ? [{ debugName: "selectedSchema" }] : []));
6670
+ schemaSearchText = '';
6671
+ schemaTypeFilter = signal('all', ...(ngDevMode ? [{ debugName: "schemaTypeFilter" }] : []));
6588
6672
  // Computed values for better performance
6589
6673
  hasTemplates = computed(() => this.templatesData().results.length > 0, ...(ngDevMode ? [{ debugName: "hasTemplates" }] : []));
6590
6674
  hasLabGroups = computed(() => this.labGroupsData().results.length > 0, ...(ngDevMode ? [{ debugName: "hasLabGroups" }] : []));
6675
+ hasSchemas = computed(() => this.schemasData().results.length > 0, ...(ngDevMode ? [{ debugName: "hasSchemas" }] : []));
6591
6676
  showTemplatesPagination = computed(() => this.templatesData().count > this.pageSize(), ...(ngDevMode ? [{ debugName: "showTemplatesPagination" }] : []));
6592
6677
  showLabGroupsPagination = computed(() => this.labGroupsData().count > this.labGroupsPageSize(), ...(ngDevMode ? [{ debugName: "showLabGroupsPagination" }] : []));
6593
6678
  // Filtered and sorted templates
@@ -6626,6 +6711,10 @@ class ColumnTemplates {
6626
6711
  aValue = a.visibility?.toLowerCase() || '';
6627
6712
  bValue = b.visibility?.toLowerCase() || '';
6628
6713
  break;
6714
+ case 'schema':
6715
+ aValue = (a.schemaName || a.sourceSchema || '').toLowerCase();
6716
+ bValue = (b.schemaName || b.sourceSchema || '').toLowerCase();
6717
+ break;
6629
6718
  default:
6630
6719
  return 0;
6631
6720
  }
@@ -6638,10 +6727,11 @@ class ColumnTemplates {
6638
6727
  }
6639
6728
  return filtered;
6640
6729
  }, ...(ngDevMode ? [{ debugName: "filteredTemplates" }] : []));
6641
- constructor(fb, metadataColumnTemplateService, labGroupService, modalService) {
6730
+ constructor(fb, metadataColumnTemplateService, labGroupService, schemaService, modalService) {
6642
6731
  this.fb = fb;
6643
6732
  this.metadataColumnTemplateService = metadataColumnTemplateService;
6644
6733
  this.labGroupService = labGroupService;
6734
+ this.schemaService = schemaService;
6645
6735
  this.modalService = modalService;
6646
6736
  this.searchForm = this.fb.group({
6647
6737
  search: [''],
@@ -6673,9 +6763,120 @@ class ColumnTemplates {
6673
6763
  search: '',
6674
6764
  labGroupId: null,
6675
6765
  visibility: null,
6766
+ schemaId: null,
6676
6767
  limit: this.pageSize(),
6677
6768
  offset: 0
6678
6769
  });
6770
+ this.loadSchemas();
6771
+ }
6772
+ loadSchemas() {
6773
+ const params = this.getSchemaQueryParams();
6774
+ this.schemaService.getSchemas(params).subscribe({
6775
+ next: (response) => {
6776
+ this.schemasData.set(response);
6777
+ },
6778
+ error: (error) => {
6779
+ console.error('Error loading schemas:', error);
6780
+ this.schemasData.set({ count: 0, results: [] });
6781
+ }
6782
+ });
6783
+ }
6784
+ getSchemaQueryParams(search) {
6785
+ const params = {
6786
+ limit: 20,
6787
+ isActive: true
6788
+ };
6789
+ if (search) {
6790
+ params.search = search;
6791
+ }
6792
+ const typeFilter = this.schemaTypeFilter();
6793
+ if (typeFilter === 'builtin') {
6794
+ params.isBuiltin = true;
6795
+ }
6796
+ else if (typeFilter === 'user') {
6797
+ params.isBuiltin = false;
6798
+ }
6799
+ return params;
6800
+ }
6801
+ searchSchemas = (text$) => text$.pipe(debounceTime(200), distinctUntilChanged(), switchMap$1(term => {
6802
+ const params = this.getSchemaQueryParams(term.length >= 1 ? term : undefined);
6803
+ return this.schemaService.getSchemas(params).pipe(map$1(response => response.results), catchError(() => of([])));
6804
+ }));
6805
+ onSchemaTypeFilterChange(type) {
6806
+ this.schemaTypeFilter.set(type);
6807
+ this.clearSchemaFilter();
6808
+ this.loadSchemas();
6809
+ }
6810
+ formatSchema = (schema) => {
6811
+ return schema.displayName || schema.name;
6812
+ };
6813
+ onSchemaSelected = (event) => {
6814
+ const schema = event.item;
6815
+ if (schema) {
6816
+ this.selectedSchema.set(schema);
6817
+ this.currentPage.set(1);
6818
+ this.searchParams.update(params => ({
6819
+ ...params,
6820
+ schemaId: schema.id,
6821
+ offset: 0
6822
+ }));
6823
+ }
6824
+ };
6825
+ clearSchemaFilter() {
6826
+ this.selectedSchema.set(null);
6827
+ this.schemaSearchText = '';
6828
+ this.currentPage.set(1);
6829
+ this.searchParams.update(params => ({
6830
+ ...params,
6831
+ schemaId: null,
6832
+ offset: 0
6833
+ }));
6834
+ }
6835
+ filterBySchema(template) {
6836
+ if (template.schema) {
6837
+ const schema = this.schemasData().results.find(s => s.id === template.schema);
6838
+ if (schema) {
6839
+ this.selectedSchema.set(schema);
6840
+ this.schemaSearchText = schema.displayName || schema.name;
6841
+ this.currentPage.set(1);
6842
+ this.searchParams.update(params => ({
6843
+ ...params,
6844
+ schemaId: schema.id,
6845
+ offset: 0
6846
+ }));
6847
+ }
6848
+ else {
6849
+ this.schemaService.getSchema(template.schema).subscribe({
6850
+ next: (schema) => {
6851
+ this.selectedSchema.set(schema);
6852
+ this.schemaSearchText = schema.displayName || schema.name;
6853
+ this.currentPage.set(1);
6854
+ this.searchParams.update(params => ({
6855
+ ...params,
6856
+ schemaId: schema.id,
6857
+ offset: 0
6858
+ }));
6859
+ }
6860
+ });
6861
+ }
6862
+ }
6863
+ }
6864
+ filterBySourceSchema(sourceSchema) {
6865
+ this.schemaService.getSchemas({ search: sourceSchema, limit: 1, isActive: true }).subscribe({
6866
+ next: (response) => {
6867
+ if (response.results.length > 0) {
6868
+ const schema = response.results[0];
6869
+ this.selectedSchema.set(schema);
6870
+ this.schemaSearchText = schema.displayName || schema.name;
6871
+ this.currentPage.set(1);
6872
+ this.searchParams.update(params => ({
6873
+ ...params,
6874
+ schemaId: schema.id,
6875
+ offset: 0
6876
+ }));
6877
+ }
6878
+ }
6879
+ });
6679
6880
  }
6680
6881
  loadLabGroupsWithParams(params) {
6681
6882
  this.labGroupService.getMyLabGroups({ limit: params.limit, offset: params.offset }).subscribe({
@@ -6695,13 +6896,14 @@ class ColumnTemplates {
6695
6896
  // Reset to first page when searching
6696
6897
  this.currentPage.set(1);
6697
6898
  // Update search params signal - this will trigger the effect
6698
- this.searchParams.set({
6899
+ // Note: schemaId is handled separately via typeahead
6900
+ this.searchParams.update(params => ({
6901
+ ...params,
6699
6902
  search: formValue.search || '',
6700
6903
  labGroupId: formValue.labGroupId || null,
6701
6904
  visibility: formValue.visibility || null,
6702
- limit: this.pageSize(),
6703
- offset: 0 // Always start from first page when searching
6704
- });
6905
+ offset: 0
6906
+ }));
6705
6907
  });
6706
6908
  }
6707
6909
  loadTemplatesWithParams(params) {
@@ -6710,6 +6912,7 @@ class ColumnTemplates {
6710
6912
  search: params.search || undefined,
6711
6913
  labGroupId: params.labGroupId || undefined,
6712
6914
  visibility: params.visibility || undefined,
6915
+ schemaId: params.schemaId || undefined,
6713
6916
  limit: params.limit,
6714
6917
  offset: params.offset
6715
6918
  }).subscribe({
@@ -6882,13 +7085,13 @@ class ColumnTemplates {
6882
7085
  // Trigger a refresh by updating the search params
6883
7086
  this.searchParams.update(params => ({ ...params }));
6884
7087
  }
6885
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ColumnTemplates, deps: [{ token: i1.FormBuilder }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }, { token: i2.NgbModal }], target: i0.ɵɵFactoryTarget.Component });
6886
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: ColumnTemplates, isStandalone: true, selector: "ccv-column-templates", ngImport: i0, template: "<div class=\"column-templates-container\">\n <!-- Header Section -->\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 <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-clipboard-list me-2\"></i>Column Templates\n </h4>\n @if (selectedLabGroup()) {\n <span class=\"badge bg-primary ms-3\">{{ selectedLabGroup()!.name }}</span>\n }\n </div>\n <div class=\"d-flex gap-2\">\n <button class=\"btn btn-primary btn-sm\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>New Template\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"templates-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"search\"\n class=\"form-control\"\n id=\"templateSearch\"\n formControlName=\"search\"\n placeholder=\"Search templates...\">\n <label for=\"templateSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Templates\n </label>\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\n formControlName=\"labGroupId\"\n (change)=\"onLabGroupChange($event)\">\n <option [ngValue]=\"null\">All Lab Groups</option>\n @if (hasLabGroups()) {\n @for (group of labGroupsData().results; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n }\n </select>\n <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n\n <!-- Lab Groups Pagination -->\n @if (showLabGroupsPagination()) {\n <div class=\"d-flex justify-content-center mt-2\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"labGroupsCurrentPage()\"\n [pageSize]=\"labGroupsPageSize()\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"false\"\n [rotate]=\"true\"\n (pageChange)=\"onLabGroupsPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-check form-switch mt-3\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"globalTemplates\"\n formControlName=\"visibility\">\n <label class=\"form-check-label\" for=\"globalTemplates\">\n <i class=\"bi bi-globe me-1\"></i>Global Templates Only\n </label>\n </div>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Templates Table -->\n <div class=\"card\">\n <div class=\"card-header\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Column Templates\n <span class=\"badge bg-primary ms-2\">{{ templatesData().count }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n \n <!-- Column Filter -->\n @if (hasTemplates()) {\n <div class=\"d-flex align-items-center\">\n <div class=\"input-group input-group-sm me-3\" style=\"width: 300px;\">\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 by column name or template...\"\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\">\n {{ filteredTemplatesCount }}/{{ totalTemplatesCount }} templates\n </small>\n }\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th class=\"sortable\" \n (click)=\"sortBy('name')\"\n [class.sorted]=\"isSorted('name')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-tag me-1\"></i>Template Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnName')\"\n [class.sorted]=\"isSorted('columnName')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-list me-1\"></i>Column Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnName')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnType')\"\n [class.sorted]=\"isSorted('columnType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-layers me-1\"></i>Type</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('ontologyType')\"\n [class.sorted]=\"isSorted('ontologyType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-diagram-3 me-1\"></i>Ontology</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('ontologyType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('visibility')\"\n [class.sorted]=\"isSorted('visibility')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-globe me-1\"></i>Visibility</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('visibility')\"></i>\n </div>\n </th>\n <th><i class=\"bi bi-folder me-1\"></i>Category</th>\n <th><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (template of filteredTemplates(); track template.id) {\n <tr>\n <td>\n <strong>{{ template.name }}</strong>\n @if (template.isSystemTemplate) {\n <i class=\"bi bi-shield-check text-success ms-2\" title=\"System template\"></i>\n }\n </td>\n <td>{{ template.columnName }}</td>\n <td>\n <span class=\"badge bg-secondary\">{{ template.columnType }}</span>\n </td>\n <td>\n @if (template.ontologyType) {\n <span class=\"badge bg-success\">{{ template.ontologyType }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @switch (template.visibility) {\n @case ('public') {\n <span class=\"badge bg-success\">Public</span>\n }\n @case ('group') {\n <span class=\"badge bg-warning\">Lab Group</span>\n }\n @case ('private') {\n <span class=\"badge bg-secondary\">Private</span>\n }\n @default {\n <span class=\"badge bg-secondary\">{{ template.visibility }}</span>\n }\n }\n </td>\n <td>\n @if (template.category) {\n <span class=\"badge bg-secondary\">{{ template.category }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template)\"\n title=\"Edit template\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n class=\"btn btn-outline-info\"\n (click)=\"duplicateTemplate(template)\"\n title=\"Duplicate template\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button\n class=\"btn btn-outline-danger\"\n (click)=\"deleteTemplate(template)\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showTemplatesPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"templatesData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-clipboard-list fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Column Templates Found</h5>\n <p class=\"text-muted\">\n @if (searchForm.get('search')?.value || searchForm.get('labGroupId')?.value) {\n Try adjusting your search criteria or create a new template.\n } @else {\n Get started by creating your first column template.\n }\n </p>\n <button class=\"btn btn-primary\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Template\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".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}\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: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "ngmodule", type: NgbPaginationModule }] });
7088
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ColumnTemplates, deps: [{ token: i1.FormBuilder }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }, { token: SchemaService }, { token: i2.NgbModal }], target: i0.ɵɵFactoryTarget.Component });
7089
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: ColumnTemplates, isStandalone: true, selector: "ccv-column-templates", ngImport: i0, template: "<div class=\"column-templates-container\">\n <!-- Header Section -->\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 <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-clipboard-list me-2\"></i>Column Templates\n </h4>\n @if (selectedLabGroup()) {\n <span class=\"badge bg-primary ms-3\">{{ selectedLabGroup()!.name }}</span>\n }\n @if (selectedSchema()) {\n <span class=\"badge bg-info ms-2\">{{ selectedSchema()!.displayName || selectedSchema()!.name }}</span>\n }\n </div>\n <div class=\"d-flex gap-2\">\n <button class=\"btn btn-primary btn-sm\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>New Template\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"templates-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"search\"\n class=\"form-control\"\n id=\"templateSearch\"\n formControlName=\"search\"\n placeholder=\"Search templates...\">\n <label for=\"templateSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Templates\n </label>\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\n formControlName=\"labGroupId\"\n (change)=\"onLabGroupChange($event)\">\n <option [ngValue]=\"null\">All Lab Groups</option>\n @if (hasLabGroups()) {\n @for (group of labGroupsData().results; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n }\n </select>\n <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n\n <!-- Lab Groups Pagination -->\n @if (showLabGroupsPagination()) {\n <div class=\"d-flex justify-content-center mt-2\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"labGroupsCurrentPage()\"\n [pageSize]=\"labGroupsPageSize()\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"false\"\n [rotate]=\"true\"\n (pageChange)=\"onLabGroupsPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"schemaSearch\"\n placeholder=\"Search schemas...\"\n [ngbTypeahead]=\"searchSchemas\"\n [resultFormatter]=\"formatSchema\"\n [inputFormatter]=\"formatSchema\"\n (selectItem)=\"onSchemaSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [(ngModel)]=\"schemaSearchText\">\n <label for=\"schemaSearch\">\n <i class=\"bi bi-file-earmark-text me-1\"></i>Schema\n </label>\n </div>\n @if (selectedSchema()) {\n <div class=\"d-flex align-items-center mt-1\">\n <small class=\"text-muted me-2\">Filtering: {{ selectedSchema()!.displayName || selectedSchema()!.name }}</small>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary py-0 px-1\"\n (click)=\"clearSchemaFilter()\"\n title=\"Clear schema filter\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n }\n </div>\n </form>\n <div class=\"row mt-3\">\n <div class=\"col-md-4\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"globalTemplates\"\n formControlName=\"visibility\">\n <label class=\"form-check-label\" for=\"globalTemplates\">\n <i class=\"bi bi-globe me-1\"></i>Global Templates Only\n </label>\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"btn-group btn-group-sm\" role=\"group\" aria-label=\"Schema type filter\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'all' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('all')\">\n All Schemas\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'builtin' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('builtin')\">\n Builtin\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'user' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('user')\">\n User\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Templates Table -->\n <div class=\"card\">\n <div class=\"card-header\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Column Templates\n <span class=\"badge bg-primary ms-2\">{{ templatesData().count }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n \n <!-- Column Filter -->\n @if (hasTemplates()) {\n <div class=\"d-flex align-items-center\">\n <div class=\"input-group input-group-sm me-3\" style=\"width: 300px;\">\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 by column name or template...\"\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\">\n {{ filteredTemplatesCount }}/{{ totalTemplatesCount }} templates\n </small>\n }\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th class=\"sortable\" \n (click)=\"sortBy('name')\"\n [class.sorted]=\"isSorted('name')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-tag me-1\"></i>Template Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnName')\"\n [class.sorted]=\"isSorted('columnName')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-list me-1\"></i>Column Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnName')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnType')\"\n [class.sorted]=\"isSorted('columnType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-layers me-1\"></i>Type</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('ontologyType')\"\n [class.sorted]=\"isSorted('ontologyType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-diagram-3 me-1\"></i>Ontology</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('ontologyType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('visibility')\"\n [class.sorted]=\"isSorted('visibility')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-globe me-1\"></i>Visibility</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('visibility')\"></i>\n </div>\n </th>\n <th class=\"sortable\"\n (click)=\"sortBy('schema')\"\n [class.sorted]=\"isSorted('schema')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-file-earmark-text me-1\"></i>Schema</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('schema')\"></i>\n </div>\n </th>\n <th><i class=\"bi bi-folder me-1\"></i>Category</th>\n <th><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (template of filteredTemplates(); track template.id) {\n <tr>\n <td>\n <strong>{{ template.name }}</strong>\n @if (template.isSystemTemplate) {\n <i class=\"bi bi-shield-check text-success ms-2\" title=\"System template\"></i>\n }\n </td>\n <td>{{ template.columnName }}</td>\n <td>\n <span class=\"badge bg-secondary\">{{ template.columnType }}</span>\n </td>\n <td>\n @if (template.ontologyType) {\n <span class=\"badge bg-success\">{{ template.ontologyType }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @switch (template.visibility) {\n @case ('public') {\n <span class=\"badge bg-success\">Public</span>\n }\n @case ('group') {\n <span class=\"badge bg-warning\">Lab Group</span>\n }\n @case ('private') {\n <span class=\"badge bg-secondary\">Private</span>\n }\n @default {\n <span class=\"badge bg-secondary\">{{ template.visibility }}</span>\n }\n }\n </td>\n <td>\n @if (template.schemaName) {\n <span\n class=\"badge bg-info cursor-pointer\"\n role=\"button\"\n (click)=\"filterBySchema(template)\"\n title=\"Click to filter by this schema\">\n {{ template.schemaName }}\n </span>\n } @else if (template.sourceSchema) {\n <span\n class=\"badge bg-info cursor-pointer\"\n role=\"button\"\n (click)=\"filterBySourceSchema(template.sourceSchema)\"\n title=\"Click to filter by this schema\">\n {{ template.sourceSchema }}\n </span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @if (template.category) {\n <span class=\"badge bg-secondary\">{{ template.category }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template)\"\n title=\"Edit template\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n class=\"btn btn-outline-info\"\n (click)=\"duplicateTemplate(template)\"\n title=\"Duplicate template\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button\n class=\"btn btn-outline-danger\"\n (click)=\"deleteTemplate(template)\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showTemplatesPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"templatesData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-clipboard-list fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Column Templates Found</h5>\n <p class=\"text-muted\">\n @if (searchForm.get('search')?.value || searchForm.get('labGroupId')?.value || searchForm.get('schemaId')?.value) {\n Try adjusting your search criteria or create a new template.\n } @else {\n Get started by creating your first column template.\n }\n </p>\n <button class=\"btn btn-primary\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Template\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".cursor-pointer{cursor:pointer}.cursor-pointer:hover{opacity:.8;text-decoration:underline}.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}\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: FormsModule }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "directive", type: i2.NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "ngmodule", type: NgbPaginationModule }] });
6887
7090
  }
6888
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ColumnTemplates, decorators: [{
7091
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: ColumnTemplates, decorators: [{
6889
7092
  type: Component,
6890
- args: [{ selector: 'ccv-column-templates', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule, NgbPaginationModule], template: "<div class=\"column-templates-container\">\n <!-- Header Section -->\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 <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-clipboard-list me-2\"></i>Column Templates\n </h4>\n @if (selectedLabGroup()) {\n <span class=\"badge bg-primary ms-3\">{{ selectedLabGroup()!.name }}</span>\n }\n </div>\n <div class=\"d-flex gap-2\">\n <button class=\"btn btn-primary btn-sm\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>New Template\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"templates-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"search\"\n class=\"form-control\"\n id=\"templateSearch\"\n formControlName=\"search\"\n placeholder=\"Search templates...\">\n <label for=\"templateSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Templates\n </label>\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\n formControlName=\"labGroupId\"\n (change)=\"onLabGroupChange($event)\">\n <option [ngValue]=\"null\">All Lab Groups</option>\n @if (hasLabGroups()) {\n @for (group of labGroupsData().results; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n }\n </select>\n <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n\n <!-- Lab Groups Pagination -->\n @if (showLabGroupsPagination()) {\n <div class=\"d-flex justify-content-center mt-2\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"labGroupsCurrentPage()\"\n [pageSize]=\"labGroupsPageSize()\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"false\"\n [rotate]=\"true\"\n (pageChange)=\"onLabGroupsPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-check form-switch mt-3\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"globalTemplates\"\n formControlName=\"visibility\">\n <label class=\"form-check-label\" for=\"globalTemplates\">\n <i class=\"bi bi-globe me-1\"></i>Global Templates Only\n </label>\n </div>\n </div>\n </form>\n </div>\n </div>\n\n <!-- Templates Table -->\n <div class=\"card\">\n <div class=\"card-header\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Column Templates\n <span class=\"badge bg-primary ms-2\">{{ templatesData().count }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n \n <!-- Column Filter -->\n @if (hasTemplates()) {\n <div class=\"d-flex align-items-center\">\n <div class=\"input-group input-group-sm me-3\" style=\"width: 300px;\">\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 by column name or template...\"\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\">\n {{ filteredTemplatesCount }}/{{ totalTemplatesCount }} templates\n </small>\n }\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th class=\"sortable\" \n (click)=\"sortBy('name')\"\n [class.sorted]=\"isSorted('name')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-tag me-1\"></i>Template Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnName')\"\n [class.sorted]=\"isSorted('columnName')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-list me-1\"></i>Column Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnName')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnType')\"\n [class.sorted]=\"isSorted('columnType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-layers me-1\"></i>Type</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('ontologyType')\"\n [class.sorted]=\"isSorted('ontologyType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-diagram-3 me-1\"></i>Ontology</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('ontologyType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('visibility')\"\n [class.sorted]=\"isSorted('visibility')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-globe me-1\"></i>Visibility</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('visibility')\"></i>\n </div>\n </th>\n <th><i class=\"bi bi-folder me-1\"></i>Category</th>\n <th><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (template of filteredTemplates(); track template.id) {\n <tr>\n <td>\n <strong>{{ template.name }}</strong>\n @if (template.isSystemTemplate) {\n <i class=\"bi bi-shield-check text-success ms-2\" title=\"System template\"></i>\n }\n </td>\n <td>{{ template.columnName }}</td>\n <td>\n <span class=\"badge bg-secondary\">{{ template.columnType }}</span>\n </td>\n <td>\n @if (template.ontologyType) {\n <span class=\"badge bg-success\">{{ template.ontologyType }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @switch (template.visibility) {\n @case ('public') {\n <span class=\"badge bg-success\">Public</span>\n }\n @case ('group') {\n <span class=\"badge bg-warning\">Lab Group</span>\n }\n @case ('private') {\n <span class=\"badge bg-secondary\">Private</span>\n }\n @default {\n <span class=\"badge bg-secondary\">{{ template.visibility }}</span>\n }\n }\n </td>\n <td>\n @if (template.category) {\n <span class=\"badge bg-secondary\">{{ template.category }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template)\"\n title=\"Edit template\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n class=\"btn btn-outline-info\"\n (click)=\"duplicateTemplate(template)\"\n title=\"Duplicate template\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button\n class=\"btn btn-outline-danger\"\n (click)=\"deleteTemplate(template)\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showTemplatesPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"templatesData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-clipboard-list fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Column Templates Found</h5>\n <p class=\"text-muted\">\n @if (searchForm.get('search')?.value || searchForm.get('labGroupId')?.value) {\n Try adjusting your search criteria or create a new template.\n } @else {\n Get started by creating your first column template.\n }\n </p>\n <button class=\"btn btn-primary\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Template\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".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}\n"] }]
6891
- }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }, { type: i2.NgbModal }] });
7093
+ args: [{ selector: 'ccv-column-templates', standalone: true, imports: [CommonModule, ReactiveFormsModule, FormsModule, NgbModule, NgbPaginationModule, NgbTypeahead], template: "<div class=\"column-templates-container\">\n <!-- Header Section -->\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 <h4 class=\"navbar-brand m-0 fw-bold\">\n <i class=\"bi bi-clipboard-list me-2\"></i>Column Templates\n </h4>\n @if (selectedLabGroup()) {\n <span class=\"badge bg-primary ms-3\">{{ selectedLabGroup()!.name }}</span>\n }\n @if (selectedSchema()) {\n <span class=\"badge bg-info ms-2\">{{ selectedSchema()!.displayName || selectedSchema()!.name }}</span>\n }\n </div>\n <div class=\"d-flex gap-2\">\n <button class=\"btn btn-primary btn-sm\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>New Template\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"templates-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"search\"\n class=\"form-control\"\n id=\"templateSearch\"\n formControlName=\"search\"\n placeholder=\"Search templates...\">\n <label for=\"templateSearch\">\n <i class=\"bi bi-search me-1\"></i>Search Templates\n </label>\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <select\n class=\"form-select\"\n id=\"labGroupSelect\"\n formControlName=\"labGroupId\"\n (change)=\"onLabGroupChange($event)\">\n <option [ngValue]=\"null\">All Lab Groups</option>\n @if (hasLabGroups()) {\n @for (group of labGroupsData().results; track group.id) {\n <option [ngValue]=\"group.id\">{{ group.name }}</option>\n }\n }\n </select>\n <label for=\"labGroupSelect\">Lab Group</label>\n </div>\n\n <!-- Lab Groups Pagination -->\n @if (showLabGroupsPagination()) {\n <div class=\"d-flex justify-content-center mt-2\">\n <ngb-pagination\n [collectionSize]=\"labGroupsData().count\"\n [page]=\"labGroupsCurrentPage()\"\n [pageSize]=\"labGroupsPageSize()\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"false\"\n [rotate]=\"true\"\n (pageChange)=\"onLabGroupsPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <div class=\"form-floating\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"schemaSearch\"\n placeholder=\"Search schemas...\"\n [ngbTypeahead]=\"searchSchemas\"\n [resultFormatter]=\"formatSchema\"\n [inputFormatter]=\"formatSchema\"\n (selectItem)=\"onSchemaSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\"\n [(ngModel)]=\"schemaSearchText\">\n <label for=\"schemaSearch\">\n <i class=\"bi bi-file-earmark-text me-1\"></i>Schema\n </label>\n </div>\n @if (selectedSchema()) {\n <div class=\"d-flex align-items-center mt-1\">\n <small class=\"text-muted me-2\">Filtering: {{ selectedSchema()!.displayName || selectedSchema()!.name }}</small>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary py-0 px-1\"\n (click)=\"clearSchemaFilter()\"\n title=\"Clear schema filter\">\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n }\n </div>\n </form>\n <div class=\"row mt-3\">\n <div class=\"col-md-4\">\n <div class=\"form-check form-switch\">\n <input\n class=\"form-check-input\"\n type=\"checkbox\"\n id=\"globalTemplates\"\n formControlName=\"visibility\">\n <label class=\"form-check-label\" for=\"globalTemplates\">\n <i class=\"bi bi-globe me-1\"></i>Global Templates Only\n </label>\n </div>\n </div>\n <div class=\"col-md-4\">\n <div class=\"btn-group btn-group-sm\" role=\"group\" aria-label=\"Schema type filter\">\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'all' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('all')\">\n All Schemas\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'builtin' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('builtin')\">\n Builtin\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class]=\"schemaTypeFilter() === 'user' ? 'btn-primary' : 'btn-outline-primary'\"\n (click)=\"onSchemaTypeFilterChange('user')\">\n User\n </button>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Templates Table -->\n <div class=\"card\">\n <div class=\"card-header\">\n <div class=\"d-flex justify-content-between align-items-center mb-3\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Column Templates\n <span class=\"badge bg-primary ms-2\">{{ templatesData().count }}</span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n \n <!-- Column Filter -->\n @if (hasTemplates()) {\n <div class=\"d-flex align-items-center\">\n <div class=\"input-group input-group-sm me-3\" style=\"width: 300px;\">\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 by column name or template...\"\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\">\n {{ filteredTemplatesCount }}/{{ totalTemplatesCount }} templates\n </small>\n }\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (hasTemplates()) {\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th class=\"sortable\" \n (click)=\"sortBy('name')\"\n [class.sorted]=\"isSorted('name')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-tag me-1\"></i>Template Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('name')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnName')\"\n [class.sorted]=\"isSorted('columnName')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-list me-1\"></i>Column Name</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnName')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('columnType')\"\n [class.sorted]=\"isSorted('columnType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-layers me-1\"></i>Type</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('columnType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('ontologyType')\"\n [class.sorted]=\"isSorted('ontologyType')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-diagram-3 me-1\"></i>Ontology</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('ontologyType')\"></i>\n </div>\n </th>\n <th class=\"sortable\" \n (click)=\"sortBy('visibility')\"\n [class.sorted]=\"isSorted('visibility')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-globe me-1\"></i>Visibility</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('visibility')\"></i>\n </div>\n </th>\n <th class=\"sortable\"\n (click)=\"sortBy('schema')\"\n [class.sorted]=\"isSorted('schema')\">\n <div class=\"d-flex align-items-center justify-content-between\">\n <span><i class=\"bi bi-file-earmark-text me-1\"></i>Schema</span>\n <i class=\"bi ms-1\" [class]=\"getSortIcon('schema')\"></i>\n </div>\n </th>\n <th><i class=\"bi bi-folder me-1\"></i>Category</th>\n <th><i class=\"bi bi-gear me-1\"></i>Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (template of filteredTemplates(); track template.id) {\n <tr>\n <td>\n <strong>{{ template.name }}</strong>\n @if (template.isSystemTemplate) {\n <i class=\"bi bi-shield-check text-success ms-2\" title=\"System template\"></i>\n }\n </td>\n <td>{{ template.columnName }}</td>\n <td>\n <span class=\"badge bg-secondary\">{{ template.columnType }}</span>\n </td>\n <td>\n @if (template.ontologyType) {\n <span class=\"badge bg-success\">{{ template.ontologyType }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @switch (template.visibility) {\n @case ('public') {\n <span class=\"badge bg-success\">Public</span>\n }\n @case ('group') {\n <span class=\"badge bg-warning\">Lab Group</span>\n }\n @case ('private') {\n <span class=\"badge bg-secondary\">Private</span>\n }\n @default {\n <span class=\"badge bg-secondary\">{{ template.visibility }}</span>\n }\n }\n </td>\n <td>\n @if (template.schemaName) {\n <span\n class=\"badge bg-info cursor-pointer\"\n role=\"button\"\n (click)=\"filterBySchema(template)\"\n title=\"Click to filter by this schema\">\n {{ template.schemaName }}\n </span>\n } @else if (template.sourceSchema) {\n <span\n class=\"badge bg-info cursor-pointer\"\n role=\"button\"\n (click)=\"filterBySourceSchema(template.sourceSchema)\"\n title=\"Click to filter by this schema\">\n {{ template.sourceSchema }}\n </span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n @if (template.category) {\n <span class=\"badge bg-secondary\">{{ template.category }}</span>\n } @else {\n <span class=\"text-muted\">-</span>\n }\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button\n class=\"btn btn-outline-primary\"\n (click)=\"editTemplate(template)\"\n title=\"Edit template\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button\n class=\"btn btn-outline-info\"\n (click)=\"duplicateTemplate(template)\"\n title=\"Duplicate template\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button\n class=\"btn btn-outline-danger\"\n (click)=\"deleteTemplate(template)\"\n title=\"Delete template\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Pagination -->\n @if (showTemplatesPagination()) {\n <div class=\"d-flex justify-content-center mt-3 p-3\">\n <ngb-pagination\n [collectionSize]=\"templatesData().count\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm\">\n </ngb-pagination>\n </div>\n }\n } @else {\n <div class=\"empty-state text-center py-5\">\n <i class=\"bi bi-clipboard-list fs-1 text-muted mb-3\"></i>\n <h5 class=\"text-muted\">No Column Templates Found</h5>\n <p class=\"text-muted\">\n @if (searchForm.get('search')?.value || searchForm.get('labGroupId')?.value || searchForm.get('schemaId')?.value) {\n Try adjusting your search criteria or create a new template.\n } @else {\n Get started by creating your first column template.\n }\n </p>\n <button class=\"btn btn-primary\" (click)=\"createTemplate()\">\n <i class=\"bi bi-plus me-1\"></i>Create First Template\n </button>\n </div>\n }\n </div>\n </div>\n </div>\n</div>\n", styles: [".cursor-pointer{cursor:pointer}.cursor-pointer:hover{opacity:.8;text-decoration:underline}.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}\n"] }]
7094
+ }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }, { type: SchemaService }, { type: i2.NgbModal }] });
6892
7095
 
6893
7096
  class FavoriteManagement {
6894
7097
  fb;
@@ -7611,10 +7814,10 @@ class FavoriteManagement {
7611
7814
  return 'bi-star';
7612
7815
  return 'bi-circle';
7613
7816
  }
7614
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FavoriteManagement, deps: [{ token: i1.FormBuilder }, { token: FavouriteMetadataOptionService }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }, { token: i3.ToastService }, { token: i2.NgbModal }, { token: i3.AuthService }], target: i0.ɵɵFactoryTarget.Component });
7615
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: FavoriteManagement, isStandalone: true, selector: "ccv-favorite-management", ngImport: i0, template: "<div class=\"favorite-management-container\">\n <!-- Header Section -->\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 <h4 class=\"mb-0\">\n <i class=\"bi bi-heart-fill me-2 text-danger\"></i>Favorite Management\n </h4>\n <span class=\"badge bg-primary ms-3\">{{ filteredFavorites().length }} favorites</span>\n </div>\n\n <div class=\"d-flex gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n (click)=\"openCreateModal()\"\n [disabled]=\"isLoading()\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Favorite\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"tables-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-3\">\n <label for=\"search\" class=\"form-label\">Search</label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"search\"\n formControlName=\"search\"\n placeholder=\"Search by name or value\">\n </div>\n </div>\n\n <div class=\"col-md-3\">\n <label for=\"scope\" class=\"form-label\">Scope</label>\n <select class=\"form-select\" id=\"scope\" formControlName=\"scope\">\n <option value=\"\">All Scopes</option>\n <option value=\"personal\">Personal</option>\n <option value=\"lab_group\">Lab Group</option>\n <option value=\"global\">Global</option>\n </select>\n </div>\n\n <div class=\"col-md-3\">\n <label for=\"labGroup\" class=\"form-label\">Lab Group</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"labGroup\"\n formControlName=\"labGroup\"\n placeholder=\"All Lab Groups\"\n [ngbTypeahead]=\"searchLabGroups\"\n [resultFormatter]=\"formatLabGroup\"\n [inputFormatter]=\"inputLabGroupFormatter\"\n (selectItem)=\"onLabGroupSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n </div>\n\n <div class=\"col-md-3\">\n <label for=\"columnType\" class=\"form-label\">Column Type</label>\n <select class=\"form-select\" id=\"columnType\" formControlName=\"columnType\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n </div>\n </form>\n </div>\n </div>\n\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Favorites options\n <span class=\"badge bg-primary ms-2\"></span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (!isLoading()) {\n\n @if (filteredFavorites().length === 0) {\n <div class=\"text-center py-5\">\n <i class=\"bi bi-heart display-1 text-muted\"></i>\n <h5 class=\"mt-3 text-muted\">No Favorites Found</h5>\n @if (searchTerm() || scopeFilter() || columnTypeFilter()) {\n <p class=\"text-muted\">Try adjusting your search criteria</p>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"clearFilters()\">\n <i class=\"bi bi-arrow-clockwise me-1\"></i>Clear Filters\n </button>\n } @else {\n <p class=\"text-muted\">Create your first favorite to get started</p>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openCreateModal()\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add First Favorite\n </button>\n }\n </div>\n }\n\n <!-- Favorites Table -->\n @if (filteredFavorites().length > 0) {\n <div class=\"card\">\n <div class=\"card-body p-0\">\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th scope=\"col\" style=\"width: 200px;\">Column</th>\n <th scope=\"col\" style=\"width: 200px;\">Display Name</th>\n <th scope=\"col\">Value</th>\n <th scope=\"col\" style=\"width: 150px;\">Scope</th>\n <th scope=\"col\" style=\"width: 120px;\">Created</th>\n <th scope=\"col\" style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (favorite of paginatedFavorites(); track favorite.id) {\n <tr>\n <td>\n <div class=\"d-flex align-items-center gap-2\">\n <div class=\"d-flex align-items-center\">\n <i class=\"me-2\" [class]=\"getColumnTypeIcon(favorite) + ' ' + getColumnTypeClass(favorite)\"></i>\n <span class=\"fw-semibold\">{{ getCleanColumnName(favorite.name) }}</span>\n </div>\n </div>\n </td>\n <td>\n <div class=\"text-truncate\" [title]=\"favorite.displayValue || 'No display name'\">\n @if (favorite.displayValue && favorite.displayValue.trim()) {\n {{ favorite.displayValue }}\n } @else {\n <span class=\"text-muted fst-italic\">No display name</span>\n }\n </div>\n </td>\n <td>\n <div style=\"max-width: 300px;\">\n @if (isComplexValue(favorite.value)) {\n <!-- Complex value with key-value pairs -->\n <div class=\"d-flex flex-wrap gap-1\">\n @for (pair of parseComplexValue(favorite.value); track $index) {\n <span class=\"badge bg-light text-dark border\">\n <small><strong>{{ pair.key }}:</strong> {{ pair.value }}</small>\n </span>\n }\n </div>\n } @else {\n <!-- Simple value -->\n <div class=\"text-truncate\" [title]=\"favorite.value\">\n {{ favorite.value }}\n </div>\n }\n </div>\n </td>\n <td>\n <span [class]=\"getScopeClass(favorite)\">\n @if (favorite.isGlobal) {\n <i class=\"bi bi-globe me-1\"></i>Global\n } @else if (favorite.labGroup) {\n <i class=\"bi bi-people me-1\"></i>Lab Group\n } @else {\n <i class=\"bi bi-person me-1\"></i>Personal\n }\n </span>\n @if (favorite.userUsername && !favorite.isGlobal) {\n <br><small class=\"text-muted\">{{ favorite.userUsername }}</small>\n }\n @if (favorite.labGroupName) {\n <br><small class=\"text-muted\">{{ favorite.labGroupName }}</small>\n }\n </td>\n <td>\n <small class=\"text-muted\">\n {{ favorite.createdAt | date:'MMM d, y' }}\n </small>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\">\n @if (canEditFavorite(favorite)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openEditModal(favorite)\"\n title=\"Edit favorite\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n @if (canDeleteFavorite(favorite)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-danger\"\n (click)=\"deleteFavorite(favorite)\"\n title=\"Delete favorite\">\n <i class=\"bi bi-trash\"></i>\n </button>\n }\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n <!-- Pagination -->\n @if (totalPages() > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-4\">\n <div class=\"d-flex align-items-center gap-2\">\n <span class=\"text-muted\">Show:</span>\n <select\n class=\"form-select form-select-sm\"\n 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 <span class=\"text-muted\">per page</span>\n </div>\n\n <ngb-pagination\n [collectionSize]=\"totalFavoritesCount()\"\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\n <span class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * pageSize()) + 1 }}-{{ Math.min(currentPage() * pageSize(), totalFavoritesCount()) }}\n of {{ totalFavoritesCount() }} favorites\n </span>\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n</div>\n\n<!-- Edit/Create Modal -->\n@if (showEditModal()) {\n <div class=\"modal fade show d-block\" tabindex=\"-1\" style=\"background: rgba(0,0,0,0.5);\">\n <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-heart me-2\"></i>{{ editModalTitle }}\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeEditModal()\"></button>\n </div>\n\n <div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"saveFavorite()\">\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <label for=\"favName\" class=\"form-label\">\n Column Name *\n @if (columnTemplatesLoaded()) {\n <small class=\"text-success ms-1\">\n <i class=\"bi bi-search\"></i> Autocomplete Available\n </small>\n }\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"favName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"e.g., organism\"\n [ngbTypeahead]=\"searchColumnNames\"\n (selectItem)=\"onColumnNameSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n <div class=\"form-text\">\n @if (columnTemplatesLoaded()) {\n Type to search available column names from templates\n } @else {\n Loading column suggestions...\n }\n </div>\n <div class=\"invalid-feedback\">\n @if (editForm.get('name')?.errors?.['required']) {\n Column name is required\n }\n @if (editForm.get('name')?.errors?.['maxlength']) {\n Column name must be less than 255 characters\n }\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"favType\" class=\"form-label\">\n Column Type *\n @if (columnTemplatesLoaded()) {\n <small class=\"text-success ms-1\">\n <i class=\"bi bi-search\"></i> Autocomplete Available\n </small>\n }\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"favType\"\n formControlName=\"type\"\n [class.is-invalid]=\"editForm.get('type')?.invalid && editForm.get('type')?.touched\"\n placeholder=\"e.g., characteristics[organism]\"\n [ngbTypeahead]=\"searchColumnTypes\"\n (selectItem)=\"onColumnTypeSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n <div class=\"form-text\">\n @if (columnTemplatesLoaded()) {\n Type to search available column types (filtered by selected name)\n } @else {\n Loading column suggestions...\n }\n </div>\n <div class=\"invalid-feedback\">\n @if (editForm.get('type')?.errors?.['required']) {\n Column type is required\n }\n @if (editForm.get('type')?.errors?.['maxlength']) {\n Column type must be less than 255 characters\n }\n </div>\n </div>\n </div>\n\n <div class=\"mb-3\">\n <label for=\"favValue\" class=\"form-label\">\n Value\n @if (editForm.get('templateId')?.value) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Advanced Editor Available\n </span>\n }\n @if (specialSyntaxType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n </label>\n\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"favValue\"\n formControlName=\"value\"\n [class.is-invalid]=\"editForm.get('value')?.invalid && editForm.get('value')?.touched\"\n placeholder=\"Enter favorite value\"\n readonly>\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openValueEditModal()\"\n title=\"Open advanced value editor\">\n <i class=\"bi bi-pencil me-1\"></i>Edit\n </button>\n </div>\n\n <div class=\"form-text\">\n @if (editForm.get('templateId')?.value) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to use advanced value editor with ontology autocomplete suggestions\n } @else if (specialSyntaxType()) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to use enhanced {{ specialSyntaxType() }} editor for structured input\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set the value (select column name and type for smart suggestions)\n }\n </div>\n <div class=\"invalid-feedback\">\n @if (editForm.get('value')?.errors?.['maxlength']) {\n Value must be less than 500 characters\n }\n </div>\n </div>\n\n <div class=\"mb-3\">\n <label for=\"favDisplayValue\" class=\"form-label\">Display Value</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"favDisplayValue\"\n formControlName=\"displayValue\"\n placeholder=\"Optional: Human-readable version\">\n <div class=\"form-text\">If empty, will use the same value as above</div>\n </div>\n\n <div class=\"mb-3\">\n <label for=\"favScope\" class=\"form-label\">Scope *</label>\n <select\n class=\"form-select\"\n id=\"favScope\"\n formControlName=\"scope\">\n <option value=\"personal\">Personal (Only visible to me)</option>\n @if (userLabGroups().length > 0) {\n <option value=\"lab_group\">Lab Group (Visible to my lab group)</option>\n }\n @if (isAdmin()) {\n <option value=\"global\">Global (Visible to everyone)</option>\n }\n </select>\n </div>\n\n <!-- Lab Group Selection (shown when lab_group scope is selected) -->\n @if (editForm.get('scope')?.value === 'lab_group' && userLabGroups().length > 1) {\n <div class=\"mb-3\">\n <label for=\"favLabGroup\" class=\"form-label\">Lab Group *</label>\n <select\n class=\"form-select\"\n id=\"favLabGroup\"\n formControlName=\"labGroupId\">\n <option value=\"\">Select a lab group</option>\n @for (labGroup of userLabGroups(); track labGroup.id) {\n <option [value]=\"labGroup.id\">{{ labGroup.name }}</option>\n }\n </select>\n <div class=\"form-text\">Choose which lab group this favorite will be visible to</div>\n </div>\n }\n </form>\n </div>\n\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"closeEditModal()\">\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)=\"saveFavorite()\"\n [disabled]=\"editForm.invalid\">\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEditMode() ? 'Update' : 'Create' }} Favorite\n </button>\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [""], 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.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: RouterModule }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "directive", type: i2.NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
7817
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FavoriteManagement, deps: [{ token: i1.FormBuilder }, { token: FavouriteMetadataOptionService }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }, { token: i3.ToastService }, { token: i2.NgbModal }, { token: i3.AuthService }], target: i0.ɵɵFactoryTarget.Component });
7818
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: FavoriteManagement, isStandalone: true, selector: "ccv-favorite-management", ngImport: i0, template: "<div class=\"favorite-management-container\">\n <!-- Header Section -->\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 <h4 class=\"mb-0\">\n <i class=\"bi bi-heart-fill me-2 text-danger\"></i>Favorite Management\n </h4>\n <span class=\"badge bg-primary ms-3\">{{ filteredFavorites().length }} favorites</span>\n </div>\n\n <div class=\"d-flex gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-primary btn-sm\"\n (click)=\"openCreateModal()\"\n [disabled]=\"isLoading()\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add Favorite\n </button>\n </div>\n </div>\n </div>\n </nav>\n\n <div class=\"tables-content p-4\">\n <!-- Search and Filters Section -->\n <div class=\"card mb-4\">\n <div class=\"card-header\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-search me-2\"></i>Search & Filters\n </h5>\n </div>\n <div class=\"card-body\">\n <form [formGroup]=\"searchForm\" class=\"row g-3\">\n <div class=\"col-md-3\">\n <label for=\"search\" class=\"form-label\">Search</label>\n <div class=\"input-group\">\n <span class=\"input-group-text\">\n <i class=\"bi bi-search\"></i>\n </span>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"search\"\n formControlName=\"search\"\n placeholder=\"Search by name or value\">\n </div>\n </div>\n\n <div class=\"col-md-3\">\n <label for=\"scope\" class=\"form-label\">Scope</label>\n <select class=\"form-select\" id=\"scope\" formControlName=\"scope\">\n <option value=\"\">All Scopes</option>\n <option value=\"personal\">Personal</option>\n <option value=\"lab_group\">Lab Group</option>\n <option value=\"global\">Global</option>\n </select>\n </div>\n\n <div class=\"col-md-3\">\n <label for=\"labGroup\" class=\"form-label\">Lab Group</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"labGroup\"\n formControlName=\"labGroup\"\n placeholder=\"All Lab Groups\"\n [ngbTypeahead]=\"searchLabGroups\"\n [resultFormatter]=\"formatLabGroup\"\n [inputFormatter]=\"inputLabGroupFormatter\"\n (selectItem)=\"onLabGroupSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n </div>\n\n <div class=\"col-md-3\">\n <label for=\"columnType\" class=\"form-label\">Column Type</label>\n <select class=\"form-select\" id=\"columnType\" formControlName=\"columnType\">\n @for (type of columnTypes; track type.value) {\n <option [value]=\"type.value\">{{ type.label }}</option>\n }\n </select>\n </div>\n </form>\n </div>\n </div>\n\n <div class=\"card\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <h5 class=\"mb-0\">\n <i class=\"bi bi-table me-2\"></i>Favorites options\n <span class=\"badge bg-primary ms-2\"></span>\n </h5>\n @if (isLoading()) {\n <div class=\"spinner-border spinner-border-sm text-primary\" role=\"status\">\n <span class=\"visually-hidden\">Loading...</span>\n </div>\n }\n </div>\n <div class=\"card-body p-0\">\n @if (!isLoading()) {\n\n @if (filteredFavorites().length === 0) {\n <div class=\"text-center py-5\">\n <i class=\"bi bi-heart display-1 text-muted\"></i>\n <h5 class=\"mt-3 text-muted\">No Favorites Found</h5>\n @if (searchTerm() || scopeFilter() || columnTypeFilter()) {\n <p class=\"text-muted\">Try adjusting your search criteria</p>\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"clearFilters()\">\n <i class=\"bi bi-arrow-clockwise me-1\"></i>Clear Filters\n </button>\n } @else {\n <p class=\"text-muted\">Create your first favorite to get started</p>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openCreateModal()\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add First Favorite\n </button>\n }\n </div>\n }\n\n <!-- Favorites Table -->\n @if (filteredFavorites().length > 0) {\n <div class=\"card\">\n <div class=\"card-body p-0\">\n <div class=\"table-responsive\">\n <table class=\"table table-hover mb-0\">\n <thead>\n <tr>\n <th scope=\"col\" style=\"width: 200px;\">Column</th>\n <th scope=\"col\" style=\"width: 200px;\">Display Name</th>\n <th scope=\"col\">Value</th>\n <th scope=\"col\" style=\"width: 150px;\">Scope</th>\n <th scope=\"col\" style=\"width: 120px;\">Created</th>\n <th scope=\"col\" style=\"width: 120px;\">Actions</th>\n </tr>\n </thead>\n <tbody>\n @for (favorite of paginatedFavorites(); track favorite.id) {\n <tr>\n <td>\n <div class=\"d-flex align-items-center gap-2\">\n <div class=\"d-flex align-items-center\">\n <i class=\"me-2\" [class]=\"getColumnTypeIcon(favorite) + ' ' + getColumnTypeClass(favorite)\"></i>\n <span class=\"fw-semibold\">{{ getCleanColumnName(favorite.name) }}</span>\n </div>\n </div>\n </td>\n <td>\n <div class=\"text-truncate\" [title]=\"favorite.displayValue || 'No display name'\">\n @if (favorite.displayValue && favorite.displayValue.trim()) {\n {{ favorite.displayValue }}\n } @else {\n <span class=\"text-muted fst-italic\">No display name</span>\n }\n </div>\n </td>\n <td>\n <div style=\"max-width: 300px;\">\n @if (isComplexValue(favorite.value)) {\n <!-- Complex value with key-value pairs -->\n <div class=\"d-flex flex-wrap gap-1\">\n @for (pair of parseComplexValue(favorite.value); track $index) {\n <span class=\"badge bg-light text-dark border\">\n <small><strong>{{ pair.key }}:</strong> {{ pair.value }}</small>\n </span>\n }\n </div>\n } @else {\n <!-- Simple value -->\n <div class=\"text-truncate\" [title]=\"favorite.value\">\n {{ favorite.value }}\n </div>\n }\n </div>\n </td>\n <td>\n <span [class]=\"getScopeClass(favorite)\">\n @if (favorite.isGlobal) {\n <i class=\"bi bi-globe me-1\"></i>Global\n } @else if (favorite.labGroup) {\n <i class=\"bi bi-people me-1\"></i>Lab Group\n } @else {\n <i class=\"bi bi-person me-1\"></i>Personal\n }\n </span>\n @if (favorite.userUsername && !favorite.isGlobal) {\n <br><small class=\"text-muted\">{{ favorite.userUsername }}</small>\n }\n @if (favorite.labGroupName) {\n <br><small class=\"text-muted\">{{ favorite.labGroupName }}</small>\n }\n </td>\n <td>\n <small class=\"text-muted\">\n {{ favorite.createdAt | date:'MMM d, y' }}\n </small>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\">\n @if (canEditFavorite(favorite)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openEditModal(favorite)\"\n title=\"Edit favorite\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n }\n @if (canDeleteFavorite(favorite)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-danger\"\n (click)=\"deleteFavorite(favorite)\"\n title=\"Delete favorite\">\n <i class=\"bi bi-trash\"></i>\n </button>\n }\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n </div>\n\n <!-- Pagination -->\n @if (totalPages() > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-4\">\n <div class=\"d-flex align-items-center gap-2\">\n <span class=\"text-muted\">Show:</span>\n <select\n class=\"form-select form-select-sm\"\n 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 <span class=\"text-muted\">per page</span>\n </div>\n\n <ngb-pagination\n [collectionSize]=\"totalFavoritesCount()\"\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\n <span class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * pageSize()) + 1 }}-{{ Math.min(currentPage() * pageSize(), totalFavoritesCount()) }}\n of {{ totalFavoritesCount() }} favorites\n </span>\n </div>\n }\n }\n }\n </div>\n </div>\n </div>\n</div>\n\n<!-- Edit/Create Modal -->\n@if (showEditModal()) {\n <div class=\"modal fade show d-block\" tabindex=\"-1\" style=\"background: rgba(0,0,0,0.5);\">\n <div class=\"modal-dialog modal-lg\">\n <div class=\"modal-content\">\n <div class=\"modal-header\">\n <h5 class=\"modal-title\">\n <i class=\"bi bi-heart me-2\"></i>{{ editModalTitle }}\n </h5>\n <button type=\"button\" class=\"btn-close\" (click)=\"closeEditModal()\"></button>\n </div>\n\n <div class=\"modal-body\">\n <form [formGroup]=\"editForm\" (ngSubmit)=\"saveFavorite()\">\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <label for=\"favName\" class=\"form-label\">\n Column Name *\n @if (columnTemplatesLoaded()) {\n <small class=\"text-success ms-1\">\n <i class=\"bi bi-search\"></i> Autocomplete Available\n </small>\n }\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"favName\"\n formControlName=\"name\"\n [class.is-invalid]=\"editForm.get('name')?.invalid && editForm.get('name')?.touched\"\n placeholder=\"e.g., organism\"\n [ngbTypeahead]=\"searchColumnNames\"\n (selectItem)=\"onColumnNameSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n <div class=\"form-text\">\n @if (columnTemplatesLoaded()) {\n Type to search available column names from templates\n } @else {\n Loading column suggestions...\n }\n </div>\n <div class=\"invalid-feedback\">\n @if (editForm.get('name')?.errors?.['required']) {\n Column name is required\n }\n @if (editForm.get('name')?.errors?.['maxlength']) {\n Column name must be less than 255 characters\n }\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"favType\" class=\"form-label\">\n Column Type *\n @if (columnTemplatesLoaded()) {\n <small class=\"text-success ms-1\">\n <i class=\"bi bi-search\"></i> Autocomplete Available\n </small>\n }\n </label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"favType\"\n formControlName=\"type\"\n [class.is-invalid]=\"editForm.get('type')?.invalid && editForm.get('type')?.touched\"\n placeholder=\"e.g., characteristics[organism]\"\n [ngbTypeahead]=\"searchColumnTypes\"\n (selectItem)=\"onColumnTypeSelected($event)\"\n [editable]=\"true\"\n [showHint]=\"true\"\n [focusFirst]=\"false\"\n container=\"body\">\n <div class=\"form-text\">\n @if (columnTemplatesLoaded()) {\n Type to search available column types (filtered by selected name)\n } @else {\n Loading column suggestions...\n }\n </div>\n <div class=\"invalid-feedback\">\n @if (editForm.get('type')?.errors?.['required']) {\n Column type is required\n }\n @if (editForm.get('type')?.errors?.['maxlength']) {\n Column type must be less than 255 characters\n }\n </div>\n </div>\n </div>\n\n <div class=\"mb-3\">\n <label for=\"favValue\" class=\"form-label\">\n Value\n @if (editForm.get('templateId')?.value) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search me-1\"></i>Advanced Editor Available\n </span>\n }\n @if (specialSyntaxType()) {\n <span class=\"badge bg-info ms-2\">\n <i class=\"bi bi-gear me-1\"></i>{{ getSpecialInputTitle() }}\n </span>\n }\n </label>\n\n <div class=\"input-group\">\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"favValue\"\n formControlName=\"value\"\n [class.is-invalid]=\"editForm.get('value')?.invalid && editForm.get('value')?.touched\"\n placeholder=\"Enter favorite value\"\n readonly>\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openValueEditModal()\"\n title=\"Open advanced value editor\">\n <i class=\"bi bi-pencil me-1\"></i>Edit\n </button>\n </div>\n\n <div class=\"form-text\">\n @if (editForm.get('templateId')?.value) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to use advanced value editor with ontology autocomplete suggestions\n } @else if (specialSyntaxType()) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to use enhanced {{ specialSyntaxType() }} editor for structured input\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Click \"Edit\" to set the value (select column name and type for smart suggestions)\n }\n </div>\n <div class=\"invalid-feedback\">\n @if (editForm.get('value')?.errors?.['maxlength']) {\n Value must be less than 500 characters\n }\n </div>\n </div>\n\n <div class=\"mb-3\">\n <label for=\"favDisplayValue\" class=\"form-label\">Display Value</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"favDisplayValue\"\n formControlName=\"displayValue\"\n placeholder=\"Optional: Human-readable version\">\n <div class=\"form-text\">If empty, will use the same value as above</div>\n </div>\n\n <div class=\"mb-3\">\n <label for=\"favScope\" class=\"form-label\">Scope *</label>\n <select\n class=\"form-select\"\n id=\"favScope\"\n formControlName=\"scope\">\n <option value=\"personal\">Personal (Only visible to me)</option>\n @if (userLabGroups().length > 0) {\n <option value=\"lab_group\">Lab Group (Visible to my lab group)</option>\n }\n @if (isAdmin()) {\n <option value=\"global\">Global (Visible to everyone)</option>\n }\n </select>\n </div>\n\n <!-- Lab Group Selection (shown when lab_group scope is selected) -->\n @if (editForm.get('scope')?.value === 'lab_group' && userLabGroups().length > 1) {\n <div class=\"mb-3\">\n <label for=\"favLabGroup\" class=\"form-label\">Lab Group *</label>\n <select\n class=\"form-select\"\n id=\"favLabGroup\"\n formControlName=\"labGroupId\">\n <option value=\"\">Select a lab group</option>\n @for (labGroup of userLabGroups(); track labGroup.id) {\n <option [value]=\"labGroup.id\">{{ labGroup.name }}</option>\n }\n </select>\n <div class=\"form-text\">Choose which lab group this favorite will be visible to</div>\n </div>\n }\n </form>\n </div>\n\n <div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"closeEditModal()\">\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)=\"saveFavorite()\"\n [disabled]=\"editForm.invalid\">\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEditMode() ? 'Update' : 'Create' }} Favorite\n </button>\n </div>\n </div>\n </div>\n </div>\n}\n", styles: [""], 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.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: RouterModule }, { kind: "ngmodule", type: NgbModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "directive", type: i2.NgbTypeahead, selector: "input[ngbTypeahead]", inputs: ["autocomplete", "container", "editable", "focusFirst", "inputFormatter", "ngbTypeahead", "resultFormatter", "resultTemplate", "selectOnExact", "showHint", "placement", "popperOptions", "popupClass"], outputs: ["selectItem"], exportAs: ["ngbTypeahead"] }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
7616
7819
  }
7617
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FavoriteManagement, decorators: [{
7820
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: FavoriteManagement, decorators: [{
7618
7821
  type: Component,
7619
7822
  args: [{ selector: 'ccv-favorite-management', standalone: true, imports: [
7620
7823
  CommonModule,
@@ -7679,10 +7882,10 @@ class SearchReplaceModal {
7679
7882
  }
7680
7883
  return '';
7681
7884
  }
7682
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SearchReplaceModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
7683
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SearchReplaceModal, isStandalone: true, selector: "ccv-search-replace-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-search me-2\"></i>Search and Replace Values\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n <form [formGroup]=\"searchReplaceForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"row\">\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n @if (isTableContext) {\r\n <strong>Table Context:</strong> Search and replace values across {{ config.tableName }}\r\n } @else {\r\n <strong>Column Context:</strong> Search and replace values in {{ config.columnName }}\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (isTableContext && config.columns && config.columns.length > 0) {\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"columnSelect\" class=\"form-label\">\r\n Target Column <small class=\"text-muted\">(optional - leave empty to search all columns)</small>\r\n </label>\r\n <select\r\n class=\"form-select\"\r\n id=\"columnSelect\"\r\n formControlName=\"columnId\">\r\n <option [ngValue]=\"null\">All Columns</option>\r\n @for (column of config.columns; track column.id) {\r\n <option [ngValue]=\"column.id\">{{ column.displayName || column.name }}</option>\r\n }\r\n </select>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Select a specific column to limit the search, or leave as \"All Columns\" to search everywhere.\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"oldValue\" class=\"form-label\">Find Value *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"oldValue\"\r\n formControlName=\"oldValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('oldValue')?.invalid && searchReplaceForm.get('oldValue')?.touched\"\r\n placeholder=\"Enter value to find\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('oldValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Exact match - this will find all cells with exactly this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"newValue\" class=\"form-label\">Replace With *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"newValue\"\r\n formControlName=\"newValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('newValue')?.invalid && searchReplaceForm.get('newValue')?.touched\"\r\n placeholder=\"Enter replacement value\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('newValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n All matching values will be replaced with this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"updatePools\"\r\n formControlName=\"updatePools\">\r\n <label class=\"form-check-label\" for=\"updatePools\">\r\n Update Sample Pools\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n When enabled, this will also update values in related sample pools.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <div class=\"alert alert-warning\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n <strong>Warning:</strong> This operation cannot be undone. All matching values will be permanently replaced.\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"onSubmit()\"\r\n [disabled]=\"searchReplaceForm.invalid || isProcessing()\">\r\n @if (isProcessing()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n }\r\n <i class=\"bi bi-arrow-repeat me-1\"></i>\r\n Replace All\r\n </button>\r\n</div>\r\n", styles: [""], 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"] }] });
7885
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SearchReplaceModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
7886
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SearchReplaceModal, isStandalone: true, selector: "ccv-search-replace-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-search me-2\"></i>Search and Replace Values\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n <form [formGroup]=\"searchReplaceForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"row\">\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n @if (isTableContext) {\r\n <strong>Table Context:</strong> Search and replace values across {{ config.tableName }}\r\n } @else {\r\n <strong>Column Context:</strong> Search and replace values in {{ config.columnName }}\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (isTableContext && config.columns && config.columns.length > 0) {\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"columnSelect\" class=\"form-label\">\r\n Target Column <small class=\"text-muted\">(optional - leave empty to search all columns)</small>\r\n </label>\r\n <select\r\n class=\"form-select\"\r\n id=\"columnSelect\"\r\n formControlName=\"columnId\">\r\n <option [ngValue]=\"null\">All Columns</option>\r\n @for (column of config.columns; track column.id) {\r\n <option [ngValue]=\"column.id\">{{ column.displayName || column.name }}</option>\r\n }\r\n </select>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Select a specific column to limit the search, or leave as \"All Columns\" to search everywhere.\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"oldValue\" class=\"form-label\">Find Value *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"oldValue\"\r\n formControlName=\"oldValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('oldValue')?.invalid && searchReplaceForm.get('oldValue')?.touched\"\r\n placeholder=\"Enter value to find\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('oldValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Exact match - this will find all cells with exactly this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"newValue\" class=\"form-label\">Replace With *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"newValue\"\r\n formControlName=\"newValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('newValue')?.invalid && searchReplaceForm.get('newValue')?.touched\"\r\n placeholder=\"Enter replacement value\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('newValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n All matching values will be replaced with this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"updatePools\"\r\n formControlName=\"updatePools\">\r\n <label class=\"form-check-label\" for=\"updatePools\">\r\n Update Sample Pools\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n When enabled, this will also update values in related sample pools.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <div class=\"alert alert-warning\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n <strong>Warning:</strong> This operation cannot be undone. All matching values will be permanently replaced.\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"onSubmit()\"\r\n [disabled]=\"searchReplaceForm.invalid || isProcessing()\">\r\n @if (isProcessing()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n }\r\n <i class=\"bi bi-arrow-repeat me-1\"></i>\r\n Replace All\r\n </button>\r\n</div>\r\n", styles: [""], 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"] }] });
7684
7887
  }
7685
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SearchReplaceModal, decorators: [{
7888
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SearchReplaceModal, decorators: [{
7686
7889
  type: Component,
7687
7890
  args: [{ selector: 'ccv-search-replace-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-search me-2\"></i>Search and Replace Values\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n <form [formGroup]=\"searchReplaceForm\" (ngSubmit)=\"onSubmit()\">\r\n <div class=\"row\">\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n @if (isTableContext) {\r\n <strong>Table Context:</strong> Search and replace values across {{ config.tableName }}\r\n } @else {\r\n <strong>Column Context:</strong> Search and replace values in {{ config.columnName }}\r\n }\r\n </div>\r\n </div>\r\n\r\n @if (isTableContext && config.columns && config.columns.length > 0) {\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"columnSelect\" class=\"form-label\">\r\n Target Column <small class=\"text-muted\">(optional - leave empty to search all columns)</small>\r\n </label>\r\n <select\r\n class=\"form-select\"\r\n id=\"columnSelect\"\r\n formControlName=\"columnId\">\r\n <option [ngValue]=\"null\">All Columns</option>\r\n @for (column of config.columns; track column.id) {\r\n <option [ngValue]=\"column.id\">{{ column.displayName || column.name }}</option>\r\n }\r\n </select>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Select a specific column to limit the search, or leave as \"All Columns\" to search everywhere.\r\n </div>\r\n </div>\r\n }\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"oldValue\" class=\"form-label\">Find Value *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"oldValue\"\r\n formControlName=\"oldValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('oldValue')?.invalid && searchReplaceForm.get('oldValue')?.touched\"\r\n placeholder=\"Enter value to find\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('oldValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Exact match - this will find all cells with exactly this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <label for=\"newValue\" class=\"form-label\">Replace With *</label>\r\n <input\r\n type=\"text\"\r\n class=\"form-control\"\r\n id=\"newValue\"\r\n formControlName=\"newValue\"\r\n [class.is-invalid]=\"searchReplaceForm.get('newValue')?.invalid && searchReplaceForm.get('newValue')?.touched\"\r\n placeholder=\"Enter replacement value\">\r\n <div class=\"invalid-feedback\">\r\n {{ getFieldError('newValue') }}\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n All matching values will be replaced with this value.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12 mb-3\">\r\n <div class=\"form-check form-switch\">\r\n <input\r\n class=\"form-check-input\"\r\n type=\"checkbox\"\r\n id=\"updatePools\"\r\n formControlName=\"updatePools\">\r\n <label class=\"form-check-label\" for=\"updatePools\">\r\n Update Sample Pools\r\n </label>\r\n </div>\r\n <div class=\"form-text\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n When enabled, this will also update values in related sample pools.\r\n </div>\r\n </div>\r\n\r\n <div class=\"col-12\">\r\n <div class=\"alert alert-warning\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\r\n <strong>Warning:</strong> This operation cannot be undone. All matching values will be permanently replaced.\r\n </div>\r\n </div>\r\n </div>\r\n </form>\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\r\n </button>\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-primary\"\r\n (click)=\"onSubmit()\"\r\n [disabled]=\"searchReplaceForm.invalid || isProcessing()\">\r\n @if (isProcessing()) {\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n }\r\n <i class=\"bi bi-arrow-repeat me-1\"></i>\r\n Replace All\r\n </button>\r\n</div>\r\n" }]
7688
7891
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { config: [{
@@ -7724,10 +7927,10 @@ class SamplePoolDetailsModal {
7724
7927
  const pooledAndIndependent = this.pool?.pooledAndIndependentSamples?.length || 0;
7725
7928
  return pooledOnly + pooledAndIndependent;
7726
7929
  }
7727
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolDetailsModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
7728
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SamplePoolDetailsModal, isStandalone: true, selector: "ccv-sample-pool-details-modal", inputs: { pool: "pool" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (pool) {\n <!-- Pool Type and Basic Info -->\n <div class=\"mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <div>\n <h5 class=\"mb-2\">{{ pool.poolName }}</h5>\n <span [class]=\"poolTypeClass\">\n <i class=\"bi bi-collection me-1\"></i>{{ poolTypeLabel }}\n </span>\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted d-block\">Pool ID: {{ pool.id }}</small>\n <small class=\"text-muted\">Total Samples: {{ allSamplesCount }}</small>\n </div>\n </div>\n \n @if (pool.poolDescription) {\n <div class=\"mt-3\">\n <strong>Description:</strong>\n <p class=\"mt-1 mb-0\">{{ pool.poolDescription }}</p>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-diagram-3 me-2\"></i>Sample Configuration\n </h6>\n \n <div class=\"row\">\n <!-- Pooled Only Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-collection text-primary me-2\"></i>Pooled Only Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledOnlySamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledOnlySamples && pool.pooledOnlySamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledOnlySamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled-only samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-diagram-2 text-success me-2\"></i>Pooled & Independent Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledAndIndependentSamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledAndIndependentSamples && pool.pooledAndIndependentSamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledAndIndependentSamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled & independent samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Template Sample -->\n </div>\n\n <!-- Metadata Columns -->\n @if (pool.metadataColumns && pool.metadataColumns.length > 0) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-table me-2\"></i>Metadata Columns ({{ pool.metadataColumns.length }})\n </h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm\">\n <thead>\n <tr>\n <th>Column Name</th>\n <th>Type</th>\n <th>Value</th>\n <th>Ontology</th>\n </tr>\n </thead>\n <tbody>\n @for (column of pool.metadataColumns; track column.id) {\n <tr>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n @if (column.value) {\n <code>{{ column.value }}</code>\n } @else {\n <em class=\"text-muted\">No default value</em>\n }\n </td>\n <td>\n @if (column.ontologyType) {\n <span class=\"badge bg-info\">{{ column.ontologyType }}</span>\n } @else {\n <em class=\"text-muted\">None</em>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- Timestamps -->\n <div class=\"mt-4\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-plus me-1\"></i>\n Created: {{ pool.createdAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-check me-1\"></i>\n Updated: {{ pool.updatedAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"text-center py-4\">\n <i class=\"bi bi-exclamation-triangle text-warning display-4\"></i>\n <h5 class=\"mt-3 text-muted\">No Pool Data Available</h5>\n <p class=\"text-muted\">Pool information could not be loaded.</p>\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
7930
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SamplePoolDetailsModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
7931
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SamplePoolDetailsModal, isStandalone: true, selector: "ccv-sample-pool-details-modal", inputs: { pool: "pool" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (pool) {\n <!-- Pool Type and Basic Info -->\n <div class=\"mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <div>\n <h5 class=\"mb-2\">{{ pool.poolName }}</h5>\n <span [class]=\"poolTypeClass\">\n <i class=\"bi bi-collection me-1\"></i>{{ poolTypeLabel }}\n </span>\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted d-block\">Pool ID: {{ pool.id }}</small>\n <small class=\"text-muted\">Total Samples: {{ allSamplesCount }}</small>\n </div>\n </div>\n \n @if (pool.poolDescription) {\n <div class=\"mt-3\">\n <strong>Description:</strong>\n <p class=\"mt-1 mb-0\">{{ pool.poolDescription }}</p>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-diagram-3 me-2\"></i>Sample Configuration\n </h6>\n \n <div class=\"row\">\n <!-- Pooled Only Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-collection text-primary me-2\"></i>Pooled Only Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledOnlySamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledOnlySamples && pool.pooledOnlySamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledOnlySamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled-only samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-diagram-2 text-success me-2\"></i>Pooled & Independent Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledAndIndependentSamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledAndIndependentSamples && pool.pooledAndIndependentSamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledAndIndependentSamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled & independent samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Template Sample -->\n </div>\n\n <!-- Metadata Columns -->\n @if (pool.metadataColumns && pool.metadataColumns.length > 0) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-table me-2\"></i>Metadata Columns ({{ pool.metadataColumns.length }})\n </h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm\">\n <thead>\n <tr>\n <th>Column Name</th>\n <th>Type</th>\n <th>Value</th>\n <th>Ontology</th>\n </tr>\n </thead>\n <tbody>\n @for (column of pool.metadataColumns; track column.id) {\n <tr>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n @if (column.value) {\n <code>{{ column.value }}</code>\n } @else {\n <em class=\"text-muted\">No default value</em>\n }\n </td>\n <td>\n @if (column.ontologyType) {\n <span class=\"badge bg-info\">{{ column.ontologyType }}</span>\n } @else {\n <em class=\"text-muted\">None</em>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- Timestamps -->\n <div class=\"mt-4\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-plus me-1\"></i>\n Created: {{ pool.createdAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-check me-1\"></i>\n Updated: {{ pool.updatedAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"text-center py-4\">\n <i class=\"bi bi-exclamation-triangle text-warning display-4\"></i>\n <h5 class=\"mt-3 text-muted\">No Pool Data Available</h5>\n <p class=\"text-muted\">Pool information could not be loaded.</p>\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbModule }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
7729
7932
  }
7730
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolDetailsModal, decorators: [{
7933
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SamplePoolDetailsModal, decorators: [{
7731
7934
  type: Component,
7732
7935
  args: [{ selector: 'ccv-sample-pool-details-modal', standalone: true, imports: [CommonModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection me-2\"></i>{{ title }}\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onClose()\"></button>\n</div>\n\n<div class=\"modal-body\">\n @if (pool) {\n <!-- Pool Type and Basic Info -->\n <div class=\"mb-4\">\n <div class=\"d-flex justify-content-between align-items-start mb-3\">\n <div>\n <h5 class=\"mb-2\">{{ pool.poolName }}</h5>\n <span [class]=\"poolTypeClass\">\n <i class=\"bi bi-collection me-1\"></i>{{ poolTypeLabel }}\n </span>\n </div>\n <div class=\"text-end\">\n <small class=\"text-muted d-block\">Pool ID: {{ pool.id }}</small>\n <small class=\"text-muted\">Total Samples: {{ allSamplesCount }}</small>\n </div>\n </div>\n \n @if (pool.poolDescription) {\n <div class=\"mt-3\">\n <strong>Description:</strong>\n <p class=\"mt-1 mb-0\">{{ pool.poolDescription }}</p>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-diagram-3 me-2\"></i>Sample Configuration\n </h6>\n \n <div class=\"row\">\n <!-- Pooled Only Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-collection text-primary me-2\"></i>Pooled Only Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledOnlySamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledOnlySamples && pool.pooledOnlySamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledOnlySamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled-only samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"col-md-6 mb-3\">\n <div class=\"card h-100\">\n <div class=\"card-body\">\n <h6 class=\"card-title\">\n <i class=\"bi bi-diagram-2 text-success me-2\"></i>Pooled & Independent Samples\n </h6>\n <p class=\"card-text\">\n <small class=\"text-muted\">{{ pool.pooledAndIndependentSamples.length || 0 }} samples</small>\n </p>\n <div class=\"sample-list\">\n @if (pool.pooledAndIndependentSamples && pool.pooledAndIndependentSamples.length > 0) {\n <div class=\"sample-numbers\">{{ pooledAndIndependentSamplesText }}</div>\n } @else {\n <em class=\"text-muted\">No pooled & independent samples</em>\n }\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <!-- Template Sample -->\n </div>\n\n <!-- Metadata Columns -->\n @if (pool.metadataColumns && pool.metadataColumns.length > 0) {\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">\n <i class=\"bi bi-table me-2\"></i>Metadata Columns ({{ pool.metadataColumns.length }})\n </h6>\n <div class=\"table-responsive\">\n <table class=\"table table-sm\">\n <thead>\n <tr>\n <th>Column Name</th>\n <th>Type</th>\n <th>Value</th>\n <th>Ontology</th>\n </tr>\n </thead>\n <tbody>\n @for (column of pool.metadataColumns; track column.id) {\n <tr>\n <td>\n <strong>{{ column.displayName || column.name }}</strong>\n </td>\n <td>\n <span class=\"badge bg-light text-dark\">{{ column.type }}</span>\n </td>\n <td>\n @if (column.value) {\n <code>{{ column.value }}</code>\n } @else {\n <em class=\"text-muted\">No default value</em>\n }\n </td>\n <td>\n @if (column.ontologyType) {\n <span class=\"badge bg-info\">{{ column.ontologyType }}</span>\n } @else {\n <em class=\"text-muted\">None</em>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n }\n\n <!-- Timestamps -->\n <div class=\"mt-4\">\n <div class=\"row\">\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-plus me-1\"></i>\n Created: {{ pool.createdAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n <div class=\"col-md-6\">\n <small class=\"text-muted\">\n <i class=\"bi bi-calendar-check me-1\"></i>\n Updated: {{ pool.updatedAt | date:'MMM d, y, h:mm a' }}\n </small>\n </div>\n </div>\n </div>\n\n } @else {\n <div class=\"text-center py-4\">\n <i class=\"bi bi-exclamation-triangle text-warning display-4\"></i>\n <h5 class=\"mt-3 text-muted\">No Pool Data Available</h5>\n <p class=\"text-muted\">Pool information could not be loaded.</p>\n </div>\n }\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"onClose()\">\n <i class=\"bi bi-x-circle me-1\"></i>Close\n </button>\n</div>" }]
7733
7936
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }], propDecorators: { pool: [{
@@ -7877,10 +8080,10 @@ class SamplePoolEditModal {
7877
8080
  get totalSamplesCount() {
7878
8081
  return this.pooledOnlySamplesCount + this.pooledAndIndependentSamplesCount;
7879
8082
  }
7880
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolEditModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
7881
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SamplePoolEditModal, isStandalone: true, selector: "ccv-sample-pool-edit-modal", inputs: { pool: "pool", maxSampleCount: "maxSampleCount" }, outputs: { poolSaved: "poolSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection 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 \n <!-- Pool Name -->\n <div class=\"mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n <!-- Description -->\n <div class=\"mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Pool Type and Template Sample -->\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">Sample Configuration</h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ pooledOnlySamplesCount }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooledOnlySamplesText\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ pooledAndIndependentSamplesCount }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooledAndIndependentSamplesText\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalSamplesCount }}\n @if (totalSamplesCount > maxSampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ maxSampleCount }})\n </span>\n }\n </div>\n </div>\n\n <!-- Character Count -->\n <div class=\"mb-2\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Name: {{ editForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (hasChanges) {\n <small class=\"text-info\">\n <i class=\"bi bi-info-circle me-1\"></i>Changes detected\n </small>\n }\n </div>\n </div>\n\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() || !hasChanges\">\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 Save Pool\n </button>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.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 }] });
8083
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SamplePoolEditModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
8084
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SamplePoolEditModal, isStandalone: true, selector: "ccv-sample-pool-edit-modal", inputs: { pool: "pool", maxSampleCount: "maxSampleCount" }, outputs: { poolSaved: "poolSaved" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection 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 \n <!-- Pool Name -->\n <div class=\"mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n <!-- Description -->\n <div class=\"mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Pool Type and Template Sample -->\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">Sample Configuration</h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ pooledOnlySamplesCount }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooledOnlySamplesText\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ pooledAndIndependentSamplesCount }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooledAndIndependentSamplesText\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalSamplesCount }}\n @if (totalSamplesCount > maxSampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ maxSampleCount }})\n </span>\n }\n </div>\n </div>\n\n <!-- Character Count -->\n <div class=\"mb-2\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Name: {{ editForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (hasChanges) {\n <small class=\"text-info\">\n <i class=\"bi bi-info-circle me-1\"></i>Changes detected\n </small>\n }\n </div>\n </div>\n\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() || !hasChanges\">\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 Save Pool\n </button>\n</div>", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.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 }] });
7882
8085
  }
7883
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolEditModal, decorators: [{
8086
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SamplePoolEditModal, decorators: [{
7884
8087
  type: Component,
7885
8088
  args: [{ selector: 'ccv-sample-pool-edit-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-collection 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 \n <!-- Pool Name -->\n <div class=\"mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n <!-- Description -->\n <div class=\"mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Pool Type and Template Sample -->\n <div class=\"row\">\n <div class=\"col-md-6 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-muted mb-3\">Sample Configuration</h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ pooledOnlySamplesCount }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooledOnlySamplesText\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ pooledAndIndependentSamplesCount }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooledAndIndependentSamplesText\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalSamplesCount }}\n @if (totalSamplesCount > maxSampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ maxSampleCount }})\n </span>\n }\n </div>\n </div>\n\n <!-- Character Count -->\n <div class=\"mb-2\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <small class=\"text-muted\">\n Name: {{ editForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (hasChanges) {\n <small class=\"text-info\">\n <i class=\"bi bi-info-circle me-1\"></i>Changes detected\n </small>\n }\n </div>\n </div>\n\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() || !hasChanges\">\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 Save Pool\n </button>\n</div>" }]
7886
8089
  }], ctorParameters: () => [], propDecorators: { pool: [{
@@ -8219,10 +8422,10 @@ class SamplePoolCreateModal {
8219
8422
  }
8220
8423
  return false;
8221
8424
  }
8222
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolCreateModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: SamplePoolService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
8223
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: SamplePoolCreateModal, isStandalone: true, selector: "ccv-sample-pool-create-modal", inputs: { metadataTable: "metadataTable" }, outputs: { poolCreated: "poolCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Table for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Select Samples <span class=\"text-muted\">({{ selectedSamplesCount }} of {{ availableSamplesCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllNonPooledSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearAllSelections()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"selectedSamplesCount === availableSamplesCount && selectedSamplesCount > 0\"\n [indeterminate]=\"selectedSamplesCount > 0 && selectedSamplesCount < availableSamplesCount\"\n (change)=\"toggleSelectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <tr\n [class.table-active]=\"sample.selected\"\n [class.table-warning]=\"sample.pooled\"\n (click)=\"!sample.pooled && toggleSampleSelection(sample.sampleNumber)\"\n [style.cursor]=\"sample.pooled ? 'not-allowed' : 'pointer'\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sample.selected\"\n [disabled]=\"sample.pooled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSampleSelection(sample.sampleNumber)\"\n />\n </td>\n <td class=\"fw-semibold\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1 text-warning\" title=\"Already in another pool\"></i>\n }\n </td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), sample.sampleNumber) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (totalPages > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }}-{{ Math.min(currentPage() * samplesPerPage, samples().length) }} of {{ samples().length }}\n </small>\n <ngb-pagination\n [collectionSize]=\"samples().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"samplesPerPage\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"goToPage($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to add to the pool. Samples with <i class=\"bi bi-lock-fill\"></i> are already in another pool.\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\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]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { 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: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }] });
8425
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SamplePoolCreateModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: SamplePoolService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
8426
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: SamplePoolCreateModal, isStandalone: true, selector: "ccv-sample-pool-create-modal", inputs: { metadataTable: "metadataTable" }, outputs: { poolCreated: "poolCreated" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Table for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Select Samples <span class=\"text-muted\">({{ selectedSamplesCount }} of {{ availableSamplesCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllNonPooledSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearAllSelections()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"selectedSamplesCount === availableSamplesCount && selectedSamplesCount > 0\"\n [indeterminate]=\"selectedSamplesCount > 0 && selectedSamplesCount < availableSamplesCount\"\n (change)=\"toggleSelectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <tr\n [class.table-active]=\"sample.selected\"\n [class.table-warning]=\"sample.pooled\"\n (click)=\"!sample.pooled && toggleSampleSelection(sample.sampleNumber)\"\n [style.cursor]=\"sample.pooled ? 'not-allowed' : 'pointer'\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sample.selected\"\n [disabled]=\"sample.pooled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSampleSelection(sample.sampleNumber)\"\n />\n </td>\n <td class=\"fw-semibold\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1 text-warning\" title=\"Already in another pool\"></i>\n }\n </td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), sample.sampleNumber) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (totalPages > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }}-{{ Math.min(currentPage() * samplesPerPage, samples().length) }} of {{ samples().length }}\n </small>\n <ngb-pagination\n [collectionSize]=\"samples().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"samplesPerPage\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"goToPage($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to add to the pool. Samples with <i class=\"bi bi-lock-fill\"></i> are already in another pool.\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\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]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { 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.MinValidator, selector: "input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]", inputs: ["min"] }, { kind: "directive", type: i1.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { 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: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }] });
8224
8427
  }
8225
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolCreateModal, decorators: [{
8428
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: SamplePoolCreateModal, decorators: [{
8226
8429
  type: Component,
8227
8430
  args: [{ selector: 'ccv-sample-pool-create-modal', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbModule], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-plus-circle me-2\"></i>Create Sample Pool\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"onCancel()\"></button>\n</div>\n\n<div class=\"modal-body\" style=\"max-height: 70vh; overflow-y: auto;\">\n <form [formGroup]=\"createForm\" (ngSubmit)=\"onSubmit()\">\n \n <!-- Pool Basic Info -->\n <div class=\"row mb-4\">\n <div class=\"col-12\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-info-circle me-2\"></i>Pool Information\n </h6>\n </div>\n \n <!-- Pool Name -->\n <div class=\"col-md-6 mb-3\">\n <label for=\"poolName\" class=\"form-label\">Pool Name *</label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"poolName\"\n formControlName=\"poolName\"\n [class.is-invalid]=\"isFieldInvalid('poolName')\"\n placeholder=\"Enter pool name\">\n @if (isFieldInvalid('poolName')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('poolName') }}\n </div>\n }\n </div>\n\n\n <!-- Description -->\n <div class=\"col-12 mb-3\">\n <label for=\"poolDescription\" class=\"form-label\">Description</label>\n <textarea \n class=\"form-control\"\n id=\"poolDescription\"\n formControlName=\"poolDescription\"\n rows=\"2\"\n placeholder=\"Optional pool description\"></textarea>\n </div>\n\n <!-- Reference Pool Checkbox -->\n <div class=\"col-12 mb-3\">\n <div class=\"form-check\">\n <input \n class=\"form-check-input\" \n type=\"checkbox\" \n id=\"isReference\" \n formControlName=\"isReference\">\n <label class=\"form-check-label\" for=\"isReference\">\n <strong>Reference Pool</strong>\n <div class=\"form-text\">Check if this is a reference pool</div>\n </label>\n </div>\n </div>\n </div>\n\n <!-- Sample Selection Methods -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-check2-square me-2\"></i>Sample Selection\n </h6>\n\n <!-- Selection Mode Tabs -->\n <div class=\"btn-group mb-3\" role=\"group\">\n <input type=\"radio\" class=\"btn-check\" id=\"manualMode\" [value]=\"'manual'\" \n [checked]=\"selectionMode() === 'manual'\" (click)=\"selectionMode.set('manual')\">\n <label class=\"btn btn-outline-primary\" for=\"manualMode\">\n <i class=\"bi bi-hand-index me-1\"></i>Manual Selection\n </label>\n\n <input type=\"radio\" class=\"btn-check\" id=\"rangeMode\" [value]=\"'range'\" \n [checked]=\"selectionMode() === 'range'\" (click)=\"selectionMode.set('range')\">\n <label class=\"btn btn-outline-primary\" for=\"rangeMode\">\n <i class=\"bi bi-sliders me-1\"></i>Range Selection\n </label>\n </div>\n\n <!-- Range Selection Controls -->\n @if (selectionMode() === 'range') {\n <div class=\"card mb-3\">\n <div class=\"card-body\">\n <div class=\"row align-items-end\">\n <div class=\"col-md-4\">\n <label for=\"rangeStart\" class=\"form-label\">Start Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeStart\"\n formControlName=\"range_start\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_start')\">\n @if (isFieldInvalid('range_start')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_start') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <label for=\"rangeEnd\" class=\"form-label\">End Sample</label>\n <input \n type=\"number\" \n class=\"form-control\"\n id=\"rangeEnd\"\n formControlName=\"range_end\"\n min=\"1\"\n [max]=\"metadataTable.sampleCount\"\n [class.is-invalid]=\"isFieldInvalid('range_end')\">\n @if (isFieldInvalid('range_end')) {\n <div class=\"invalid-feedback\">\n {{ getFieldError('range_end') }}\n </div>\n }\n </div>\n <div class=\"col-md-4\">\n <button type=\"button\" class=\"btn btn-success w-100\" (click)=\"selectSamplesInRange()\">\n <i class=\"bi bi-plus-square me-1\"></i>Select Range\n </button>\n </div>\n </div>\n <div class=\"form-text mt-2\">\n Select samples from {{ createForm.get('range_start')?.value || 1 }} \n to {{ createForm.get('range_end')?.value || 1 }} \n ({{ Math.abs((createForm.get('range_end')?.value || 1) - (createForm.get('range_start')?.value || 1)) + 1 }} samples)\n </div>\n </div>\n </div>\n }\n\n <!-- Sample Table for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Select Samples <span class=\"text-muted\">({{ selectedSamplesCount }} of {{ availableSamplesCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllNonPooledSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearAllSelections()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"selectedSamplesCount === availableSamplesCount && selectedSamplesCount > 0\"\n [indeterminate]=\"selectedSamplesCount > 0 && selectedSamplesCount < availableSamplesCount\"\n (change)=\"toggleSelectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <tr\n [class.table-active]=\"sample.selected\"\n [class.table-warning]=\"sample.pooled\"\n (click)=\"!sample.pooled && toggleSampleSelection(sample.sampleNumber)\"\n [style.cursor]=\"sample.pooled ? 'not-allowed' : 'pointer'\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sample.selected\"\n [disabled]=\"sample.pooled\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSampleSelection(sample.sampleNumber)\"\n />\n </td>\n <td class=\"fw-semibold\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1 text-warning\" title=\"Already in another pool\"></i>\n }\n </td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), sample.sampleNumber) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), sample.sampleNumber) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (totalPages > 1) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }}-{{ Math.min(currentPage() * samplesPerPage, samples().length) }} of {{ samples().length }}\n </small>\n <ngb-pagination\n [collectionSize]=\"samples().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"samplesPerPage\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"goToPage($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to add to the pool. Samples with <i class=\"bi bi-lock-fill\"></i> are already in another pool.\n </div>\n </div>\n }\n\n <!-- Selection Summary -->\n @if (selectedSamplesCount > 0) {\n <div class=\"alert alert-info\">\n <div class=\"d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>{{ selectedSamplesCount }}</strong> samples selected\n </span>\n <div class=\"btn-group btn-group-sm\">\n <button type=\"button\" class=\"btn btn-outline-primary\" (click)=\"assignToPooledOnly()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled Only\n </button>\n <button type=\"button\" class=\"btn btn-outline-success\" (click)=\"assignToPooledAndIndependent()\">\n <i class=\"bi bi-arrow-right me-1\"></i>Pooled & Independent\n </button>\n </div>\n </div>\n </div>\n }\n </div>\n\n <!-- Sample Configuration -->\n <div class=\"mb-4\">\n <h6 class=\"text-primary mb-3\">\n <i class=\"bi bi-gear me-2\"></i>Sample Configuration\n </h6>\n \n <!-- Pooled Only Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledOnlySamples\" class=\"form-label\">\n Pooled Only Samples\n <span class=\"badge bg-primary ms-2\">{{ parseSampleNumbers(createForm.get('pooled_only_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledOnlySamples\"\n formControlName=\"pooled_only_samples_text\"\n placeholder=\"e.g., 1, 2, 3, 5-8\">\n <div class=\"form-text\">\n Enter sample numbers separated by commas. Ranges like \"5-8\" are supported.\n </div>\n </div>\n\n <!-- Pooled and Independent Samples -->\n <div class=\"mb-3\">\n <label for=\"pooledAndIndependentSamples\" class=\"form-label\">\n Pooled & Independent Samples\n <span class=\"badge bg-success ms-2\">{{ parseSampleNumbers(createForm.get('pooled_and_independent_samples_text')?.value || '').length }} samples</span>\n </label>\n <input \n type=\"text\" \n class=\"form-control\"\n id=\"pooledAndIndependentSamples\"\n formControlName=\"pooled_and_independent_samples_text\"\n placeholder=\"e.g., 4, 9, 10-15\">\n <div class=\"form-text\">\n Enter sample numbers that are both pooled and run independently.\n </div>\n </div>\n\n <!-- Total Count Display -->\n <div class=\"alert\" \n [class.alert-success]=\"totalPooledSamplesCount > 0 && totalPooledSamplesCount <= metadataTable.sampleCount\"\n [class.alert-warning]=\"totalPooledSamplesCount === 0\"\n [class.alert-danger]=\"totalPooledSamplesCount > metadataTable.sampleCount\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Total Samples:</strong> {{ totalPooledSamplesCount }}\n @if (totalPooledSamplesCount > metadataTable.sampleCount) {\n <span class=\"text-danger ms-2\">\n (Exceeds maximum of {{ metadataTable.sampleCount }})\n </span>\n } @else if (totalPooledSamplesCount === 0) {\n <span class=\"text-warning ms-2\">\n (Please select at least one sample)\n </span>\n }\n </div>\n </div>\n\n <!-- Form Status -->\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <small class=\"text-muted\">\n Name: {{ createForm.get('poolName')?.value?.length || 0 }}/255 characters\n </small>\n @if (totalPooledSamplesCount > 0) {\n <small class=\"text-success\">\n <i class=\"bi bi-check-circle me-1\"></i>Ready to create\n </small>\n }\n </div>\n\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]=\"createForm.invalid || isLoading() || totalPooledSamplesCount === 0 || totalPooledSamplesCount > metadataTable.sampleCount\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-plus-circle me-1\"></i>\n Create Pool\n </button>\n</div>\n" }]
8228
8431
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: SamplePoolService }, { type: i3.ToastService }], propDecorators: { metadataTable: [{
@@ -9415,10 +9618,10 @@ class MetadataTableDetails {
9415
9618
  }
9416
9619
  });
9417
9620
  }
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 });
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" }] });
9621
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", 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 });
9622
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", 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" }] });
9420
9623
  }
9421
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableDetails, decorators: [{
9624
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataTableDetails, decorators: [{
9422
9625
  type: Component,
9423
9626
  args: [{ selector: 'ccv-metadata-table-details', standalone: true, imports: [CommonModule, RouterModule, NgbModule, DragDropModule], 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"] }]
9424
9627
  }], ctorParameters: () => [{ type: i1$1.ActivatedRoute }, { type: i1$1.Router }, { type: MetadataTableService }, { type: AsyncExportService }, { type: ChunkedUploadService }, { type: SamplePoolService }, { type: MetadataColumnService }, { type: i3.ToastService }, { type: AsyncTaskUIService }, { type: i2.NgbModal }], propDecorators: { fileInput: [{
@@ -9597,10 +9800,10 @@ class BasicAutofillComponent {
9597
9800
  return !!(field && field.invalid && field.touched);
9598
9801
  }
9599
9802
  Math = Math;
9600
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: BasicAutofillComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
9601
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: BasicAutofillComponent, isStandalone: true, selector: "ccv-basic-autofill", inputs: { config: "config" }, outputs: { submit: "submit" }, ngImport: i0, template: "<form [formGroup]=\"autofillForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Column:</strong> {{ config.column.displayName || config.column.name }} ({{ config.column.type }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Fill Mode</label>\n <div class=\"btn-group w-100\">\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'template'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'template'\"\n (click)=\"fillMode.set('template'); autofillForm.patchValue({ fillMode: 'template' })\"\n >\n <i class=\"bi bi-code-square me-1\"></i>Template\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'range'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'range'\"\n (click)=\"fillMode.set('range'); autofillForm.patchValue({ fillMode: 'range' })\"\n >\n <i class=\"bi bi-123 me-1\"></i>Numeric Range\n </button>\n </div>\n </div>\n\n @if (fillMode() === 'template') {\n <div class=\"col-12 mb-3\">\n <label for=\"template\" class=\"form-label\">\n Value Template *\n </label>\n <input\n type=\"text\"\n id=\"template\"\n class=\"form-control\"\n formControlName=\"template\"\n placeholder=\"run {sample_index}\"\n [class.is-invalid]=\"isFieldInvalid('template')\"\n />\n @if (isFieldInvalid('template')) {\n <div class=\"invalid-feedback\">Template is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use <code>{{ '{' }}sample_index{{ '}' }}</code>, <code>{{ '{' }}index{{ '}' }}</code>, or <code>{{ '{' }}n{{ '}' }}</code> as placeholder for sample number.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Quick Templates</label>\n <div class=\"d-flex flex-wrap gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('run {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>run {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('sample_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>sample_{{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('assay {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>assay {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('replicate_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>replicate_{{ '{' }}n{{ '}' }}\n </button>\n </div>\n </div>\n }\n\n @if (fillMode() === 'range') {\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-6\">\n <label for=\"rangeStart\" class=\"form-label\">\n Start Value *\n </label>\n <input\n type=\"number\"\n id=\"rangeStart\"\n class=\"form-control\"\n formControlName=\"rangeStart\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeStart')\"\n />\n @if (isFieldInvalid('rangeStart')) {\n <div class=\"invalid-feedback\">Start value is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n First value in the sequence\n </div>\n </div>\n <div class=\"col-6\">\n <label for=\"rangeIncrement\" class=\"form-label\">\n Increment *\n </label>\n <input\n type=\"number\"\n id=\"rangeIncrement\"\n class=\"form-control\"\n formControlName=\"rangeIncrement\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeIncrement')\"\n />\n @if (isFieldInvalid('rangeIncrement')) {\n <div class=\"invalid-feedback\">Increment is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Step between values\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Apply to Samples <span class=\"text-muted\">({{ sampleIndices().length }} of {{ config.sampleCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sampleIndices().length === config.sampleCount\"\n [indeterminate]=\"sampleIndices().length > 0 && sampleIndices().length < config.sampleCount\"\n (change)=\"sampleIndices().length === config.sampleCount ? clearSelection() : selectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (i of paginatedSampleNumbers(); track i) {\n <tr\n [class.table-active]=\"isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n style=\"cursor: pointer;\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"isSampleSelected(i)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSample(i)\"\n />\n </td>\n <td class=\"fw-semibold\">{{ i }}</td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), i) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (config.sampleCount > pageSize) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize + 1 }}-{{ Math.min(currentPage() * pageSize, config.sampleCount) }} of {{ config.sampleCount }}\n </small>\n <ngb-pagination\n [collectionSize]=\"config.sampleCount\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to apply auto-fill. Auto-fill will only apply to selected samples.\n </div>\n </div>\n\n <div class=\"col-12\">\n <label class=\"form-label\">\n Preview\n @if (previewValues().length > 0) {\n <span class=\"text-muted\">(Showing {{ Math.min(10, previewValues().length) }} of {{ previewValues().length }})</span>\n }\n </label>\n\n @if (previewValues().length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No samples selected. Please select at least one sample.\n </div>\n } @else {\n <div class=\"preview-list\">\n @for (item of previewValues().slice(0, 10); track item.index) {\n <div class=\"preview-item\">\n <span class=\"badge bg-secondary me-2\">Sample {{ item.index }}</span>\n <span class=\"preview-value\">{{ item.value }}</span>\n </div>\n }\n @if (previewValues().length > 10) {\n <div class=\"preview-item text-muted\">\n <i class=\"bi bi-three-dots\"></i>\n and {{ previewValues().length - 10 }} more...\n </div>\n }\n </div>\n }\n </div>\n </div>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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: NgbPaginationModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }] });
9803
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: BasicAutofillComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
9804
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: BasicAutofillComponent, isStandalone: true, selector: "ccv-basic-autofill", inputs: { config: "config" }, outputs: { submit: "submit" }, ngImport: i0, template: "<form [formGroup]=\"autofillForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Column:</strong> {{ config.column.displayName || config.column.name }} ({{ config.column.type }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Fill Mode</label>\n <div class=\"btn-group w-100\">\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'template'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'template'\"\n (click)=\"fillMode.set('template'); autofillForm.patchValue({ fillMode: 'template' })\"\n >\n <i class=\"bi bi-code-square me-1\"></i>Template\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'range'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'range'\"\n (click)=\"fillMode.set('range'); autofillForm.patchValue({ fillMode: 'range' })\"\n >\n <i class=\"bi bi-123 me-1\"></i>Numeric Range\n </button>\n </div>\n </div>\n\n @if (fillMode() === 'template') {\n <div class=\"col-12 mb-3\">\n <label for=\"template\" class=\"form-label\">\n Value Template *\n </label>\n <input\n type=\"text\"\n id=\"template\"\n class=\"form-control\"\n formControlName=\"template\"\n placeholder=\"run {sample_index}\"\n [class.is-invalid]=\"isFieldInvalid('template')\"\n />\n @if (isFieldInvalid('template')) {\n <div class=\"invalid-feedback\">Template is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use <code>{{ '{' }}sample_index{{ '}' }}</code>, <code>{{ '{' }}index{{ '}' }}</code>, or <code>{{ '{' }}n{{ '}' }}</code> as placeholder for sample number.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Quick Templates</label>\n <div class=\"d-flex flex-wrap gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('run {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>run {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('sample_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>sample_{{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('assay {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>assay {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('replicate_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>replicate_{{ '{' }}n{{ '}' }}\n </button>\n </div>\n </div>\n }\n\n @if (fillMode() === 'range') {\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-6\">\n <label for=\"rangeStart\" class=\"form-label\">\n Start Value *\n </label>\n <input\n type=\"number\"\n id=\"rangeStart\"\n class=\"form-control\"\n formControlName=\"rangeStart\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeStart')\"\n />\n @if (isFieldInvalid('rangeStart')) {\n <div class=\"invalid-feedback\">Start value is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n First value in the sequence\n </div>\n </div>\n <div class=\"col-6\">\n <label for=\"rangeIncrement\" class=\"form-label\">\n Increment *\n </label>\n <input\n type=\"number\"\n id=\"rangeIncrement\"\n class=\"form-control\"\n formControlName=\"rangeIncrement\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeIncrement')\"\n />\n @if (isFieldInvalid('rangeIncrement')) {\n <div class=\"invalid-feedback\">Increment is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Step between values\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Apply to Samples <span class=\"text-muted\">({{ sampleIndices().length }} of {{ config.sampleCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sampleIndices().length === config.sampleCount\"\n [indeterminate]=\"sampleIndices().length > 0 && sampleIndices().length < config.sampleCount\"\n (change)=\"sampleIndices().length === config.sampleCount ? clearSelection() : selectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (i of paginatedSampleNumbers(); track i) {\n <tr\n [class.table-active]=\"isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n style=\"cursor: pointer;\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"isSampleSelected(i)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSample(i)\"\n />\n </td>\n <td class=\"fw-semibold\">{{ i }}</td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), i) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (config.sampleCount > pageSize) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize + 1 }}-{{ Math.min(currentPage() * pageSize, config.sampleCount) }} of {{ config.sampleCount }}\n </small>\n <ngb-pagination\n [collectionSize]=\"config.sampleCount\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to apply auto-fill. Auto-fill will only apply to selected samples.\n </div>\n </div>\n\n <div class=\"col-12\">\n <label class=\"form-label\">\n Preview\n @if (previewValues().length > 0) {\n <span class=\"text-muted\">(Showing {{ Math.min(10, previewValues().length) }} of {{ previewValues().length }})</span>\n }\n </label>\n\n @if (previewValues().length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No samples selected. Please select at least one sample.\n </div>\n } @else {\n <div class=\"preview-list\">\n @for (item of previewValues().slice(0, 10); track item.index) {\n <div class=\"preview-item\">\n <span class=\"badge bg-secondary me-2\">Sample {{ item.index }}</span>\n <span class=\"preview-value\">{{ item.value }}</span>\n </div>\n }\n @if (previewValues().length > 10) {\n <div class=\"preview-item text-muted\">\n <i class=\"bi bi-three-dots\"></i>\n and {{ previewValues().length - 10 }} more...\n </div>\n }\n </div>\n }\n </div>\n </div>\n</form>\n", styles: [""], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]" }, { 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: NgbPaginationModule }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }] });
9602
9805
  }
9603
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: BasicAutofillComponent, decorators: [{
9806
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: BasicAutofillComponent, decorators: [{
9604
9807
  type: Component,
9605
9808
  args: [{ selector: 'ccv-basic-autofill', standalone: true, imports: [CommonModule, ReactiveFormsModule, NgbPaginationModule], template: "<form [formGroup]=\"autofillForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n <strong>Column:</strong> {{ config.column.displayName || config.column.name }} ({{ config.column.type }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Fill Mode</label>\n <div class=\"btn-group w-100\">\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'template'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'template'\"\n (click)=\"fillMode.set('template'); autofillForm.patchValue({ fillMode: 'template' })\"\n >\n <i class=\"bi bi-code-square me-1\"></i>Template\n </button>\n <button\n type=\"button\"\n class=\"btn\"\n [class.btn-primary]=\"fillMode() === 'range'\"\n [class.btn-outline-secondary]=\"fillMode() !== 'range'\"\n (click)=\"fillMode.set('range'); autofillForm.patchValue({ fillMode: 'range' })\"\n >\n <i class=\"bi bi-123 me-1\"></i>Numeric Range\n </button>\n </div>\n </div>\n\n @if (fillMode() === 'template') {\n <div class=\"col-12 mb-3\">\n <label for=\"template\" class=\"form-label\">\n Value Template *\n </label>\n <input\n type=\"text\"\n id=\"template\"\n class=\"form-control\"\n formControlName=\"template\"\n placeholder=\"run {sample_index}\"\n [class.is-invalid]=\"isFieldInvalid('template')\"\n />\n @if (isFieldInvalid('template')) {\n <div class=\"invalid-feedback\">Template is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Use <code>{{ '{' }}sample_index{{ '}' }}</code>, <code>{{ '{' }}index{{ '}' }}</code>, or <code>{{ '{' }}n{{ '}' }}</code> as placeholder for sample number.\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label class=\"form-label\">Quick Templates</label>\n <div class=\"d-flex flex-wrap gap-2\">\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('run {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>run {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('sample_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>sample_{{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('assay {sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>assay {{ '{' }}n{{ '}' }}\n </button>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-secondary\"\n (click)=\"useTemplate('replicate_{sample_index}')\"\n >\n <i class=\"bi bi-lightning me-1\"></i>replicate_{{ '{' }}n{{ '}' }}\n </button>\n </div>\n </div>\n }\n\n @if (fillMode() === 'range') {\n <div class=\"col-12 mb-3\">\n <div class=\"row\">\n <div class=\"col-6\">\n <label for=\"rangeStart\" class=\"form-label\">\n Start Value *\n </label>\n <input\n type=\"number\"\n id=\"rangeStart\"\n class=\"form-control\"\n formControlName=\"rangeStart\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeStart')\"\n />\n @if (isFieldInvalid('rangeStart')) {\n <div class=\"invalid-feedback\">Start value is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n First value in the sequence\n </div>\n </div>\n <div class=\"col-6\">\n <label for=\"rangeIncrement\" class=\"form-label\">\n Increment *\n </label>\n <input\n type=\"number\"\n id=\"rangeIncrement\"\n class=\"form-control\"\n formControlName=\"rangeIncrement\"\n placeholder=\"1\"\n [class.is-invalid]=\"isFieldInvalid('rangeIncrement')\"\n />\n @if (isFieldInvalid('rangeIncrement')) {\n <div class=\"invalid-feedback\">Increment is required</div>\n }\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Step between values\n </div>\n </div>\n </div>\n </div>\n }\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Apply to Samples <span class=\"text-muted\">({{ sampleIndices().length }} of {{ config.sampleCount }} selected)</span>\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"selectAllSamples()\"\n title=\"Select All\"\n >\n <i class=\"bi bi-check-all\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"clearSelection()\"\n title=\"Clear Selection\"\n >\n <i class=\"bi bi-x\"></i>\n </button>\n </div>\n </div>\n\n <div class=\"table-responsive\">\n <table class=\"table table-sm table-hover\">\n <thead>\n <tr>\n <th style=\"width: 50px;\">\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"sampleIndices().length === config.sampleCount\"\n [indeterminate]=\"sampleIndices().length > 0 && sampleIndices().length < config.sampleCount\"\n (change)=\"sampleIndices().length === config.sampleCount ? clearSelection() : selectAllSamples()\"\n />\n </th>\n <th style=\"width: 80px;\">Sample</th>\n <th>{{ sourceNameColumn()?.displayName || sourceNameColumn()?.name || 'Source Name' }}</th>\n <th>{{ assayNameColumn()?.displayName || assayNameColumn()?.name || 'Assay Name' }}</th>\n <th>{{ labelColumn()?.displayName || labelColumn()?.name || 'Label' }}</th>\n <th>{{ fractionIdentifierColumn()?.displayName || fractionIdentifierColumn()?.name || 'Fraction Identifier' }}</th>\n </tr>\n </thead>\n <tbody>\n @for (i of paginatedSampleNumbers(); track i) {\n <tr\n [class.table-active]=\"isSampleSelected(i)\"\n (click)=\"toggleSample(i)\"\n style=\"cursor: pointer;\"\n >\n <td>\n <input\n type=\"checkbox\"\n class=\"form-check-input\"\n [checked]=\"isSampleSelected(i)\"\n (click)=\"$event.stopPropagation()\"\n (change)=\"toggleSample(i)\"\n />\n </td>\n <td class=\"fw-semibold\">{{ i }}</td>\n <td>{{ getColumnValueForSample(sourceNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(assayNameColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(labelColumn(), i) || '-' }}</td>\n <td>{{ getColumnValueForSample(fractionIdentifierColumn(), i) || '-' }}</td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n @if (config.sampleCount > pageSize) {\n <div class=\"d-flex justify-content-between align-items-center mt-2\">\n <small class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize + 1 }}-{{ Math.min(currentPage() * pageSize, config.sampleCount) }} of {{ config.sampleCount }}\n </small>\n <ngb-pagination\n [collectionSize]=\"config.sampleCount\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize\"\n [maxSize]=\"3\"\n [boundaryLinks]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n size=\"sm\"\n ></ngb-pagination>\n </div>\n }\n\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Select samples to apply auto-fill. Auto-fill will only apply to selected samples.\n </div>\n </div>\n\n <div class=\"col-12\">\n <label class=\"form-label\">\n Preview\n @if (previewValues().length > 0) {\n <span class=\"text-muted\">(Showing {{ Math.min(10, previewValues().length) }} of {{ previewValues().length }})</span>\n }\n </label>\n\n @if (previewValues().length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No samples selected. Please select at least one sample.\n </div>\n } @else {\n <div class=\"preview-list\">\n @for (item of previewValues().slice(0, 10); track item.index) {\n <div class=\"preview-item\">\n <span class=\"badge bg-secondary me-2\">Sample {{ item.index }}</span>\n <span class=\"preview-value\">{{ item.value }}</span>\n </div>\n }\n @if (previewValues().length > 10) {\n <div class=\"preview-item text-muted\">\n <i class=\"bi bi-three-dots\"></i>\n and {{ previewValues().length - 10 }} more...\n </div>\n }\n </div>\n }\n </div>\n </div>\n</form>\n" }]
9606
9809
  }], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { config: [{
@@ -9751,10 +9954,10 @@ class AdvancedAutofillComponent {
9751
9954
  };
9752
9955
  this.submit.emit(request);
9753
9956
  }
9754
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AdvancedAutofillComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
9755
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: AdvancedAutofillComponent, isStandalone: true, selector: "ccv-advanced-autofill", inputs: { config: "config" }, outputs: { submit: "submit" }, ngImport: i0, template: "<form [formGroup]=\"advancedForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Advanced autofill allows you to generate complex sample variations using templates and multiple column variables.\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"templateSamples\" class=\"form-label\">\n Template Samples *\n </label>\n <input\n type=\"text\"\n id=\"templateSamples\"\n class=\"form-control\"\n formControlName=\"templateSamples\"\n placeholder=\"1,2,3 or 1-5\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter sample indices (e.g., \"1,2,3\" or \"1-5\")\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"targetSampleCount\" class=\"form-label\">\n Target Sample Count *\n </label>\n <input\n type=\"number\"\n id=\"targetSampleCount\"\n class=\"form-control\"\n formControlName=\"targetSampleCount\"\n [max]=\"config.sampleCount\"\n placeholder=\"Enter target count\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Total samples to fill (max: {{ config.sampleCount }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"fillStrategy\" class=\"form-label\">\n Fill Strategy *\n </label>\n <select\n id=\"fillStrategy\"\n class=\"form-select\"\n formControlName=\"fillStrategy\"\n >\n <option value=\"cartesian_product\">Cartesian Product (All Combinations)</option>\n <option value=\"sequential\">Sequential (Cycle Each)</option>\n <option value=\"interleaved\">Interleaved Pattern</option>\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n How variations are combined across samples\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Column Variations\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"addVariation('range')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Range\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"addVariation('list')\"\n >\n <i class=\"bi bi-plus me-1\"></i>List\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-info\"\n (click)=\"addVariation('pattern')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Pattern\n </button>\n </div>\n </div>\n\n @if (variations.length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No variations added. Click above to add column variations.\n </div>\n }\n\n @for (variation of variations.controls; track $index) {\n <div class=\"card mb-2\" [formGroupName]=\"$index\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <h6 class=\"card-title mb-0\">Variation {{ $index + 1 }}</h6>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-danger\"\n (click)=\"removeVariation($index)\"\n >\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n\n <div class=\"row\">\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Column *</label>\n <select class=\"form-select\" formControlName=\"columnId\">\n <option [ngValue]=\"null\">Select Column</option>\n @for (col of config.allColumns || []; track col.id) {\n <option [ngValue]=\"col.id\">{{ col.displayName || col.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Type *</label>\n <select class=\"form-select\" formControlName=\"type\">\n <option value=\"range\">Range</option>\n <option value=\"list\">List</option>\n <option value=\"pattern\">Pattern</option>\n </select>\n </div>\n\n @if (getVariationType($index) === 'range') {\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Start</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"start\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">End</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"end\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Step</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"step\" />\n </div>\n }\n\n @if (getVariationType($index) === 'list') {\n <div class=\"col-12 mb-2\">\n <label class=\"form-label\">\n Values (comma-separated)\n @if (canOpenAutocomplete($index)) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search\"></i> Autocomplete Available\n </span>\n }\n </label>\n <div class=\"input-group\">\n <input type=\"text\" class=\"form-control\" formControlName=\"values\" placeholder=\"value1, value2, value3\" />\n @if (canOpenAutocomplete($index)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openAutocompleteModal($index)\"\n title=\"Open autocomplete search\">\n <i class=\"bi bi-search\"></i>\n </button>\n }\n </div>\n <div class=\"form-text\">\n @if (canOpenAutocomplete($index)) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter values manually or use autocomplete search button\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter comma-separated values\n }\n </div>\n </div>\n }\n\n @if (getVariationType($index) === 'pattern') {\n <div class=\"col-md-8 mb-2\">\n <label class=\"form-label\">Pattern</label>\n <input type=\"text\" class=\"form-control\" formControlName=\"pattern\" placeholder=\"sample_{{ '{' }}i{{ '}' }}\" />\n <div class=\"form-text\">Use {{ '{' }}i{{ '}' }} as placeholder</div>\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Count</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"count\" />\n </div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n</form>\n", styles: [""], 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][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.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { 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: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }] });
9957
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AdvancedAutofillComponent, deps: [{ token: i1.FormBuilder }], target: i0.ɵɵFactoryTarget.Component });
9958
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: AdvancedAutofillComponent, isStandalone: true, selector: "ccv-advanced-autofill", inputs: { config: "config" }, outputs: { submit: "submit" }, ngImport: i0, template: "<form [formGroup]=\"advancedForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Advanced autofill allows you to generate complex sample variations using templates and multiple column variables.\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"templateSamples\" class=\"form-label\">\n Template Samples *\n </label>\n <input\n type=\"text\"\n id=\"templateSamples\"\n class=\"form-control\"\n formControlName=\"templateSamples\"\n placeholder=\"1,2,3 or 1-5\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter sample indices (e.g., \"1,2,3\" or \"1-5\")\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"targetSampleCount\" class=\"form-label\">\n Target Sample Count *\n </label>\n <input\n type=\"number\"\n id=\"targetSampleCount\"\n class=\"form-control\"\n formControlName=\"targetSampleCount\"\n [max]=\"config.sampleCount\"\n placeholder=\"Enter target count\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Total samples to fill (max: {{ config.sampleCount }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"fillStrategy\" class=\"form-label\">\n Fill Strategy *\n </label>\n <select\n id=\"fillStrategy\"\n class=\"form-select\"\n formControlName=\"fillStrategy\"\n >\n <option value=\"cartesian_product\">Cartesian Product (All Combinations)</option>\n <option value=\"sequential\">Sequential (Cycle Each)</option>\n <option value=\"interleaved\">Interleaved Pattern</option>\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n How variations are combined across samples\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Column Variations\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"addVariation('range')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Range\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"addVariation('list')\"\n >\n <i class=\"bi bi-plus me-1\"></i>List\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-info\"\n (click)=\"addVariation('pattern')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Pattern\n </button>\n </div>\n </div>\n\n @if (variations.length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No variations added. Click above to add column variations.\n </div>\n }\n\n @for (variation of variations.controls; track $index) {\n <div class=\"card mb-2\" [formGroupName]=\"$index\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <h6 class=\"card-title mb-0\">Variation {{ $index + 1 }}</h6>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-danger\"\n (click)=\"removeVariation($index)\"\n >\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n\n <div class=\"row\">\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Column *</label>\n <select class=\"form-select\" formControlName=\"columnId\">\n <option [ngValue]=\"null\">Select Column</option>\n @for (col of config.allColumns || []; track col.id) {\n <option [ngValue]=\"col.id\">{{ col.displayName || col.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Type *</label>\n <select class=\"form-select\" formControlName=\"type\">\n <option value=\"range\">Range</option>\n <option value=\"list\">List</option>\n <option value=\"pattern\">Pattern</option>\n </select>\n </div>\n\n @if (getVariationType($index) === 'range') {\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Start</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"start\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">End</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"end\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Step</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"step\" />\n </div>\n }\n\n @if (getVariationType($index) === 'list') {\n <div class=\"col-12 mb-2\">\n <label class=\"form-label\">\n Values (comma-separated)\n @if (canOpenAutocomplete($index)) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search\"></i> Autocomplete Available\n </span>\n }\n </label>\n <div class=\"input-group\">\n <input type=\"text\" class=\"form-control\" formControlName=\"values\" placeholder=\"value1, value2, value3\" />\n @if (canOpenAutocomplete($index)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openAutocompleteModal($index)\"\n title=\"Open autocomplete search\">\n <i class=\"bi bi-search\"></i>\n </button>\n }\n </div>\n <div class=\"form-text\">\n @if (canOpenAutocomplete($index)) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter values manually or use autocomplete search button\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter comma-separated values\n }\n </div>\n </div>\n }\n\n @if (getVariationType($index) === 'pattern') {\n <div class=\"col-md-8 mb-2\">\n <label class=\"form-label\">Pattern</label>\n <input type=\"text\" class=\"form-control\" formControlName=\"pattern\" placeholder=\"sample_{{ '{' }}i{{ '}' }}\" />\n <div class=\"form-text\">Use {{ '{' }}i{{ '}' }} as placeholder</div>\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Count</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"count\" />\n </div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n</form>\n", styles: [""], 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.NumberValueAccessor, selector: "input[type=number][formControlName],input[type=number][formControl],input[type=number][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.MaxValidator, selector: "input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]", inputs: ["max"] }, { 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: "directive", type: i1.FormGroupName, selector: "[formGroupName]", inputs: ["formGroupName"] }] });
9756
9959
  }
9757
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AdvancedAutofillComponent, decorators: [{
9960
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: AdvancedAutofillComponent, decorators: [{
9758
9961
  type: Component,
9759
9962
  args: [{ selector: 'ccv-advanced-autofill', standalone: true, imports: [CommonModule, ReactiveFormsModule], template: "<form [formGroup]=\"advancedForm\">\n <div class=\"row\">\n <div class=\"col-12 mb-3\">\n <div class=\"alert alert-info\">\n <i class=\"bi bi-info-circle me-2\"></i>\n Advanced autofill allows you to generate complex sample variations using templates and multiple column variables.\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"templateSamples\" class=\"form-label\">\n Template Samples *\n </label>\n <input\n type=\"text\"\n id=\"templateSamples\"\n class=\"form-control\"\n formControlName=\"templateSamples\"\n placeholder=\"1,2,3 or 1-5\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter sample indices (e.g., \"1,2,3\" or \"1-5\")\n </div>\n </div>\n\n <div class=\"col-md-6 mb-3\">\n <label for=\"targetSampleCount\" class=\"form-label\">\n Target Sample Count *\n </label>\n <input\n type=\"number\"\n id=\"targetSampleCount\"\n class=\"form-control\"\n formControlName=\"targetSampleCount\"\n [max]=\"config.sampleCount\"\n placeholder=\"Enter target count\"\n />\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n Total samples to fill (max: {{ config.sampleCount }})\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <label for=\"fillStrategy\" class=\"form-label\">\n Fill Strategy *\n </label>\n <select\n id=\"fillStrategy\"\n class=\"form-select\"\n formControlName=\"fillStrategy\"\n >\n <option value=\"cartesian_product\">Cartesian Product (All Combinations)</option>\n <option value=\"sequential\">Sequential (Cycle Each)</option>\n <option value=\"interleaved\">Interleaved Pattern</option>\n </select>\n <div class=\"form-text\">\n <i class=\"bi bi-info-circle me-1\"></i>\n How variations are combined across samples\n </div>\n </div>\n\n <div class=\"col-12 mb-3\">\n <div class=\"d-flex justify-content-between align-items-center mb-2\">\n <label class=\"form-label mb-0\">\n Column Variations\n </label>\n <div class=\"btn-group btn-group-sm\">\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"addVariation('range')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Range\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"addVariation('list')\"\n >\n <i class=\"bi bi-plus me-1\"></i>List\n </button>\n <button\n type=\"button\"\n class=\"btn btn-outline-info\"\n (click)=\"addVariation('pattern')\"\n >\n <i class=\"bi bi-plus me-1\"></i>Pattern\n </button>\n </div>\n </div>\n\n @if (variations.length === 0) {\n <div class=\"alert alert-warning\">\n <i class=\"bi bi-exclamation-triangle me-2\"></i>\n No variations added. Click above to add column variations.\n </div>\n }\n\n @for (variation of variations.controls; track $index) {\n <div class=\"card mb-2\" [formGroupName]=\"$index\">\n <div class=\"card-body\">\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\n <h6 class=\"card-title mb-0\">Variation {{ $index + 1 }}</h6>\n <button\n type=\"button\"\n class=\"btn btn-sm btn-outline-danger\"\n (click)=\"removeVariation($index)\"\n >\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n\n <div class=\"row\">\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Column *</label>\n <select class=\"form-select\" formControlName=\"columnId\">\n <option [ngValue]=\"null\">Select Column</option>\n @for (col of config.allColumns || []; track col.id) {\n <option [ngValue]=\"col.id\">{{ col.displayName || col.name }}</option>\n }\n </select>\n </div>\n\n <div class=\"col-md-6 mb-2\">\n <label class=\"form-label\">Type *</label>\n <select class=\"form-select\" formControlName=\"type\">\n <option value=\"range\">Range</option>\n <option value=\"list\">List</option>\n <option value=\"pattern\">Pattern</option>\n </select>\n </div>\n\n @if (getVariationType($index) === 'range') {\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Start</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"start\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">End</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"end\" />\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Step</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"step\" />\n </div>\n }\n\n @if (getVariationType($index) === 'list') {\n <div class=\"col-12 mb-2\">\n <label class=\"form-label\">\n Values (comma-separated)\n @if (canOpenAutocomplete($index)) {\n <span class=\"badge bg-success ms-2\">\n <i class=\"bi bi-search\"></i> Autocomplete Available\n </span>\n }\n </label>\n <div class=\"input-group\">\n <input type=\"text\" class=\"form-control\" formControlName=\"values\" placeholder=\"value1, value2, value3\" />\n @if (canOpenAutocomplete($index)) {\n <button\n type=\"button\"\n class=\"btn btn-outline-primary\"\n (click)=\"openAutocompleteModal($index)\"\n title=\"Open autocomplete search\">\n <i class=\"bi bi-search\"></i>\n </button>\n }\n </div>\n <div class=\"form-text\">\n @if (canOpenAutocomplete($index)) {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter values manually or use autocomplete search button\n } @else {\n <i class=\"bi bi-info-circle me-1\"></i>\n Enter comma-separated values\n }\n </div>\n </div>\n }\n\n @if (getVariationType($index) === 'pattern') {\n <div class=\"col-md-8 mb-2\">\n <label class=\"form-label\">Pattern</label>\n <input type=\"text\" class=\"form-control\" formControlName=\"pattern\" placeholder=\"sample_{{ '{' }}i{{ '}' }}\" />\n <div class=\"form-text\">Use {{ '{' }}i{{ '}' }} as placeholder</div>\n </div>\n <div class=\"col-md-4 mb-2\">\n <label class=\"form-label\">Count</label>\n <input type=\"number\" class=\"form-control\" formControlName=\"count\" />\n </div>\n }\n </div>\n </div>\n </div>\n }\n </div>\n </div>\n</form>\n" }]
9760
9963
  }], ctorParameters: () => [{ type: i1.FormBuilder }], propDecorators: { config: [{
@@ -9800,10 +10003,10 @@ class MetadataColumnAutofillModal {
9800
10003
  close() {
9801
10004
  this.activeModal.dismiss('cancel');
9802
10005
  }
9803
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnAutofillModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
9804
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataColumnAutofillModal, isStandalone: true, selector: "ccv-metadata-column-autofill-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <ul ngbNav #nav=\"ngbNav\" [(activeId)]=\"activeTab\" class=\"nav-tabs mb-3\">\n <li [ngbNavItem]=\"1\">\n <button ngbNavLink>\n <i class=\"bi bi-pencil me-1\"></i>Basic\n </button>\n <ng-template ngbNavContent>\n @if (basicConfig()) {\n <ccv-basic-autofill\n [config]=\"basicConfig()!\"\n (submit)=\"onBasicSubmit($event)\"\n ></ccv-basic-autofill>\n }\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"2\">\n <button ngbNavLink>\n <i class=\"bi bi-gear me-1\"></i>Advanced\n </button>\n <ng-template ngbNavContent>\n @if (advancedConfig()) {\n <ccv-advanced-autofill\n [config]=\"advancedConfig()!\"\n (submit)=\"onAdvancedSubmit($event)\"\n ></ccv-advanced-autofill>\n }\n </ng-template>\n </li>\n </ul>\n\n <div [ngbNavOutlet]=\"nav\"></div>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n >\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n</div>\n", styles: [".table-responsive{max-height:400px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.table-responsive table{margin-bottom:0}.table-responsive table thead{position:sticky;top:0;background-color:var(--bs-tertiary-bg);z-index:1}.table-responsive table thead th{border-bottom:2px solid var(--bs-border-color);font-weight:600;font-size:.875rem;padding:.75rem}.table-responsive table tbody tr:hover{background-color:var(--bs-secondary-bg)}.table-responsive table tbody tr.table-active{background-color:var(--bs-primary-bg-subtle)}.table-responsive table tbody tr.table-active:hover{background-color:var(--bs-primary-bg-subtle);filter:brightness(.95)}.table-responsive table tbody td{font-size:.875rem;vertical-align:middle;padding:.75rem}.preview-list{max-height:250px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);padding:.75rem;background-color:var(--bs-tertiary-bg)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color)}.preview-item:last-child{margin-bottom:0}.preview-value{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-weight:500;color:var(--bs-body-color)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbNavModule }, { kind: "directive", type: i2.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i2.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i2.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i2.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i2.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i2.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i2.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "component", type: BasicAutofillComponent, selector: "ccv-basic-autofill", inputs: ["config"], outputs: ["submit"] }, { kind: "component", type: AdvancedAutofillComponent, selector: "ccv-advanced-autofill", inputs: ["config"], outputs: ["submit"] }] });
10006
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnAutofillModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
10007
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: MetadataColumnAutofillModal, isStandalone: true, selector: "ccv-metadata-column-autofill-modal", inputs: { config: "config" }, ngImport: i0, template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <ul ngbNav #nav=\"ngbNav\" [(activeId)]=\"activeTab\" class=\"nav-tabs mb-3\">\n <li [ngbNavItem]=\"1\">\n <button ngbNavLink>\n <i class=\"bi bi-pencil me-1\"></i>Basic\n </button>\n <ng-template ngbNavContent>\n @if (basicConfig()) {\n <ccv-basic-autofill\n [config]=\"basicConfig()!\"\n (submit)=\"onBasicSubmit($event)\"\n ></ccv-basic-autofill>\n }\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"2\">\n <button ngbNavLink>\n <i class=\"bi bi-gear me-1\"></i>Advanced\n </button>\n <ng-template ngbNavContent>\n @if (advancedConfig()) {\n <ccv-advanced-autofill\n [config]=\"advancedConfig()!\"\n (submit)=\"onAdvancedSubmit($event)\"\n ></ccv-advanced-autofill>\n }\n </ng-template>\n </li>\n </ul>\n\n <div [ngbNavOutlet]=\"nav\"></div>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n >\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n</div>\n", styles: [".table-responsive{max-height:400px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.table-responsive table{margin-bottom:0}.table-responsive table thead{position:sticky;top:0;background-color:var(--bs-tertiary-bg);z-index:1}.table-responsive table thead th{border-bottom:2px solid var(--bs-border-color);font-weight:600;font-size:.875rem;padding:.75rem}.table-responsive table tbody tr:hover{background-color:var(--bs-secondary-bg)}.table-responsive table tbody tr.table-active{background-color:var(--bs-primary-bg-subtle)}.table-responsive table tbody tr.table-active:hover{background-color:var(--bs-primary-bg-subtle);filter:brightness(.95)}.table-responsive table tbody td{font-size:.875rem;vertical-align:middle;padding:.75rem}.preview-list{max-height:250px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);padding:.75rem;background-color:var(--bs-tertiary-bg)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color)}.preview-item:last-child{margin-bottom:0}.preview-value{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-weight:500;color:var(--bs-body-color)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: NgbNavModule }, { kind: "directive", type: i2.NgbNavContent, selector: "ng-template[ngbNavContent]" }, { kind: "directive", type: i2.NgbNav, selector: "[ngbNav]", inputs: ["activeId", "animation", "destroyOnHide", "orientation", "roles", "keyboard"], outputs: ["activeIdChange", "shown", "hidden", "navChange"], exportAs: ["ngbNav"] }, { kind: "directive", type: i2.NgbNavItem, selector: "[ngbNavItem]", inputs: ["destroyOnHide", "disabled", "domId", "ngbNavItem"], outputs: ["shown", "hidden"], exportAs: ["ngbNavItem"] }, { kind: "directive", type: i2.NgbNavItemRole, selector: "[ngbNavItem]:not(ng-container)" }, { kind: "directive", type: i2.NgbNavLinkButton, selector: "button[ngbNavLink]" }, { kind: "directive", type: i2.NgbNavLinkBase, selector: "[ngbNavLink]" }, { kind: "component", type: i2.NgbNavOutlet, selector: "[ngbNavOutlet]", inputs: ["paneRole", "ngbNavOutlet"] }, { kind: "component", type: BasicAutofillComponent, selector: "ccv-basic-autofill", inputs: ["config"], outputs: ["submit"] }, { kind: "component", type: AdvancedAutofillComponent, selector: "ccv-advanced-autofill", inputs: ["config"], outputs: ["submit"] }] });
9805
10008
  }
9806
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnAutofillModal, decorators: [{
10009
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnAutofillModal, decorators: [{
9807
10010
  type: Component,
9808
10011
  args: [{ selector: 'ccv-metadata-column-autofill-modal', standalone: true, imports: [CommonModule, NgbNavModule, BasicAutofillComponent, AdvancedAutofillComponent], template: "<div class=\"modal-header\">\n <h4 class=\"modal-title\">\n <i class=\"bi bi-magic me-2\"></i>Auto-fill Column Values\n </h4>\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\n</div>\n\n<div class=\"modal-body\">\n <ul ngbNav #nav=\"ngbNav\" [(activeId)]=\"activeTab\" class=\"nav-tabs mb-3\">\n <li [ngbNavItem]=\"1\">\n <button ngbNavLink>\n <i class=\"bi bi-pencil me-1\"></i>Basic\n </button>\n <ng-template ngbNavContent>\n @if (basicConfig()) {\n <ccv-basic-autofill\n [config]=\"basicConfig()!\"\n (submit)=\"onBasicSubmit($event)\"\n ></ccv-basic-autofill>\n }\n </ng-template>\n </li>\n\n <li [ngbNavItem]=\"2\">\n <button ngbNavLink>\n <i class=\"bi bi-gear me-1\"></i>Advanced\n </button>\n <ng-template ngbNavContent>\n @if (advancedConfig()) {\n <ccv-advanced-autofill\n [config]=\"advancedConfig()!\"\n (submit)=\"onAdvancedSubmit($event)\"\n ></ccv-advanced-autofill>\n }\n </ng-template>\n </li>\n </ul>\n\n <div [ngbNavOutlet]=\"nav\"></div>\n</div>\n\n<div class=\"modal-footer\">\n <button\n type=\"button\"\n class=\"btn btn-outline-secondary\"\n (click)=\"close()\"\n >\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n</div>\n", styles: [".table-responsive{max-height:400px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius)}.table-responsive table{margin-bottom:0}.table-responsive table thead{position:sticky;top:0;background-color:var(--bs-tertiary-bg);z-index:1}.table-responsive table thead th{border-bottom:2px solid var(--bs-border-color);font-weight:600;font-size:.875rem;padding:.75rem}.table-responsive table tbody tr:hover{background-color:var(--bs-secondary-bg)}.table-responsive table tbody tr.table-active{background-color:var(--bs-primary-bg-subtle)}.table-responsive table tbody tr.table-active:hover{background-color:var(--bs-primary-bg-subtle);filter:brightness(.95)}.table-responsive table tbody td{font-size:.875rem;vertical-align:middle;padding:.75rem}.preview-list{max-height:250px;overflow-y:auto;border:1px solid var(--bs-border-color);border-radius:var(--bs-border-radius);padding:.75rem;background-color:var(--bs-tertiary-bg)}.preview-item{padding:.5rem .75rem;margin-bottom:.5rem;background-color:var(--bs-body-bg);border-radius:var(--bs-border-radius);display:flex;align-items:center;border:1px solid var(--bs-border-color)}.preview-item:last-child{margin-bottom:0}.preview-value{font-family:SFMono-Regular,Consolas,Liberation Mono,Menlo,Courier,monospace;font-weight:500;color:var(--bs-body-color)}\n"] }]
9809
10012
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }], propDecorators: { config: [{
@@ -9899,10 +10102,10 @@ class MetadataColumnHistoryModal {
9899
10102
  return 'badge bg-secondary';
9900
10103
  }
9901
10104
  }
9902
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnHistoryModal, deps: [{ token: i2.NgbActiveModal }, { token: MetadataColumnService }], target: i0.ɵɵFactoryTarget.Component });
9903
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.7", type: MetadataColumnHistoryModal, isStandalone: true, selector: "ccv-metadata-column-history-modal", inputs: { config: "config" }, outputs: { closed: "closed" }, ngImport: i0, template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-clock-history me-2\"></i>Column History: {{ config.columnName }}\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n @if (error) {\r\n <div class=\"alert alert-danger\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ error }}\r\n </div>\r\n }\r\n\r\n @if (loading && history.length === 0) {\r\n <div class=\"text-center py-4\">\r\n <div class=\"spinner-border text-primary\" role=\"status\">\r\n <span class=\"visually-hidden\">Loading...</span>\r\n </div>\r\n <p class=\"mt-2 text-muted\">Loading history...</p>\r\n </div>\r\n }\r\n\r\n @if (history.length > 0) {\r\n <div class=\"mb-3 text-muted\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Showing {{ history.length }} of {{ totalCount }} records\r\n </div>\r\n\r\n <div class=\"timeline\">\r\n @for (record of history; track record.historyId) {\r\n <div class=\"timeline-item mb-4\">\r\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\r\n <div>\r\n <span [ngClass]=\"getHistoryTypeClass(record.historyType)\">\r\n {{ record.historyType }}\r\n </span>\r\n <small class=\"text-muted ms-2\">\r\n {{ record.historyDate | date:'short' }}\r\n </small>\r\n </div>\r\n @if (record.historyUser) {\r\n <span class=\"badge bg-secondary\">\r\n <i class=\"bi bi-person me-1\"></i>{{ record.historyUser }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (record.changes.length > 0) {\r\n <div class=\"card\">\r\n <div class=\"card-body\">\r\n <h6 class=\"card-title\">Changes:</h6>\r\n <div class=\"table-responsive\">\r\n <table class=\"table table-sm table-bordered mb-0\">\r\n <thead>\r\n <tr>\r\n <th>Field</th>\r\n <th>Old Value</th>\r\n <th>New Value</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (change of record.changes; track change.field) {\r\n <tr>\r\n <td><strong>{{ getChangeLabel(change.field) }}</strong></td>\r\n <td>\r\n <code class=\"text-muted\">{{ formatValue(change.oldValue) }}</code>\r\n </td>\r\n <td>\r\n <code class=\"text-success\">{{ formatValue(change.newValue) }}</code>\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"alert alert-info mb-0\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n Initial record - no changes\r\n </div>\r\n }\r\n\r\n <details class=\"mt-2\">\r\n <summary class=\"text-muted\" style=\"cursor: pointer;\">\r\n <i class=\"bi bi-eye me-1\"></i>View full snapshot\r\n </summary>\r\n <div class=\"card mt-2\">\r\n <div class=\"card-body\">\r\n <pre class=\"mb-0\"><code>{{ formatValue(record.snapshot) }}</code></pre>\r\n </div>\r\n </div>\r\n </details>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (hasMore) {\r\n <div class=\"text-center mt-3\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-primary\"\r\n (click)=\"loadMore()\"\r\n [disabled]=\"loading\">\r\n @if (loading) {\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n }\r\n Load More\r\n </button>\r\n </div>\r\n }\r\n }\r\n\r\n @if (!loading && history.length === 0 && !error) {\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n No history records found for this column.\r\n </div>\r\n }\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"close()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Close\r\n </button>\r\n</div>\r\n", styles: [".timeline{position:relative;padding-left:0}.timeline-item{position:relative;padding-left:20px;border-left:2px solid #dee2e6}.timeline-item:last-child{border-left-color:transparent}.timeline-item:before{content:\"\";position:absolute;left:-6px;top:0;width:10px;height:10px;border-radius:50%;background-color:#0d6efd;border:2px solid #fff}details summary{font-size:.875rem}details summary:hover{text-decoration:underline}pre code{display:block;max-height:300px;overflow-y:auto;font-size:.85rem}.table{font-size:.875rem}.table code{white-space:pre-wrap;word-break:break-word;display:block;max-height:100px;overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
10105
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnHistoryModal, deps: [{ token: i2.NgbActiveModal }, { token: MetadataColumnService }], target: i0.ɵɵFactoryTarget.Component });
10106
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.17", type: MetadataColumnHistoryModal, isStandalone: true, selector: "ccv-metadata-column-history-modal", inputs: { config: "config" }, outputs: { closed: "closed" }, ngImport: i0, template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-clock-history me-2\"></i>Column History: {{ config.columnName }}\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n @if (error) {\r\n <div class=\"alert alert-danger\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ error }}\r\n </div>\r\n }\r\n\r\n @if (loading && history.length === 0) {\r\n <div class=\"text-center py-4\">\r\n <div class=\"spinner-border text-primary\" role=\"status\">\r\n <span class=\"visually-hidden\">Loading...</span>\r\n </div>\r\n <p class=\"mt-2 text-muted\">Loading history...</p>\r\n </div>\r\n }\r\n\r\n @if (history.length > 0) {\r\n <div class=\"mb-3 text-muted\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Showing {{ history.length }} of {{ totalCount }} records\r\n </div>\r\n\r\n <div class=\"timeline\">\r\n @for (record of history; track record.historyId) {\r\n <div class=\"timeline-item mb-4\">\r\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\r\n <div>\r\n <span [ngClass]=\"getHistoryTypeClass(record.historyType)\">\r\n {{ record.historyType }}\r\n </span>\r\n <small class=\"text-muted ms-2\">\r\n {{ record.historyDate | date:'short' }}\r\n </small>\r\n </div>\r\n @if (record.historyUser) {\r\n <span class=\"badge bg-secondary\">\r\n <i class=\"bi bi-person me-1\"></i>{{ record.historyUser }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (record.changes.length > 0) {\r\n <div class=\"card\">\r\n <div class=\"card-body\">\r\n <h6 class=\"card-title\">Changes:</h6>\r\n <div class=\"table-responsive\">\r\n <table class=\"table table-sm table-bordered mb-0\">\r\n <thead>\r\n <tr>\r\n <th>Field</th>\r\n <th>Old Value</th>\r\n <th>New Value</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (change of record.changes; track change.field) {\r\n <tr>\r\n <td><strong>{{ getChangeLabel(change.field) }}</strong></td>\r\n <td>\r\n <code class=\"text-muted\">{{ formatValue(change.oldValue) }}</code>\r\n </td>\r\n <td>\r\n <code class=\"text-success\">{{ formatValue(change.newValue) }}</code>\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"alert alert-info mb-0\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n Initial record - no changes\r\n </div>\r\n }\r\n\r\n <details class=\"mt-2\">\r\n <summary class=\"text-muted\" style=\"cursor: pointer;\">\r\n <i class=\"bi bi-eye me-1\"></i>View full snapshot\r\n </summary>\r\n <div class=\"card mt-2\">\r\n <div class=\"card-body\">\r\n <pre class=\"mb-0\"><code>{{ formatValue(record.snapshot) }}</code></pre>\r\n </div>\r\n </div>\r\n </details>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (hasMore) {\r\n <div class=\"text-center mt-3\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-primary\"\r\n (click)=\"loadMore()\"\r\n [disabled]=\"loading\">\r\n @if (loading) {\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n }\r\n Load More\r\n </button>\r\n </div>\r\n }\r\n }\r\n\r\n @if (!loading && history.length === 0 && !error) {\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n No history records found for this column.\r\n </div>\r\n }\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"close()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Close\r\n </button>\r\n</div>\r\n", styles: [".timeline{position:relative;padding-left:0}.timeline-item{position:relative;padding-left:20px;border-left:2px solid #dee2e6}.timeline-item:last-child{border-left-color:transparent}.timeline-item:before{content:\"\";position:absolute;left:-6px;top:0;width:10px;height:10px;border-radius:50%;background-color:#0d6efd;border:2px solid #fff}details summary{font-size:.875rem}details summary:hover{text-decoration:underline}pre code{display:block;max-height:300px;overflow-y:auto;font-size:.85rem}.table{font-size:.875rem}.table code{white-space:pre-wrap;word-break:break-word;display:block;max-height:100px;overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
9904
10107
  }
9905
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnHistoryModal, decorators: [{
10108
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: MetadataColumnHistoryModal, decorators: [{
9906
10109
  type: Component,
9907
10110
  args: [{ selector: 'ccv-metadata-column-history-modal', standalone: true, imports: [CommonModule], template: "<div class=\"modal-header\">\r\n <h4 class=\"modal-title\">\r\n <i class=\"bi bi-clock-history me-2\"></i>Column History: {{ config.columnName }}\r\n </h4>\r\n <button type=\"button\" class=\"btn-close\" (click)=\"close()\"></button>\r\n</div>\r\n\r\n<div class=\"modal-body\">\r\n @if (error) {\r\n <div class=\"alert alert-danger\">\r\n <i class=\"bi bi-exclamation-triangle me-2\"></i>{{ error }}\r\n </div>\r\n }\r\n\r\n @if (loading && history.length === 0) {\r\n <div class=\"text-center py-4\">\r\n <div class=\"spinner-border text-primary\" role=\"status\">\r\n <span class=\"visually-hidden\">Loading...</span>\r\n </div>\r\n <p class=\"mt-2 text-muted\">Loading history...</p>\r\n </div>\r\n }\r\n\r\n @if (history.length > 0) {\r\n <div class=\"mb-3 text-muted\">\r\n <i class=\"bi bi-info-circle me-1\"></i>\r\n Showing {{ history.length }} of {{ totalCount }} records\r\n </div>\r\n\r\n <div class=\"timeline\">\r\n @for (record of history; track record.historyId) {\r\n <div class=\"timeline-item mb-4\">\r\n <div class=\"d-flex justify-content-between align-items-start mb-2\">\r\n <div>\r\n <span [ngClass]=\"getHistoryTypeClass(record.historyType)\">\r\n {{ record.historyType }}\r\n </span>\r\n <small class=\"text-muted ms-2\">\r\n {{ record.historyDate | date:'short' }}\r\n </small>\r\n </div>\r\n @if (record.historyUser) {\r\n <span class=\"badge bg-secondary\">\r\n <i class=\"bi bi-person me-1\"></i>{{ record.historyUser }}\r\n </span>\r\n }\r\n </div>\r\n\r\n @if (record.changes.length > 0) {\r\n <div class=\"card\">\r\n <div class=\"card-body\">\r\n <h6 class=\"card-title\">Changes:</h6>\r\n <div class=\"table-responsive\">\r\n <table class=\"table table-sm table-bordered mb-0\">\r\n <thead>\r\n <tr>\r\n <th>Field</th>\r\n <th>Old Value</th>\r\n <th>New Value</th>\r\n </tr>\r\n </thead>\r\n <tbody>\r\n @for (change of record.changes; track change.field) {\r\n <tr>\r\n <td><strong>{{ getChangeLabel(change.field) }}</strong></td>\r\n <td>\r\n <code class=\"text-muted\">{{ formatValue(change.oldValue) }}</code>\r\n </td>\r\n <td>\r\n <code class=\"text-success\">{{ formatValue(change.newValue) }}</code>\r\n </td>\r\n </tr>\r\n }\r\n </tbody>\r\n </table>\r\n </div>\r\n </div>\r\n </div>\r\n } @else {\r\n <div class=\"alert alert-info mb-0\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n Initial record - no changes\r\n </div>\r\n }\r\n\r\n <details class=\"mt-2\">\r\n <summary class=\"text-muted\" style=\"cursor: pointer;\">\r\n <i class=\"bi bi-eye me-1\"></i>View full snapshot\r\n </summary>\r\n <div class=\"card mt-2\">\r\n <div class=\"card-body\">\r\n <pre class=\"mb-0\"><code>{{ formatValue(record.snapshot) }}</code></pre>\r\n </div>\r\n </div>\r\n </details>\r\n </div>\r\n }\r\n </div>\r\n\r\n @if (hasMore) {\r\n <div class=\"text-center mt-3\">\r\n <button\r\n type=\"button\"\r\n class=\"btn btn-outline-primary\"\r\n (click)=\"loadMore()\"\r\n [disabled]=\"loading\">\r\n @if (loading) {\r\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\r\n }\r\n Load More\r\n </button>\r\n </div>\r\n }\r\n }\r\n\r\n @if (!loading && history.length === 0 && !error) {\r\n <div class=\"alert alert-info\">\r\n <i class=\"bi bi-info-circle me-2\"></i>\r\n No history records found for this column.\r\n </div>\r\n }\r\n</div>\r\n\r\n<div class=\"modal-footer\">\r\n <button type=\"button\" class=\"btn btn-secondary\" (click)=\"close()\">\r\n <i class=\"bi bi-x-circle me-1\"></i>Close\r\n </button>\r\n</div>\r\n", styles: [".timeline{position:relative;padding-left:0}.timeline-item{position:relative;padding-left:20px;border-left:2px solid #dee2e6}.timeline-item:last-child{border-left-color:transparent}.timeline-item:before{content:\"\";position:absolute;left:-6px;top:0;width:10px;height:10px;border-radius:50%;background-color:#0d6efd;border:2px solid #fff}details summary{font-size:.875rem}details summary:hover{text-decoration:underline}pre code{display:block;max-height:300px;overflow-y:auto;font-size:.85rem}.table{font-size:.875rem}.table code{white-space:pre-wrap;word-break:break-word;display:block;max-height:100px;overflow-y:auto}\n"] }]
9908
10111
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }, { type: MetadataColumnService }], propDecorators: { config: [{
@@ -9912,14 +10115,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImpor
9912
10115
  }] } });
9913
10116
 
9914
10117
  class CupcakeVanilla {
9915
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CupcakeVanilla, deps: [], target: i0.ɵɵFactoryTarget.Component });
9916
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CupcakeVanilla, isStandalone: true, selector: "ccv-cupcake-vanilla", ngImport: i0, template: `
10118
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CupcakeVanilla, deps: [], target: i0.ɵɵFactoryTarget.Component });
10119
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.17", type: CupcakeVanilla, isStandalone: true, selector: "ccv-cupcake-vanilla", ngImport: i0, template: `
9917
10120
  <p>
9918
10121
  cupcake-vanilla works!
9919
10122
  </p>
9920
10123
  `, isInline: true, styles: [""] });
9921
10124
  }
9922
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CupcakeVanilla, decorators: [{
10125
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.17", ngImport: i0, type: CupcakeVanilla, decorators: [{
9923
10126
  type: Component,
9924
10127
  args: [{ selector: 'ccv-cupcake-vanilla', imports: [], template: `
9925
10128
  <p>