@noatgnu/cupcake-vanilla 1.2.11 → 1.2.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 } from '@angular/core';
2
+ import { Injectable, signal, computed, EventEmitter, inject, Output, Input, Component, effect, ViewChild } from '@angular/core';
3
3
  import * as i3 from '@noatgnu/cupcake-core';
4
4
  import { BaseApiService, WebSocketService, NotificationService as NotificationService$1, LabGroupService, ToastService, ResourceVisibility } from '@noatgnu/cupcake-core';
5
5
  import { switchMap, map, tap, takeUntil, filter as filter$1 } 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, BehaviorSubject, firstValueFrom } from 'rxjs';
6
+ import { from, Observable, throwError, NEVER, debounceTime, distinctUntilChanged, filter, switchMap as switchMap$1, map as map$1, catchError, of, tap as tap$1, Subject, BehaviorSubject, 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';
@@ -13,6 +13,7 @@ import * as i2 from '@ng-bootstrap/ng-bootstrap';
13
13
  import { NgbTypeahead, NgbModule, NgbNavModule, NgbActiveModal, NgbModal, NgbPaginationModule } from '@ng-bootstrap/ng-bootstrap';
14
14
  import * as i5$1 from '@angular/cdk/drag-drop';
15
15
  import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
16
+ import * as i1$1 from '@angular/router';
16
17
  import { RouterModule } from '@angular/router';
17
18
 
18
19
  var OntologyType;
@@ -657,10 +658,16 @@ class MetadataTableService extends BaseApiService {
657
658
  confirmed
658
659
  });
659
660
  }
660
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
661
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableService, providedIn: 'root' });
661
+ /**
662
+ * Replace all occurrences of a specific value across columns in this table
663
+ */
664
+ replaceColumnValue(id, request) {
665
+ return this.post(`${this.apiUrl}/metadata-tables/${id}/replace_column_value/`, request);
666
+ }
667
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
668
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableService, providedIn: 'root' });
662
669
  }
663
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableService, decorators: [{
670
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableService, decorators: [{
664
671
  type: Injectable,
665
672
  args: [{
666
673
  providedIn: 'root'
@@ -749,10 +756,16 @@ class MetadataColumnService extends BaseApiService {
749
756
  const httpParams = this.buildHttpParams(params);
750
757
  return this.get(`${this.apiUrl}/metadata-columns/detect_ontology_type/`, { params: httpParams });
751
758
  }
752
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataColumnService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
753
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataColumnService, providedIn: 'root' });
759
+ /**
760
+ * Replace all occurrences of a specific value with a new value in this column
761
+ */
762
+ replaceValue(id, request) {
763
+ return this.post(`${this.apiUrl}/metadata-columns/${id}/replace_value/`, request);
764
+ }
765
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
766
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnService, providedIn: 'root' });
754
767
  }
755
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataColumnService, decorators: [{
768
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnService, decorators: [{
756
769
  type: Injectable,
757
770
  args: [{
758
771
  providedIn: 'root'
@@ -803,10 +816,10 @@ class SamplePoolService extends BaseApiService {
803
816
  getMetadataColumns(id) {
804
817
  return this.get(`${this.apiUrl}/sample-pools/${id}/metadata_columns/`);
805
818
  }
806
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SamplePoolService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
807
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SamplePoolService, providedIn: 'root' });
819
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
820
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolService, providedIn: 'root' });
808
821
  }
809
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SamplePoolService, decorators: [{
822
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolService, decorators: [{
810
823
  type: Injectable,
811
824
  args: [{
812
825
  providedIn: 'root'
@@ -943,10 +956,10 @@ class MetadataTableTemplateService extends BaseApiService {
943
956
  createTableFromSchemas(request) {
944
957
  return this.post(`${this.apiUrl}/metadata-table-templates/create_table_from_schemas/`, request);
945
958
  }
946
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
947
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableTemplateService, providedIn: 'root' });
959
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
960
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateService, providedIn: 'root' });
948
961
  }
949
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableTemplateService, decorators: [{
962
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateService, decorators: [{
950
963
  type: Injectable,
951
964
  args: [{
952
965
  providedIn: 'root'
@@ -991,10 +1004,10 @@ class FavouriteMetadataOptionService extends BaseApiService {
991
1004
  deleteFavouriteMetadataOption(id) {
992
1005
  return this.delete(`${this.apiUrl}/favourite-options/${id}/`);
993
1006
  }
994
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: FavouriteMetadataOptionService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
995
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: FavouriteMetadataOptionService, providedIn: 'root' });
1007
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FavouriteMetadataOptionService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1008
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FavouriteMetadataOptionService, providedIn: 'root' });
996
1009
  }
997
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: FavouriteMetadataOptionService, decorators: [{
1010
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FavouriteMetadataOptionService, decorators: [{
998
1011
  type: Injectable,
999
1012
  args: [{
1000
1013
  providedIn: 'root'
@@ -1081,10 +1094,10 @@ class MetadataColumnTemplateService extends BaseApiService {
1081
1094
  });
1082
1095
  return this.get(`${this.apiUrl}/column-templates/ontology_suggestions/`, { params: httpParams });
1083
1096
  }
1084
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataColumnTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1085
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataColumnTemplateService, providedIn: 'root' });
1097
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1098
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateService, providedIn: 'root' });
1086
1099
  }
1087
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataColumnTemplateService, decorators: [{
1100
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateService, decorators: [{
1088
1101
  type: Injectable,
1089
1102
  args: [{
1090
1103
  providedIn: 'root'
@@ -1123,10 +1136,10 @@ class MetadataColumnTemplateShareService extends BaseApiService {
1123
1136
  deleteMetadataColumnTemplateShare(id) {
1124
1137
  return this.delete(`${this.apiUrl}/template-shares/${id}/`);
1125
1138
  }
1126
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataColumnTemplateShareService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1127
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataColumnTemplateShareService, providedIn: 'root' });
1139
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateShareService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1140
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateShareService, providedIn: 'root' });
1128
1141
  }
1129
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataColumnTemplateShareService, decorators: [{
1142
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataColumnTemplateShareService, decorators: [{
1130
1143
  type: Injectable,
1131
1144
  args: [{
1132
1145
  providedIn: 'root'
@@ -1179,10 +1192,10 @@ class MetadataManagementService extends BaseApiService {
1179
1192
  exportSdrfFile(request) {
1180
1193
  return this.post(`${this.apiUrl}/metadata-management/export_sdrf_file/`, request, { responseType: 'blob' });
1181
1194
  }
1182
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataManagementService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1183
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataManagementService, providedIn: 'root' });
1195
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataManagementService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1196
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataManagementService, providedIn: 'root' });
1184
1197
  }
1185
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataManagementService, decorators: [{
1198
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataManagementService, decorators: [{
1186
1199
  type: Injectable,
1187
1200
  args: [{
1188
1201
  providedIn: 'root'
@@ -1202,10 +1215,10 @@ class Websocket extends WebSocketService {
1202
1215
  getLabGroupUpdates() {
1203
1216
  return this.filterMessages('lab_group.update');
1204
1217
  }
1205
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: Websocket, deps: [{ token: i3.AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
1206
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: Websocket, providedIn: 'root' });
1218
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: Websocket, deps: [{ token: i3.AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
1219
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: Websocket, providedIn: 'root' });
1207
1220
  }
1208
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: Websocket, decorators: [{
1221
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: Websocket, decorators: [{
1209
1222
  type: Injectable,
1210
1223
  args: [{
1211
1224
  providedIn: 'root'
@@ -1225,10 +1238,10 @@ class AdminWebSocketService extends WebSocketService {
1225
1238
  getSystemNotifications() {
1226
1239
  return this.filterMessages('system.notification');
1227
1240
  }
1228
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AdminWebSocketService, deps: [{ token: i3.AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
1229
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AdminWebSocketService, providedIn: 'root' });
1241
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AdminWebSocketService, deps: [{ token: i3.AuthService }], target: i0.ɵɵFactoryTarget.Injectable });
1242
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AdminWebSocketService, providedIn: 'root' });
1230
1243
  }
1231
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AdminWebSocketService, decorators: [{
1244
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AdminWebSocketService, decorators: [{
1232
1245
  type: Injectable,
1233
1246
  args: [{
1234
1247
  providedIn: 'root'
@@ -1514,10 +1527,10 @@ class NotificationService extends NotificationService$1 {
1514
1527
  createVanillaNotification(type, title, message, level = 'info', autoClose = true) {
1515
1528
  this.createLocalNotification(type, title, message, level, autoClose);
1516
1529
  }
1517
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: NotificationService, deps: [{ token: Websocket }, { token: AdminWebSocketService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Injectable });
1518
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: NotificationService, providedIn: 'root' });
1530
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NotificationService, deps: [{ token: Websocket }, { token: AdminWebSocketService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Injectable });
1531
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NotificationService, providedIn: 'root' });
1519
1532
  }
1520
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: NotificationService, decorators: [{
1533
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: NotificationService, decorators: [{
1521
1534
  type: Injectable,
1522
1535
  args: [{
1523
1536
  providedIn: 'root'
@@ -1585,10 +1598,10 @@ class OntologySearchService extends BaseApiService {
1585
1598
  limit
1586
1599
  });
1587
1600
  }
1588
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: OntologySearchService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1589
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: OntologySearchService, providedIn: 'root' });
1601
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: OntologySearchService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1602
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: OntologySearchService, providedIn: 'root' });
1590
1603
  }
1591
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: OntologySearchService, decorators: [{
1604
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: OntologySearchService, decorators: [{
1592
1605
  type: Injectable,
1593
1606
  args: [{
1594
1607
  providedIn: 'root'
@@ -1621,10 +1634,10 @@ class SchemaService extends BaseApiService {
1621
1634
  syncBuiltin() {
1622
1635
  return this.post(`${this.apiUrl}/schemas/sync_builtin/`, {});
1623
1636
  }
1624
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SchemaService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1625
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SchemaService, providedIn: 'root' });
1637
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SchemaService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1638
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SchemaService, providedIn: 'root' });
1626
1639
  }
1627
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SchemaService, decorators: [{
1640
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SchemaService, decorators: [{
1628
1641
  type: Injectable,
1629
1642
  args: [{
1630
1643
  providedIn: 'root'
@@ -1715,10 +1728,10 @@ class SDRFDefaultsService extends BaseApiService {
1715
1728
  getQuickValues() {
1716
1729
  return this.get(`${this.apiUrl}/sdrf-defaults/quick_values/`);
1717
1730
  }
1718
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SDRFDefaultsService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1719
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SDRFDefaultsService, providedIn: 'root' });
1731
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SDRFDefaultsService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1732
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SDRFDefaultsService, providedIn: 'root' });
1720
1733
  }
1721
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SDRFDefaultsService, decorators: [{
1734
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SDRFDefaultsService, decorators: [{
1722
1735
  type: Injectable,
1723
1736
  args: [{
1724
1737
  providedIn: 'root'
@@ -1758,10 +1771,10 @@ class AsyncTaskService extends BaseApiService {
1758
1771
  const httpParams = this.buildHttpParams({ token });
1759
1772
  return this.get(`${this.apiUrl}/async-tasks/${id}/download/`, { params: httpParams, responseType: 'blob' });
1760
1773
  }
1761
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncTaskService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1762
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncTaskService, providedIn: 'root' });
1774
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1775
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskService, providedIn: 'root' });
1763
1776
  }
1764
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncTaskService, decorators: [{
1777
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskService, decorators: [{
1765
1778
  type: Injectable,
1766
1779
  args: [{
1767
1780
  providedIn: 'root'
@@ -1781,10 +1794,10 @@ class AsyncExportService extends BaseApiService {
1781
1794
  multipleExcelTemplates(request) {
1782
1795
  return this.post(`${this.apiUrl}/async-export/multiple_excel_templates/`, request);
1783
1796
  }
1784
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncExportService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1785
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncExportService, providedIn: 'root' });
1797
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncExportService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1798
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncExportService, providedIn: 'root' });
1786
1799
  }
1787
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncExportService, decorators: [{
1800
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncExportService, decorators: [{
1788
1801
  type: Injectable,
1789
1802
  args: [{
1790
1803
  providedIn: 'root'
@@ -1969,10 +1982,10 @@ class ChunkedUploadService extends BaseApiService {
1969
1982
  uploadNextChunk();
1970
1983
  });
1971
1984
  }
1972
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ChunkedUploadService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1973
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ChunkedUploadService, providedIn: 'root' });
1985
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedUploadService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
1986
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedUploadService, providedIn: 'root' });
1974
1987
  }
1975
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ChunkedUploadService, decorators: [{
1988
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedUploadService, decorators: [{
1976
1989
  type: Injectable,
1977
1990
  args: [{
1978
1991
  providedIn: 'root'
@@ -2017,10 +2030,10 @@ class AsyncImportService extends BaseApiService {
2017
2030
  result: result
2018
2031
  })));
2019
2032
  }
2020
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncImportService, deps: [{ token: ChunkedUploadService }], target: i0.ɵɵFactoryTarget.Injectable });
2021
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncImportService, providedIn: 'root' });
2033
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncImportService, deps: [{ token: ChunkedUploadService }], target: i0.ɵɵFactoryTarget.Injectable });
2034
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncImportService, providedIn: 'root' });
2022
2035
  }
2023
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncImportService, decorators: [{
2036
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncImportService, decorators: [{
2024
2037
  type: Injectable,
2025
2038
  args: [{
2026
2039
  providedIn: 'root'
@@ -2031,10 +2044,10 @@ class AsyncValidationService extends BaseApiService {
2031
2044
  metadataTable(request) {
2032
2045
  return this.post(`${this.apiUrl}/async-validation/metadata_table/`, request);
2033
2046
  }
2034
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncValidationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2035
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncValidationService, providedIn: 'root' });
2047
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncValidationService, deps: null, target: i0.ɵɵFactoryTarget.Injectable });
2048
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncValidationService, providedIn: 'root' });
2036
2049
  }
2037
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncValidationService, decorators: [{
2050
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncValidationService, decorators: [{
2038
2051
  type: Injectable,
2039
2052
  args: [{
2040
2053
  providedIn: 'root'
@@ -2216,10 +2229,10 @@ class ChunkedImportService extends BaseApiService {
2216
2229
  };
2217
2230
  return this.post(`${this.apiUrl}/async-import/chunked_file/`, payload);
2218
2231
  }
2219
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ChunkedImportService, deps: [{ token: ChunkedUploadService }], target: i0.ɵɵFactoryTarget.Injectable });
2220
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ChunkedImportService, providedIn: 'root' });
2232
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedImportService, deps: [{ token: ChunkedUploadService }], target: i0.ɵɵFactoryTarget.Injectable });
2233
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedImportService, providedIn: 'root' });
2221
2234
  }
2222
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ChunkedImportService, decorators: [{
2235
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ChunkedImportService, decorators: [{
2223
2236
  type: Injectable,
2224
2237
  args: [{
2225
2238
  providedIn: 'root'
@@ -2503,10 +2516,10 @@ class SdrfSyntaxService {
2503
2516
  getSyntheticPeptideValues() {
2504
2517
  return [...this.SYNTHETIC_PEPTIDE_VALUES];
2505
2518
  }
2506
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfSyntaxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2507
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfSyntaxService, providedIn: 'root' });
2519
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfSyntaxService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2520
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfSyntaxService, providedIn: 'root' });
2508
2521
  }
2509
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfSyntaxService, decorators: [{
2522
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfSyntaxService, decorators: [{
2510
2523
  type: Injectable,
2511
2524
  args: [{
2512
2525
  providedIn: 'root'
@@ -2604,10 +2617,10 @@ class ElectronService {
2604
2617
  return () => unsubscribe();
2605
2618
  });
2606
2619
  }
2607
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ElectronService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2608
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ElectronService, providedIn: 'root' });
2620
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ElectronService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
2621
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ElectronService, providedIn: 'root' });
2609
2622
  }
2610
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ElectronService, decorators: [{
2623
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ElectronService, decorators: [{
2611
2624
  type: Injectable,
2612
2625
  args: [{
2613
2626
  providedIn: 'root'
@@ -2711,10 +2724,10 @@ class SdrfAgeInput {
2711
2724
  }
2712
2725
  }
2713
2726
  }
2714
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfAgeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
2715
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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"] }] });
2727
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfAgeInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
2728
+ 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"] }] });
2716
2729
  }
2717
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfAgeInput, decorators: [{
2730
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfAgeInput, decorators: [{
2718
2731
  type: Component,
2719
2732
  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" }]
2720
2733
  }], propDecorators: { value: [{
@@ -3005,10 +3018,10 @@ class SdrfModificationInput {
3005
3018
  this.updateAvailableSpecifications(mockSuggestion);
3006
3019
  }
3007
3020
  }
3008
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfModificationInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3009
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 }] });
3021
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfModificationInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3022
+ 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 }] });
3010
3023
  }
3011
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfModificationInput, decorators: [{
3024
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfModificationInput, decorators: [{
3012
3025
  type: Component,
3013
3026
  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" }]
3014
3027
  }], propDecorators: { value: [{
@@ -3101,10 +3114,10 @@ class SdrfSpikedCompoundInput {
3101
3114
  get showPeptideFields() {
3102
3115
  return this.spikedForm.get('CT')?.value === 'peptide';
3103
3116
  }
3104
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfSpikedCompoundInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3105
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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"] }] });
3117
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfSpikedCompoundInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3118
+ 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"] }] });
3106
3119
  }
3107
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfSpikedCompoundInput, decorators: [{
3120
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfSpikedCompoundInput, decorators: [{
3108
3121
  type: Component,
3109
3122
  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" }]
3110
3123
  }], propDecorators: { value: [{
@@ -3237,10 +3250,10 @@ class SdrfCleavageInput {
3237
3250
  onSearchTypeChange(type) {
3238
3251
  this.searchType.set(type);
3239
3252
  }
3240
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfCleavageInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3241
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 }] });
3253
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfCleavageInput, deps: [], target: i0.ɵɵFactoryTarget.Component });
3254
+ 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 }] });
3242
3255
  }
3243
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SdrfCleavageInput, decorators: [{
3256
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SdrfCleavageInput, decorators: [{
3244
3257
  type: Component,
3245
3258
  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" }]
3246
3259
  }], propDecorators: { value: [{
@@ -3447,7 +3460,12 @@ class MetadataValueEditModal {
3447
3460
  clearValue() {
3448
3461
  this.editForm.get('value')?.setValue('');
3449
3462
  this.editForm.get('value')?.markAsTouched();
3450
- this.selectedFavorite.set(null); // Clear selected favorite when manually clearing
3463
+ this.selectedFavorite.set(null);
3464
+ }
3465
+ setValue(value) {
3466
+ this.editForm.get('value')?.setValue(value);
3467
+ this.editForm.get('value')?.markAsTouched();
3468
+ this.selectedFavorite.set(null);
3451
3469
  }
3452
3470
  // Favorite options methods
3453
3471
  loadFavoriteOptions() {
@@ -3716,10 +3734,10 @@ class MetadataValueEditModal {
3716
3734
  }
3717
3735
  }
3718
3736
  }
3719
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", 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 });
3720
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 <!-- 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" }] });
3737
+ 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 });
3738
+ 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" }] });
3721
3739
  }
3722
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataValueEditModal, decorators: [{
3740
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataValueEditModal, decorators: [{
3723
3741
  type: Component,
3724
3742
  args: [{ selector: 'ccv-metadata-value-edit-modal', standalone: true, imports: [
3725
3743
  CommonModule,
@@ -3730,7 +3748,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
3730
3748
  SdrfModificationInput,
3731
3749
  SdrfCleavageInput,
3732
3750
  SdrfSpikedCompoundInput
3733
- ], 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 <!-- 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"] }]
3751
+ ], 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"] }]
3734
3752
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i3.AuthService }, { type: FavouriteMetadataOptionService }, { type: MetadataColumnService }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }, { type: OntologySearchService }], propDecorators: { config: [{
3735
3753
  type: Input
3736
3754
  }], valueSaved: [{
@@ -3809,10 +3827,10 @@ class ExcelExportModalComponent {
3809
3827
  cancel() {
3810
3828
  this.activeModal.dismiss();
3811
3829
  }
3812
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ExcelExportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3813
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 }] });
3830
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ExcelExportModalComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
3831
+ 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 }] });
3814
3832
  }
3815
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ExcelExportModalComponent, decorators: [{
3833
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ExcelExportModalComponent, decorators: [{
3816
3834
  type: Component,
3817
3835
  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" }]
3818
3836
  }] });
@@ -3829,10 +3847,10 @@ class ValidationResultsModal {
3829
3847
  return timestamp;
3830
3848
  }
3831
3849
  }
3832
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ValidationResultsModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
3833
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 }] });
3850
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ValidationResultsModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
3851
+ 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 }] });
3834
3852
  }
3835
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ValidationResultsModal, decorators: [{
3853
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ValidationResultsModal, decorators: [{
3836
3854
  type: Component,
3837
3855
  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"] }]
3838
3856
  }], propDecorators: { results: [{
@@ -4288,10 +4306,10 @@ class AsyncTaskUIService {
4288
4306
  break;
4289
4307
  }
4290
4308
  }
4291
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncTaskUIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4292
- static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncTaskUIService, providedIn: 'root' });
4309
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskUIService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
4310
+ static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskUIService, providedIn: 'root' });
4293
4311
  }
4294
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: AsyncTaskUIService, decorators: [{
4312
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: AsyncTaskUIService, decorators: [{
4295
4313
  type: Injectable,
4296
4314
  args: [{
4297
4315
  providedIn: 'root'
@@ -4337,10 +4355,10 @@ class MetadataValidationModal {
4337
4355
  }
4338
4356
  });
4339
4357
  }
4340
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataValidationModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
4341
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 }] });
4358
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataValidationModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
4359
+ 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 }] });
4342
4360
  }
4343
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataValidationModal, decorators: [{
4361
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataValidationModal, decorators: [{
4344
4362
  type: Component,
4345
4363
  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"] }]
4346
4364
  }], propDecorators: { config: [{
@@ -4558,10 +4576,10 @@ Do you want to continue?`;
4558
4576
  };
4559
4577
  return displayNames[fieldName] || fieldName;
4560
4578
  }
4561
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableService }, { token: i3.LabGroupService }], target: i0.ɵɵFactoryTarget.Component });
4562
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 }] });
4579
+ 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 });
4580
+ 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 }] });
4563
4581
  }
4564
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableEditModal, decorators: [{
4582
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableEditModal, decorators: [{
4565
4583
  type: Component,
4566
4584
  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" }]
4567
4585
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableService }, { type: i3.LabGroupService }], propDecorators: { table: [{
@@ -4626,7 +4644,9 @@ class ColumnEditModal {
4626
4644
  mandatory: [false],
4627
4645
  hidden: [false],
4628
4646
  readonly: [false],
4629
- notApplicable: [false]
4647
+ notApplicable: [false],
4648
+ notAvailable: [false],
4649
+ staffOnly: [false]
4630
4650
  });
4631
4651
  }
4632
4652
  ngOnInit() {
@@ -4654,7 +4674,9 @@ class ColumnEditModal {
4654
4674
  mandatory: this.column.mandatory || false,
4655
4675
  hidden: this.column.hidden || false,
4656
4676
  readonly: this.column.readonly || false,
4657
- notApplicable: this.column.notApplicable || false
4677
+ notApplicable: this.column.notApplicable || false,
4678
+ notAvailable: this.column.notAvailable || false,
4679
+ staffOnly: this.column.staffOnly || false
4658
4680
  });
4659
4681
  }
4660
4682
  }
@@ -4678,7 +4700,9 @@ class ColumnEditModal {
4678
4700
  mandatory: formValue.mandatory || false,
4679
4701
  hidden: formValue.hidden || false,
4680
4702
  readonly: formValue.readonly || false,
4681
- notApplicable: formValue.notApplicable || false
4703
+ notApplicable: formValue.notApplicable || false,
4704
+ notAvailable: formValue.notAvailable || false,
4705
+ staffOnly: formValue.staffOnly || false
4682
4706
  };
4683
4707
  this.columnSaved.emit(columnData);
4684
4708
  }
@@ -4733,7 +4757,9 @@ class ColumnEditModal {
4733
4757
  ontologyType: ontologyType,
4734
4758
  enableTypeahead: enableTypeahead,
4735
4759
  currentValue: this.editForm.get('value')?.value || '',
4736
- context: this.isEdit ? 'table' : 'template'
4760
+ context: this.isEdit ? 'table' : 'template',
4761
+ allowNotApplicable: this.editForm.get('notApplicable')?.value || false,
4762
+ allowNotAvailable: this.editForm.get('notAvailable')?.value || false
4737
4763
  };
4738
4764
  const hasTemplate = this.selectedTemplate || this.column?.template || this.templateId;
4739
4765
  if (hasTemplate) {
@@ -4761,6 +4787,10 @@ class ColumnEditModal {
4761
4787
  modalRef.componentInstance.onClose();
4762
4788
  });
4763
4789
  }
4790
+ setDefaultValue(value) {
4791
+ this.editForm.get('value')?.setValue(value);
4792
+ this.editForm.get('value')?.markAsTouched();
4793
+ }
4764
4794
  // Template selection methods
4765
4795
  loadColumnTemplates() {
4766
4796
  this.isLoadingTemplates = true;
@@ -5093,12 +5123,12 @@ class ColumnEditModal {
5093
5123
  }
5094
5124
  return columnType;
5095
5125
  }
5096
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ColumnEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataColumnTemplateService }, { token: SdrfSyntaxService }], target: i0.ɵɵFactoryTarget.Component });
5097
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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>N/A\n </label>\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 <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"] }] });
5126
+ 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 });
5127
+ 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"] }] });
5098
5128
  }
5099
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ColumnEditModal, decorators: [{
5129
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ColumnEditModal, decorators: [{
5100
5130
  type: Component,
5101
- 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>N/A\n </label>\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 <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"] }]
5131
+ 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"] }]
5102
5132
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataColumnTemplateService }, { type: SdrfSyntaxService }], propDecorators: { column: [{
5103
5133
  type: Input
5104
5134
  }], templateId: [{
@@ -5415,10 +5445,10 @@ class MetadataTableTemplateEditModal {
5415
5445
  columns.length = 0;
5416
5446
  columns.push(...regroupedColumns);
5417
5447
  }
5418
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableTemplateService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
5419
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary ms-2\" (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 ms-2\" (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 </h6>\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;\"><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 <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.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.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i5$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
5448
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }, { token: i2.NgbModal }, { token: MetadataTableTemplateService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
5449
+ 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 <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary ms-2\" (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 ms-2\" (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 </h6>\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;\"><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 <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.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.ontologyType) {\n <span class=\"badge badge-pill bg-success\" title=\"Has Ontology\">\n <i class=\"bi bi-tags\"></i>\n </span>\n }\n </div>\n </td>\n <td>\n <div class=\"btn-group btn-group-sm\" role=\"group\">\n <button \n type=\"button\"\n class=\"btn btn-outline-primary\" \n (click)=\"editColumn(column)\"\n title=\"Edit column\">\n <i class=\"bi bi-pencil\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-info\" \n (click)=\"duplicateColumn(column)\"\n title=\"Duplicate column\">\n <i class=\"bi bi-copy\"></i>\n </button>\n <button \n type=\"button\"\n class=\"btn btn-outline-danger\" \n (click)=\"removeColumn(column)\"\n title=\"Remove column\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n } @else {\n <div class=\"text-center py-4 text-muted\">\n <i class=\"bi bi-columns-gap fs-1 mb-2\"></i>\n <p>No columns in this template yet.</p>\n <div class=\"d-flex gap-2 justify-content-center\">\n <button type=\"button\" class=\"btn btn-primary btn-sm\" (click)=\"addColumn()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n @if (isEdit && template?.id) {\n <button type=\"button\" class=\"btn btn-success btn-sm\" (click)=\"addColumnWithAutoReorder()\" \n [disabled]=\"isLoading()\"\n title=\"Add column with automatic schema-based reordering\">\n <i class=\"bi bi-plus-circle me-1\"></i>Add with Auto-Reorder\n </button>\n }\n </div>\n </div>\n }\n </div>\n </div>\n </form>\n</div>\n\n<div class=\"modal-footer\">\n <button type=\"button\" class=\"btn btn-outline-secondary\" (click)=\"onCancel()\">\n <i class=\"bi bi-x-circle me-1\"></i>Cancel\n </button>\n <button \n type=\"button\" \n class=\"btn btn-primary\" \n (click)=\"onSubmit()\"\n [disabled]=\"editForm.invalid || isLoading()\">\n @if (isLoading()) {\n <span class=\"spinner-border spinner-border-sm me-2\"></span>\n }\n <i class=\"bi bi-check-circle me-1\"></i>\n {{ isEdit ? 'Update' : 'Create' }} Template\n </button>\n</div>\n", styles: [".column-row{transition:transform .2s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-disabled{opacity:.5}.column-row.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.column-row.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--cupcake-card-bg)}.column-row.cdk-drag-preview td{border:none!important}.column-row.cdk-drag-placeholder{opacity:0}.drag-handle{cursor:grab;color:var(--cupcake-text-muted)}.drag-handle:hover{color:var(--cupcake-text)}.drag-handle:active{cursor:grabbing}.column-row[data-column-group]{position:relative}.column-row[data-column-group]:before{content:\"\";position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--bs-primary)}.badge.badge-pill{border-radius:50px;font-size:.65em;padding:.25em .5em}.modal-lg{max-width:900px}.table-responsive{border-radius:.375rem;overflow:hidden}.table-sm{font-size:.875rem}.table-sm th,.table-sm td{padding:.5rem;vertical-align:middle}.text-center.py-4{background:var(--cupcake-bg-tertiary);border-radius:.375rem;margin:1rem 0}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: ReactiveFormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.CheckboxControlValueAccessor, selector: "input[type=checkbox][formControlName],input[type=checkbox][formControl],input[type=checkbox][ngModel]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.FormGroupDirective, selector: "[formGroup]", inputs: ["formGroup"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "directive", type: i1.FormControlName, selector: "[formControlName]", inputs: ["formControlName", "disabled", "ngModel"], outputs: ["ngModelChange"] }, { kind: "ngmodule", type: NgbModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i5$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }] });
5420
5450
  }
5421
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableTemplateEditModal, decorators: [{
5451
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplateEditModal, decorators: [{
5422
5452
  type: Component,
5423
5453
  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 <h6 class=\"text-muted border-bottom pb-2\">\n <i class=\"bi bi-columns-gap me-2\"></i>Template Columns\n <button type=\"button\" class=\"btn btn-sm btn-outline-primary ms-2\" (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 ms-2\" (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 </h6>\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;\"><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 <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.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.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"] }]
5424
5454
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: i2.NgbModal }, { type: MetadataTableTemplateService }, { type: i3.ToastService }], propDecorators: { template: [{
@@ -5533,10 +5563,10 @@ class TemplateSchemaSelectionModal {
5533
5563
  onCancel() {
5534
5564
  this.activeModal.dismiss();
5535
5565
  }
5536
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: TemplateSchemaSelectionModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
5537
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 }] });
5566
+ 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 });
5567
+ 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 }] });
5538
5568
  }
5539
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: TemplateSchemaSelectionModal, decorators: [{
5569
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TemplateSchemaSelectionModal, decorators: [{
5540
5570
  type: Component,
5541
5571
  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"] }]
5542
5572
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { availableSchemas: [{
@@ -5642,10 +5672,10 @@ class SchemaSelectionModal {
5642
5672
  };
5643
5673
  return categoryMap[category.toLowerCase()] || 'bg-secondary';
5644
5674
  }
5645
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SchemaSelectionModal, deps: [{ token: i2.NgbActiveModal }, { token: SchemaService }, { token: i3.ToastService }], target: i0.ɵɵFactoryTarget.Component });
5646
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 }] });
5675
+ 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 });
5676
+ 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 }] });
5647
5677
  }
5648
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: SchemaSelectionModal, decorators: [{
5678
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SchemaSelectionModal, decorators: [{
5649
5679
  type: Component,
5650
5680
  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"] }]
5651
5681
  }], ctorParameters: () => [{ type: i2.NgbActiveModal }, { type: SchemaService }, { type: i3.ToastService }] });
@@ -5730,10 +5760,10 @@ class TableCreationModalComponent {
5730
5760
  };
5731
5761
  return labels[fieldName] || fieldName;
5732
5762
  }
5733
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: TableCreationModalComponent, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
5734
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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"] }] });
5763
+ 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 });
5764
+ 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"] }] });
5735
5765
  }
5736
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: TableCreationModalComponent, decorators: [{
5766
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: TableCreationModalComponent, decorators: [{
5737
5767
  type: Component,
5738
5768
  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"] }]
5739
5769
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { template: [{
@@ -6158,10 +6188,10 @@ class MetadataTableTemplates {
6158
6188
  }
6159
6189
  });
6160
6190
  }
6161
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", 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 });
6162
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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"] }] });
6191
+ 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 });
6192
+ 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"] }] });
6163
6193
  }
6164
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: MetadataTableTemplates, decorators: [{
6194
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableTemplates, decorators: [{
6165
6195
  type: Component,
6166
6196
  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" }]
6167
6197
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: MetadataTableTemplateService }, { type: i3.LabGroupService }, { type: i2.NgbModal }, { type: i3.ToastService }, { type: AsyncTaskUIService }] });
@@ -6201,6 +6231,8 @@ class ColumnTemplateEditModal {
6201
6231
  visibility: ['private', Validators.required],
6202
6232
  labGroup: [null],
6203
6233
  enableTypeahead: [false],
6234
+ notApplicable: [false],
6235
+ notAvailable: [false],
6204
6236
  excelValidation: [false],
6205
6237
  isActive: [true],
6206
6238
  tags: [''],
@@ -6232,6 +6264,8 @@ class ColumnTemplateEditModal {
6232
6264
  visibility: this.template.visibility || 'private',
6233
6265
  labGroup: this.template.labGroup || null,
6234
6266
  enableTypeahead: this.template.enableTypeahead || false,
6267
+ notApplicable: this.template.notApplicable || false,
6268
+ notAvailable: this.template.notAvailable || false,
6235
6269
  excelValidation: this.template.excelValidation || false,
6236
6270
  isActive: this.template.isActive !== false,
6237
6271
  tags: this.template.tags || '',
@@ -6290,6 +6324,10 @@ class ColumnTemplateEditModal {
6290
6324
  control?.markAsTouched();
6291
6325
  });
6292
6326
  }
6327
+ setDefaultValue(value) {
6328
+ this.editForm.get('defaultValue')?.setValue(value);
6329
+ this.editForm.get('defaultValue')?.markAsTouched();
6330
+ }
6293
6331
  onCancel() {
6294
6332
  this.activeModal.dismiss();
6295
6333
  }
@@ -6314,12 +6352,12 @@ class ColumnTemplateEditModal {
6314
6352
  get title() {
6315
6353
  return this.isEdit ? 'Edit Column Template' : 'Create Column Template';
6316
6354
  }
6317
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ColumnTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
6318
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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\">Default Value</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Optional default value\">\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]=\"ontology.value\">{{ 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=\"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: "pipe", type: i5.JsonPipe, name: "json" }] });
6355
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ColumnTemplateEditModal, deps: [{ token: i1.FormBuilder }, { token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
6356
+ 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\">Default Value</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 <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Optional default value\">\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]=\"ontology.value\">{{ 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: "pipe", type: i5.JsonPipe, name: "json" }] });
6319
6357
  }
6320
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ColumnTemplateEditModal, decorators: [{
6358
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ColumnTemplateEditModal, decorators: [{
6321
6359
  type: Component,
6322
- 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\">Default Value</label>\n <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Optional default value\">\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]=\"ontology.value\">{{ 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=\"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" }]
6360
+ 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\">Default Value</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 <input\n type=\"text\"\n class=\"form-control\"\n id=\"defaultValue\"\n formControlName=\"defaultValue\"\n placeholder=\"Optional default value\">\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]=\"ontology.value\">{{ 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" }]
6323
6361
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { template: [{
6324
6362
  type: Input
6325
6363
  }], labGroups: [{
@@ -6662,10 +6700,10 @@ class ColumnTemplates {
6662
6700
  // Trigger a refresh by updating the search params
6663
6701
  this.searchParams.update(params => ({ ...params }));
6664
6702
  }
6665
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ColumnTemplates, deps: [{ token: i1.FormBuilder }, { token: MetadataColumnTemplateService }, { token: i3.LabGroupService }, { token: i2.NgbModal }], target: i0.ɵɵFactoryTarget.Component });
6666
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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 }] });
6703
+ 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 });
6704
+ 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 }] });
6667
6705
  }
6668
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: ColumnTemplates, decorators: [{
6706
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: ColumnTemplates, decorators: [{
6669
6707
  type: Component,
6670
6708
  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"] }]
6671
6709
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }, { type: i2.NgbModal }] });
@@ -7391,10 +7429,10 @@ class FavoriteManagement {
7391
7429
  return 'bi-star';
7392
7430
  return 'bi-circle';
7393
7431
  }
7394
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", 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 });
7395
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.3", 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" }] });
7432
+ 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 });
7433
+ 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" }] });
7396
7434
  }
7397
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: FavoriteManagement, decorators: [{
7435
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: FavoriteManagement, decorators: [{
7398
7436
  type: Component,
7399
7437
  args: [{ selector: 'ccv-favorite-management', standalone: true, imports: [
7400
7438
  CommonModule,
@@ -7405,15 +7443,1708 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
7405
7443
  ], 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" }]
7406
7444
  }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: FavouriteMetadataOptionService }, { type: MetadataColumnTemplateService }, { type: i3.LabGroupService }, { type: i3.ToastService }, { type: i2.NgbModal }, { type: i3.AuthService }] });
7407
7445
 
7446
+ class SearchReplaceModal {
7447
+ fb;
7448
+ activeModal;
7449
+ config;
7450
+ searchReplaceForm;
7451
+ isProcessing = signal(false, ...(ngDevMode ? [{ debugName: "isProcessing" }] : []));
7452
+ result = signal(null, ...(ngDevMode ? [{ debugName: "result" }] : []));
7453
+ constructor(fb, activeModal) {
7454
+ this.fb = fb;
7455
+ this.activeModal = activeModal;
7456
+ this.searchReplaceForm = this.fb.group({
7457
+ oldValue: ['', [Validators.required, Validators.minLength(1)]],
7458
+ newValue: ['', [Validators.required]],
7459
+ columnId: [null],
7460
+ columnName: [''],
7461
+ updatePools: [false]
7462
+ });
7463
+ }
7464
+ get isTableContext() {
7465
+ return this.config?.context === 'table';
7466
+ }
7467
+ get isColumnContext() {
7468
+ return this.config?.context === 'column';
7469
+ }
7470
+ onSubmit() {
7471
+ if (this.searchReplaceForm.valid) {
7472
+ this.activeModal.close({
7473
+ action: 'replace',
7474
+ data: this.searchReplaceForm.value
7475
+ });
7476
+ }
7477
+ else {
7478
+ this.markFormGroupTouched();
7479
+ }
7480
+ }
7481
+ markFormGroupTouched() {
7482
+ Object.keys(this.searchReplaceForm.controls).forEach(key => {
7483
+ const control = this.searchReplaceForm.get(key);
7484
+ control?.markAsTouched();
7485
+ });
7486
+ }
7487
+ onCancel() {
7488
+ this.activeModal.dismiss();
7489
+ }
7490
+ getFieldError(fieldName) {
7491
+ const control = this.searchReplaceForm.get(fieldName);
7492
+ if (control?.errors && control.touched) {
7493
+ if (control.errors['required'])
7494
+ return `${fieldName} is required`;
7495
+ if (control.errors['minlength'])
7496
+ return `${fieldName} must be at least ${control.errors['minlength'].requiredLength} characters`;
7497
+ }
7498
+ return '';
7499
+ }
7500
+ 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 });
7501
+ 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.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"] }] });
7502
+ }
7503
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SearchReplaceModal, decorators: [{
7504
+ type: Component,
7505
+ 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.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" }]
7506
+ }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }], propDecorators: { config: [{
7507
+ type: Input
7508
+ }] } });
7509
+
7510
+ class SamplePoolDetailsModal {
7511
+ activeModal;
7512
+ pool;
7513
+ constructor(activeModal) {
7514
+ this.activeModal = activeModal;
7515
+ }
7516
+ onClose() {
7517
+ this.activeModal.close();
7518
+ }
7519
+ get title() {
7520
+ return this.pool?.poolName || 'Sample Pool Details';
7521
+ }
7522
+ get poolTypeLabel() {
7523
+ return this.pool?.isReference ? 'Reference Pool' : 'Sample Pool';
7524
+ }
7525
+ get poolTypeClass() {
7526
+ return this.pool?.isReference ? 'badge bg-info' : 'badge bg-primary';
7527
+ }
7528
+ get pooledOnlySamplesText() {
7529
+ if (!this.pool?.pooledOnlySamples || this.pool.pooledOnlySamples.length === 0) {
7530
+ return 'None';
7531
+ }
7532
+ return this.pool.pooledOnlySamples.join(', ');
7533
+ }
7534
+ get pooledAndIndependentSamplesText() {
7535
+ if (!this.pool?.pooledAndIndependentSamples || this.pool.pooledAndIndependentSamples.length === 0) {
7536
+ return 'None';
7537
+ }
7538
+ return this.pool.pooledAndIndependentSamples.join(', ');
7539
+ }
7540
+ get allSamplesCount() {
7541
+ const pooledOnly = this.pool?.pooledOnlySamples?.length || 0;
7542
+ const pooledAndIndependent = this.pool?.pooledAndIndependentSamples?.length || 0;
7543
+ return pooledOnly + pooledAndIndependent;
7544
+ }
7545
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolDetailsModal, deps: [{ token: i2.NgbActiveModal }], target: i0.ɵɵFactoryTarget.Component });
7546
+ 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.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" }] });
7547
+ }
7548
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolDetailsModal, decorators: [{
7549
+ type: Component,
7550
+ 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.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>" }]
7551
+ }], ctorParameters: () => [{ type: i2.NgbActiveModal }], propDecorators: { pool: [{
7552
+ type: Input
7553
+ }] } });
7554
+
7555
+ class SamplePoolEditModal {
7556
+ pool;
7557
+ maxSampleCount = 100;
7558
+ poolSaved = new EventEmitter();
7559
+ editForm;
7560
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
7561
+ fb = inject(FormBuilder);
7562
+ activeModal = inject(NgbActiveModal);
7563
+ samplePoolService = inject(SamplePoolService);
7564
+ constructor() {
7565
+ this.editForm = this.fb.group({
7566
+ poolName: ['', [Validators.required, Validators.maxLength(255)]],
7567
+ poolDescription: [''],
7568
+ isReference: [false],
7569
+ pooledOnlySamplesText: [''],
7570
+ pooledAndIndependentSamplesText: ['']
7571
+ });
7572
+ }
7573
+ ngOnInit() {
7574
+ if (this.pool) {
7575
+ this.editForm.patchValue({
7576
+ poolName: this.pool.poolName,
7577
+ poolDescription: this.pool.poolDescription || '',
7578
+ isReference: this.pool.isReference,
7579
+ pooledOnlySamplesText: this.pool.pooledOnlySamples?.join(', ') || '',
7580
+ pooledAndIndependentSamplesText: this.pool.pooledAndIndependentSamples?.join(', ') || ''
7581
+ });
7582
+ }
7583
+ }
7584
+ onSubmit() {
7585
+ if (this.editForm.valid && this.pool.id) {
7586
+ this.isLoading.set(true);
7587
+ const formValue = this.editForm.value;
7588
+ // Parse sample arrays from text input
7589
+ const pooledOnlySamples = this.parseSampleNumbers(formValue.pooledOnlySamplesText);
7590
+ const pooledAndIndependentSamples = this.parseSampleNumbers(formValue.pooledAndIndependentSamplesText);
7591
+ const updateData = {
7592
+ poolName: formValue.poolName.trim(),
7593
+ poolDescription: formValue.poolDescription?.trim() || undefined,
7594
+ isReference: formValue.isReference,
7595
+ pooledOnlySamples: pooledOnlySamples,
7596
+ pooledAndIndependentSamples: pooledAndIndependentSamples
7597
+ };
7598
+ this.samplePoolService.updateSamplePool(this.pool.id, updateData).subscribe({
7599
+ next: (updatedPool) => {
7600
+ this.poolSaved.emit(updatedPool);
7601
+ this.activeModal.close(updatedPool);
7602
+ },
7603
+ error: (error) => {
7604
+ console.error('Error updating sample pool:', error);
7605
+ this.isLoading.set(false);
7606
+ }
7607
+ });
7608
+ }
7609
+ }
7610
+ onCancel() {
7611
+ this.activeModal.dismiss();
7612
+ }
7613
+ get title() {
7614
+ return `Edit ${this.pool?.poolName || 'Sample Pool'}`;
7615
+ }
7616
+ get hasChanges() {
7617
+ if (!this.pool)
7618
+ return false;
7619
+ const formValue = this.editForm.value;
7620
+ return (formValue.poolName !== this.pool.poolName ||
7621
+ formValue.poolDescription !== (this.pool.poolDescription || '') ||
7622
+ formValue.isReference !== this.pool.isReference ||
7623
+ formValue.pooledOnlySamplesText !== (this.pool.pooledOnlySamples?.join(', ') || '') ||
7624
+ formValue.pooledAndIndependentSamplesText !== (this.pool.pooledAndIndependentSamples?.join(', ') || ''));
7625
+ }
7626
+ parseSampleNumbers(text) {
7627
+ if (!text?.trim())
7628
+ return [];
7629
+ const results = [];
7630
+ text
7631
+ .split(',')
7632
+ .map(s => s.trim())
7633
+ .filter(s => s.length > 0)
7634
+ .forEach(s => {
7635
+ // Check if it's a range (e.g., "9-12")
7636
+ if (s.includes('-')) {
7637
+ const parts = s.split('-').map(p => p.trim());
7638
+ if (parts.length === 2) {
7639
+ const start = parseInt(parts[0], 10);
7640
+ const end = parseInt(parts[1], 10);
7641
+ if (!isNaN(start) && !isNaN(end) && start > 0 && end > 0 && start <= end && end <= this.maxSampleCount) {
7642
+ // Add all numbers in the range
7643
+ for (let i = start; i <= end; i++) {
7644
+ results.push(i);
7645
+ }
7646
+ }
7647
+ }
7648
+ }
7649
+ else {
7650
+ // Single number
7651
+ const num = parseInt(s, 10);
7652
+ if (!isNaN(num) && num > 0 && num <= this.maxSampleCount) {
7653
+ results.push(num);
7654
+ }
7655
+ }
7656
+ });
7657
+ // Remove duplicates and sort
7658
+ return [...new Set(results)].sort((a, b) => a - b);
7659
+ }
7660
+ // Form validation helpers
7661
+ isFieldInvalid(fieldName) {
7662
+ const field = this.editForm.get(fieldName);
7663
+ return !!(field && field.invalid && field.touched);
7664
+ }
7665
+ getFieldError(fieldName) {
7666
+ const field = this.editForm.get(fieldName);
7667
+ if (!field || !field.errors)
7668
+ return '';
7669
+ if (field.errors['required']) {
7670
+ return `${this.getFieldDisplayName(fieldName)} is required`;
7671
+ }
7672
+ if (field.errors['maxlength']) {
7673
+ return `${this.getFieldDisplayName(fieldName)} must be less than ${field.errors['maxlength'].requiredLength} characters`;
7674
+ }
7675
+ return 'Invalid value';
7676
+ }
7677
+ getFieldDisplayName(fieldName) {
7678
+ const displayNames = {
7679
+ 'poolName': 'Pool name',
7680
+ 'poolDescription': 'Description',
7681
+ 'pooledOnlySamplesText': 'Pooled only samples',
7682
+ 'pooledAndIndependentSamplesText': 'Pooled and independent samples'
7683
+ };
7684
+ return displayNames[fieldName] || fieldName;
7685
+ }
7686
+ // Helper methods for template
7687
+ get pooledOnlySamplesCount() {
7688
+ const text = this.editForm.get('pooledOnlySamplesText')?.value || '';
7689
+ return this.parseSampleNumbers(text).length;
7690
+ }
7691
+ get pooledAndIndependentSamplesCount() {
7692
+ const text = this.editForm.get('pooledAndIndependentSamplesText')?.value || '';
7693
+ return this.parseSampleNumbers(text).length;
7694
+ }
7695
+ get totalSamplesCount() {
7696
+ return this.pooledOnlySamplesCount + this.pooledAndIndependentSamplesCount;
7697
+ }
7698
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolEditModal, deps: [], target: i0.ɵɵFactoryTarget.Component });
7699
+ 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 }] });
7700
+ }
7701
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolEditModal, decorators: [{
7702
+ type: Component,
7703
+ 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>" }]
7704
+ }], ctorParameters: () => [], propDecorators: { pool: [{
7705
+ type: Input
7706
+ }], maxSampleCount: [{
7707
+ type: Input
7708
+ }], poolSaved: [{
7709
+ type: Output
7710
+ }] } });
7711
+
7712
+ class SamplePoolCreateModal {
7713
+ fb;
7714
+ activeModal;
7715
+ samplePoolService;
7716
+ toastService;
7717
+ metadataTable;
7718
+ poolCreated = new EventEmitter();
7719
+ // Expose Math to template
7720
+ Math = Math;
7721
+ createForm;
7722
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
7723
+ // Sample selection management
7724
+ samples = signal([], ...(ngDevMode ? [{ debugName: "samples" }] : []));
7725
+ pooledOnlySamples = signal([], ...(ngDevMode ? [{ debugName: "pooledOnlySamples" }] : []));
7726
+ pooledAndIndependentSamples = signal([], ...(ngDevMode ? [{ debugName: "pooledAndIndependentSamples" }] : []));
7727
+ // UI state
7728
+ selectionMode = signal('manual', ...(ngDevMode ? [{ debugName: "selectionMode" }] : []));
7729
+ currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
7730
+ samplesPerPage = 20;
7731
+ constructor(fb, activeModal, samplePoolService, toastService) {
7732
+ this.fb = fb;
7733
+ this.activeModal = activeModal;
7734
+ this.samplePoolService = samplePoolService;
7735
+ this.toastService = toastService;
7736
+ this.createForm = this.fb.group({
7737
+ poolName: ['', [Validators.required, Validators.maxLength(255)]],
7738
+ poolDescription: [''],
7739
+ isReference: [false],
7740
+ range_start: [1, [Validators.min(1)]],
7741
+ range_end: [10, [Validators.min(1)]],
7742
+ pooled_only_samples_text: [''],
7743
+ pooled_and_independent_samples_text: ['']
7744
+ });
7745
+ }
7746
+ ngOnInit() {
7747
+ if (!this.metadataTable) {
7748
+ this.activeModal.dismiss('No metadata table provided');
7749
+ return;
7750
+ }
7751
+ // Initialize sample list
7752
+ this.initializeSamples();
7753
+ // Update range end validator based on sample count
7754
+ const rangeEndControl = this.createForm.get('range_end');
7755
+ if (rangeEndControl) {
7756
+ rangeEndControl.setValidators([
7757
+ Validators.min(1),
7758
+ Validators.max(this.metadataTable.sampleCount)
7759
+ ]);
7760
+ rangeEndControl.updateValueAndValidity();
7761
+ }
7762
+ const rangeStartControl = this.createForm.get('range_start');
7763
+ if (rangeStartControl) {
7764
+ rangeStartControl.setValidators([
7765
+ Validators.min(1),
7766
+ Validators.max(this.metadataTable.sampleCount)
7767
+ ]);
7768
+ rangeStartControl.updateValueAndValidity();
7769
+ }
7770
+ }
7771
+ initializeSamples() {
7772
+ const sampleItems = [];
7773
+ const existingPooledSamples = this.getExistingPooledSamples();
7774
+ for (let i = 1; i <= this.metadataTable.sampleCount; i++) {
7775
+ sampleItems.push({
7776
+ sampleNumber: i,
7777
+ selected: false,
7778
+ pooled: existingPooledSamples.includes(i)
7779
+ });
7780
+ }
7781
+ this.samples.set(sampleItems);
7782
+ }
7783
+ getExistingPooledSamples() {
7784
+ if (!this.metadataTable.samplePools)
7785
+ return [];
7786
+ const pooledSamples = [];
7787
+ this.metadataTable.samplePools.forEach((pool) => {
7788
+ pooledSamples.push(...pool.pooled_only_samples || []);
7789
+ pooledSamples.push(...pool.pooled_and_independent_samples || []);
7790
+ });
7791
+ return [...new Set(pooledSamples)]; // Remove duplicates
7792
+ }
7793
+ // Sample selection methods
7794
+ toggleSampleSelection(sampleNumber) {
7795
+ const samples = this.samples();
7796
+ const sampleIndex = samples.findIndex(s => s.sampleNumber === sampleNumber);
7797
+ if (sampleIndex >= 0) {
7798
+ samples[sampleIndex].selected = !samples[sampleIndex].selected;
7799
+ this.samples.set([...samples]);
7800
+ this.updateSampleTextInputs();
7801
+ }
7802
+ }
7803
+ selectSamplesInRange() {
7804
+ const start = this.createForm.get('range_start')?.value || 1;
7805
+ const end = this.createForm.get('range_end')?.value || 1;
7806
+ if (start > end) {
7807
+ this.toastService.error('Start sample must be less than or equal to end sample');
7808
+ return;
7809
+ }
7810
+ const samples = this.samples();
7811
+ for (let i = start; i <= end; i++) {
7812
+ const sampleIndex = samples.findIndex(s => s.sampleNumber === i);
7813
+ if (sampleIndex >= 0) {
7814
+ samples[sampleIndex].selected = true;
7815
+ }
7816
+ }
7817
+ this.samples.set([...samples]);
7818
+ this.updateSampleTextInputs();
7819
+ this.toastService.success(`Selected samples ${start} to ${end}`);
7820
+ }
7821
+ clearAllSelections() {
7822
+ const samples = this.samples();
7823
+ samples.forEach(s => s.selected = false);
7824
+ this.samples.set([...samples]);
7825
+ this.updateSampleTextInputs();
7826
+ }
7827
+ updateSampleTextInputs() {
7828
+ const selectedSamples = this.samples().filter(s => s.selected).map(s => s.sampleNumber);
7829
+ const pooledOnly = this.pooledOnlySamples();
7830
+ const pooledAndIndependent = this.pooledAndIndependentSamples();
7831
+ // For now, put all selected samples in pooled_only
7832
+ this.createForm.patchValue({
7833
+ pooled_only_samples_text: selectedSamples.join(', '),
7834
+ pooled_and_independent_samples_text: pooledAndIndependent.join(', ')
7835
+ });
7836
+ }
7837
+ // Pool type assignment
7838
+ assignToPooledOnly() {
7839
+ const selectedSamples = this.samples().filter(s => s.selected).map(s => s.sampleNumber);
7840
+ this.pooledOnlySamples.set(selectedSamples);
7841
+ this.updateSampleTextInputs();
7842
+ }
7843
+ assignToPooledAndIndependent() {
7844
+ const selectedSamples = this.samples().filter(s => s.selected).map(s => s.sampleNumber);
7845
+ this.pooledAndIndependentSamples.set(selectedSamples);
7846
+ this.updateSampleTextInputs();
7847
+ }
7848
+ onSubmit() {
7849
+ if (this.createForm.valid) {
7850
+ this.isLoading.set(true);
7851
+ const formValue = this.createForm.value;
7852
+ // Parse sample arrays from text input or use current assignments
7853
+ const pooledOnlySamples = this.parseSampleNumbers(formValue.pooled_only_samples_text);
7854
+ const pooledAndIndependentSamples = this.parseSampleNumbers(formValue.pooled_and_independent_samples_text);
7855
+ if (pooledOnlySamples.length === 0 && pooledAndIndependentSamples.length === 0) {
7856
+ this.toastService.error('Please select at least one sample for the pool');
7857
+ this.isLoading.set(false);
7858
+ return;
7859
+ }
7860
+ const createData = {
7861
+ poolName: formValue.poolName.trim(),
7862
+ poolDescription: formValue.poolDescription?.trim() || undefined,
7863
+ isReference: formValue.isReference,
7864
+ pooledOnlySamples: pooledOnlySamples,
7865
+ pooledAndIndependentSamples: pooledAndIndependentSamples,
7866
+ metadataTable: this.metadataTable.id
7867
+ };
7868
+ this.samplePoolService.createSamplePool(createData).subscribe({
7869
+ next: (createdPool) => {
7870
+ this.toastService.success(`Sample pool "${createdPool.poolName}" created successfully`);
7871
+ this.poolCreated.emit(createdPool);
7872
+ this.activeModal.close(createdPool);
7873
+ },
7874
+ error: (error) => {
7875
+ console.error('Error creating sample pool:', error);
7876
+ this.toastService.error('Failed to create sample pool');
7877
+ this.isLoading.set(false);
7878
+ }
7879
+ });
7880
+ }
7881
+ }
7882
+ onCancel() {
7883
+ this.activeModal.dismiss();
7884
+ }
7885
+ // Helper methods
7886
+ parseSampleNumbers(text) {
7887
+ if (!text?.trim())
7888
+ return [];
7889
+ const numbers = [];
7890
+ const parts = text.split(',').map(s => s.trim()).filter(s => s.length > 0);
7891
+ parts.forEach(part => {
7892
+ if (part.includes('-')) {
7893
+ // Handle range like "5-8"
7894
+ const [start, end] = part.split('-').map(s => parseInt(s.trim(), 10));
7895
+ if (!isNaN(start) && !isNaN(end) && start <= end) {
7896
+ for (let i = start; i <= end; i++) {
7897
+ if (i > 0 && i <= this.metadataTable.sampleCount) {
7898
+ numbers.push(i);
7899
+ }
7900
+ }
7901
+ }
7902
+ }
7903
+ else {
7904
+ // Handle single number
7905
+ const num = parseInt(part, 10);
7906
+ if (!isNaN(num) && num > 0 && num <= this.metadataTable.sampleCount) {
7907
+ numbers.push(num);
7908
+ }
7909
+ }
7910
+ });
7911
+ return [...new Set(numbers)].sort((a, b) => a - b); // Remove duplicates and sort
7912
+ }
7913
+ // Computed properties for template
7914
+ get selectedSamplesCount() {
7915
+ return this.samples().filter(s => s.selected).length;
7916
+ }
7917
+ get totalPooledSamplesCount() {
7918
+ const pooledOnly = this.parseSampleNumbers(this.createForm.get('pooled_only_samples_text')?.value || '');
7919
+ const pooledAndIndependent = this.parseSampleNumbers(this.createForm.get('pooled_and_independent_samples_text')?.value || '');
7920
+ return pooledOnly.length + pooledAndIndependent.length;
7921
+ }
7922
+ get paginatedSamples() {
7923
+ const samples = this.samples();
7924
+ const start = (this.currentPage() - 1) * this.samplesPerPage;
7925
+ return samples.slice(start, start + this.samplesPerPage);
7926
+ }
7927
+ get totalPages() {
7928
+ return Math.ceil(this.samples().length / this.samplesPerPage);
7929
+ }
7930
+ // Form validation helpers
7931
+ isFieldInvalid(fieldName) {
7932
+ const field = this.createForm.get(fieldName);
7933
+ return !!(field && field.invalid && field.touched);
7934
+ }
7935
+ getFieldError(fieldName) {
7936
+ const field = this.createForm.get(fieldName);
7937
+ if (!field || !field.errors)
7938
+ return '';
7939
+ if (field.errors['required']) {
7940
+ return `${this.getFieldDisplayName(fieldName)} is required`;
7941
+ }
7942
+ if (field.errors['maxlength']) {
7943
+ return `${this.getFieldDisplayName(fieldName)} must be less than ${field.errors['maxlength'].requiredLength} characters`;
7944
+ }
7945
+ if (field.errors['min']) {
7946
+ return `${this.getFieldDisplayName(fieldName)} must be at least ${field.errors['min'].min}`;
7947
+ }
7948
+ if (field.errors['max']) {
7949
+ return `${this.getFieldDisplayName(fieldName)} must be no more than ${field.errors['max'].max}`;
7950
+ }
7951
+ return 'Invalid value';
7952
+ }
7953
+ getFieldDisplayName(fieldName) {
7954
+ const displayNames = {
7955
+ 'poolName': 'Pool name',
7956
+ 'poolDescription': 'Description',
7957
+ 'range_start': 'Range start',
7958
+ 'range_end': 'Range end'
7959
+ };
7960
+ return displayNames[fieldName] || fieldName;
7961
+ }
7962
+ // Pagination
7963
+ goToPage(page) {
7964
+ if (page >= 1 && page <= this.totalPages) {
7965
+ this.currentPage.set(page);
7966
+ }
7967
+ }
7968
+ previousPage() {
7969
+ if (this.currentPage() > 1) {
7970
+ this.currentPage.set(this.currentPage() - 1);
7971
+ }
7972
+ }
7973
+ nextPage() {
7974
+ if (this.currentPage() < this.totalPages) {
7975
+ this.currentPage.set(this.currentPage() + 1);
7976
+ }
7977
+ }
7978
+ 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 });
7979
+ 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 Grid for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"card mb-3\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-grid me-2\"></i>Select Samples \n <span class=\"badge bg-info ms-2\">{{ selectedSamplesCount }} selected</span>\n </span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"clearAllSelections()\">\n <i class=\"bi bi-x-circle me-1\"></i>Clear All\n </button>\n </div>\n <div class=\"card-body\" style=\"max-height: 300px; overflow-y: auto;\">\n <!-- Sample Grid -->\n <div class=\"row g-1 mb-3\">\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <div class=\"col-2\">\n <button \n type=\"button\" \n class=\"btn btn-sm w-100\"\n [class.btn-primary]=\"sample.selected && !sample.pooled\"\n [class.btn-success]=\"sample.selected && sample.pooled\"\n [class.btn-outline-secondary]=\"!sample.selected && !sample.pooled\"\n [class.btn-outline-warning]=\"!sample.selected && sample.pooled\"\n [disabled]=\"sample.pooled\"\n (click)=\"toggleSampleSelection(sample.sampleNumber)\"\n [title]=\"sample.pooled ? 'Sample already in another pool' : 'Click to select/deselect'\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1\"></i>\n }\n </button>\n </div>\n }\n </div>\n\n <!-- Pagination -->\n @if (totalPages > 1) {\n <nav>\n <ul class=\"pagination pagination-sm justify-content-center\">\n <li class=\"page-item\" [class.disabled]=\"currentPage() === 1\">\n <button class=\"page-link\" type=\"button\" (click)=\"previousPage()\">Previous</button>\n </li>\n @for (page of [].constructor(Math.min(5, totalPages)); track $index) {\n @let pageNum = $index + Math.max(1, currentPage() - 2);\n @if (pageNum <= totalPages) {\n <li class=\"page-item\" [class.active]=\"pageNum === currentPage()\">\n <button class=\"page-link\" type=\"button\" (click)=\"goToPage(pageNum)\">{{ pageNum }}</button>\n </li>\n }\n }\n <li class=\"page-item\" [class.disabled]=\"currentPage() === totalPages\">\n <button class=\"page-link\" type=\"button\" (click)=\"nextPage()\">Next</button>\n </li>\n </ul>\n </nav>\n }\n\n <div class=\"form-text text-center\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }} - \n {{ Math.min(currentPage() * samplesPerPage, samples().length) }} \n of {{ samples().length }} samples\n </div>\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 }] });
7980
+ }
7981
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: SamplePoolCreateModal, decorators: [{
7982
+ type: Component,
7983
+ 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 Grid for Manual Selection -->\n @if (selectionMode() === 'manual') {\n <div class=\"card mb-3\">\n <div class=\"card-header d-flex justify-content-between align-items-center\">\n <span>\n <i class=\"bi bi-grid me-2\"></i>Select Samples \n <span class=\"badge bg-info ms-2\">{{ selectedSamplesCount }} selected</span>\n </span>\n <button type=\"button\" class=\"btn btn-sm btn-outline-danger\" (click)=\"clearAllSelections()\">\n <i class=\"bi bi-x-circle me-1\"></i>Clear All\n </button>\n </div>\n <div class=\"card-body\" style=\"max-height: 300px; overflow-y: auto;\">\n <!-- Sample Grid -->\n <div class=\"row g-1 mb-3\">\n @for (sample of paginatedSamples; track sample.sampleNumber) {\n <div class=\"col-2\">\n <button \n type=\"button\" \n class=\"btn btn-sm w-100\"\n [class.btn-primary]=\"sample.selected && !sample.pooled\"\n [class.btn-success]=\"sample.selected && sample.pooled\"\n [class.btn-outline-secondary]=\"!sample.selected && !sample.pooled\"\n [class.btn-outline-warning]=\"!sample.selected && sample.pooled\"\n [disabled]=\"sample.pooled\"\n (click)=\"toggleSampleSelection(sample.sampleNumber)\"\n [title]=\"sample.pooled ? 'Sample already in another pool' : 'Click to select/deselect'\">\n {{ sample.sampleNumber }}\n @if (sample.pooled) {\n <i class=\"bi bi-lock-fill ms-1\"></i>\n }\n </button>\n </div>\n }\n </div>\n\n <!-- Pagination -->\n @if (totalPages > 1) {\n <nav>\n <ul class=\"pagination pagination-sm justify-content-center\">\n <li class=\"page-item\" [class.disabled]=\"currentPage() === 1\">\n <button class=\"page-link\" type=\"button\" (click)=\"previousPage()\">Previous</button>\n </li>\n @for (page of [].constructor(Math.min(5, totalPages)); track $index) {\n @let pageNum = $index + Math.max(1, currentPage() - 2);\n @if (pageNum <= totalPages) {\n <li class=\"page-item\" [class.active]=\"pageNum === currentPage()\">\n <button class=\"page-link\" type=\"button\" (click)=\"goToPage(pageNum)\">{{ pageNum }}</button>\n </li>\n }\n }\n <li class=\"page-item\" [class.disabled]=\"currentPage() === totalPages\">\n <button class=\"page-link\" type=\"button\" (click)=\"nextPage()\">Next</button>\n </li>\n </ul>\n </nav>\n }\n\n <div class=\"form-text text-center\">\n Showing {{ ((currentPage() - 1) * samplesPerPage) + 1 }} - \n {{ Math.min(currentPage() * samplesPerPage, samples().length) }} \n of {{ samples().length }} samples\n </div>\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" }]
7984
+ }], ctorParameters: () => [{ type: i1.FormBuilder }, { type: i2.NgbActiveModal }, { type: SamplePoolService }, { type: i3.ToastService }], propDecorators: { metadataTable: [{
7985
+ type: Input
7986
+ }], poolCreated: [{
7987
+ type: Output
7988
+ }] } });
7989
+
7990
+ class MetadataTableDetails {
7991
+ route;
7992
+ router;
7993
+ metadataTableService;
7994
+ asyncExportService;
7995
+ chunkedUploadService;
7996
+ samplePoolService;
7997
+ metadataColumnService;
7998
+ toastService;
7999
+ asyncTaskService;
8000
+ modalService;
8001
+ destroy$ = new Subject();
8002
+ fileInput;
8003
+ excelInput;
8004
+ Math = Math;
8005
+ onColumnFilterChange(value) {
8006
+ this.columnFilter.set(value);
8007
+ }
8008
+ clearColumnFilter() {
8009
+ this.columnFilter.set('');
8010
+ }
8011
+ get filteredColumnCount() {
8012
+ return this.sortedColumns().length;
8013
+ }
8014
+ get totalColumnCount() {
8015
+ const columns = this.table()?.columns || [];
8016
+ return columns.filter(col => col && col.name).length;
8017
+ }
8018
+ sortBy(field) {
8019
+ const currentField = this.sortField();
8020
+ const currentDirection = this.sortDirection();
8021
+ if (currentField === field) {
8022
+ this.sortDirection.set(currentDirection === 'asc' ? 'desc' : 'asc');
8023
+ }
8024
+ else {
8025
+ this.sortField.set(field);
8026
+ this.sortDirection.set('asc');
8027
+ }
8028
+ }
8029
+ getSortIcon(field) {
8030
+ const currentField = this.sortField();
8031
+ const currentDirection = this.sortDirection();
8032
+ if (currentField !== field) {
8033
+ return 'bi-arrow-down-up';
8034
+ }
8035
+ return currentDirection === 'asc' ? 'bi-sort-alpha-down' : 'bi-sort-alpha-up';
8036
+ }
8037
+ isSorted(field) {
8038
+ return this.sortField() === field;
8039
+ }
8040
+ isLoading = signal(false, ...(ngDevMode ? [{ debugName: "isLoading" }] : []));
8041
+ table = signal(null, ...(ngDevMode ? [{ debugName: "table" }] : []));
8042
+ tableId = signal(null, ...(ngDevMode ? [{ debugName: "tableId" }] : []));
8043
+ viewMode = signal('table', ...(ngDevMode ? [{ debugName: "viewMode" }] : []));
8044
+ currentPage = signal(1, ...(ngDevMode ? [{ debugName: "currentPage" }] : []));
8045
+ pageSize = signal(10, ...(ngDevMode ? [{ debugName: "pageSize" }] : []));
8046
+ columnFilter = signal('', ...(ngDevMode ? [{ debugName: "columnFilter" }] : []));
8047
+ sortField = signal('', ...(ngDevMode ? [{ debugName: "sortField" }] : []));
8048
+ sortDirection = signal('asc', ...(ngDevMode ? [{ debugName: "sortDirection" }] : []));
8049
+ hasColumns = computed(() => {
8050
+ const table = this.table();
8051
+ return table?.columns && table.columns.length > 0;
8052
+ }, ...(ngDevMode ? [{ debugName: "hasColumns" }] : []));
8053
+ hasPools = computed(() => {
8054
+ const table = this.table();
8055
+ return table?.samplePools && table.samplePools.length > 0;
8056
+ }, ...(ngDevMode ? [{ debugName: "hasPools" }] : []));
8057
+ sortedColumns = computed(() => {
8058
+ const columns = this.table()?.columns || [];
8059
+ const filter = this.columnFilter().toLowerCase();
8060
+ return [...columns]
8061
+ .filter(col => col && col.name)
8062
+ .filter(col => !filter || col.name.toLowerCase().includes(filter))
8063
+ .sort((a, b) => (a.columnPosition || 0) - (b.columnPosition || 0));
8064
+ }, ...(ngDevMode ? [{ debugName: "sortedColumns" }] : []));
8065
+ dragColumnsForList = signal([], ...(ngDevMode ? [{ debugName: "dragColumnsForList" }] : []));
8066
+ sortedPools = computed(() => {
8067
+ const pools = this.table()?.samplePools || [];
8068
+ return [...pools].sort((a, b) => a.poolName.localeCompare(b.poolName));
8069
+ }, ...(ngDevMode ? [{ debugName: "sortedPools" }] : []));
8070
+ tableRows = computed(() => {
8071
+ const table = this.table();
8072
+ if (!table || !this.hasColumns())
8073
+ return [];
8074
+ const rows = [];
8075
+ for (let i = 0; i < table.sampleCount; i++) {
8076
+ const sampleIndex = i + 1;
8077
+ const row = { _sampleIndex: sampleIndex };
8078
+ this.sortedColumns().forEach(column => {
8079
+ const value = this.getSampleColumnValue(column, sampleIndex);
8080
+ row[`col_${column.id}`] = value;
8081
+ });
8082
+ rows.push(row);
8083
+ }
8084
+ return rows;
8085
+ }, ...(ngDevMode ? [{ debugName: "tableRows" }] : []));
8086
+ poolTableRows = computed(() => {
8087
+ const table = this.table();
8088
+ if (!table || !this.hasPools() || !this.hasColumns())
8089
+ return [];
8090
+ const rows = [];
8091
+ this.sortedPools().forEach(pool => {
8092
+ const row = {
8093
+ _poolName: pool.poolName,
8094
+ _poolId: pool.id,
8095
+ _poolData: pool
8096
+ };
8097
+ this.sortedColumns().forEach(column => {
8098
+ let value = '';
8099
+ const poolColumn = pool.metadataColumns?.find((pc) => (pc.id && column.id && pc.id === column.id) || pc.name === column.name);
8100
+ if (poolColumn) {
8101
+ value = poolColumn.value || '';
8102
+ }
8103
+ row[`col_${column.id}`] = value;
8104
+ });
8105
+ rows.push(row);
8106
+ });
8107
+ return rows;
8108
+ }, ...(ngDevMode ? [{ debugName: "poolTableRows" }] : []));
8109
+ paginatedRows = computed(() => {
8110
+ const rows = this.tableRows();
8111
+ const page = this.currentPage();
8112
+ const size = this.pageSize();
8113
+ const start = (page - 1) * size;
8114
+ const end = start + size;
8115
+ return rows.slice(start, end);
8116
+ }, ...(ngDevMode ? [{ debugName: "paginatedRows" }] : []));
8117
+ totalPages = computed(() => {
8118
+ const rows = this.tableRows();
8119
+ const size = this.pageSize();
8120
+ return Math.ceil(rows.length / size);
8121
+ }, ...(ngDevMode ? [{ debugName: "totalPages" }] : []));
8122
+ constructor(route, router, metadataTableService, asyncExportService, chunkedUploadService, samplePoolService, metadataColumnService, toastService, asyncTaskService, modalService) {
8123
+ this.route = route;
8124
+ this.router = router;
8125
+ this.metadataTableService = metadataTableService;
8126
+ this.asyncExportService = asyncExportService;
8127
+ this.chunkedUploadService = chunkedUploadService;
8128
+ this.samplePoolService = samplePoolService;
8129
+ this.metadataColumnService = metadataColumnService;
8130
+ this.toastService = toastService;
8131
+ this.asyncTaskService = asyncTaskService;
8132
+ this.modalService = modalService;
8133
+ }
8134
+ ngOnInit() {
8135
+ this.route.params.subscribe(params => {
8136
+ const id = Number(params['id']);
8137
+ const mode = params['mode'] || 'list';
8138
+ if (id && !isNaN(id)) {
8139
+ this.tableId.set(id);
8140
+ this.viewMode.set(mode);
8141
+ this.loadTable(id);
8142
+ this.setupAsyncTaskRefreshListener(id);
8143
+ }
8144
+ else {
8145
+ this.toastService.error('Invalid table ID');
8146
+ this.navigateBack();
8147
+ }
8148
+ });
8149
+ }
8150
+ ngOnDestroy() {
8151
+ this.destroy$.next();
8152
+ this.destroy$.complete();
8153
+ }
8154
+ setupAsyncTaskRefreshListener(tableId) {
8155
+ this.asyncTaskService.metadataTableRefresh$.pipe(takeUntil$1(this.destroy$)).subscribe((refreshedTableId) => {
8156
+ if (refreshedTableId === tableId) {
8157
+ console.log(`Import task completed for table ${refreshedTableId}, refreshing table details`);
8158
+ this.toastService.info('Refreshing table data after successful import');
8159
+ this.loadTable(tableId);
8160
+ }
8161
+ });
8162
+ }
8163
+ loadTable(id) {
8164
+ this.isLoading.set(true);
8165
+ this.metadataTableService.getMetadataTable(id).subscribe({
8166
+ next: (table) => {
8167
+ this.isLoading.set(false);
8168
+ this.table.set(table);
8169
+ const naturalOrder = [...(table.columns || [])].sort((a, b) => (a.columnPosition || 0) - (b.columnPosition || 0));
8170
+ this.dragColumnsForList.set(naturalOrder);
8171
+ },
8172
+ error: (error) => {
8173
+ this.isLoading.set(false);
8174
+ console.error('Error loading metadata table:', error);
8175
+ const errorMsg = error?.error?.detail || 'Table not found';
8176
+ this.toastService.error(errorMsg);
8177
+ if (error.status === 404) {
8178
+ this.navigateBack();
8179
+ }
8180
+ }
8181
+ });
8182
+ }
8183
+ navigateBack() {
8184
+ this.router.navigate(['/metadata-tables']);
8185
+ }
8186
+ switchToListMode() {
8187
+ const tableId = this.tableId();
8188
+ if (tableId) {
8189
+ this.router.navigate(['/metadata-tables', tableId, 'list']);
8190
+ }
8191
+ }
8192
+ switchToTableMode() {
8193
+ const tableId = this.tableId();
8194
+ if (tableId) {
8195
+ this.router.navigate(['/metadata-tables', tableId, 'table']);
8196
+ }
8197
+ }
8198
+ onPageChange(page) {
8199
+ this.currentPage.set(page);
8200
+ }
8201
+ onPageSizeChange(size) {
8202
+ this.pageSize.set(size);
8203
+ this.currentPage.set(1); // Reset to first page when changing page size
8204
+ }
8205
+ editTable() {
8206
+ const table = this.table();
8207
+ if (!table || !table.canEdit)
8208
+ return;
8209
+ const modalRef = this.modalService.open(MetadataTableEditModal, {
8210
+ size: 'lg',
8211
+ centered: true
8212
+ });
8213
+ modalRef.componentInstance.table = table;
8214
+ modalRef.componentInstance.tableSaved.subscribe((updatedTable) => {
8215
+ // Update the local table data
8216
+ this.table.set(updatedTable);
8217
+ this.toastService.success(`Table "${updatedTable.name}" updated successfully!`);
8218
+ });
8219
+ modalRef.result.catch(() => {
8220
+ // Modal was dismissed - no action needed
8221
+ });
8222
+ }
8223
+ triggerFileInput() {
8224
+ console.log('triggerFileInput called');
8225
+ this.fileInput.nativeElement.click();
8226
+ }
8227
+ triggerExcelInput() {
8228
+ console.log('triggerExcelInput called');
8229
+ this.excelInput.nativeElement.click();
8230
+ }
8231
+ importSdrf(event) {
8232
+ console.log('importSdrf called', event);
8233
+ const input = event.target;
8234
+ if (!input.files || input.files.length === 0) {
8235
+ console.log('No files selected');
8236
+ return;
8237
+ }
8238
+ const file = input.files[0];
8239
+ const table = this.table();
8240
+ if (!table)
8241
+ return;
8242
+ // Validate file type
8243
+ if (!file.name.toLowerCase().endsWith('.txt') && !file.name.toLowerCase().endsWith('.tsv')) {
8244
+ this.toastService.error('Please select a valid SDRF file (.txt or .tsv)');
8245
+ input.value = ''; // Reset input
8246
+ return;
8247
+ }
8248
+ if (confirm(`Import SDRF data into table "${table.name}"?\n\nThis will add new columns and may modify existing data.`)) {
8249
+ input.value = ''; // Reset input early
8250
+ // Use chunked upload service for import with progress tracking
8251
+ this.isLoading.set(true);
8252
+ console.log('Starting SDRF import using chunked upload:', {
8253
+ fileName: file.name,
8254
+ fileSize: file.size,
8255
+ metadataTableId: table.id
8256
+ });
8257
+ // Use chunked upload - it now triggers async import task on completion
8258
+ this.chunkedUploadService.uploadFileInChunks(file, 1024 * 1024, // 1MB chunks
8259
+ {
8260
+ metadataTableId: table.id,
8261
+ createPools: true,
8262
+ replaceExisting: false,
8263
+ onProgress: (progress) => {
8264
+ console.log(`SDRF upload progress: ${Math.round(progress)}%`);
8265
+ }
8266
+ }).subscribe({
8267
+ next: (result) => {
8268
+ this.isLoading.set(false);
8269
+ console.log('SDRF chunked upload completed:', result);
8270
+ // If a taskId is returned, monitor the async import task
8271
+ if (result?.taskId) {
8272
+ console.log('Starting async task monitoring for taskId:', result.taskId);
8273
+ this.toastService.success(`SDRF import task queued successfully! Task ID: ${result.taskId}`);
8274
+ // Mark task for monitoring and start real-time updates (same pattern as export)
8275
+ this.asyncTaskService.monitorTask(result.taskId);
8276
+ this.asyncTaskService.startRealtimeUpdates();
8277
+ }
8278
+ else {
8279
+ // Fallback for immediate processing (backward compatibility)
8280
+ this.toastService.success('SDRF file imported successfully!');
8281
+ this.loadTable(table.id);
8282
+ }
8283
+ },
8284
+ error: (error) => {
8285
+ this.isLoading.set(false);
8286
+ console.error('Error importing SDRF:', error);
8287
+ const errorMsg = error?.error?.detail || error?.error?.message || 'Failed to import SDRF file';
8288
+ this.toastService.error(errorMsg);
8289
+ }
8290
+ });
8291
+ }
8292
+ else {
8293
+ input.value = ''; // Reset input if cancelled
8294
+ }
8295
+ }
8296
+ importExcel(event) {
8297
+ const input = event.target;
8298
+ if (!input.files || input.files.length === 0)
8299
+ return;
8300
+ const file = input.files[0];
8301
+ const table = this.table();
8302
+ if (!table)
8303
+ return;
8304
+ // Validate file type
8305
+ if (!file.name.toLowerCase().endsWith('.xlsx') && !file.name.toLowerCase().endsWith('.xls')) {
8306
+ this.toastService.error('Please select a valid Excel file (.xlsx or .xls)');
8307
+ input.value = ''; // Reset input
8308
+ return;
8309
+ }
8310
+ if (confirm(`Import Excel data into table "${table.name}"?\n\nThis will update existing columns and may modify data.`)) {
8311
+ input.value = ''; // Reset input early
8312
+ // Use chunked upload service for import with progress tracking
8313
+ this.isLoading.set(true);
8314
+ console.log('Starting Excel import using chunked upload:', {
8315
+ fileName: file.name,
8316
+ fileSize: file.size,
8317
+ metadataTableId: table.id
8318
+ });
8319
+ // Use chunked upload - it now triggers async import task on completion
8320
+ this.chunkedUploadService.uploadFileInChunks(file, 1024 * 1024, // 1MB chunks
8321
+ {
8322
+ metadataTableId: table.id,
8323
+ createPools: true,
8324
+ replaceExisting: false,
8325
+ onProgress: (progress) => {
8326
+ console.log(`Excel upload progress: ${Math.round(progress)}%`);
8327
+ }
8328
+ }).subscribe({
8329
+ next: (result) => {
8330
+ this.isLoading.set(false);
8331
+ console.log('Excel chunked upload completed:', result);
8332
+ // If a taskId is returned, monitor the async import task
8333
+ if (result?.taskId) {
8334
+ console.log('Starting async task monitoring for taskId:', result.taskId);
8335
+ this.toastService.success(`Excel import task queued successfully! Task ID: ${result.taskId}`);
8336
+ // Mark task for monitoring and start real-time updates (same pattern as export)
8337
+ this.asyncTaskService.monitorTask(result.taskId);
8338
+ this.asyncTaskService.startRealtimeUpdates();
8339
+ }
8340
+ else {
8341
+ // Fallback for immediate processing (backward compatibility)
8342
+ this.toastService.success('Excel file imported successfully!');
8343
+ this.loadTable(table.id);
8344
+ }
8345
+ },
8346
+ error: (error) => {
8347
+ this.isLoading.set(false);
8348
+ console.error('Error importing Excel:', error);
8349
+ const errorMsg = error?.error?.detail || error?.error?.message || 'Failed to import Excel file';
8350
+ this.toastService.error(errorMsg);
8351
+ }
8352
+ });
8353
+ }
8354
+ else {
8355
+ input.value = ''; // Reset input if cancelled
8356
+ }
8357
+ }
8358
+ exportToSdrf(format = 'sdrf') {
8359
+ const table = this.table();
8360
+ if (!table || !table.columns || table.columns.length === 0) {
8361
+ this.toastService.error('No columns available for export');
8362
+ return;
8363
+ }
8364
+ if (format === 'excel') {
8365
+ // Show Excel export options modal
8366
+ const modalRef = this.modalService.open(ExcelExportModalComponent, {
8367
+ size: 'lg',
8368
+ backdrop: 'static'
8369
+ });
8370
+ modalRef.result.then((options) => {
8371
+ this.performExcelExport(table, options);
8372
+ }).catch(() => {
8373
+ // Modal was dismissed - no action needed
8374
+ });
8375
+ }
8376
+ else {
8377
+ // Direct SDRF export
8378
+ this.performSdrfExport(table);
8379
+ }
8380
+ }
8381
+ performExcelExport(table, options) {
8382
+ const columnIds = table.columns.map(col => col.id);
8383
+ // Prepare lab group IDs based on options
8384
+ let labGroupIds = undefined;
8385
+ if (options.includeLabGroups === 'selected') {
8386
+ labGroupIds = options.selectedLabGroupIds;
8387
+ }
8388
+ else if (options.includeLabGroups === 'all') {
8389
+ // Pass empty array to signal "include all lab groups"
8390
+ labGroupIds = [];
8391
+ }
8392
+ // For 'none', labGroupIds stays undefined
8393
+ // Use async export only
8394
+ this.asyncExportService.excelTemplate({
8395
+ metadataTableId: table.id,
8396
+ metadataColumnIds: columnIds,
8397
+ sampleNumber: table.sampleCount,
8398
+ exportFormat: 'excel',
8399
+ includePools: options.includePools,
8400
+ labGroupIds: labGroupIds
8401
+ }).subscribe({
8402
+ next: (response) => {
8403
+ this.toastService.success(`Excel export queued successfully! Task ID: ${response.taskId}`);
8404
+ // Start monitoring tasks if not already started
8405
+ this.asyncTaskService.startRealtimeUpdates();
8406
+ },
8407
+ error: (error) => {
8408
+ console.error('Error queuing Excel export:', error);
8409
+ const errorMsg = error?.error?.detail || error?.error?.message || 'Failed to queue Excel export';
8410
+ this.toastService.error(errorMsg);
8411
+ }
8412
+ });
8413
+ }
8414
+ performSdrfExport(table) {
8415
+ const columnIds = table.columns.map(col => col.id);
8416
+ // Use async export only
8417
+ this.asyncExportService.sdrfFile({
8418
+ metadataTableId: table.id,
8419
+ metadataColumnIds: columnIds,
8420
+ sampleNumber: table.sampleCount,
8421
+ exportFormat: 'sdrf',
8422
+ includePools: true
8423
+ }).subscribe({
8424
+ next: (response) => {
8425
+ this.toastService.success(`SDRF export queued successfully! Task ID: ${response.taskId}`);
8426
+ // Start monitoring tasks if not already started
8427
+ this.asyncTaskService.startRealtimeUpdates();
8428
+ },
8429
+ error: (error) => {
8430
+ console.error('Error queuing SDRF export:', error);
8431
+ const errorMsg = error?.error?.detail || error?.error?.message || 'Failed to queue SDRF export';
8432
+ this.toastService.error(errorMsg);
8433
+ }
8434
+ });
8435
+ }
8436
+ handleExportRequest(exportRequest, format) {
8437
+ exportRequest.subscribe({
8438
+ next: (response) => {
8439
+ this.isLoading.set(false);
8440
+ this.toastService.success(`${format.toUpperCase()} export task queued successfully! Task ID: ${response.taskId}`);
8441
+ // Start monitoring tasks if not already started
8442
+ this.asyncTaskService.startRealtimeUpdates();
8443
+ },
8444
+ error: (error) => {
8445
+ this.isLoading.set(false);
8446
+ console.error('Error exporting table:', error);
8447
+ const errorMsg = error?.error?.detail || 'Failed to export table';
8448
+ this.toastService.error(errorMsg);
8449
+ }
8450
+ });
8451
+ }
8452
+ deleteTable() {
8453
+ const table = this.table();
8454
+ if (!table)
8455
+ return;
8456
+ const confirmMessage = `Are you sure you want to delete the table "${table.name}"?\n\nThis action cannot be undone.`;
8457
+ if (confirm(confirmMessage)) {
8458
+ this.isLoading.set(true);
8459
+ this.metadataTableService.deleteMetadataTable(table.id).subscribe({
8460
+ next: () => {
8461
+ this.toastService.success(`Table "${table.name}" deleted successfully!`);
8462
+ this.navigateBack();
8463
+ },
8464
+ error: (error) => {
8465
+ this.isLoading.set(false);
8466
+ console.error('Error deleting metadata table:', error);
8467
+ const errorMsg = error?.error?.detail || error?.message || 'Failed to delete table. Please try again.';
8468
+ this.toastService.error(errorMsg);
8469
+ }
8470
+ });
8471
+ }
8472
+ }
8473
+ getTableStatusBadge() {
8474
+ const table = this.table();
8475
+ if (!table)
8476
+ return '';
8477
+ if (table.isPublished)
8478
+ return 'Published';
8479
+ if (table.isLocked)
8480
+ return 'Locked';
8481
+ return 'Draft';
8482
+ }
8483
+ getTableStatusClass() {
8484
+ const table = this.table();
8485
+ if (!table)
8486
+ return '';
8487
+ if (table.isPublished)
8488
+ return 'bg-success';
8489
+ if (table.isLocked)
8490
+ return 'bg-warning';
8491
+ return 'bg-secondary';
8492
+ }
8493
+ getColumnTypeIcon(column) {
8494
+ const type = column.type?.toLowerCase() || '';
8495
+ if (type.includes(ColumnType.CHARACTERISTICS))
8496
+ return 'bi-tags';
8497
+ if (type.includes(ColumnType.FACTOR_VALUE.replace('_', ' ')))
8498
+ return 'bi-sliders';
8499
+ if (type.includes(ColumnType.COMMENT))
8500
+ return 'bi-chat-left-text';
8501
+ if (type.includes(ColumnType.SOURCE_NAME.replace('_', ' ')))
8502
+ return 'bi-diagram-3';
8503
+ if (type === ColumnType.SPECIAL)
8504
+ return 'bi-star';
8505
+ return 'bi-circle';
8506
+ }
8507
+ getColumnTypeClass(column) {
8508
+ const type = column.type?.toLowerCase() || '';
8509
+ if (type.includes(ColumnType.CHARACTERISTICS))
8510
+ return 'text-primary';
8511
+ if (type.includes(ColumnType.FACTOR_VALUE.replace('_', ' ')))
8512
+ return 'text-success';
8513
+ if (type.includes(ColumnType.COMMENT))
8514
+ return 'text-info';
8515
+ if (type.includes(ColumnType.SOURCE_NAME.replace('_', ' ')))
8516
+ return 'text-warning';
8517
+ if (type === ColumnType.SPECIAL)
8518
+ return 'text-danger';
8519
+ return 'text-muted';
8520
+ }
8521
+ getColumnHeaderClass(column) {
8522
+ let baseClass = '';
8523
+ const type = column.type?.toLowerCase() || '';
8524
+ if (type.includes(ColumnType.CHARACTERISTICS))
8525
+ baseClass = 'bg-primary-subtle text-primary-emphasis';
8526
+ else if (type.includes(ColumnType.FACTOR_VALUE.replace('_', ' ')))
8527
+ baseClass = 'bg-success-subtle text-success-emphasis';
8528
+ else if (type.includes(ColumnType.COMMENT))
8529
+ baseClass = 'bg-info-subtle text-info-emphasis';
8530
+ else if (type.includes(ColumnType.SOURCE_NAME.replace('_', ' ')))
8531
+ baseClass = 'bg-warning-subtle text-warning-emphasis';
8532
+ else if (type === ColumnType.SPECIAL)
8533
+ baseClass = 'bg-danger-subtle text-danger-emphasis';
8534
+ else
8535
+ baseClass = 'bg-body-secondary text-body';
8536
+ // Add hidden column styling
8537
+ if (column.hidden) {
8538
+ baseClass += ' opacity-50 text-decoration-line-through';
8539
+ }
8540
+ return baseClass;
8541
+ }
8542
+ getShortColumnName(column) {
8543
+ let name = column.name;
8544
+ // Handle different column types
8545
+ const type = column.type?.toLowerCase() || '';
8546
+ if (type.includes(ColumnType.CHARACTERISTICS)) {
8547
+ name = name.replace(/^characteristics\s*\[/i, '').replace(/\]$/, '');
8548
+ }
8549
+ else if (type.includes(ColumnType.FACTOR_VALUE.replace('_', ' '))) {
8550
+ name = name.replace(/^factor\s*value\s*\[/i, '').replace(/\]$/, '');
8551
+ }
8552
+ else if (type.includes(ColumnType.COMMENT)) {
8553
+ name = name.replace(/^comment\s*\[/i, '').replace(/\]$/, '');
8554
+ }
8555
+ else if (type === ColumnType.SPECIAL) {
8556
+ // Special columns have no prefix or brackets - use name as is
8557
+ return name;
8558
+ }
8559
+ // Remove any remaining square brackets for other types
8560
+ name = name.replace(/[\[\]]/g, '');
8561
+ // Clean up extra spaces
8562
+ name = name.trim();
8563
+ return name || column.name; // Fallback to original name if cleaning results in empty string
8564
+ }
8565
+ hasModifiers(column) {
8566
+ return !!(column.modifiers && Array.isArray(column.modifiers) && column.modifiers.length > 0);
8567
+ }
8568
+ getColumnValueDisplay(column) {
8569
+ const defaultValue = column.value || '';
8570
+ if (!this.hasModifiers(column)) {
8571
+ return defaultValue || 'No default value';
8572
+ }
8573
+ return defaultValue ? `Default: ${defaultValue}` : 'Default: (empty)';
8574
+ }
8575
+ getModifiersDisplay(column) {
8576
+ if (!this.hasModifiers(column)) {
8577
+ return [];
8578
+ }
8579
+ return column.modifiers.map(modifier => `Samples ${modifier.samples}: "${modifier.value}"`);
8580
+ }
8581
+ // Sample Pool Methods
8582
+ getPoolSampleCount(pool) {
8583
+ return pool.pooledOnlySamples.length + pool.pooledAndIndependentSamples.length;
8584
+ }
8585
+ getPoolSampleDisplay(pool) {
8586
+ const pooledOnly = pool.pooledOnlySamples.length;
8587
+ const pooledAndIndependent = pool.pooledAndIndependentSamples.length;
8588
+ const total = pooledOnly + pooledAndIndependent;
8589
+ if (pooledOnly > 0 && pooledAndIndependent > 0) {
8590
+ return `${total} samples (${pooledOnly} pool-only, ${pooledAndIndependent} shared)`;
8591
+ }
8592
+ else if (pooledOnly > 0) {
8593
+ return `${pooledOnly} pool-only samples`;
8594
+ }
8595
+ else if (pooledAndIndependent > 0) {
8596
+ return `${pooledAndIndependent} shared samples`;
8597
+ }
8598
+ else {
8599
+ return '0 samples';
8600
+ }
8601
+ }
8602
+ getPoolSamples(pool) {
8603
+ const allSamples = [...pool.pooledOnlySamples, ...pool.pooledAndIndependentSamples].sort((a, b) => a - b);
8604
+ if (allSamples.length === 0)
8605
+ return 'None';
8606
+ // Group consecutive numbers into ranges
8607
+ const ranges = [];
8608
+ let start = allSamples[0];
8609
+ let end = allSamples[0];
8610
+ for (let i = 1; i < allSamples.length; i++) {
8611
+ if (allSamples[i] === end + 1) {
8612
+ end = allSamples[i];
8613
+ }
8614
+ else {
8615
+ ranges.push(start === end ? start.toString() : `${start}-${end}`);
8616
+ start = end = allSamples[i];
8617
+ }
8618
+ }
8619
+ ranges.push(start === end ? start.toString() : `${start}-${end}`);
8620
+ return ranges.join(', ');
8621
+ }
8622
+ getPoolTypeIcon(pool) {
8623
+ return pool.isReference ? 'bi-star-fill' : 'bi-layers';
8624
+ }
8625
+ getPoolTypeClass(pool) {
8626
+ return pool.isReference ? 'text-warning' : 'text-primary';
8627
+ }
8628
+ getPoolTypeLabel(pool) {
8629
+ return pool.isReference ? 'Reference Pool' : 'Sample Pool';
8630
+ }
8631
+ viewPoolDetails(pool) {
8632
+ const modalRef = this.modalService.open(SamplePoolDetailsModal, {
8633
+ size: 'lg',
8634
+ centered: true
8635
+ });
8636
+ modalRef.componentInstance.pool = pool;
8637
+ }
8638
+ editPool(pool) {
8639
+ const table = this.table();
8640
+ if (!table || !table.canEdit)
8641
+ return;
8642
+ const modalRef = this.modalService.open(SamplePoolEditModal, {
8643
+ size: 'lg',
8644
+ centered: true
8645
+ });
8646
+ modalRef.componentInstance.pool = pool;
8647
+ modalRef.componentInstance.maxSampleCount = table.sampleCount;
8648
+ modalRef.componentInstance.poolSaved.subscribe((updatedPool) => {
8649
+ // Reload the entire table to sync all pool-related changes
8650
+ this.loadTable(table.id);
8651
+ this.toastService.success(`Pool "${updatedPool.poolName}" updated successfully!`);
8652
+ });
8653
+ }
8654
+ createPool() {
8655
+ const table = this.table();
8656
+ if (!table || !table.canEdit)
8657
+ return;
8658
+ const modalRef = this.modalService.open(SamplePoolCreateModal, {
8659
+ size: 'xl',
8660
+ centered: true,
8661
+ backdrop: 'static'
8662
+ });
8663
+ modalRef.componentInstance.metadataTable = table;
8664
+ modalRef.componentInstance.poolCreated.subscribe((createdPool) => {
8665
+ // Reload the entire table to sync all pool-related changes
8666
+ this.loadTable(table.id);
8667
+ this.toastService.success(`Pool "${createdPool.poolName}" created successfully!`);
8668
+ });
8669
+ modalRef.result.catch(() => {
8670
+ // Modal was dismissed - no action needed
8671
+ });
8672
+ }
8673
+ deletePool(pool) {
8674
+ const table = this.table();
8675
+ if (!table || !pool.id)
8676
+ return;
8677
+ const confirmMessage = `Are you sure you want to delete the pool "${pool.poolName}"?\n\nThis action cannot be undone.`;
8678
+ if (confirm(confirmMessage)) {
8679
+ this.samplePoolService.deleteSamplePool(pool.id).subscribe({
8680
+ next: () => {
8681
+ // Reload the entire table to sync all pool-related changes
8682
+ this.loadTable(table.id);
8683
+ this.toastService.success(`Pool "${pool.poolName}" deleted successfully!`);
8684
+ },
8685
+ error: (error) => {
8686
+ console.error('Error deleting pool:', error);
8687
+ this.toastService.error(`Failed to delete pool "${pool.poolName}"`);
8688
+ }
8689
+ });
8690
+ }
8691
+ }
8692
+ // Metadata value editing methods
8693
+ editColumnValue(column) {
8694
+ const table = this.table();
8695
+ if (!table || !table.canEdit || !column.id)
8696
+ return;
8697
+ // Prepare sample data for multi-sample editing
8698
+ const sampleData = this.tableRows().map(row => ({
8699
+ index: row._sampleIndex,
8700
+ value: row[`col_${column.id}`] || column.value || '',
8701
+ sourceName: row.sourceName || `Sample ${row._sampleIndex}`
8702
+ }));
8703
+ const config = {
8704
+ columnId: column.id,
8705
+ columnName: column.name,
8706
+ columnType: column.type,
8707
+ ontologyType: column.ontologyType,
8708
+ enableTypeahead: true,
8709
+ currentValue: column.value,
8710
+ context: 'table',
8711
+ tableId: table.id,
8712
+ enableMultiSampleEdit: true,
8713
+ sampleData: sampleData,
8714
+ maxSampleCount: table.sampleCount
8715
+ };
8716
+ const modalRef = this.modalService.open(MetadataValueEditModal, {
8717
+ size: 'xl',
8718
+ backdrop: 'static'
8719
+ });
8720
+ modalRef.componentInstance.config = config;
8721
+ // Debug log to check if multi-sample editing is enabled
8722
+ console.log('Multi-sample editing config:', {
8723
+ enableMultiSampleEdit: config.enableMultiSampleEdit,
8724
+ sampleDataLength: config.sampleData?.length,
8725
+ hasMultiSampleEdit: config.enableMultiSampleEdit && config.sampleData && config.sampleData.length > 0
8726
+ });
8727
+ modalRef.componentInstance.valueSaved.subscribe((result) => {
8728
+ let apiData;
8729
+ if (typeof result === 'string') {
8730
+ // Simple string value - update default
8731
+ apiData = {
8732
+ value: result,
8733
+ value_type: 'default'
8734
+ };
8735
+ }
8736
+ else {
8737
+ // Multi-sample edit - update specific samples
8738
+ apiData = {
8739
+ value: result.value,
8740
+ sample_indices: result.sampleIndices,
8741
+ value_type: 'sample_specific'
8742
+ };
8743
+ }
8744
+ // Use the API to update the column value
8745
+ const requestData = {
8746
+ value: typeof result === 'string' ? result : result.value,
8747
+ sampleIndices: typeof result === 'string' ? undefined : result.sampleIndices,
8748
+ valueType: typeof result === 'string' ? 'default' : 'sample_specific'
8749
+ };
8750
+ this.metadataColumnService.updateColumnValue(column.id, requestData).subscribe({
8751
+ next: (response) => {
8752
+ // Update the local column data with the returned column
8753
+ const currentTable = this.table();
8754
+ if (currentTable && currentTable.columns && response.column) {
8755
+ const updatedColumns = currentTable.columns.map(col => col.id === column.id ? response.column : col);
8756
+ this.table.set({ ...currentTable, columns: updatedColumns });
8757
+ }
8758
+ this.toastService.success('Column value updated successfully!');
8759
+ modalRef.componentInstance.onClose();
8760
+ },
8761
+ error: (error) => {
8762
+ console.error('Error updating column value:', error);
8763
+ this.toastService.error('Failed to update column value');
8764
+ }
8765
+ });
8766
+ });
8767
+ }
8768
+ // Toggle column hidden property
8769
+ toggleColumnHidden(column) {
8770
+ const table = this.table();
8771
+ if (!table || !table.canEdit || !column.id)
8772
+ return;
8773
+ const newHiddenValue = !column.hidden;
8774
+ this.metadataColumnService.patchMetadataColumn(column.id, { hidden: newHiddenValue }).subscribe({
8775
+ next: (updatedColumn) => {
8776
+ // Update the local column data
8777
+ const currentTable = this.table();
8778
+ if (currentTable && currentTable.columns) {
8779
+ const updatedColumns = currentTable.columns.map(col => col.id === column.id ? updatedColumn : col);
8780
+ this.table.set({ ...currentTable, columns: updatedColumns });
8781
+ // Reload the table data to sync pool columns
8782
+ this.loadTable(table.id);
8783
+ }
8784
+ this.toastService.success(`Column "${column.name}" visibility updated successfully!`);
8785
+ },
8786
+ error: (error) => {
8787
+ console.error('Error updating column hidden property:', error);
8788
+ this.toastService.error('Failed to update column visibility');
8789
+ }
8790
+ });
8791
+ }
8792
+ // View column details
8793
+ viewColumnDetails(column) {
8794
+ // For now, we'll show an alert with column details
8795
+ // This can be enhanced with a proper modal later
8796
+ const details = [
8797
+ `Name: ${column.name}`,
8798
+ `Type: ${column.type}`,
8799
+ `Hidden: ${column.hidden ? 'Yes' : 'No'}`,
8800
+ `Mandatory: ${column.mandatory ? 'Yes' : 'No'}`,
8801
+ `Read Only: ${column.readonly ? 'Yes' : 'No'}`,
8802
+ `Default Value: ${column.value || 'None'}`,
8803
+ `Ontology Type: ${column.ontologyType || 'None'}`,
8804
+ `Enable Typeahead: ${column.enableTypeahead ? 'Yes' : 'No'}`
8805
+ ].join('\n');
8806
+ alert(`Column Details:\n\n${details}`);
8807
+ }
8808
+ // Edit column settings
8809
+ editColumnSettings(column) {
8810
+ const table = this.table();
8811
+ if (!table || !table.canEdit || !column.id)
8812
+ return;
8813
+ const modalRef = this.modalService.open(ColumnEditModal, {
8814
+ size: 'lg',
8815
+ backdrop: 'static',
8816
+ keyboard: false
8817
+ });
8818
+ // Configure modal for editing existing column
8819
+ modalRef.componentInstance.column = column;
8820
+ modalRef.componentInstance.templateId = null;
8821
+ modalRef.componentInstance.isEdit = true;
8822
+ // Handle column save
8823
+ modalRef.componentInstance.columnSaved.subscribe((columnData) => {
8824
+ this.updateColumnSettings(column.id, columnData);
8825
+ modalRef.componentInstance.onClose();
8826
+ });
8827
+ }
8828
+ // Update column settings
8829
+ updateColumnSettings(columnId, columnData) {
8830
+ this.metadataColumnService.patchMetadataColumn(columnId, columnData).subscribe({
8831
+ next: (updatedColumn) => {
8832
+ // Update the local column data
8833
+ const currentTable = this.table();
8834
+ if (currentTable && currentTable.columns) {
8835
+ const updatedColumns = currentTable.columns.map(col => col.id === columnId ? updatedColumn : col);
8836
+ this.table.set({ ...currentTable, columns: updatedColumns });
8837
+ }
8838
+ this.toastService.success(`Column "${updatedColumn.name}" updated successfully!`);
8839
+ // Reload the table to sync any changes
8840
+ this.loadTable(currentTable.id);
8841
+ },
8842
+ error: (error) => {
8843
+ console.error('Error updating column settings:', error);
8844
+ this.toastService.error('Failed to update column settings');
8845
+ }
8846
+ });
8847
+ }
8848
+ // Pool metadata value editing method
8849
+ editPoolColumnValue(pool, column) {
8850
+ const table = this.table();
8851
+ if (!table || !table.canEdit)
8852
+ return;
8853
+ // Find the pool's metadata column that corresponds to this table column
8854
+ const poolColumn = pool.metadataColumns?.find(pc => pc.name === column.name);
8855
+ if (!poolColumn || !poolColumn.id)
8856
+ return;
8857
+ const config = {
8858
+ columnId: poolColumn.id,
8859
+ columnName: poolColumn.name,
8860
+ columnType: poolColumn.type,
8861
+ ontologyType: poolColumn.ontologyType,
8862
+ enableTypeahead: true,
8863
+ currentValue: poolColumn.value,
8864
+ context: 'pool',
8865
+ tableId: table.id,
8866
+ poolId: pool.id
8867
+ };
8868
+ const modalRef = this.modalService.open(MetadataValueEditModal, {
8869
+ size: 'lg',
8870
+ backdrop: 'static'
8871
+ });
8872
+ modalRef.componentInstance.config = config;
8873
+ modalRef.componentInstance.valueSaved.subscribe((newValue) => {
8874
+ // Update the pool's metadata column value locally
8875
+ const currentTable = this.table();
8876
+ if (currentTable && currentTable.samplePools) {
8877
+ const updatedPools = currentTable.samplePools.map(p => {
8878
+ if (p.id === pool.id) {
8879
+ const updatedMetadataColumns = p.metadataColumns.map((mc) => mc.id === poolColumn.id ? { ...mc, value: newValue } : mc);
8880
+ return { ...p, metadataColumns: updatedMetadataColumns };
8881
+ }
8882
+ return p;
8883
+ });
8884
+ this.table.set({ ...currentTable, samplePools: updatedPools });
8885
+ }
8886
+ this.toastService.success('Pool column value updated successfully!');
8887
+ modalRef.componentInstance.onClose();
8888
+ });
8889
+ }
8890
+ // Sample-specific metadata value editing method
8891
+ editSampleColumnValue(column, sampleIndex) {
8892
+ const table = this.table();
8893
+ if (!table || !table.canEdit || !column.id)
8894
+ return;
8895
+ // Get current value for this sample (could be default or modified)
8896
+ const currentValue = this.getSampleColumnValue(column, sampleIndex);
8897
+ const config = {
8898
+ columnId: column.id,
8899
+ columnName: column.name,
8900
+ columnType: column.type,
8901
+ ontologyType: column.ontologyType,
8902
+ enableTypeahead: true,
8903
+ currentValue: currentValue,
8904
+ context: 'table',
8905
+ tableId: table.id
8906
+ };
8907
+ const modalRef = this.modalService.open(MetadataValueEditModal, {
8908
+ size: 'lg',
8909
+ backdrop: 'static'
8910
+ });
8911
+ modalRef.componentInstance.config = config;
8912
+ modalRef.componentInstance.valueSaved.subscribe((newValue) => {
8913
+ // Use the API to update the sample-specific value
8914
+ this.metadataColumnService.updateColumnValue(column.id, {
8915
+ value: newValue,
8916
+ sampleIndices: [sampleIndex],
8917
+ valueType: 'sample_specific'
8918
+ }).subscribe({
8919
+ next: (response) => {
8920
+ // Update the local column data with the returned column
8921
+ const currentTable = this.table();
8922
+ if (currentTable && currentTable.columns && response.column) {
8923
+ const updatedColumns = currentTable.columns.map(col => col.id === column.id ? response.column : col);
8924
+ this.table.set({ ...currentTable, columns: updatedColumns });
8925
+ }
8926
+ this.toastService.success(`Sample ${sampleIndex} value updated successfully!`);
8927
+ modalRef.componentInstance.onClose();
8928
+ },
8929
+ error: (error) => {
8930
+ console.error('Error updating sample value:', error);
8931
+ this.toastService.error(`Failed to update sample ${sampleIndex} value`);
8932
+ }
8933
+ });
8934
+ });
8935
+ }
8936
+ // Helper method to get current value for a specific sample
8937
+ getSampleColumnValue(column, sampleIndex) {
8938
+ // Check if this sample has a specific modifier
8939
+ if (column.modifiers && column.modifiers.length > 0) {
8940
+ for (const modifier of column.modifiers) {
8941
+ if (modifier?.samples) {
8942
+ const sampleRanges = modifier.samples.split(',').map((s) => s.trim()).filter((s) => s.length > 0);
8943
+ for (const range of sampleRanges) {
8944
+ if (range.includes('-')) {
8945
+ const parts = range.split('-').map((n) => n.trim());
8946
+ if (parts.length === 2) {
8947
+ const start = parseInt(parts[0]);
8948
+ const end = parseInt(parts[1]);
8949
+ if (!isNaN(start) && !isNaN(end) && sampleIndex >= start && sampleIndex <= end) {
8950
+ return modifier.value || '';
8951
+ }
8952
+ }
8953
+ }
8954
+ else {
8955
+ const sampleNum = parseInt(range);
8956
+ if (!isNaN(sampleNum) && sampleNum === sampleIndex) {
8957
+ return modifier.value || '';
8958
+ }
8959
+ }
8960
+ }
8961
+ }
8962
+ }
8963
+ }
8964
+ // Return default value if no modifier found
8965
+ return column.value || '';
8966
+ }
8967
+ // Remove column method
8968
+ removeColumn(column) {
8969
+ const table = this.table();
8970
+ if (!table || !table.canEdit || !column.id)
8971
+ return;
8972
+ const confirmMessage = `Are you sure you want to remove the column "${column.name}"?\n\nThis action cannot be undone and will remove all data in this column.`;
8973
+ if (confirm(confirmMessage)) {
8974
+ this.metadataColumnService.deleteMetadataColumn(column.id).subscribe({
8975
+ next: () => {
8976
+ // Remove the column from the local table data
8977
+ const currentTable = this.table();
8978
+ if (currentTable && currentTable.columns) {
8979
+ const updatedColumns = currentTable.columns.filter(col => col.id !== column.id);
8980
+ this.table.set({ ...currentTable, columns: updatedColumns });
8981
+ }
8982
+ this.toastService.success(`Column "${column.name}" removed successfully!`);
8983
+ },
8984
+ error: (error) => {
8985
+ console.error('Error removing column:', error);
8986
+ this.toastService.error(`Failed to remove column "${column.name}"`);
8987
+ }
8988
+ });
8989
+ }
8990
+ }
8991
+ /**
8992
+ * Open modal to add a new column with automatic reordering
8993
+ */
8994
+ openAddColumnModal() {
8995
+ const modalRef = this.modalService.open(ColumnEditModal, {
8996
+ size: 'lg',
8997
+ backdrop: 'static',
8998
+ keyboard: false
8999
+ });
9000
+ // Configure modal for adding new column
9001
+ modalRef.componentInstance.column = null;
9002
+ modalRef.componentInstance.templateId = null;
9003
+ modalRef.componentInstance.isEdit = false;
9004
+ // Handle column save
9005
+ modalRef.componentInstance.columnSaved.subscribe((columnData) => {
9006
+ this.addColumnWithAutoReorder(columnData);
9007
+ modalRef.componentInstance.onClose();
9008
+ });
9009
+ }
9010
+ /**
9011
+ * Add a new column using the new API with automatic reordering
9012
+ */
9013
+ addColumnWithAutoReorder(columnData) {
9014
+ const currentTable = this.table();
9015
+ if (!currentTable) {
9016
+ this.toastService.error('No table selected');
9017
+ return;
9018
+ }
9019
+ this.isLoading.set(true);
9020
+ this.metadataTableService.addColumnWithAutoReorder(currentTable.id, {
9021
+ columnData: columnData
9022
+ }).subscribe({
9023
+ next: (response) => {
9024
+ // Update the local table data with the new column
9025
+ const updatedColumns = [...(currentTable.columns || []), response.column];
9026
+ this.table.set({ ...currentTable, columns: updatedColumns });
9027
+ // Show success message with reordering info
9028
+ let message = `Column "${response.column.name}" added successfully!`;
9029
+ if (response.reordered) {
9030
+ message += ` Columns reordered using ${response.schemaIdsUsed.length} schema(s).`;
9031
+ }
9032
+ this.toastService.success(message);
9033
+ // Refresh the table to get the latest column positions
9034
+ this.loadTable(currentTable.id);
9035
+ },
9036
+ error: (error) => {
9037
+ console.error('Error adding column:', error);
9038
+ this.toastService.error('Failed to add column');
9039
+ },
9040
+ complete: () => {
9041
+ this.isLoading.set(false);
9042
+ }
9043
+ });
9044
+ }
9045
+ /**
9046
+ * Reorder columns by schema selection
9047
+ */
9048
+ reorderColumns() {
9049
+ const currentTable = this.table();
9050
+ if (!currentTable) {
9051
+ this.toastService.error('No table selected');
9052
+ return;
9053
+ }
9054
+ const modalRef = this.modalService.open(SchemaSelectionModal, {
9055
+ size: 'lg',
9056
+ backdrop: 'static'
9057
+ });
9058
+ modalRef.componentInstance.title = 'Reorder Columns by Schema';
9059
+ modalRef.componentInstance.description = `Select schemas to reorder columns in "${currentTable.name}". Columns will be arranged based on the selected schema order.`;
9060
+ modalRef.result.then((result) => {
9061
+ if (result && result.selectedSchemaIds) {
9062
+ this.performColumnReorder(currentTable.id, result.selectedSchemaIds);
9063
+ }
9064
+ }).catch(() => {
9065
+ // Modal was dismissed - no action needed
9066
+ });
9067
+ }
9068
+ /**
9069
+ * Perform the actual column reordering with async task monitoring
9070
+ */
9071
+ performColumnReorder(tableId, schemaIds) {
9072
+ this.isLoading.set(true);
9073
+ this.metadataTableService.reorderColumnsBySchemaAsync(tableId, schemaIds).subscribe({
9074
+ next: (response) => {
9075
+ console.log('Column reorder task started:', response);
9076
+ this.toastService.success(`Column reorder task queued successfully! Task ID: ${response.taskId}`);
9077
+ // Mark task for monitoring and start real-time updates (same pattern as imports)
9078
+ this.asyncTaskService.monitorTask(response.taskId);
9079
+ this.asyncTaskService.startRealtimeUpdates();
9080
+ this.isLoading.set(false);
9081
+ },
9082
+ error: (error) => {
9083
+ console.error('Error starting column reorder:', error);
9084
+ this.toastService.error('Failed to start column reordering');
9085
+ this.isLoading.set(false);
9086
+ }
9087
+ });
9088
+ }
9089
+ onColumnDrop(event) {
9090
+ if (event.previousIndex === event.currentIndex)
9091
+ return;
9092
+ const currentTable = this.table();
9093
+ if (!currentTable?.canEdit)
9094
+ return;
9095
+ const dragColumns = [...this.dragColumnsForList()];
9096
+ moveItemInArray(dragColumns, event.previousIndex, event.currentIndex);
9097
+ // Update columnPosition for all columns in the new order
9098
+ dragColumns.forEach((col, index) => {
9099
+ col.columnPosition = index;
9100
+ });
9101
+ this.dragColumnsForList.set(dragColumns);
9102
+ // Update the main table data too
9103
+ const updatedTableColumns = [...(currentTable.columns || [])];
9104
+ dragColumns.forEach((dragCol, index) => {
9105
+ const tableColIndex = updatedTableColumns.findIndex(col => col.id === dragCol.id);
9106
+ if (tableColIndex !== -1) {
9107
+ updatedTableColumns[tableColIndex] = { ...updatedTableColumns[tableColIndex], columnPosition: index };
9108
+ }
9109
+ });
9110
+ this.table.update(table => table ? { ...table, columns: updatedTableColumns } : null);
9111
+ const draggedColumn = dragColumns[event.currentIndex];
9112
+ const newPosition = event.currentIndex;
9113
+ console.log(`Moving column "${draggedColumn.name}" to position ${newPosition}`);
9114
+ this.metadataTableService.reorderColumn(currentTable.id, draggedColumn.id, newPosition).subscribe({
9115
+ next: () => {
9116
+ this.toastService.success('Column reordered successfully');
9117
+ this.loadTable(currentTable.id);
9118
+ },
9119
+ error: () => {
9120
+ this.toastService.error('Failed to reorder column');
9121
+ this.loadTable(currentTable.id);
9122
+ }
9123
+ });
9124
+ }
9125
+ 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 });
9126
+ 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-secondary\"\n (click)=\"editColumnSettings(column)\"\n title=\"Edit column settings\">\n <i class=\"bi bi-gear\"></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-danger header-edit-btn\"\n (click)=\"removeColumn(column)\"\n [title]=\"'Remove ' + column.name + ' column'\">\n <i class=\"bi bi-trash\"></i>\n </button>\n </div>\n }\n </div>\n </th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of paginatedRows(); track row._sampleIndex) {\n <tr class=\"sample-row\" [class.editable]=\"table()!.canEdit\">\n <td class=\"text-muted fw-semibold\">{{ row._sampleIndex }}</td>\n @for (column of sortedColumns(); track column.id) {\n <td\n class=\"sample-cell\"\n [class.opacity-50]=\"column.hidden\"\n [attr.data-sample-index]=\"row._sampleIndex\"\n [attr.data-column-id]=\"column.id\"\n (click)=\"table()!.canEdit ? editSampleColumnValue(column, row._sampleIndex) : null\"\n [title]=\"table()!.canEdit ? 'Click to edit ' + column.name + ' for sample ' + row._sampleIndex : ''\">\n {{ row['col_' + column.id] || '-' }}\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n\n <!-- Table Mode Pagination -->\n @if (viewMode() === 'table' && totalPages() > 1) {\n <div class=\"d-flex justify-content-between align-items-center p-3 border-top\">\n <div class=\"d-flex align-items-center gap-2\">\n <span class=\"text-muted\">Rows per page:</span>\n <select class=\"form-select form-select-sm\" style=\"width: auto;\"\n [value]=\"pageSize()\"\n (change)=\"onPageSizeChange(+($any($event.target).value))\">\n <option value=\"10\">10</option>\n <option value=\"25\">25</option>\n <option value=\"50\">50</option>\n <option value=\"100\">100</option>\n </select>\n </div>\n <ngb-pagination\n [collectionSize]=\"tableRows().length\"\n [page]=\"currentPage()\"\n [pageSize]=\"pageSize()\"\n [maxSize]=\"5\"\n [boundaryLinks]=\"true\"\n [rotate]=\"true\"\n (pageChange)=\"onPageChange($event)\"\n class=\"pagination-sm mb-0\">\n </ngb-pagination>\n <span class=\"text-muted\">\n Showing {{ (currentPage() - 1) * pageSize() + 1 }}-{{ Math.min(currentPage() * pageSize(), tableRows().length) }}\n of {{ tableRows().length }} samples\n </span>\n </div>\n }\n </div>\n }\n } @else {\n <div class=\"card-body text-center py-5\">\n <div class=\"text-muted\">\n <i class=\"bi bi-inbox display-1 mb-3\"></i>\n <h5>No Columns Found</h5>\n <p>This table doesn't have any columns defined yet.</p>\n @if (table()!.canEdit) {\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openAddColumnModal()\">\n <i class=\"bi bi-plus me-1\"></i>Add First Column\n </button>\n }\n </div>\n </div>\n }\n </div>\n\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";.header-cell{position:relative}.header-cell .header-actions{opacity:0;transition:opacity .15s ease}thead th:hover .header-actions{opacity:1}.header-edit-btn{--bs-btn-padding-y: .125rem;--bs-btn-padding-x: .25rem;--bs-btn-font-size: .65rem;border:none!important}.header-edit-btn.btn-outline-primary:hover{background-color:var(--bs-primary);border-color:var(--bs-primary)}.header-edit-btn.btn-outline-danger:hover{background-color:var(--bs-danger);border-color:var(--bs-danger)}.sample-row.editable .sample-cell{cursor:pointer;transition:background-color .1s ease}.sample-row.editable .sample-cell:hover{background-color:#28a74514;position:relative}.sample-row.editable .sample-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:#28a74599;font-size:.75em;pointer-events:none}.pool-row.editable td.clickable{cursor:pointer;transition:background-color .1s ease}.pool-row.editable td.clickable:hover{background-color:#0dcaf01a;color:var(--bs-info)}.pool-row.editable td.clickable:hover .bi-pencil-square{opacity:.8}.pool-row.editable td.clickable .bi-pencil-square{opacity:.4;transition:opacity .1s ease}.pool-row.editable .pool-cell{cursor:pointer;transition:background-color .1s ease}.pool-row.editable .pool-cell:hover{background-color:#ffc10726;position:relative}.pool-row.editable .pool-cell:hover:after{content:\"\\270e\";position:absolute;right:4px;top:50%;transform:translateY(-50%);color:#ffc107cc;font-size:.75em;pointer-events:none}@media (max-width: 768px){.sample-row.editable .sample-cell,.pool-row.editable .pool-cell{transition:none}}.sortable{cursor:pointer;user-select:none;transition:background-color .2s ease}.sortable:hover{background-color:rgba(var(--bs-primary-rgb),.1)}.sortable.sorted{background-color:rgba(var(--bs-primary-rgb),.15)}.sortable.sorted .bi{color:var(--bs-primary)}.sortable .bi{opacity:.5;transition:all .2s ease;font-size:.75em}.sortable:hover .bi{opacity:.8}.drag-handle{cursor:grab;opacity:.6;transition:opacity .2s ease}.drag-handle:hover{opacity:1}.drag-handle:active{cursor:grabbing}.cdk-drag-preview{box-sizing:border-box;border-radius:4px;box-shadow:0 5px 5px -3px #0003,0 8px 10px 1px #00000024,0 3px 14px 2px #0000001f;background:var(--bs-light);border:1px solid var(--bs-border-color)}.cdk-drag-preview .header-cell{padding:.5rem;font-size:.875rem;font-weight:500}.cdk-drag-placeholder{opacity:.5;background:rgba(var(--bs-primary-rgb),.1)}.cdk-drag-animating{transition:transform .25s cubic-bezier(0,0,.2,1)}.cdk-drop-list-dragging th:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i1$1.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }, { kind: "ngmodule", type: NgbModule }, { kind: "directive", type: i2.NgbDropdown, selector: "[ngbDropdown]", inputs: ["autoClose", "dropdownClass", "open", "placement", "popperOptions", "container", "display"], outputs: ["openChange"], exportAs: ["ngbDropdown"] }, { kind: "directive", type: i2.NgbDropdownToggle, selector: "[ngbDropdownToggle]" }, { kind: "directive", type: i2.NgbDropdownMenu, selector: "[ngbDropdownMenu]" }, { kind: "component", type: i2.NgbPagination, selector: "ngb-pagination", inputs: ["disabled", "boundaryLinks", "directionLinks", "ellipses", "rotate", "collectionSize", "maxSize", "page", "pageSize", "size"], outputs: ["pageChange"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i5$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer", "cdkDropListHasAnchor"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i5$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "directive", type: i5$1.CdkDragHandle, selector: "[cdkDragHandle]", inputs: ["cdkDragHandleDisabled"] }, { kind: "pipe", type: i5.DatePipe, name: "date" }] });
9127
+ }
9128
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: MetadataTableDetails, decorators: [{
9129
+ type: Component,
9130
+ 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-secondary\"\n (click)=\"editColumnSettings(column)\"\n title=\"Edit column settings\">\n <i class=\"bi bi-gear\"></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-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"] }]
9131
+ }], 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: [{
9132
+ type: ViewChild,
9133
+ args: ['fileInput']
9134
+ }], excelInput: [{
9135
+ type: ViewChild,
9136
+ args: ['excelInput']
9137
+ }] } });
9138
+
7408
9139
  class CupcakeVanilla {
7409
- static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: CupcakeVanilla, deps: [], target: i0.ɵɵFactoryTarget.Component });
7410
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.3", type: CupcakeVanilla, isStandalone: true, selector: "ccv-cupcake-vanilla", ngImport: i0, template: `
9140
+ static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CupcakeVanilla, deps: [], target: i0.ɵɵFactoryTarget.Component });
9141
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.7", type: CupcakeVanilla, isStandalone: true, selector: "ccv-cupcake-vanilla", ngImport: i0, template: `
7411
9142
  <p>
7412
9143
  cupcake-vanilla works!
7413
9144
  </p>
7414
9145
  `, isInline: true, styles: [""] });
7415
9146
  }
7416
- i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImport: i0, type: CupcakeVanilla, decorators: [{
9147
+ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.7", ngImport: i0, type: CupcakeVanilla, decorators: [{
7417
9148
  type: Component,
7418
9149
  args: [{ selector: 'ccv-cupcake-vanilla', imports: [], template: `
7419
9150
  <p>
@@ -7430,5 +9161,5 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
7430
9161
  * Generated bundle index. Do not edit.
7431
9162
  */
7432
9163
 
7433
- export { AdminWebSocketService, AsyncExportService, AsyncImportService, AsyncTaskService, AsyncTaskUIService, AsyncValidationService, COLUMN_TYPE_CONFIGS, ChunkedImportService, ChunkedUploadService, ColumnEditModal, ColumnTemplateEditModal, ColumnTemplates, ColumnType, CupcakeVanilla, ElectronService, ExcelExportModalComponent, FavoriteManagement, FavouriteMetadataOptionService, MSTermType, MetadataColumnService, MetadataColumnTemplateService, MetadataColumnTemplateShareService, MetadataManagementService, MetadataTableEditModal, MetadataTableService, MetadataTableTemplateEditModal, MetadataTableTemplateService, MetadataTableTemplates, MetadataValidationModal, MetadataValueEditModal, NotificationService, NotificationType, OFFICIAL_SDRF_COLUMNS, ONTOLOGY_TYPE_CONFIGS, OntologySearchService, OntologyType, OntologyTypeLabels, OntologyUtils, SDRFDefaultsService, SamplePoolService, SchemaSelectionModal, SchemaService, SdrfAgeInput, SdrfCleavageInput, SdrfColumnCategory, SdrfModificationInput, SdrfSpikedCompoundInput, SdrfSyntaxService, TASK_STATUS_COLORS, TASK_STATUS_LABELS, TASK_TYPE_LABELS, TableCreationModalComponent, TemplatePermissionLabels, TemplatePermissionLevel, TemplateSchemaSelectionModal, ValidationResultsModal, Websocket, getRequiredSdrfColumns, getSdrfColumnByName, getSdrfColumnsByCategory, isSdrfColumnRequired, isUnimodFullData };
9164
+ export { AdminWebSocketService, AsyncExportService, AsyncImportService, AsyncTaskService, AsyncTaskUIService, AsyncValidationService, COLUMN_TYPE_CONFIGS, ChunkedImportService, ChunkedUploadService, ColumnEditModal, ColumnTemplateEditModal, ColumnTemplates, ColumnType, CupcakeVanilla, ElectronService, ExcelExportModalComponent, FavoriteManagement, FavouriteMetadataOptionService, MSTermType, MetadataColumnService, MetadataColumnTemplateService, MetadataColumnTemplateShareService, MetadataManagementService, MetadataTableDetails, MetadataTableEditModal, MetadataTableService, MetadataTableTemplateEditModal, MetadataTableTemplateService, MetadataTableTemplates, MetadataValidationModal, MetadataValueEditModal, NotificationService, NotificationType, OFFICIAL_SDRF_COLUMNS, ONTOLOGY_TYPE_CONFIGS, OntologySearchService, OntologyType, OntologyTypeLabels, OntologyUtils, SDRFDefaultsService, SamplePoolCreateModal, SamplePoolDetailsModal, SamplePoolEditModal, SamplePoolService, SchemaSelectionModal, SchemaService, SdrfAgeInput, SdrfCleavageInput, SdrfColumnCategory, SdrfModificationInput, SdrfSpikedCompoundInput, SdrfSyntaxService, SearchReplaceModal, TASK_STATUS_COLORS, TASK_STATUS_LABELS, TASK_TYPE_LABELS, TableCreationModalComponent, TemplatePermissionLabels, TemplatePermissionLevel, TemplateSchemaSelectionModal, ValidationResultsModal, Websocket, getRequiredSdrfColumns, getSdrfColumnByName, getSdrfColumnsByCategory, isSdrfColumnRequired, isUnimodFullData };
7434
9165
  //# sourceMappingURL=noatgnu-cupcake-vanilla.mjs.map